package api import ( "errors" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "hightube/internal/db" "hightube/internal/model" "hightube/internal/monitor" "hightube/internal/utils" ) const adminSessionCookieName = "hightube_admin_session" // AuthMiddleware intercepts requests, validates JWT, and injects user_id into context func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { user, err := authenticateRequest(c) if err != nil { switch { case errors.Is(err, errMissingToken): c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization is required"}) case errors.Is(err, errInvalidToken): c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"}) case errors.Is(err, errUserNotFound): c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) case errors.Is(err, errDisabledAccount): c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"}) default: c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication failed"}) } c.Abort() return } c.Set("user_id", user.ID) c.Set("username", user.Username) c.Set("role", user.Role) c.Next() } } func AdminMiddleware() gin.HandlerFunc { return func(c *gin.Context) { role, ok := c.Get("role") if !ok || role != "admin" { c.JSON(http.StatusForbidden, gin.H{"error": "admin access required"}) c.Abort() return } c.Next() } } func RequestMetricsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { monitor.IncrementRequestCount() c.Next() if c.Writer.Status() >= http.StatusBadRequest { monitor.IncrementErrorCount() } } } var ( errMissingToken = errors.New("missing token") errInvalidToken = errors.New("invalid token") errUserNotFound = errors.New("user not found") errDisabledAccount = errors.New("disabled account") ) func authenticateRequest(c *gin.Context) (*model.User, error) { tokenStr := extractToken(c) if tokenStr == "" { return nil, errMissingToken } claims, err := utils.ParseToken(tokenStr) if err != nil { return nil, errInvalidToken } userID, err := strconv.ParseUint(claims.Subject, 10, 32) if err != nil { return nil, errInvalidToken } var user model.User if err := db.DB.First(&user, uint(userID)).Error; err != nil { return nil, errUserNotFound } if !user.Enabled { return nil, errDisabledAccount } return &user, nil } func extractToken(c *gin.Context) string { authHeader := strings.TrimSpace(c.GetHeader("Authorization")) if authHeader != "" { parts := strings.Split(authHeader, " ") if len(parts) == 2 && parts[0] == "Bearer" { return strings.TrimSpace(parts[1]) } } cookieToken, err := c.Cookie(adminSessionCookieName) if err == nil { return strings.TrimSpace(cookieToken) } return "" } // CORSMiddleware handles cross-origin requests from web clients func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } }