Files
MyClub/internal/middleware/auth.go
T
Tomas Dvorak 8762bde4bf dev day #89
2025-11-11 10:29:30 +01:00

170 lines
4.3 KiB
Go

package middleware
import (
"net/http"
"strings"
"fotbal-club/internal/config"
"fotbal-club/internal/models"
"fotbal-club/pkg/utils"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// JWTAuth is a middleware that checks for a valid JWT token
func JWTAuth(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
// Admin token shortcut (DEV/TEST ONLY): allow only outside production
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" && config.AppConfig.AdminAccessToken != "" {
header := c.GetHeader("X-Admin-Token")
if header != "" && header == config.AppConfig.AdminAccessToken {
c.Set("userRole", "admin")
c.Set("user", &models.User{Role: "admin"})
c.Next()
return
}
}
authHeader := c.GetHeader("Authorization")
var tokenString string
if authHeader != "" {
// Extract the token from the header (format: "Bearer <token>")
tokenParts := strings.Split(authHeader, " ")
if len(tokenParts) == 2 && tokenParts[0] == "Bearer" {
tokenString = tokenParts[1]
} else {
// If header present but malformed, reject early
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
c.Abort()
return
}
} else {
// Fallback: try HttpOnly cookie set by server
if cookie, err := c.Request.Cookie("auth_token"); err == nil {
tokenString = cookie.Value
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header or auth cookie is required"})
c.Abort()
return
}
}
claims, err := utils.ParseJWT(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
}
// Check if user exists
var user models.User
if err := db.First(&user, claims.UserID).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
c.Abort()
return
}
// Add user information to the context
c.Set("user", &user)
// Also expose parsed JWT claims for helpers/utilities
c.Set("claims", claims)
c.Set("userID", user.ID)
c.Set("userRole", user.Role)
c.Next()
}
}
// JWTOptional attempts to authenticate the request if a token or auth cookie is present.
func JWTOptional(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" {
if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" {
c.Set("userRole", "admin")
c.Set("user", &models.User{Role: "admin"})
c.Next()
return
}
}
var tokenString string
if authHeader := c.GetHeader("Authorization"); authHeader != "" {
parts := strings.Split(authHeader, " ")
if len(parts) == 2 && parts[0] == "Bearer" {
tokenString = parts[1]
}
}
if tokenString == "" {
if cookie, err := c.Request.Cookie("auth_token"); err == nil {
tokenString = cookie.Value
}
}
if tokenString == "" {
c.Next()
return
}
claims, err := utils.ParseJWT(tokenString)
if err != nil {
c.Next()
return
}
var user models.User
if err := db.First(&user, claims.UserID).Error; err != nil {
c.Next()
return
}
c.Set("user", &user)
c.Set("claims", claims)
c.Set("userID", user.ID)
c.Set("userRole", user.Role)
c.Next()
}
}
// DevBypass checks for special dev header and grants admin role when not in production
func DevBypass() gin.HandlerFunc {
return func(c *gin.Context) {
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" {
if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" {
c.Set("userRole", "admin")
// set a placeholder user
c.Set("user", &models.User{Role: "admin"})
c.Next()
return
}
}
c.Next()
}
}
// RoleAuth is a middleware that checks if the user has the required role
// Admin always has access. Editor has access to content creation (articles, activities).
func RoleAuth(requiredRole string) gin.HandlerFunc {
return func(c *gin.Context) {
userRole, exists := c.Get("userRole")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User role not found"})
c.Abort()
return
}
// Admin always has full access
if userRole == "admin" {
c.Next()
return
}
// Check if user has the required role
if userRole != requiredRole {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
c.Abort()
return
}
c.Next()
}
}