You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
3.4 KiB
Go

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)
}