package middleware import ( "context" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/dhao2001/mygo/internal/api" "github.com/dhao2001/mygo/internal/service" ) const ( userIDKey = "user_id" principalKey = "principal" ) type accessAuthenticator interface { AuthenticateAccessToken(ctx context.Context, token string) (*service.Principal, error) } // AuthRequired returns a Gin middleware that validates JWT access tokens. // On success, it injects the principal and user ID into the context. func AuthRequired(authenticator accessAuthenticator) 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 } principal, err := authenticator.AuthenticateAccessToken(c.Request.Context(), parts[1]) if err != nil { api.RespondError(c, err) c.Abort() return } c.Set(principalKey, *principal) c.Set(userIDKey, principal.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 } // GetPrincipal extracts the authenticated principal injected by AuthRequired. func GetPrincipal(c *gin.Context) (service.Principal, bool) { v, ok := c.Get(principalKey) if !ok || v == nil { return service.Principal{}, false } principal, ok := v.(service.Principal) return principal, ok } // MustGetPrincipal extracts the authenticated principal injected by AuthRequired. func MustGetPrincipal(c *gin.Context) service.Principal { principal, ok := GetPrincipal(c) if !ok { panic("principal not found in context - is AuthRequired middleware applied?") } return principal } // AdminRequired returns a Gin middleware that gates access to admin-only endpoints. // It must be applied after AuthRequired. func AdminRequired() gin.HandlerFunc { return func(c *gin.Context) { principal, ok := GetPrincipal(c) if !ok { api.Error(c, http.StatusUnauthorized, "missing user context") c.Abort() return } if !principal.IsAdmin { api.Error(c, http.StatusForbidden, "admin access required") c.Abort() return } c.Next() } }