package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/dhao2001/mygo/internal/api" "github.com/dhao2001/mygo/internal/auth" "github.com/dhao2001/mygo/internal/repository" ) const userIDKey = "user_id" // AuthRequired returns a Gin middleware that validates JWT access tokens. // On success, it injects the user ID into the context via c.Get("user_id"). func AuthRequired(jwtSecret []byte) gin.HandlerFunc { return func(c *gin.Context) { header := c.GetHeader("Authorization") if header == "" { api.Error(c, http.StatusUnauthorized, "missing authorization header") c.Abort() return } parts := strings.SplitN(header, " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") { api.Error(c, http.StatusUnauthorized, "invalid authorization header format") c.Abort() return } claims, err := auth.ParseToken(parts[1], jwtSecret) if err != nil { api.Error(c, http.StatusUnauthorized, "invalid or expired token") c.Abort() return } if claims.Type != auth.TokenAccess { api.Error(c, http.StatusUnauthorized, "invalid token type") c.Abort() return } c.Set(userIDKey, claims.UserID) c.Next() } } // GetUserID extracts the user ID injected by AuthRequired. // Returns an empty string if the key is not present (e.g., optional auth contexts). func GetUserID(c *gin.Context) string { v, _ := c.Get(userIDKey) if v == nil { return "" } return v.(string) } // MustGetUserID extracts the user ID injected by AuthRequired. // It panics if the key is missing — this indicates a programming error // (a handler registered without the AuthRequired middleware). func MustGetUserID(c *gin.Context) string { userID := GetUserID(c) if userID == "" { panic("user_id not found in context — is AuthRequired middleware applied?") } return userID } // AdminRequired returns a Gin middleware that gates access to admin-only // endpoints. It must be applied AFTER AuthRequired. It fetches the user from // the repository, and returns 403 if the user is not an admin. Soft-deleted // users are not found by FindByID and will receive a 401 response. func AdminRequired(userRepo repository.UserRepository) gin.HandlerFunc { return func(c *gin.Context) { userID := GetUserID(c) if userID == "" { api.Error(c, http.StatusUnauthorized, "missing user context") c.Abort() return } user, err := userRepo.FindByID(c.Request.Context(), userID) if err != nil { api.Error(c, http.StatusUnauthorized, "user not found") c.Abort() return } if !user.IsAdmin { api.Error(c, http.StatusForbidden, "admin access required") c.Abort() return } c.Next() } }