package handlers import ( "net/http" "smoop-api/internal/crypto" "smoop-api/internal/models" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) // UserContext holds structured user information from JWT type UserContext struct { ID uint `json:"id"` Username string `json:"username"` Role models.Role `json:"role"` } func Auth(jwtMgr *crypto.JWTManager) gin.HandlerFunc { return func(c *gin.Context) { h := c.GetHeader("Authorization") if !strings.HasPrefix(h, "Bearer ") { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"}) return } tok := strings.TrimPrefix(h, "Bearer ") token, err := jwtMgr.Parse(tok) if err != nil || !token.Valid { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) return } claims, _ := token.Claims.(jwt.MapClaims) userContext := UserContext{ ID: uint(claims["sub"].(float64)), Username: claims["name"].(string), Role: models.Role(claims["role"].(string)), } c.Set("user", userContext) c.Set("claims", claims) c.Next() } } func RequireRole(role string) gin.HandlerFunc { return func(c *gin.Context) { userContext, exists := c.Get("user") if !exists { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) } user, ok := userContext.(UserContext) if !ok { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid user data"}) return } if string(user.Role) != role { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "forbidden"}) return } c.Next() } } // helpers used by handlers func MustClaims(c *gin.Context) map[string]interface{} { val, ok := c.Get("claims") if !ok { return jwt.MapClaims{} } switch t := val.(type) { case jwt.MapClaims: return t case map[string]interface{}: return jwt.MapClaims(t) default: return jwt.MapClaims{} } } func ClaimUserID(claims map[string]interface{}) uint { if claims == nil { return 0 } if v, ok := claims["sub"]; ok { switch n := v.(type) { case float64: return uint(n) case int: return uint(n) case int64: return uint(n) } } return 0 } func ClaimRole(claims map[string]interface{}) string { if r, ok := claims["role"].(string); ok { return r } return "" } // New helper to get UserContext from context func GetUserContext(c *gin.Context) (UserContext, bool) { userContext, exists := c.Get("user") if !exists { return UserContext{}, false } user, ok := userContext.(UserContext) return user, ok }