Files
telecom-platform/apps/api-server/internal/middleware/auth.go
T
nutcas3 3eab9c7d60 feat: Add authentication middleware with JWT validation, RBAC permission checks, and role-based access control
- Add AuthMiddleware with Authorization header parsing, Bearer token extraction, JWT validation, user lookup, and context storage
- Add RequirePermission middleware with user authentication check, database context retrieval, and permission validation
- Add RequireCasbinPermission middleware with user_id context check, Casbin permission validation, and error handling
- Add RequireRole middleware
2026-04-23 02:38:13 +03:00

181 lines
4.5 KiB
Go

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 <token>" 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 <token>" 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()
}
}