package middleware import ( "net/http" "os" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) var jwtSecret = []byte(os.Getenv("JWT_SECRET")) type Claims struct { UserID string `json:"user_id"` Role string `json:"role"` jwt.RegisteredClaims } func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { log := StructuredLog(c).With(). Str("event", "auth_middleware"). Logger() var tokenString string cookie, err := c.Cookie("auth_token") if err == nil && cookie != "" { tokenString = cookie } if tokenString == "" { authHeader := c.GetHeader("Authorization") if authHeader != "" { parts := strings.Split(authHeader, " ") if len(parts) == 2 && parts[0] == "Bearer" { tokenString = parts[1] } } } if tokenString == "" { log.Warn().Str("reason", "no_token").Msg("Auth failed - no token provided") abortUnauthorized(c) return } claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrTokenSignatureInvalid } return jwtSecret, nil }) if err != nil { log.Warn(). Str("reason", "token_parse_error"). Err(err). Msg("Auth failed - token parse error") abortUnauthorized(c) return } if !token.Valid { log.Warn().Str("reason", "invalid_token").Msg("Auth failed - invalid token") abortUnauthorized(c) return } c.Set("user_id", claims.UserID) c.Set("role", claims.Role) c.Set("username", claims.UserID) log.Debug().Str("user_id", claims.UserID).Str("role", claims.Role).Msg("Auth successful") c.Next() } } func abortUnauthorized(c *gin.Context) { if strings.Contains(c.GetHeader("Accept"), "text/html") { c.Redirect(http.StatusFound, "/login") } else { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "unauthorized", }) } c.Abort() } func RequireRole(roles ...string) gin.HandlerFunc { return func(c *gin.Context) { log := StructuredLog(c).With(). Str("event", "role_check"). Logger() roleValue, exists := c.Get("role") if !exists { log.Warn().Str("reason", "no_role").Msg("Role check failed - no role in context") abortForbidden(c) return } userRole, ok := roleValue.(string) if !ok { log.Warn().Str("reason", "invalid_role_type").Msg("Role check failed - invalid role type") abortForbidden(c) return } for _, allowed := range roles { if userRole == allowed { log.Debug().Str("required_roles", strings.Join(roles, ",")).Str("user_role", userRole).Msg("Role check passed") c.Next() return } } log.Warn(). Str("required_roles", strings.Join(roles, ",")). Str("user_role", userRole). Msg("Role check failed - insufficient permissions") abortForbidden(c) } } func abortForbidden(c *gin.Context) { if strings.Contains(c.GetHeader("Accept"), "text/html") { c.Redirect(http.StatusFound, "/") } else { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "error": "forbidden", }) } c.Abort() }