// Package middleware provides HTTP middleware for the application package middleware import ( "strings" "accounting-app/pkg/api" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // ContextKey type for context keys type ContextKey string const ( // UserIDKey is the context key for user ID UserIDKey ContextKey = "user_id" // UserEmailKey is the context key for user email UserEmailKey ContextKey = "user_email" ) // AuthMiddlewareStruct is a struct-based auth middleware type AuthMiddlewareStruct struct { authService *service.AuthService } // NewAuthMiddleware creates a new AuthMiddlewareStruct func NewAuthMiddleware(authService *service.AuthService) *AuthMiddlewareStruct { return &AuthMiddlewareStruct{authService: authService} } // RequireAuth returns a middleware that requires authentication func (m *AuthMiddlewareStruct) RequireAuth() gin.HandlerFunc { return AuthMiddleware(m.authService) } // AuthMiddleware creates a JWT authentication middleware // Feature: api-interface-optimization // Validates: Requirements 12.3, 12.4 func AuthMiddleware(authService *service.AuthService) gin.HandlerFunc { return func(c *gin.Context) { // Get Authorization header authHeader := c.GetHeader("Authorization") if authHeader == "" { api.Unauthorized(c, "Authorization header is required") c.Abort() return } // Check Bearer prefix parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { api.Unauthorized(c, "Invalid authorization header format") c.Abort() return } tokenString := parts[1] // Validate token claims, err := authService.ValidateToken(tokenString) if err != nil { switch err { case service.ErrTokenExpired: api.Unauthorized(c, "Token has expired") case service.ErrInvalidToken: api.Unauthorized(c, "Invalid token") default: api.Unauthorized(c, "Authentication failed") } c.Abort() return } // Set user info in context c.Set(string(UserIDKey), claims.UserID) c.Set(string(UserEmailKey), claims.Email) c.Next() } } // OptionalAuthMiddleware creates an optional JWT authentication middleware // This middleware will set user info if a valid token is provided, but won't block requests without tokens func OptionalAuthMiddleware(authService *service.AuthService) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.Next() return } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { c.Next() return } tokenString := parts[1] claims, err := authService.ValidateToken(tokenString) if err == nil { c.Set(string(UserIDKey), claims.UserID) c.Set(string(UserEmailKey), claims.Email) } c.Next() } } // GetUserID retrieves the user ID from the context func GetUserID(c *gin.Context) (uint, bool) { userID, exists := c.Get(string(UserIDKey)) if !exists { return 0, false } id, ok := userID.(uint) return id, ok } // GetUserEmail retrieves the user email from the context func GetUserEmail(c *gin.Context) (string, bool) { email, exists := c.Get(string(UserEmailKey)) if !exists { return "", false } e, ok := email.(string) return e, ok }