319 lines
9.6 KiB
Go
319 lines
9.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/dto"
|
|
"veza-backend-api/internal/response"
|
|
"veza-backend-api/internal/services"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// AuthHandler gère les requêtes d'authentification pour T0151
|
|
type AuthHandler struct {
|
|
authService *AuthService // Changed to *AuthService (from the current package)
|
|
sessionService *services.SessionService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewAuthHandler crée une nouvelle instance d'AuthHandler
|
|
func NewAuthHandler(authService *AuthService, sessionService *services.SessionService, logger *zap.Logger) *AuthHandler { // Changed to *AuthService
|
|
return &AuthHandler{
|
|
authService: authService,
|
|
sessionService: sessionService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Register gère l'inscription d'un nouvel utilisateur
|
|
func (h *AuthHandler) Register(c *gin.Context) {
|
|
var req dto.RegisterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errorMsg := err.Error()
|
|
if strings.Contains(errorMsg, "Password") && strings.Contains(errorMsg, "min") {
|
|
errorMsg = "Le mot de passe doit contenir au moins 12 caractères"
|
|
} else if strings.Contains(errorMsg, "PasswordConfirm") && strings.Contains(errorMsg, "eqfield") {
|
|
errorMsg = "Les mots de passe ne correspondent pas"
|
|
} else if strings.Contains(errorMsg, "Email") && strings.Contains(errorMsg, "email") {
|
|
errorMsg = "Format d'email invalide"
|
|
} else if strings.Contains(errorMsg, "required") {
|
|
if strings.Contains(errorMsg, "Password") {
|
|
errorMsg = "Le mot de passe est requis"
|
|
} else if strings.Contains(errorMsg, "Email") {
|
|
errorMsg = "L'email est requis"
|
|
} else if strings.Contains(errorMsg, "PasswordConfirm") {
|
|
errorMsg = "La confirmation du mot de passe est requise"
|
|
}
|
|
}
|
|
|
|
h.logger.Warn("Invalid registration request", zap.Error(err), zap.String("error_message", errorMsg))
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, errorMsg)
|
|
return
|
|
}
|
|
|
|
h.logger.Info("Received registration request", zap.Any("req", req))
|
|
user, err := h.authService.Register(c.Request.Context(), req.Email, req.Username, req.Password)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "already exists") {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusConflict, err.Error())
|
|
return
|
|
}
|
|
if strings.Contains(err.Error(), "validation") || strings.Contains(err.Error(), "invalid") {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusInternalServerError, "Failed to create user")
|
|
return
|
|
}
|
|
|
|
response := dto.RegisterResponse{
|
|
User: dto.UserResponse{
|
|
ID: user.ID,
|
|
Email: user.Email,
|
|
Username: user.Username,
|
|
},
|
|
Token: dto.TokenResponse{},
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// Login gère la connexion d'un utilisateur
|
|
func (h *AuthHandler) Login(c *gin.Context) {
|
|
var req dto.LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
user, tokens, err := h.authService.Login(c.Request.Context(), req.Email, req.Password, req.RememberMe)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "email not verified") {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H (403 -> ErrCodeForbidden)
|
|
response.Error(c, http.StatusForbidden, err.Error())
|
|
return
|
|
}
|
|
if strings.Contains(err.Error(), "invalid credentials") {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusUnauthorized, "Invalid credentials")
|
|
return
|
|
}
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusInternalServerError, "Failed to authenticate")
|
|
return
|
|
}
|
|
|
|
if h.sessionService != nil {
|
|
ipAddress := c.ClientIP()
|
|
userAgent := c.GetHeader("User-Agent")
|
|
if userAgent == "" {
|
|
userAgent = "Unknown"
|
|
}
|
|
|
|
expiresIn := 30 * 24 * time.Hour
|
|
if req.RememberMe {
|
|
expiresIn = 90 * 24 * time.Hour
|
|
}
|
|
|
|
sessionReq := &services.SessionCreateRequest{
|
|
UserID: user.ID,
|
|
Token: tokens.AccessToken,
|
|
IPAddress: ipAddress,
|
|
UserAgent: userAgent,
|
|
ExpiresIn: expiresIn,
|
|
}
|
|
|
|
if _, err := h.sessionService.CreateSession(c.Request.Context(), sessionReq); err != nil {
|
|
h.logger.Warn("Failed to create session after login",
|
|
zap.String("user_id", user.ID.String()),
|
|
zap.String("ip_address", ipAddress),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
response := dto.LoginResponse{
|
|
User: dto.UserResponse{
|
|
ID: user.ID,
|
|
Email: user.Email,
|
|
},
|
|
Token: dto.TokenResponse{
|
|
AccessToken: tokens.AccessToken,
|
|
RefreshToken: tokens.RefreshToken,
|
|
ExpiresIn: int(h.authService.JWTService.Config.AccessTokenTTL.Seconds()),
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Refresh gère le rafraîchissement d'un access token
|
|
func (h *AuthHandler) Refresh(c *gin.Context) {
|
|
var req dto.RefreshRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
tokens, err := h.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") {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusUnauthorized, "Invalid refresh token")
|
|
return
|
|
}
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusInternalServerError, "Failed to refresh token")
|
|
return
|
|
}
|
|
|
|
response := dto.TokenResponse{
|
|
AccessToken: tokens.AccessToken,
|
|
RefreshToken: tokens.RefreshToken,
|
|
ExpiresIn: 900,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// CheckUsername vérifie la disponibilité d'un nom d'utilisateur
|
|
func (h *AuthHandler) CheckUsername(c *gin.Context) {
|
|
username := c.Query("username")
|
|
if username == "" {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, "Username is required")
|
|
return
|
|
}
|
|
|
|
_, err := h.authService.GetUserByUsername(c.Request.Context(), username)
|
|
available := err != nil
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"available": available,
|
|
"username": username,
|
|
})
|
|
}
|
|
|
|
// GetMe retourne les informations de l'utilisateur connecté
|
|
func (h *AuthHandler) GetMe(c *gin.Context) {
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": userID,
|
|
"email": c.GetString("email"),
|
|
"role": c.GetString("role"),
|
|
})
|
|
}
|
|
|
|
// Logout déconnecte l'utilisateur
|
|
func (h *AuthHandler) Logout(c *gin.Context) {
|
|
userIDInterface, exists := c.Get("user_id")
|
|
if !exists {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusUnauthorized, "Unauthorized")
|
|
return
|
|
}
|
|
|
|
userID, ok := userIDInterface.(uuid.UUID)
|
|
if !ok {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusInternalServerError, "Invalid user ID type in context")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
RefreshToken string `json:"refresh_token" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, "Refresh token is required")
|
|
return
|
|
}
|
|
|
|
if err := h.authService.Logout(c.Request.Context(), userID, req.RefreshToken); err != nil {
|
|
h.logger.Error("Failed to logout (revoke token)", zap.Error(err))
|
|
}
|
|
|
|
if h.sessionService != nil {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
|
|
token := strings.TrimPrefix(authHeader, "Bearer ")
|
|
if err := h.sessionService.RevokeSession(c.Request.Context(), token); err != nil {
|
|
h.logger.Warn("Failed to revoke session on logout", zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
|
|
}
|
|
|
|
// VerifyEmail gère la vérification de l'email
|
|
func (h *AuthHandler) VerifyEmail(c *gin.Context) {
|
|
token := c.Query("token")
|
|
if token == "" {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, "Token required")
|
|
return
|
|
}
|
|
|
|
if err := h.authService.VerifyEmail(c.Request.Context(), token); err != nil {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"})
|
|
}
|
|
|
|
// ResendVerification gère la demande de renvoi d'email de vérification
|
|
func (h *AuthHandler) ResendVerification(c *gin.Context) {
|
|
var req struct {
|
|
Email string `json:"email" binding:"required,email"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := h.authService.ResendVerificationEmail(c.Request.Context(), req.Email); err != nil {
|
|
if err.Error() == "email already verified" {
|
|
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
|
response.Error(c, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Verification email sent if account exists"})
|
|
}
|
|
|
|
// GetUserByUsername gets a user by username
|
|
func (h *AuthHandler) GetUserByUsername(c *gin.Context) {
|
|
username := c.Param("username")
|
|
user, err := h.authService.GetUserByUsername(c.Request.Context(), username)
|
|
if err != nil {
|
|
response.NotFound(c, "User not found")
|
|
return
|
|
}
|
|
response.Success(c, user)
|
|
}
|