package middleware import ( "errors" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/treytartt/casera-api/internal/config" "github.com/treytartt/casera-api/internal/models" "github.com/treytartt/casera-api/internal/repositories" ) const ( // AdminUserKey is the context key for the authenticated admin user AdminUserKey = "admin_user" // AdminClaimsKey is the context key for JWT claims AdminClaimsKey = "admin_claims" ) // AdminClaims represents the JWT claims for admin authentication type AdminClaims struct { AdminID uint `json:"admin_id"` Email string `json:"email"` Role models.AdminRole `json:"role"` jwt.RegisteredClaims } // AdminAuthMiddleware creates a middleware that validates admin JWT tokens func AdminAuthMiddleware(cfg *config.Config, adminRepo *repositories.AdminRepository) gin.HandlerFunc { return func(c *gin.Context) { var tokenString string // Get token from Authorization header authHeader := c.GetHeader("Authorization") if authHeader != "" { // Check Bearer prefix parts := strings.SplitN(authHeader, " ", 2) if len(parts) == 2 && strings.ToLower(parts[0]) == "bearer" { tokenString = parts[1] } } // If no header token, check query parameter (for WebSocket connections) if tokenString == "" { tokenString = c.Query("token") } if tokenString == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization required"}) return } // Parse and validate token claims := &AdminClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { // Validate signing method if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } return []byte(cfg.Security.SecretKey), nil }) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) return } if !token.Valid { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token is not valid"}) return } // Get admin user from database admin, err := adminRepo.FindByID(claims.AdminID) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Admin user not found"}) return } // Check if admin is active if !admin.IsActive { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Admin account is disabled"}) return } // Store admin and claims in context c.Set(AdminUserKey, admin) c.Set(AdminClaimsKey, claims) c.Next() } } // GenerateAdminToken creates a new JWT token for an admin user func GenerateAdminToken(admin *models.AdminUser, cfg *config.Config) (string, error) { // Token expires in 24 hours expirationTime := time.Now().Add(24 * time.Hour) claims := &AdminClaims{ AdminID: admin.ID, Email: admin.Email, Role: admin.Role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(time.Now()), Subject: admin.Email, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(cfg.Security.SecretKey)) } // RequireSuperAdmin middleware requires the admin to have super_admin role func RequireSuperAdmin() gin.HandlerFunc { return func(c *gin.Context) { admin, exists := c.Get(AdminUserKey) if !exists { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Admin authentication required"}) return } adminUser := admin.(*models.AdminUser) if !adminUser.IsSuperAdmin() { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Super admin privileges required"}) return } c.Next() } }