veza/veza-backend-api/internal/api/router.go

338 lines
12 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package api
import (
"context"
2025-12-13 02:34:34 +00:00
"fmt"
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
"os"
"strconv"
"strings"
"time"
2025-12-03 19:29:37 +00:00
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"veza-backend-api/internal/config"
"veza-backend-api/internal/database"
"veza-backend-api/internal/handlers"
2025-12-03 19:29:37 +00:00
"veza-backend-api/internal/middleware"
"veza-backend-api/internal/repositories"
"veza-backend-api/internal/services"
2025-12-03 19:29:37 +00:00
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
authcore "veza-backend-api/internal/core/auth"
)
// APIRouter gère la configuration des routes de l'API
type APIRouter struct {
db *database.Database
config *config.Config
engine *gin.Engine
logger *zap.Logger
versionManager *VersionManager // BE-SVC-019: API versioning manager
monitoringService *services.MonitoringAlertingService // INT-021: API monitoring and alerting
authService *authcore.AuthService // Set by setupAuthRoutes for admin unlock
notificationService *services.NotificationService // Shared for N1.2 Web Push
pushService *services.PushService // N1 Web Push
2025-12-03 19:29:37 +00:00
}
// NewAPIRouter crée une nouvelle instance de APIRouter
func NewAPIRouter(db *database.Database, cfg *config.Config) *APIRouter {
logger := zap.L()
2025-12-03 19:29:37 +00:00
return &APIRouter{
db: db,
config: cfg,
logger: logger,
versionManager: NewVersionManager(logger), // BE-SVC-019: Initialize version manager
2025-12-03 19:29:37 +00:00
}
}
// applyCSRFProtection applique le middleware CSRF à un groupe de routes protégées
// BE-SEC-004: Ensure all POST/PUT/DELETE endpoints validate CSRF tokens
// INT-AUTH-001: Fail-fast in production if Redis unavailable (CSRF requires Redis)
func (r *APIRouter) applyCSRFProtection(protectedGroup *gin.RouterGroup) {
if r.config == nil {
if r.logger != nil {
r.logger.Error("CSRF protection cannot be applied: config is nil")
}
// In production, fail-fast (but we can't check env if config is nil)
// This should not happen in normal operation, but we log it
return
}
if r.config.RedisClient == nil {
// In non-production, log warning and continue without CSRF
// Production case is handled by early validation in Setup() (audit 1.4)
if r.logger != nil {
r.logger.Warn("Redis not available - CSRF protection disabled (non-production environment)")
}
return
}
// CSRF protection active
if r.logger != nil {
r.logger.Info("CSRF protection enabled",
zap.String("environment", r.config.Env),
)
}
csrfMiddleware := middleware.NewCSRFMiddleware(r.config.RedisClient, r.logger)
// MVP: Désactiver CSRF en développement
csrfMiddleware.SetEnvironment(r.config.Env)
protectedGroup.Use(csrfMiddleware.Middleware())
}
// getUploadConfigWithEnv charge la configuration d'upload depuis l'environnement
// Cette fonction garantit que ENABLE_CLAMAV et CLAMAV_REQUIRED sont correctement appliqués
func getUploadConfigWithEnv() *services.UploadConfig {
uploadConfig := services.DefaultUploadConfig()
// Lire ENABLE_CLAMAV depuis l'environnement (défaut: true pour sécurité en production)
envValue := os.Getenv("ENABLE_CLAMAV")
zap.L().Debug("ENABLE_CLAMAV from env", zap.String("value", envValue))
clamAVEnabled := getEnvBool("ENABLE_CLAMAV", true)
zap.L().Debug("ENABLE_CLAMAV parsed", zap.Bool("value", clamAVEnabled))
uploadConfig.ClamAVEnabled = clamAVEnabled
// Lire CLAMAV_REQUIRED depuis l'environnement (défaut: true pour sécurité)
clamAVRequired := getEnvBool("CLAMAV_REQUIRED", true)
uploadConfig.ClamAVRequired = clamAVRequired
zap.L().Info("Upload config final",
zap.Bool("clamav_enabled", uploadConfig.ClamAVEnabled),
zap.Bool("clamav_required", uploadConfig.ClamAVRequired))
return uploadConfig
}
// getEnvBool récupère une variable d'environnement booléenne avec une valeur par défaut
func getEnvBool(key string, defaultValue bool) bool {
value := os.Getenv(key)
if value == "" {
zap.L().Debug("Env var undefined, using default", zap.String("key", key), zap.Bool("default", defaultValue))
return defaultValue
}
// Nettoyer la valeur (trim spaces)
value = strings.TrimSpace(value)
zap.L().Debug("Env var trimmed", zap.String("key", key), zap.String("value", value))
if boolValue, err := strconv.ParseBool(value); err == nil {
zap.L().Debug("Env var parsed", zap.String("key", key), zap.Bool("value", boolValue))
return boolValue
}
zap.L().Warn("Env var parse error, using default", zap.String("key", key), zap.String("value", value), zap.Bool("default", defaultValue))
return defaultValue
}
2025-12-03 19:29:37 +00:00
// Setup configure toutes les routes de l'API
2025-12-13 02:34:34 +00:00
func (r *APIRouter) Setup(router *gin.Engine) error {
2025-12-03 19:29:37 +00:00
r.engine = router
// Audit 1.4 P0: Graceful error if Redis down in production (no panic/Fatal)
if r.config != nil && r.config.Env == config.EnvProduction && r.config.RedisClient == nil {
return fmt.Errorf("CSRF protection requires Redis in production. Redis is unavailable")
}
// INT-021: Initialize monitoring and alerting service
// Initialize monitoring service if Prometheus URL is configured
prometheusURL := os.Getenv("PROMETHEUS_URL")
if prometheusURL != "" {
monitoringConfig := services.MonitoringConfig{
PrometheusURL: prometheusURL,
Logger: r.logger,
}
monitoringService, err := services.NewMonitoringAlertingService(monitoringConfig)
if err != nil {
r.logger.Warn("Failed to initialize monitoring service", zap.Error(err))
} else {
r.monitoringService = monitoringService
// Add default alert rules
for _, rule := range services.GetDefaultAlertRules() {
monitoringService.AddAlertRule(rule)
}
// Start monitoring in background
go func() {
ctx := context.Background()
if err := monitoringService.StartMonitoring(ctx, 30*time.Second); err != nil {
r.logger.Error("Monitoring service stopped", zap.Error(err))
}
}()
r.logger.Info("Monitoring and alerting service initialized", zap.String("prometheus_url", prometheusURL))
}
} else {
r.logger.Info("Monitoring service disabled (PROMETHEUS_URL not configured)")
}
// P1.1: CORS middleware MUST be first to ensure headers are always present
// Even if subsequent middlewares reject the request (panic, timeout, error),
// the CORS headers will be set, preventing intermittent CORS errors
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
// SECURITY: CORS configuration - use config.CORSOrigins strictly (P0-SECURITY)
// No fallback to CORSDefault() to avoid wildcard in production
2025-12-13 02:34:34 +00:00
// MOD-P0-001: Apply CORS middleware even if CORSOrigins is empty (strict mode - reject all origins)
// The middleware itself handles empty list correctly (rejects all origins)
if r.config != nil {
// INT-018: Validate CORS configuration before applying middleware
// In production, this will fail startup if CORS is misconfigured
if err := middleware.ValidateCORSConfiguration(r.config.CORSOrigins, r.config.Env, r.logger); err != nil {
// In production, fail startup if CORS is misconfigured
if r.config.Env == "production" {
r.logger.Fatal("CORS configuration validation failed - startup aborted", zap.Error(err))
} else {
// In development/staging, log error but continue
r.logger.Error("CORS configuration validation failed", zap.Error(err))
}
}
2025-12-03 19:29:37 +00:00
router.Use(middleware.CORS(r.config.CORSOrigins))
2025-12-13 02:34:34 +00:00
if len(r.config.CORSOrigins) == 0 {
r.logger.Warn("CORS origins not configured - strict mode enabled: ALL CORS requests will be rejected.")
}
2025-12-03 19:29:37 +00:00
} else {
2025-12-13 02:34:34 +00:00
// Fallback: if config is nil, apply CORS with empty list (strict mode)
router.Use(middleware.CORS([]string{}))
r.logger.Warn("Config is nil - CORS middleware applied in strict mode (reject all origins).")
2025-12-03 19:29:37 +00:00
}
// Middlewares globaux (after CORS)
router.Use(middleware.RequestLogger(r.logger)) // Utilisation du structured logger
router.Use(middleware.Metrics()) // Prometheus Metrics
router.Use(middleware.SentryRecover(r.logger)) // Sentry error tracking
router.Use(middleware.SecurityHeaders()) // MOD-P2-005: Security headers (HSTS, CSP, etc.)
// INT-021: Add API monitoring middleware to track failures and trigger alerts
router.Use(middleware.APIMonitoringMiddleware(r.logger, r.monitoringService))
// MOD-P1-005: Determine if stack traces should be included in logs
// Stack traces only in dev/DEBUG mode (not in production)
// Include if: APP_ENV=development OR LOG_LEVEL=DEBUG
// MOD-P1-005: Determine if stack traces should be included in logs
// Stack traces only in dev/DEBUG mode (not in production)
includeStackTrace := r.config.Env == config.EnvDevelopment || r.config.LogLevel == "DEBUG"
router.Use(middleware.ErrorHandler(r.logger, r.config.ErrorMetrics, includeStackTrace))
router.Use(middleware.Recovery(r.logger, includeStackTrace))
2025-12-03 19:29:37 +00:00
router.Use(middleware.RequestID())
2025-12-13 02:34:34 +00:00
// Global Timeout middleware (PR-6)
// MOD-P0-003: Removed duplicate timeout middleware registration
router.Use(middleware.Timeout(r.config.HandlerTimeout))
2025-12-03 19:29:37 +00:00
// Rate limiting via config.RateLimiter si disponible, sinon utiliser SimpleRateLimiter
// Toujours actif (A04) — limites assouplies en dev via config
if r.config != nil {
if r.config.RateLimiter != nil {
router.Use(r.config.RateLimiter.RateLimitMiddleware())
} else if r.config.SimpleRateLimiter != nil {
router.Use(r.config.SimpleRateLimiter.Middleware())
}
2025-12-03 19:29:37 +00:00
}
// Swagger Documentation — disabled in production (A05)
if r.config == nil || (r.config.Env != config.EnvProduction && r.config.Env != "prod") {
swaggerHandler := func(c *gin.Context) {
if c.Param("any") == "/doc.json" {
if _, err := os.Stat("./docs/swagger.json"); err == nil {
c.File("./docs/swagger.json")
return
}
}
ginSwagger.WrapHandler(swaggerFiles.Handler)(c)
}
router.GET("/swagger/*any", swaggerHandler)
router.GET("/docs", ginSwagger.WrapHandler(swaggerFiles.Handler))
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
2025-12-03 19:29:37 +00:00
// BE-SVC-019: API versioning endpoint (before version middleware)
router.GET("/api/versions", VersionInfoHandler(r.versionManager))
// BE-SVC-019: Apply version middleware to API routes
router.Use(VersionMiddleware(r.versionManager))
// P1.6: Health endpoint for Docker/K8s healthchecks
// Must be before other routes to avoid middleware overhead
// DUPLICATE REMOVED to fix panic.
2025-12-03 19:29:37 +00:00
// Routes core publiques (health, metrics, upload info)
r.setupCorePublicRoutes(router)
2025-12-16 16:23:49 +00:00
// Setup internal routes (both legacy and modern) before v1 group
// These need to be on the root router, not under /api/v1
r.setupInternalRoutes(router)
2025-12-03 19:29:37 +00:00
// Groupe API v1 (nouveau frontend React)
v1 := router.Group("/api/v1")
{
2026-02-07 19:36:48 +00:00
// Auth routes first so r.authService is set for admin unlock in setupCoreProtectedRoutes
2025-12-13 02:34:34 +00:00
if err := r.setupAuthRoutes(v1); err != nil {
return err
}
2026-02-07 19:36:48 +00:00
// Routes core protégées (sessions, uploads, audit, admin, conversations)
r.setupCoreProtectedRoutes(v1)
2025-12-03 19:29:37 +00:00
// Action 5.2.1.1: Validation endpoint for pre-validation
r.setupValidateRoutes(v1)
2025-12-03 19:29:37 +00:00
// Réactivation des routes User et Track pour Phase 1
r.setupUserRoutes(v1)
r.setupTrackRoutes(v1)
// BE-API-007: Roles management routes
r.setupRoleRoutes(v1)
2025-12-03 19:29:37 +00:00
// Réactivation des routes Chat pour Phase 4
r.setupChatRoutes(v1)
// Réactivation des routes Playlists pour Phase 5
r.setupPlaylistRoutes(v1)
// Réactivation des routes Webhooks
r.setupWebhookRoutes(v1)
2025-12-03 19:29:37 +00:00
// Marketplace Routes (v1.2.0)
r.setupMarketplaceRoutes(v1)
// BE-API-035: Analytics routes
r.setupAnalyticsRoutes(v1)
// Social Routes
r.setupSocialRoutes(v1)
// Inventory / Gear Routes
r.setupGearRoutes(v1)
// Queue Routes
r.setupQueueRoutes(v1)
// Developer Portal (API Keys)
r.setupDeveloperRoutes(v1)
// Live Streams Routes
r.setupLiveRoutes(v1)
// Cloud Storage Routes (v0.501 C1)
r.setupCloudRoutes(v1)
// Unified search GET /search (tracks, users, playlists)
r.setupSearchRoutes(v1)
2025-12-03 19:29:37 +00:00
}
2025-12-13 02:34:34 +00:00
return nil
2025-12-03 19:29:37 +00:00
}
// setupChatRoutes configure les routes de chat
func (r *APIRouter) setupChatRoutes(router *gin.RouterGroup) {
// BE-API-006: Use NewChatServiceWithDB to enable stats functionality
chatService := services.NewChatServiceWithDB(r.config.ChatJWTSecret, r.db.GormDB, r.logger)
2025-12-03 19:29:37 +00:00
userRepo := repositories.NewGormUserRepository(r.db.GormDB)
userService := services.NewUserServiceWithDB(userRepo, r.db.GormDB)
chatHandler := handlers.NewChatHandler(chatService, userService, r.logger)
chat := router.Group("/chat")
{
if r.config.AuthMiddleware != nil {
chat.Use(r.config.AuthMiddleware.RequireAuth())
// BE-SEC-004: Apply CSRF protection to all state-changing endpoints
r.applyCSRFProtection(chat)
2025-12-03 19:29:37 +00:00
chat.POST("/token", chatHandler.GetToken)
chat.GET("/stats", chatHandler.GetStats) // BE-API-006: Chat stats endpoint
2025-12-03 19:29:37 +00:00
}
}
}