package middleware import ( "errors" "net/http" "strings" "time" "github.com/labstack/echo/v4" "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) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { var tokenString string // Get token from Authorization header only. // Query parameter authentication is intentionally not supported // because tokens in URLs leak into server logs and browser history. authHeader := c.Request().Header.Get("Authorization") if authHeader != "" { // Check Bearer prefix parts := strings.SplitN(authHeader, " ", 2) if len(parts) == 2 && strings.ToLower(parts[0]) == "bearer" { tokenString = parts[1] } } if tokenString == "" { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Authorization required"}) } // 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 { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Invalid token"}) } if !token.Valid { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Token is not valid"}) } // Get admin user from database admin, err := adminRepo.FindByID(claims.AdminID) if err != nil { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Admin user not found"}) } // Check if admin is active if !admin.IsActive { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Admin account is disabled"}) } // Store admin and claims in context c.Set(AdminUserKey, admin) c.Set(AdminClaimsKey, claims) return next(c) } } } // 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() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { admin := c.Get(AdminUserKey) if admin == nil { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Admin authentication required"}) } adminUser, ok := admin.(*models.AdminUser) if !ok { return c.JSON(http.StatusUnauthorized, map[string]interface{}{"error": "Admin authentication required"}) } if !adminUser.IsSuperAdmin() { return c.JSON(http.StatusForbidden, map[string]interface{}{"error": "Super admin privileges required"}) } return next(c) } } }