package auth import ( "errors" "fmt" "strings" "github.com/gofiber/fiber/v2" ) // UserContext holds user information extracted from JWT type UserContext struct { UserID string `json:"user_id"` Username string `json:"username"` Email string `json:"email"` Roles []string `json:"roles"` } // GetUserFromContext extracts user information from fiber context func GetUserFromContext(c *fiber.Ctx) (*UserContext, error) { userID := c.Locals(UserIDKey) if userID == nil { return nil, errors.New(ErrUserNotAuthenticated) } username, _ := c.Locals(UsernameKey).(string) email, _ := c.Locals(EmailKey).(string) roles, _ := c.Locals(RolesKey).([]string) return &UserContext{ UserID: userID.(string), Username: username, Email: email, Roles: roles, }, nil } // IsAuthenticated checks if user is authenticated func IsAuthenticated(c *fiber.Ctx) bool { return c.Locals(UserIDKey) != nil } // HasRole checks if user has a specific role func HasRole(c *fiber.Ctx, role string) bool { return hasRole(c, role) } // hasRole is the internal implementation for role checking func hasRole(c *fiber.Ctx, role string) bool { roles, ok := c.Locals(RolesKey).([]string) if !ok { return false } for _, r := range roles { if r == role { return true } } return false } // hasAnyRole is the internal implementation for checking multiple roles func HasAnyRole(c *fiber.Ctx, roles ...string) bool { userRoles, ok := c.Locals(RolesKey).([]string) if !ok { return false } for _, userRole := range userRoles { for _, role := range roles { if userRole == role { return true } } } return false } // IsAdmin checks if user has admin role func IsAdmin(c *fiber.Ctx) bool { return HasRole(c, AdminRole) } // Token processing helper functions // extractToken extracts Bearer token from Authorization header func extractToken(c *fiber.Ctx) (string, error) { authHeader := c.Get(AuthorizationHeader) if authHeader == "" { return "", errors.New(ErrAuthHeaderRequired) } tokenString := strings.TrimPrefix(authHeader, BearerPrefix) if tokenString == authHeader { return "", errors.New(ErrInvalidAuthHeaderFormat) } return tokenString, nil } // setUserContext sets user information in fiber context from claims func setUserContext(c *fiber.Ctx, claims *Claims) { c.Locals(UserIDKey, claims.UserID) c.Locals(UsernameKey, claims.Username) c.Locals(EmailKey, claims.Email) c.Locals(RolesKey, claims.Roles) } // validateAndSetContext validates token and sets user context func validateAndSetContext(c *fiber.Ctx, authService *MockAuthService, tokenString string) error { claims, err := authService.ValidateToken(c.Context(), tokenString) if err != nil { return fmt.Errorf("%s: %w", ErrInvalidToken, err) } setUserContext(c, claims) return nil } // Error response helper functions // unauthorizedError creates a standardized unauthorized error response func unauthorizedError(c *fiber.Ctx, message string, details ...string) error { response := fiber.Map{ "error": message, } if len(details) > 0 { response["details"] = details[0] } return c.Status(StatusUnauthorized).JSON(response) } // forbiddenError creates a standardized forbidden error response func forbiddenError(c *fiber.Ctx, message string, extraFields ...fiber.Map) error { response := fiber.Map{ "error": message, } for _, extra := range extraFields { for k, v := range extra { response[k] = v } } return c.Status(StatusForbidden).JSON(response) }