veza/veza-backend-api/internal/middleware/csrf.go
senke cc2ebae4dc feat: Visual masterpiece - true light mode & premium UI
🎨 **True Light/Dark Mode**
- Implemented proper light mode with inverted color scheme
- Smooth theme transitions (0.3s ease)
- Light mode colors: white backgrounds, dark text, vibrant accents
- System theme detection with proper class application

🌈 **Enhanced Theme System**
- 4 color themes work in both light and dark modes
- Cyber (cyan/magenta), Ocean (blue/teal), Forest (green/lime), Sunset (orange/purple)
- Theme-specific glassmorphism effects
- Proper contrast in light mode

 **Premium Animations**
- Float, glow-pulse, slide-in, scale-in, rotate-in animations
- Smooth page transitions
- Hover effects with depth (lift, glow, scale)
- Micro-interactions on all interactive elements

🎯 **Visual Polish**
- Enhanced glassmorphism for light/dark modes
- Custom scrollbar with theme colors
- Beautiful text selection
- Focus indicators for accessibility
- Premium utility classes

🔧 **Technical Improvements**
- Updated UIStore to properly apply light/dark classes
- Added data-theme attribute for CSS targeting
- Smooth scroll behavior
- Optimized transitions

The app is now a visual masterpiece with perfect light/dark mode support!
2026-01-11 02:32:21 +01:00

193 lines
5.2 KiB
Go

package middleware
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
// CSRFMiddleware crée un middleware pour la protection CSRF
// Utilise Redis pour stocker les tokens CSRF associés aux utilisateurs
type CSRFMiddleware struct {
redisClient *redis.Client
logger *zap.Logger
ttl time.Duration // TTL pour les tokens CSRF (défaut: 1 heure)
env string // Environnement (development, staging, production)
}
// NewCSRFMiddleware crée une nouvelle instance du middleware CSRF
func NewCSRFMiddleware(redisClient *redis.Client, logger *zap.Logger) *CSRFMiddleware {
return &CSRFMiddleware{
redisClient: redisClient,
logger: logger,
ttl: 1 * time.Hour, // Tokens CSRF valides pendant 1 heure
env: "", // Sera défini via SetEnvironment
}
}
// SetEnvironment définit l'environnement pour le middleware
func (m *CSRFMiddleware) SetEnvironment(env string) {
m.env = env
}
// Middleware retourne le handler Gin pour la protection CSRF
func (m *CSRFMiddleware) Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// MVP: Désactiver CSRF en développement pour faciliter les tests
if m.env == "development" || m.env == "dev" {
c.Next()
return
}
// Ignorer GET, HEAD, OPTIONS (méthodes sûres)
method := c.Request.Method
if method == "GET" || method == "HEAD" || method == "OPTIONS" {
c.Next()
return
}
// Récupérer le userID depuis le contexte (défini par AuthMiddleware)
userIDInterface, exists := c.Get("user_id")
if !exists {
// Si pas d'utilisateur authentifié, pas besoin de CSRF
// (les routes publiques comme login/register sont exclues)
c.Next()
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
m.logger.Warn("Invalid user_id type in context for CSRF check")
c.Next()
return
}
// Récupérer le token CSRF depuis le header
token := c.GetHeader("X-CSRF-Token")
if token == "" {
c.JSON(403, gin.H{
"success": false,
"error": gin.H{
"code": 403,
"message": "CSRF token required",
},
})
c.Abort()
return
}
// Vérifier le token dans Redis
ctx := c.Request.Context()
key := m.getCSRFKey(userID)
storedToken, err := m.redisClient.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
m.logger.Warn("CSRF token not found in Redis",
zap.String("user_id", userID.String()),
zap.String("ip", c.ClientIP()),
)
c.JSON(403, gin.H{
"success": false,
"error": gin.H{
"code": 403,
"message": "Invalid or expired CSRF token",
},
})
c.Abort()
return
}
m.logger.Error("Failed to get CSRF token from Redis",
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(500, gin.H{
"success": false,
"error": gin.H{
"code": 500,
"message": "Internal server error",
},
})
c.Abort()
return
}
// Comparer les tokens
if storedToken != token {
m.logger.Warn("CSRF token mismatch",
zap.String("user_id", userID.String()),
zap.String("ip", c.ClientIP()),
)
c.JSON(403, gin.H{
"success": false,
"error": gin.H{
"code": 403,
"message": "Invalid CSRF token",
},
})
c.Abort()
return
}
// Token valide, continuer
c.Next()
}
}
// getCSRFKey génère la clé Redis pour un token CSRF
func (m *CSRFMiddleware) getCSRFKey(userID uuid.UUID) string {
return fmt.Sprintf("csrf:token:%s", userID.String())
}
// GenerateToken génère un nouveau token CSRF et le stocke dans Redis
func (m *CSRFMiddleware) GenerateToken(ctx context.Context, userID uuid.UUID) (string, error) {
// Générer un token aléatoire de 32 bytes (64 caractères hex)
tokenBytes := make([]byte, 32)
if _, err := rand.Read(tokenBytes); err != nil {
return "", fmt.Errorf("failed to generate CSRF token: %w", err)
}
token := hex.EncodeToString(tokenBytes)
// MVP: Si Redis n'est pas disponible, on retourne quand même le token
// mais on ne peut pas le stocker pour validation ultérieure.
// En développement, le middleware de validation ignorera cette absence.
if m.redisClient == nil {
if m.env == "development" || m.env == "dev" || m.env == "" {
return token, nil
}
return "", fmt.Errorf("redis client is nil and environment is not development")
}
// Stocker le token dans Redis avec TTL
key := m.getCSRFKey(userID)
if err := m.redisClient.Set(ctx, key, token, m.ttl).Err(); err != nil {
return "", fmt.Errorf("failed to store CSRF token: %w", err)
}
return token, nil
}
// GetToken récupère le token CSRF actuel pour un utilisateur
func (m *CSRFMiddleware) GetToken(ctx context.Context, userID uuid.UUID) (string, error) {
// Si Redis n'est pas disponible, on en génère un nouveau sans stocker
if m.redisClient == nil {
return m.GenerateToken(ctx, userID)
}
key := m.getCSRFKey(userID)
token, err := m.redisClient.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
// Pas de token existant, en générer un nouveau
return m.GenerateToken(ctx, userID)
}
return "", fmt.Errorf("failed to get CSRF token: %w", err)
}
return token, nil
}