package auth import ( "context" "net/http" "os" "strings" "github.com/gin-gonic/gin" "account/internal/store" ) // RequireActiveUser ensures the user account is active func RequireActiveUser(s store.Store) gin.HandlerFunc { return func(c *gin.Context) { userID := GetUserID(c) if userID == "" || userID == "system" { c.Next() return } user, err := s.GetUserByID(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found"}) c.Abort() return } if !user.Active { c.JSON(http.StatusForbidden, gin.H{ "error": "account_suspended", "message": "your account has been suspended", }) c.Abort() return } c.Next() } } // Context keys for storing user information type contextKey string const ( userIDKey contextKey = "user_id" emailKey contextKey = "email" rolesKey contextKey = "roles" mfaKey contextKey = "mfa_verified" bearerPrefix = "Bearer " ) // AuthMiddleware is a middleware that validates JWT access tokens func (s *TokenService) AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{ "error": "missing authorization header", }) c.Abort() return } if !strings.HasPrefix(authHeader, bearerPrefix) { c.JSON(http.StatusUnauthorized, gin.H{ "error": "invalid authorization header format", }) c.Abort() return } token := strings.TrimPrefix(authHeader, bearerPrefix) claims, err := s.ValidateAccessToken(token) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "error": "invalid or expired token", "detail": err.Error(), }) c.Abort() return } // Store claims in context ctx := context.WithValue(c.Request.Context(), userIDKey, claims.UserID) ctx = context.WithValue(ctx, emailKey, claims.Email) ctx = context.WithValue(ctx, rolesKey, claims.Roles) ctx = context.WithValue(ctx, mfaKey, claims.MFA) c.Request = c.Request.WithContext(ctx) c.Next() } } // InternalAuthMiddleware validates internal service-to-service authentication // using a shared token from the X-Service-Token header func InternalAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { serviceToken := c.GetHeader("X-Service-Token") if serviceToken == "" { c.JSON(http.StatusUnauthorized, gin.H{ "error": "missing service token", }) c.Abort() return } expectedToken := os.Getenv("INTERNAL_SERVICE_TOKEN") if expectedToken == "" { c.JSON(http.StatusInternalServerError, gin.H{ "error": "internal service token not configured", }) c.Abort() return } if serviceToken != expectedToken { c.JSON(http.StatusUnauthorized, gin.H{ "error": "invalid service token", }) c.Abort() return } // Set internal service context ctx := context.WithValue(c.Request.Context(), userIDKey, "system") ctx = context.WithValue(ctx, emailKey, "internal@system.service") ctx = context.WithValue(ctx, rolesKey, []string{"internal_service"}) c.Request = c.Request.WithContext(ctx) c.Next() } } // RequireMFA is a middleware that requires MFA verification func RequireMFA() gin.HandlerFunc { return func(c *gin.Context) { mfaVerified := c.Request.Context().Value(mfaKey) if mfaVerified == nil || !mfaVerified.(bool) { c.JSON(http.StatusForbidden, gin.H{ "error": "MFA verification required", }) c.Abort() return } c.Next() } } // RequireRole is a middleware that requires a specific role func RequireRole(role string) gin.HandlerFunc { return func(c *gin.Context) { roles := c.Request.Context().Value(rolesKey) if roles == nil { c.JSON(http.StatusForbidden, gin.H{ "error": "no roles found in token", }) c.Abort() return } roleSlice, ok := roles.([]string) if !ok { c.JSON(http.StatusForbidden, gin.H{ "error": "invalid roles format in token", }) c.Abort() return } for _, r := range roleSlice { if r == role { c.Next() return } } c.JSON(http.StatusForbidden, gin.H{ "error": "insufficient permissions", "required_role": role, }) c.Abort() } } // GetUserID extracts user ID from context func GetUserID(c *gin.Context) string { userID := c.Request.Context().Value(userIDKey) if userID == nil { return "" } return userID.(string) } // GetEmail extracts email from context func GetEmail(c *gin.Context) string { email := c.Request.Context().Value(emailKey) if email == nil { return "" } return email.(string) } // GetRoles extracts roles from context func GetRoles(c *gin.Context) []string { roles := c.Request.Context().Value(rolesKey) if roles == nil { return nil } return roles.([]string) } // IsMFAVerified checks if MFA is verified func IsMFAVerified(c *gin.Context) bool { mfa := c.Request.Context().Value(mfaKey) if mfa == nil { return false } return mfa.(bool) } // HTTPHandler represents a function that handles HTTP requests with gin context type HTTPHandler func(*gin.Context) // Wrap wraps a standard HTTP handler to work with gin func Wrap(handler HTTPHandler) gin.HandlerFunc { return func(c *gin.Context) { handler(c) } }