diff --git a/apps/api-server/internal/middleware/auth.go b/apps/api-server/internal/middleware/auth.go new file mode 100644 index 0000000..4e5797a --- /dev/null +++ b/apps/api-server/internal/middleware/auth.go @@ -0,0 +1,180 @@ +package middleware + +import ( + "net/http" + "slices" + "strings" + + "github.com/gin-gonic/gin" + "github.com/nutcas3/telecom-platform/apps/api-server/internal/models" + "github.com/nutcas3/telecom-platform/apps/api-server/internal/rbac" + "github.com/nutcas3/telecom-platform/apps/api-server/internal/services" + "gorm.io/gorm" +) + +// AuthMiddleware creates a JWT authentication middleware +func AuthMiddleware(authService *services.AuthService) gin.HandlerFunc { + return func(c *gin.Context) { + // Get token from Authorization header + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"}) + c.Abort() + return + } + + // Extract token from "Bearer " format + tokenParts := strings.Split(authHeader, " ") + if len(tokenParts) != 2 || tokenParts[0] != "Bearer" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"}) + c.Abort() + return + } + + token := tokenParts[1] + + // Validate token + claims, err := authService.GetJWTService().ValidateToken(token) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + c.Abort() + return + } + + // Get user from database + user, err := authService.GetUserByID(claims.UserID) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + c.Abort() + return + } + + // Store user and claims in context + c.Set("user", user) + c.Set("user_id", claims.UserID) + c.Set("username", claims.Username) + c.Set("role", claims.Role) + + c.Next() + } +} + +// RequirePermission creates a middleware that checks for specific permissions +func RequirePermission(resource, action string) gin.HandlerFunc { + return func(c *gin.Context) { + user, exists := c.Get("user") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + c.Abort() + return + } + + // Get database from context (should be set by the main server) + db, exists := c.Get("db") + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Database not available"}) + c.Abort() + return + } + + // Check permission + if !user.(*models.User).HasPermission(db.(*gorm.DB), resource, action) { + c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) + c.Abort() + return + } + + c.Next() + } +} + +// RequireCasbinPermission creates a middleware that checks for specific permissions using Casbin +func RequireCasbinPermission(casbinService *rbac.CasbinService, resource, action string) gin.HandlerFunc { + return func(c *gin.Context) { + userID, exists := c.Get("user_id") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + c.Abort() + return + } + + allowed, err := casbinService.CheckPermission(c.Request.Context(), userID.(uint), resource, action) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check permissions"}) + c.Abort() + return + } + + if !allowed { + c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) + c.Abort() + return + } + + c.Next() + } +} + +// RequireRole creates a middleware that checks for specific roles (legacy, use RequireCasbinPermission for new features) +func RequireRole(allowedRoles ...string) gin.HandlerFunc { + return func(c *gin.Context) { + role, exists := c.Get("role") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + c.Abort() + return + } + + userRole := role.(string) + if slices.Contains(allowedRoles, userRole) { + c.Next() + return + } + + c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient role permissions"}) + c.Abort() + } +} + +// OptionalAuthMiddleware creates an optional authentication middleware +func OptionalAuthMiddleware(authService *services.AuthService) gin.HandlerFunc { + return func(c *gin.Context) { + // Get token from Authorization header + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.Next() + return + } + + // Extract token from "Bearer " format + tokenParts := strings.Split(authHeader, " ") + if len(tokenParts) != 2 || tokenParts[0] != "Bearer" { + c.Next() + return + } + + token := tokenParts[1] + + // Validate token + claims, err := authService.GetJWTService().ValidateToken(token) + if err != nil { + c.Next() + return + } + + // Get user from database + user, err := authService.GetUserByID(claims.UserID) + if err != nil { + c.Next() + return + } + + // Store user and claims in context + c.Set("user", user) + c.Set("user_id", claims.UserID) + c.Set("username", claims.Username) + c.Set("role", claims.Role) + + c.Next() + } +}