package handlers import ( "net/http" "strings" "time" "veza-backend-api/internal/core/auth" "veza-backend-api/internal/dto" apperrors "veza-backend-api/internal/errors" // "veza-backend-api/internal/response" // Removed this import "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // Login gère la connexion des utilisateurs // T0203: Intègre création de session après login avec IP et User-Agent // P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs func Login(authService *auth.AuthService, sessionService *services.SessionService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) var req dto.LoginRequest if appErr := commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } // req.RememberMe is a bool, not *bool, so no need to check for nil or indirect rememberMe := req.RememberMe user, tokens, err := authService.Login(c.Request.Context(), req.Email, req.Password, rememberMe) if err != nil { if strings.Contains(err.Error(), "email not verified") { c.JSON(http.StatusForbidden, gin.H{ "error": err.Error(), "code": "EMAIL_NOT_VERIFIED", }) return } if strings.Contains(err.Error(), "invalid credentials") { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to authenticate"}) return } if sessionService != nil { ipAddress := c.ClientIP() userAgent := c.GetHeader("User-Agent") if userAgent == "" { userAgent = "Unknown" } expiresIn := 30 * 24 * time.Hour if rememberMe { expiresIn = 90 * 24 * time.Hour } sessionReq := &services.SessionCreateRequest{ UserID: user.ID, Token: tokens.AccessToken, IPAddress: ipAddress, UserAgent: userAgent, ExpiresIn: expiresIn, } if _, err := sessionService.CreateSession(c.Request.Context(), sessionReq); err != nil { if logger != nil { logger.Warn("Failed to create session after login", zap.String("user_id", user.ID.String()), zap.String("ip_address", ipAddress), zap.Error(err), ) } } } RespondSuccess(c, http.StatusOK, dto.LoginResponse{ User: dto.UserResponse{ ID: user.ID, Email: user.Email, }, Token: dto.TokenResponse{ AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, ExpiresIn: int(authService.JWTService.Config.AccessTokenTTL.Seconds()), }, }) } } // Register gère l'inscription des utilisateurs // GO-013: Utilise validator centralisé pour validation améliorée // P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs func Register(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) var req dto.RegisterRequest if appErr := commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } user, err := authService.Register(c.Request.Context(), req.Email, req.Password) if err != nil { switch { case services.IsUserAlreadyExistsError(err): c.JSON(http.StatusConflict, gin.H{"error": "User already exists"}) case services.IsInvalidEmail(err): c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email format"}) case services.IsWeakPassword(err): c.JSON(http.StatusBadRequest, gin.H{"error": "Password does not meet requirements"}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"}) } return } RespondSuccess(c, http.StatusCreated, dto.RegisterResponse{ User: dto.UserResponse{ ID: user.ID, Email: user.Email, Username: user.Username, }, }) } } // Refresh gère le rafraîchissement d'un access token // GO-013: Utilise validator centralisé pour validation améliorée // P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs func Refresh(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) var req dto.RefreshRequest if appErr := commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } tokens, err := authService.Refresh(c.Request.Context(), req.RefreshToken) if err != nil { if strings.Contains(err.Error(), "invalid refresh token") || strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "expired") || strings.Contains(err.Error(), "token version mismatch") { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid refresh token"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to refresh token"}) return } RespondSuccess(c, http.StatusOK, dto.TokenResponse{ AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, ExpiresIn: int(authService.JWTService.Config.AccessTokenTTL.Seconds()), // Use JWT config }) } } // Logout gère la déconnexion des utilisateurs // P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs func Logout(authService *auth.AuthService, sessionService *services.SessionService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) userIDInterface, exists := c.Get("user_id") if !exists { RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated")) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type in context")) return } var req struct { RefreshToken string `json:"refresh_token" binding:"required"` } if appErr := commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } if err := authService.Logout(c.Request.Context(), userID, req.RefreshToken); err != nil { // Log the error but don't fail the request to prevent leaking info } if sessionService != nil { authHeader := c.GetHeader("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { token := strings.TrimPrefix(authHeader, "Bearer ") if err := sessionService.RevokeSession(c.Request.Context(), token); err != nil { // Log the error but don't fail the request } } } RespondSuccess(c, http.StatusOK, gin.H{"message": "Logged out successfully"}) } } // VerifyEmail gère la vérification de l'email func VerifyEmail(authService *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { token := c.Query("token") if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Token required"}) return } if err := authService.VerifyEmail(c.Request.Context(), token); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "Email verified successfully"}) } } // ResendVerification gère la demande de renvoi d'email de vérification // P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs func ResendVerification(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { commonHandler := NewCommonHandler(logger) var req dto.ResendVerificationRequest if appErr := commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } if err := authService.ResendVerificationEmail(c.Request.Context(), req.Email); err != nil { if strings.Contains(err.Error(), "email already verified") { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } } RespondSuccess(c, http.StatusOK, gin.H{"message": "Verification email sent if account exists"}) } } // CheckUsername vérifie la disponibilité d'un nom d'utilisateur func CheckUsername(authService *auth.AuthService) gin.HandlerFunc { return func(c *gin.Context) { username := c.Query("username") if username == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Username is required"}) return } _, err := authService.GetUserByUsername(c.Request.Context(), username) available := err != nil RespondSuccess(c, http.StatusOK, gin.H{ "available": available, "username": username, }) } } // GetMe retourne les informations de l'utilisateur connecté func GetMe() gin.HandlerFunc { return func(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } RespondSuccess(c, http.StatusOK, gin.H{ "id": userID, "email": c.GetString("email"), "role": c.GetString("role"), }) } }