stabilizing apps/web: SECOND BATCH - FIXING Playwright
This commit is contained in:
parent
798db1c376
commit
2b8ee6a1c4
7 changed files with 181 additions and 31 deletions
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"net/http" // MOD-P2-006: Pour pprof
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -52,6 +54,46 @@ func NewAPIRouter(db *database.Database, cfg *config.Config) *APIRouter {
|
|||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
fmt.Printf("🔍 [ROUTER] ENABLE_CLAMAV depuis env: '%s'\n", envValue)
|
||||
clamAVEnabled := getEnvBool("ENABLE_CLAMAV", true)
|
||||
fmt.Printf("🔍 [ROUTER] ENABLE_CLAMAV parsé: %v\n", clamAVEnabled)
|
||||
uploadConfig.ClamAVEnabled = clamAVEnabled
|
||||
|
||||
// Lire CLAMAV_REQUIRED depuis l'environnement (défaut: true pour sécurité)
|
||||
clamAVRequired := getEnvBool("CLAMAV_REQUIRED", true)
|
||||
uploadConfig.ClamAVRequired = clamAVRequired
|
||||
|
||||
fmt.Printf("🔧 [ROUTER] Configuration finale - ClamAVEnabled=%v, ClamAVRequired=%v\n",
|
||||
uploadConfig.ClamAVEnabled, 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 == "" {
|
||||
fmt.Printf("🔍 [ROUTER] Variable %s non définie, utilisation défaut: %v\n", key, defaultValue)
|
||||
return defaultValue
|
||||
}
|
||||
// Nettoyer la valeur (trim spaces)
|
||||
value = strings.TrimSpace(value)
|
||||
fmt.Printf("🔍 [ROUTER] Variable %s='%s' (trimmed)\n", key, value)
|
||||
if boolValue, err := strconv.ParseBool(value); err == nil {
|
||||
fmt.Printf("🔍 [ROUTER] Variable %s parsée: %v\n", key, boolValue)
|
||||
return boolValue
|
||||
}
|
||||
fmt.Printf("⚠️ [ROUTER] Erreur parsing %s='%s', utilisation défaut: %v\n", key, value, defaultValue)
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Setup configure toutes les routes de l'API
|
||||
func (r *APIRouter) Setup(router *gin.Engine) error {
|
||||
r.engine = router
|
||||
|
|
@ -350,7 +392,10 @@ func (r *APIRouter) setupTrackRoutes(router *gin.RouterGroup) {
|
|||
trackHandler.SetPermissionService(r.config.PermissionService)
|
||||
}
|
||||
// MOD-P1-001: Set upload validator for ClamAV scan before persistence
|
||||
uploadConfig := services.DefaultUploadConfig()
|
||||
// CORRECTION: Charger la config depuis l'environnement pour respecter ENABLE_CLAMAV
|
||||
uploadConfig := getUploadConfigWithEnv()
|
||||
fmt.Printf("🔧 [ROUTER] Configuration upload chargée - ClamAVEnabled=%v, ClamAVRequired=%v\n",
|
||||
uploadConfig.ClamAVEnabled, uploadConfig.ClamAVRequired)
|
||||
uploadValidator, err := services.NewUploadValidator(uploadConfig, r.logger)
|
||||
if err != nil {
|
||||
r.logger.Warn("Upload validator created with ClamAV unavailable - uploads will be rejected", zap.Error(err))
|
||||
|
|
@ -613,7 +658,8 @@ func (r *APIRouter) setupCorePublicRoutes(router *gin.Engine) {
|
|||
// Upload info endpoints (public, already in /api/v1)
|
||||
// MOD-P1-001-REFINEMENT: Permettre démarrage même si ClamAV down
|
||||
if r.db != nil && r.db.GormDB != nil {
|
||||
uploadConfig := services.DefaultUploadConfig()
|
||||
// CORRECTION: Charger la config depuis l'environnement pour respecter ENABLE_CLAMAV
|
||||
uploadConfig := getUploadConfigWithEnv()
|
||||
uploadValidator, err := services.NewUploadValidator(uploadConfig, r.logger)
|
||||
if err != nil {
|
||||
r.logger.Warn("Upload validator created with ClamAV unavailable - uploads will be rejected", zap.Error(err))
|
||||
|
|
@ -643,7 +689,8 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) {
|
|||
|
||||
// Services nécessaires
|
||||
sessionService := services.NewSessionService(r.db, r.logger)
|
||||
uploadConfig := services.DefaultUploadConfig()
|
||||
// CORRECTION: Charger la config depuis l'environnement pour respecter ENABLE_CLAMAV
|
||||
uploadConfig := getUploadConfigWithEnv()
|
||||
// MOD-P1-001-REFINEMENT: Permettre démarrage même si ClamAV down
|
||||
uploadValidator, err := services.NewUploadValidator(uploadConfig, r.logger)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -323,6 +323,13 @@ func (c *Config) initServices() error {
|
|||
|
||||
// Validateur d'upload
|
||||
uploadConfig := services.DefaultUploadConfig()
|
||||
// Lire ENABLE_CLAMAV depuis l'environnement (défaut: true pour sécurité en production)
|
||||
// En développement, peut être désactivé si ClamAV n'est pas disponible
|
||||
clamAVEnabled := getEnvBool("ENABLE_CLAMAV", true)
|
||||
uploadConfig.ClamAVEnabled = clamAVEnabled
|
||||
if !clamAVEnabled {
|
||||
c.Logger.Warn("ENABLE_CLAMAV=false - ClamAV virus scanning is disabled. This should only be used in development environments.")
|
||||
}
|
||||
// MOD-P1-002: Lire CLAMAV_REQUIRED depuis l'environnement (défaut: true pour sécurité)
|
||||
clamAVRequired := getEnvBool("CLAMAV_REQUIRED", true)
|
||||
uploadConfig.ClamAVRequired = clamAVRequired
|
||||
|
|
|
|||
|
|
@ -152,18 +152,24 @@ func (h *TrackHandler) respondWithError(c *gin.Context, httpStatus int, message
|
|||
// @Failure 500 {object} response.APIResponse "Internal Error"
|
||||
// @Router /tracks [post]
|
||||
func (h *TrackHandler) UploadTrack(c *gin.Context) {
|
||||
fmt.Printf("📥 [UPLOAD] Réception requête upload track\n")
|
||||
|
||||
// MOD-P1-RES-003: Utiliser helper fail-secure au lieu de c.MustGet()
|
||||
userID, ok := h.getUserID(c)
|
||||
if !ok {
|
||||
fmt.Printf("❌ [UPLOAD] Utilisateur non authentifié\n")
|
||||
return // Erreur déjà envoyée par getUserID
|
||||
}
|
||||
fmt.Printf("👤 [UPLOAD] User ID: %s\n", userID.String())
|
||||
|
||||
fileHeader, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur récupération fichier: %v\n", err)
|
||||
// MOD-P1-RES-001: Utiliser RespondWithAppError au lieu de response.BadRequest
|
||||
h.respondWithError(c, http.StatusBadRequest, "no file provided")
|
||||
return
|
||||
}
|
||||
fmt.Printf("📂 [UPLOAD] Fichier reçu: %s (taille: %d bytes)\n", fileHeader.Filename, fileHeader.Size)
|
||||
|
||||
// MOD-P1-001: Scanner le fichier avec ClamAV AVANT toute persistance
|
||||
if h.uploadValidator != nil {
|
||||
|
|
@ -221,10 +227,12 @@ func (h *TrackHandler) UploadTrack(c *gin.Context) {
|
|||
// MOD-P1-001: Le scan ClamAV a été fait ci-dessus, maintenant on peut persister
|
||||
// MOD-P2-008: UploadTrack crée le Track immédiatement et lance la copie en goroutine
|
||||
// MOD-P1-004: Ajouter timeout context pour opération DB critique (upload track)
|
||||
fmt.Printf("💾 [UPLOAD] Début sauvegarde track...\n")
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) // Upload peut prendre du temps
|
||||
defer cancel()
|
||||
track, err := h.trackService.UploadTrack(ctx, userID, fileHeader)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur sauvegarde track: %v\n", err)
|
||||
// Mapper les erreurs vers des messages utilisateur spécifiques
|
||||
errorMessage := h.mapTrackError(err)
|
||||
statusCode := h.getErrorStatusCode(err)
|
||||
|
|
|
|||
|
|
@ -158,15 +158,19 @@ func (s *TrackService) UploadTrack(ctx context.Context, userID uuid.UUID, fileHe
|
|||
}
|
||||
|
||||
// Créer le répertoire d'upload s'il n'existe pas
|
||||
fmt.Printf("📁 [UPLOAD] Vérification dossier upload: %s\n", s.uploadDir)
|
||||
if err := os.MkdirAll(s.uploadDir, 0755); err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur création dossier: %v\n", err)
|
||||
return nil, fmt.Errorf("%w: failed to create upload directory: %w", ErrStorageError, err)
|
||||
}
|
||||
fmt.Printf("✅ [UPLOAD] Dossier upload créé/vérifié: %s\n", s.uploadDir)
|
||||
|
||||
// Générer un nom de fichier unique
|
||||
timestamp := uuid.New()
|
||||
ext := filepath.Ext(fileHeader.Filename)
|
||||
filename := fmt.Sprintf("%d_%d%s", userID, timestamp, ext)
|
||||
filePath := filepath.Join(s.uploadDir, filename)
|
||||
fmt.Printf("💾 [UPLOAD] Chemin fichier de destination: %s\n", filePath)
|
||||
|
||||
// Déterminer le format depuis l'extension
|
||||
format := strings.TrimPrefix(strings.ToUpper(ext), ".")
|
||||
|
|
@ -194,11 +198,14 @@ func (s *TrackService) UploadTrack(ctx context.Context, userID uuid.UUID, fileHe
|
|||
}
|
||||
|
||||
if err := s.db.WithContext(ctx).Create(track).Error; err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur création enregistrement track: %v\n", err)
|
||||
return nil, fmt.Errorf("failed to create track record: %w", err)
|
||||
}
|
||||
fmt.Printf("✅ [UPLOAD] Enregistrement track créé en DB (ID: %s)\n", track.ID.String())
|
||||
|
||||
// MOD-P2-008: Lancer la copie fichier en goroutine avec suivi (context + cancellation)
|
||||
// La goroutine mettra à jour le Status quand terminé
|
||||
fmt.Printf("🚀 [UPLOAD] Lancement copie fichier en asynchrone...\n")
|
||||
go s.copyFileAsync(ctx, track.ID, fileHeader, filePath, userID)
|
||||
|
||||
// MOD-P2-003: Enregistrer la métrique business
|
||||
|
|
@ -222,30 +229,39 @@ func (s *TrackService) copyFileAsync(ctx context.Context, trackID uuid.UUID, fil
|
|||
defer cancel()
|
||||
|
||||
// Ouvrir le fichier source
|
||||
fmt.Printf("📂 [UPLOAD ASYNC] Ouverture fichier source...\n")
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD ASYNC] Erreur ouverture fichier source: %v\n", err)
|
||||
s.updateTrackStatus(copyCtx, trackID, models.TrackStatusFailed, fmt.Sprintf("Failed to open uploaded file: %v", err))
|
||||
s.cleanupFailedUpload(filePath, trackID, "failed to open source file")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
fmt.Printf("✅ [UPLOAD ASYNC] Fichier source ouvert\n")
|
||||
|
||||
// Créer le fichier de destination
|
||||
fmt.Printf("💾 [UPLOAD ASYNC] Création fichier destination: %s\n", filePath)
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD ASYNC] Erreur création fichier destination: %v\n", err)
|
||||
s.updateTrackStatus(copyCtx, trackID, models.TrackStatusFailed, fmt.Sprintf("Failed to create destination file: %v", err))
|
||||
s.cleanupFailedUpload(filePath, trackID, "failed to create destination file")
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
fmt.Printf("✅ [UPLOAD ASYNC] Fichier destination créé\n")
|
||||
|
||||
// Copier le fichier avec gestion d'erreurs
|
||||
fmt.Printf("📋 [UPLOAD ASYNC] Début copie fichier...\n")
|
||||
bytesWritten, err := io.Copy(dst, src)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD ASYNC] Erreur copie fichier: %v\n", err)
|
||||
s.updateTrackStatus(copyCtx, trackID, models.TrackStatusFailed, fmt.Sprintf("Failed to save file: %v", err))
|
||||
s.cleanupFailedUpload(filePath, trackID, fmt.Sprintf("copy failed: %v", err))
|
||||
return
|
||||
}
|
||||
fmt.Printf("✅ [UPLOAD ASYNC] Fichier copié avec succès (%d bytes écrits)\n", bytesWritten)
|
||||
|
||||
// Vérifier si le contexte a été annulé
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ func CORS(allowedOrigins []string) gin.HandlerFunc {
|
|||
}
|
||||
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
c.Header("Access-Control-Max-Age", "86400") // Cache preflight pour 24h
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"veza-backend-api/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"veza-backend-api/internal/models"
|
||||
)
|
||||
|
||||
// RefreshTokenService gère le stockage et la validation des refresh tokens
|
||||
|
|
@ -27,7 +28,9 @@ func NewRefreshTokenService(db *gorm.DB) *RefreshTokenService {
|
|||
// MIGRATION UUID: userID migré vers uuid.UUID
|
||||
func (s *RefreshTokenService) Store(userID uuid.UUID, token string, ttl time.Duration) error {
|
||||
tokenHash := s.hashToken(token)
|
||||
expiresAt := time.Now().Add(ttl)
|
||||
// Ajouter un buffer de 1 seconde pour garantir que expires_at > created_at
|
||||
// Cela évite les problèmes de précision des timestamps entre Go et PostgreSQL
|
||||
expiresAt := time.Now().Add(ttl).Add(1 * time.Second)
|
||||
|
||||
refreshToken := &models.RefreshToken{
|
||||
UserID: userID,
|
||||
|
|
@ -76,10 +79,12 @@ func (s *RefreshTokenService) Rotate(userID uuid.UUID, oldToken, newToken string
|
|||
|
||||
// Stocker le nouveau
|
||||
newTokenHash := s.hashToken(newToken)
|
||||
// Ajouter un buffer de 1 seconde pour garantir que expires_at > created_at
|
||||
// Cela évite les problèmes de précision des timestamps entre Go et PostgreSQL
|
||||
refreshToken := &models.RefreshToken{
|
||||
UserID: userID,
|
||||
TokenHash: newTokenHash,
|
||||
ExpiresAt: time.Now().Add(ttl),
|
||||
ExpiresAt: time.Now().Add(ttl).Add(1 * time.Second),
|
||||
}
|
||||
|
||||
return tx.Create(refreshToken).Error
|
||||
|
|
|
|||
|
|
@ -88,35 +88,82 @@ func DefaultUploadConfig() *UploadConfig {
|
|||
// MOD-P1-001-REFINEMENT: Fail-secure localisé - serveur démarre même si ClamAV down,
|
||||
// mais uploads seront rejetés lors de la validation
|
||||
func NewUploadValidator(config *UploadConfig, logger *zap.Logger) (*UploadValidator, error) {
|
||||
fmt.Printf("🔧 [UPLOAD VALIDATOR] Initialisation - ClamAVEnabled=%v, ClamAVRequired=%v\n", config.ClamAVEnabled, config.ClamAVRequired)
|
||||
|
||||
// EARLY RETURN: Si ClamAV est désactivé, ne JAMAIS toucher au réseau ClamAV
|
||||
if !config.ClamAVEnabled {
|
||||
fmt.Printf("✅ [UPLOAD VALIDATOR] ClamAV désactivé - Aucune connexion réseau ne sera tentée\n")
|
||||
logger.Info("ClamAV is disabled - virus scanning will be skipped",
|
||||
zap.Bool("clamav_enabled", config.ClamAVEnabled),
|
||||
zap.Bool("clamav_required", config.ClamAVRequired),
|
||||
)
|
||||
return &UploadValidator{
|
||||
logger: logger,
|
||||
clamdClient: nil, // Explicitement nil
|
||||
quarantineDir: config.QuarantineDir,
|
||||
clamAVRequiredButUnavailable: false,
|
||||
clamAVRequired: config.ClamAVRequired,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClamAV est activé - initialiser le client
|
||||
fmt.Printf("🛡️ [UPLOAD VALIDATOR] ClamAV activé - Initialisation client sur %s\n", config.ClamAVAddress)
|
||||
var clamdClient *clamd.Clamd
|
||||
clamAVRequiredButUnavailable := false
|
||||
|
||||
if config.ClamAVEnabled {
|
||||
clamdClient = clamd.NewClamd(config.ClamAVAddress)
|
||||
// Test connection - MOD-P1-001-REFINEMENT: Ne pas bloquer le démarrage, mais flag pour fail-secure
|
||||
if err := clamdClient.Ping(); err != nil {
|
||||
if config.ClamAVRequired {
|
||||
// MOD-P1-002: Si ClamAV est requis, rejeter les uploads (fail-secure)
|
||||
logger.Warn("ClamAV is enabled and required but unavailable - uploads will be rejected until ClamAV is available",
|
||||
zap.Error(err),
|
||||
zap.String("address", config.ClamAVAddress),
|
||||
)
|
||||
clamAVRequiredButUnavailable = true
|
||||
} else {
|
||||
// MOD-P1-002: Si ClamAV n'est pas requis, accepter uploads avec warning (mode dégradé)
|
||||
logger.Warn("ClamAV is enabled but unavailable and not required - uploads will be accepted without virus scanning (degraded mode)",
|
||||
zap.Error(err),
|
||||
zap.String("address", config.ClamAVAddress),
|
||||
zap.String("security_warning", "Virus scanning is disabled. This should only be used in development or with alternative security measures."),
|
||||
)
|
||||
clamAVRequiredButUnavailable = false
|
||||
}
|
||||
// Ne pas retourner d'erreur - le serveur peut démarrer
|
||||
} else {
|
||||
logger.Info("ClamAV connection successful")
|
||||
}
|
||||
// Créer le client ClamAV (cela ne devrait PAS faire de connexion immédiate)
|
||||
clamdClient = clamd.NewClamd(config.ClamAVAddress)
|
||||
fmt.Printf("🔌 [UPLOAD VALIDATOR] Client ClamAV créé - Test de connexion (Ping avec timeout 2s)...\n")
|
||||
|
||||
// Test connection avec timeout pour éviter blocage - MOD-P1-001-REFINEMENT: Ne pas bloquer le démarrage
|
||||
// Créer un contexte avec timeout court pour le Ping
|
||||
pingCtx, pingCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer pingCancel()
|
||||
|
||||
// Lancer le Ping dans une goroutine avec timeout
|
||||
pingDone := make(chan error, 1)
|
||||
go func() {
|
||||
pingDone <- clamdClient.Ping()
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case err = <-pingDone:
|
||||
// Ping terminé (succès ou erreur)
|
||||
fmt.Printf("🔌 [UPLOAD VALIDATOR] Ping terminé - erreur: %v\n", err)
|
||||
case <-pingCtx.Done():
|
||||
// Timeout - ClamAV ne répond pas
|
||||
err = fmt.Errorf("ClamAV ping timeout after 2s: %w", pingCtx.Err())
|
||||
fmt.Printf("⏱️ [UPLOAD VALIDATOR] Ping timeout - ClamAV ne répond pas\n")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ [UPLOAD VALIDATOR] ClamAV Ping échoué: %v\n", err)
|
||||
if config.ClamAVRequired {
|
||||
// MOD-P1-002: Si ClamAV est requis, rejeter les uploads (fail-secure)
|
||||
logger.Warn("ClamAV is enabled and required but unavailable - uploads will be rejected until ClamAV is available",
|
||||
zap.Error(err),
|
||||
zap.String("address", config.ClamAVAddress),
|
||||
)
|
||||
clamAVRequiredButUnavailable = true
|
||||
} else {
|
||||
// MOD-P1-002: Si ClamAV n'est pas requis, accepter uploads avec warning (mode dégradé)
|
||||
logger.Warn("ClamAV is enabled but unavailable and not required - uploads will be accepted without virus scanning (degraded mode)",
|
||||
zap.Error(err),
|
||||
zap.String("address", config.ClamAVAddress),
|
||||
zap.String("security_warning", "Virus scanning is disabled. This should only be used in development or with alternative security measures."),
|
||||
)
|
||||
clamAVRequiredButUnavailable = false
|
||||
}
|
||||
// Ne pas retourner d'erreur - le serveur peut démarrer
|
||||
} else {
|
||||
fmt.Printf("✅ [UPLOAD VALIDATOR] ClamAV Ping réussi - Connexion OK\n")
|
||||
logger.Info("ClamAV connection successful")
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [UPLOAD VALIDATOR] Validateur initialisé - clamdClient=%v, requiredButUnavailable=%v\n",
|
||||
clamdClient != nil, clamAVRequiredButUnavailable)
|
||||
|
||||
return &UploadValidator{
|
||||
logger: logger,
|
||||
clamdClient: clamdClient,
|
||||
|
|
@ -139,6 +186,16 @@ type ValidationResult struct {
|
|||
// ValidateFile valide un fichier uploadé
|
||||
// MOD-P1-001: Le scan ClamAV se fait AVANT toute persistance
|
||||
func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipart.FileHeader, fileType string) (*ValidationResult, error) {
|
||||
// DEBUG: Log de début de validation
|
||||
fmt.Printf("🚀 [UPLOAD VALIDATE] Début validation fichier: %s (taille: %d bytes, type: %s)\n", fileHeader.Filename, fileHeader.Size, fileType)
|
||||
fmt.Printf("🔍 [UPLOAD VALIDATE] État ClamAV - client=%v, required=%v, requiredButUnavailable=%v\n",
|
||||
uv.clamdClient != nil, uv.clamAVRequired, uv.clamAVRequiredButUnavailable)
|
||||
|
||||
// EARLY CHECK: Si ClamAV est complètement désactivé, on skip immédiatement
|
||||
if uv.clamdClient == nil {
|
||||
fmt.Printf("⏭️ [UPLOAD VALIDATE] ClamAV désactivé (client=nil) - scan antivirus ignoré\n")
|
||||
}
|
||||
|
||||
result := &ValidationResult{
|
||||
FileSize: fileHeader.Size,
|
||||
}
|
||||
|
|
@ -146,10 +203,12 @@ func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipa
|
|||
// Ouvrir le fichier
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur ouverture fichier: %v\n", err)
|
||||
result.Error = "Failed to open file"
|
||||
return result, err
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Printf("✅ [UPLOAD] Fichier ouvert avec succès\n")
|
||||
|
||||
// Lire les premiers bytes pour vérifier le magic number
|
||||
header := make([]byte, 512)
|
||||
|
|
@ -207,9 +266,11 @@ func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipa
|
|||
|
||||
// Scanner avec ClamAV si disponible
|
||||
if uv.clamdClient != nil {
|
||||
fmt.Printf("🛡️ [UPLOAD VALIDATE] ClamAV activé - Début scan antivirus...\n")
|
||||
file.Seek(0, 0)
|
||||
scanResult, err := uv.scanWithClamAV(ctx, file)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [UPLOAD] Erreur scan ClamAV: %v\n", err)
|
||||
uv.logger.Error("ClamAV scan failed",
|
||||
zap.Error(err),
|
||||
zap.String("filename", fileHeader.Filename),
|
||||
|
|
@ -221,6 +282,7 @@ func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipa
|
|||
|
||||
// MOD-P1-001: Si virus détecté, rejeter immédiatement (code 422)
|
||||
if scanResult != nil && scanResult.Status != "OK" {
|
||||
fmt.Printf("⚠️ [UPLOAD] Virus détecté: %s\n", scanResult.Description)
|
||||
result.Quarantined = true
|
||||
result.Error = fmt.Sprintf("Virus detected: %s", scanResult.Description)
|
||||
uv.logger.Warn("Virus detected in uploaded file",
|
||||
|
|
@ -230,10 +292,13 @@ func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipa
|
|||
return result, fmt.Errorf("clamav_infected: %s", scanResult.Description)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [UPLOAD VALIDATE] Scan ClamAV réussi - fichier propre\n")
|
||||
uv.logger.Info("File scanned successfully with ClamAV",
|
||||
zap.String("filename", fileHeader.Filename),
|
||||
zap.String("status", "clean"),
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("⏭️ [UPLOAD VALIDATE] ClamAV désactivé (client=nil) - scan ignoré, validation continue\n")
|
||||
}
|
||||
|
||||
// Valider l'extension du fichier
|
||||
|
|
@ -244,6 +309,7 @@ func (uv *UploadValidator) ValidateFile(ctx context.Context, fileHeader *multipa
|
|||
}
|
||||
|
||||
result.Valid = true
|
||||
fmt.Printf("✅ [UPLOAD VALIDATE] Validation complète - fichier accepté (Valid=true)\n")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue