chore: refacto
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"gestion/db"
|
||||
"gestion/models"
|
||||
"gestion/utils"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,109 +17,16 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// ============================================
|
||||
// TYPES JWT CLAIMS (Duplicés dans middleware aussi)
|
||||
// ============================================
|
||||
|
||||
type ClientClaims struct {
|
||||
ClientID int `json:"client_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
SessionID string `json:"session_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type AdminClaims struct {
|
||||
UserID int `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
SessionID string `json:"session_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STRUCTURES REQUÊTE / RÉPONSE
|
||||
// ============================================
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type RegisterClientRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Password string `json:"password" binding:"required,min=8"`
|
||||
Nom string `json:"nom" binding:"required,min=2,max=100"`
|
||||
Prenom string `json:"prenom" binding:"required,min=2,max=100"`
|
||||
Telephone string `json:"telephone" binding:"required"`
|
||||
}
|
||||
|
||||
type RegisterAdminRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Password string `json:"password" binding:"required,min=8"`
|
||||
Role string `json:"role" binding:"required,oneof=admin cabine livreur"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
User interface{} `json:"user"`
|
||||
}
|
||||
|
||||
type ProfileResponse struct {
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// VARIABLES & CONSTANTS
|
||||
// ============================================
|
||||
|
||||
var (
|
||||
clientTokenDuration = 5 * time.Hour
|
||||
adminTokenDuration = 10 * time.Hour
|
||||
userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET")) // ✅ Pour clients
|
||||
adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET")) // ✅ Pour admin/cabine/livreur
|
||||
userJWTSecret = []byte(os.Getenv("USER_JWT_SECRET"))
|
||||
adminJWTSecret = []byte(os.Getenv("ADMIN_JWT_SECRET"))
|
||||
)
|
||||
|
||||
// ============================================
|
||||
// UTILITAIRES
|
||||
// ============================================
|
||||
|
||||
func generateSessionID() string {
|
||||
bytes := make([]byte, 16)
|
||||
rand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
// validatePhoneNumber valide le format du numéro de téléphone
|
||||
func validatePhoneNumber(phone string) bool {
|
||||
// Supprimer espaces/tirets
|
||||
clean := regexp.MustCompile(`[\s\-\(\)]`).ReplaceAllString(phone, "")
|
||||
|
||||
// Format international ou national
|
||||
validFormat := regexp.MustCompile(`^(\+33|0)[1-9]\d{8}$`)
|
||||
return validFormat.MatchString(clean)
|
||||
}
|
||||
|
||||
func normalizePhoneNumber(phone string) string {
|
||||
clean := regexp.MustCompile(`[\s\-\(\)]`).ReplaceAllString(phone, "")
|
||||
|
||||
// Convertir 06... en +336...
|
||||
if strings.HasPrefix(clean, "0") {
|
||||
return "+33" + clean[1:]
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GENERATION TOKENS
|
||||
// ============================================
|
||||
|
||||
func generateClientToken(client *models.Client) (string, error) {
|
||||
sessionID := generateSessionID()
|
||||
claims := ClientClaims{
|
||||
sessionID := utils.GenerateSessionID()
|
||||
claims := models.ClientClaims{
|
||||
ClientID: client.ID,
|
||||
Username: client.Username,
|
||||
Role: "client",
|
||||
@@ -135,7 +40,7 @@ func generateClientToken(client *models.Client) (string, error) {
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString(userJWTSecret) // ✅ UTILISER userJWTSecret (CLIENT)
|
||||
tokenString, err := token.SignedString(userJWTSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -143,8 +48,8 @@ func generateClientToken(client *models.Client) (string, error) {
|
||||
}
|
||||
|
||||
func generateAdminToken(user *models.User) (string, error) {
|
||||
sessionID := generateSessionID()
|
||||
claims := AdminClaims{
|
||||
sessionID := utils.GenerateSessionID()
|
||||
claims := models.AdminClaims{
|
||||
UserID: user.ID,
|
||||
Username: user.Username,
|
||||
Role: user.Role, // ← "admin" ou "cabine" ou "livreur"
|
||||
@@ -158,21 +63,16 @@ func generateAdminToken(user *models.User) (string, error) {
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString(adminJWTSecret) // ✅ UTILISER adminJWTSecret (ADMIN/CABINE/LIVREUR)
|
||||
tokenString, err := token.SignedString(adminJWTSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HANDLERS AUTHENTIFICATION CLIENT
|
||||
// ============================================
|
||||
|
||||
// RegisterClient crée un nouveau compte client
|
||||
// POST /api/v1/auth/register
|
||||
func RegisterClient(c *gin.Context) {
|
||||
var req RegisterClientRequest
|
||||
var req models.RegisterClientRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("❌ [REGISTER_CLIENT] Erreur binding: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
@@ -183,7 +83,7 @@ func RegisterClient(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Validation téléphone
|
||||
if !validatePhoneNumber(req.Telephone) {
|
||||
if !utils.ValidatePhoneNumber(req.Telephone) {
|
||||
log.Printf("❌ [REGISTER_CLIENT] Téléphone invalide: %s", req.Telephone)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Numéro de téléphone invalide",
|
||||
@@ -191,7 +91,7 @@ func RegisterClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
normalizedPhone := normalizePhoneNumber(req.Telephone)
|
||||
normalizedPhone := utils.NormalizePhoneNumber(req.Telephone)
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// Vérifier username unique
|
||||
@@ -256,9 +156,7 @@ func RegisterClient(c *gin.Context) {
|
||||
|
||||
client.Password = ""
|
||||
|
||||
log.Printf("✅ [REGISTER_CLIENT] Client créé: %s (ID=%d)", client.Username, client.ID)
|
||||
|
||||
c.JSON(http.StatusCreated, LoginResponse{
|
||||
c.JSON(http.StatusCreated, models.LoginResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int(clientTokenDuration.Seconds()),
|
||||
@@ -275,20 +173,19 @@ func RegisterClient(c *gin.Context) {
|
||||
}
|
||||
|
||||
// AdminCreateClient crée un client depuis l'interface admin (sans session ni token)
|
||||
// POST /api/v2/admin/protected/clients
|
||||
func AdminCreateClient(c *gin.Context) {
|
||||
var req RegisterClientRequest
|
||||
var req models.RegisterClientRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||
return
|
||||
}
|
||||
|
||||
if !validatePhoneNumber(req.Telephone) {
|
||||
if !utils.ValidatePhoneNumber(req.Telephone) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Numéro de téléphone invalide"})
|
||||
return
|
||||
}
|
||||
|
||||
normalizedPhone := normalizePhoneNumber(req.Telephone)
|
||||
normalizedPhone := utils.NormalizePhoneNumber(req.Telephone)
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
if existing, _ := database.GetClientByUsername(req.Username); existing != nil {
|
||||
@@ -322,8 +219,6 @@ func AdminCreateClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [ADMIN_CREATE_CLIENT] Client créé par admin: %s (ID=%d)", client.Username, client.ID)
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Client créé avec succès",
|
||||
"client": gin.H{
|
||||
@@ -337,9 +232,8 @@ func AdminCreateClient(c *gin.Context) {
|
||||
}
|
||||
|
||||
// LoginClient authentifie un client
|
||||
// POST /api/v1/auth/login
|
||||
func LoginClient(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
var req models.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("❌ [LOGIN_CLIENT] Erreur binding: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||
@@ -361,7 +255,6 @@ func LoginClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ LIGNE 302 CORRIGÉE - Gérer l'erreur !
|
||||
token, err := generateClientToken(client)
|
||||
if err != nil {
|
||||
log.Printf("❌ [LOGIN_CLIENT] Erreur génération token: %v", err)
|
||||
@@ -382,9 +275,7 @@ func LoginClient(c *gin.Context) {
|
||||
log.Printf("⚠️ [LOGIN_CLIENT] Erreur session Redis: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ [LOGIN_CLIENT] Client authentifié: %s", req.Username)
|
||||
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
c.JSON(http.StatusOK, models.LoginResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int(clientTokenDuration.Seconds()),
|
||||
@@ -402,7 +293,6 @@ func LoginClient(c *gin.Context) {
|
||||
}
|
||||
|
||||
// ChangePassword permet à un client de changer son mot de passe
|
||||
// PUT /api/v1/auth/change-password
|
||||
func ChangePassword(c *gin.Context) {
|
||||
var req struct {
|
||||
CurrentPassword string `json:"current_password" binding:"required"`
|
||||
@@ -442,16 +332,13 @@ func ChangePassword(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [CHANGE_PASSWORD] Mot de passe changé: ID=%d", clientID)
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Mot de passe mis à jour avec succès"})
|
||||
}
|
||||
|
||||
// LogoutClient déconnecte un client
|
||||
// POST /api/v1/auth/logout
|
||||
func LogoutClient(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// Invalider la session Redis
|
||||
clientID, hasClientID := c.Get("client_id")
|
||||
if hasClientID && clientID != nil {
|
||||
if err := database.InvalidateSession(clientID.(int)); err != nil {
|
||||
@@ -459,26 +346,18 @@ func LogoutClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Révoquer le JWT token
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
|
||||
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
database.RevokeToken(tokenStr)
|
||||
}
|
||||
|
||||
log.Printf("✅ [LOGOUT_CLIENT] Client déconnecté")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Déconnexion réussie"})
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HANDLERS AUTHENTIFICATION ADMIN
|
||||
// ============================================
|
||||
|
||||
// RegisterAdmin crée un nouvel utilisateur admin/cabine/livreur
|
||||
// POST /api/v1/auth/admin/register
|
||||
func RegisterAdmin(c *gin.Context) {
|
||||
var req RegisterAdminRequest
|
||||
var req models.RegisterAdminRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("❌ [REGISTER_ADMIN] Erreur binding: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||
@@ -487,7 +366,6 @@ func RegisterAdmin(c *gin.Context) {
|
||||
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// Vérifier que l'user n'existe pas
|
||||
if existingUser, _ := database.GetUserByUsername(req.Username); existingUser != nil {
|
||||
log.Printf("❌ [REGISTER_ADMIN] Username déjà utilisé: %s", req.Username)
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Nom d'utilisateur déjà utilisé"})
|
||||
@@ -507,9 +385,6 @@ func RegisterAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [REGISTER_ADMIN] User créé: %s (role=%s, ID=%d)", user.Username, user.Role, user.ID)
|
||||
|
||||
// Générer le token
|
||||
token, err := generateAdminToken(user)
|
||||
if err != nil {
|
||||
log.Printf("❌ [REGISTER_ADMIN] Erreur génération token: %v", err)
|
||||
@@ -517,11 +392,6 @@ func RegisterAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [REGISTER_ADMIN] Token généré: %s...", token[:50])
|
||||
|
||||
// ============================================
|
||||
// ✅ CRITICAL FIX: ENREGISTRER LE TOKEN EN DB
|
||||
// ============================================
|
||||
expiresAt := time.Now().Add(adminTokenDuration)
|
||||
if err := database.SaveToken(user.ID, user.Role, token, expiresAt); err != nil {
|
||||
log.Printf("❌ [REGISTER_ADMIN] Erreur SaveToken: %v", err)
|
||||
@@ -529,11 +399,9 @@ func RegisterAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [REGISTER_ADMIN] Token enregistré en DB pour user ID: %d", user.ID)
|
||||
|
||||
user.Password = ""
|
||||
|
||||
c.JSON(http.StatusCreated, LoginResponse{
|
||||
c.JSON(http.StatusCreated, models.LoginResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int(adminTokenDuration.Seconds()),
|
||||
@@ -542,10 +410,8 @@ func RegisterAdmin(c *gin.Context) {
|
||||
}
|
||||
|
||||
// LoginAdmin authentifie un admin/cabine/livreur
|
||||
// POST /api/v1/auth/admin/login
|
||||
// ✅ AMÉLIORÉ: Meilleure gestion d'erreurs
|
||||
func LoginAdmin(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
var req models.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("❌ [LOGIN_ADMIN] Erreur binding: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Données invalides"})
|
||||
@@ -561,14 +427,12 @@ func LoginAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Vérifier que c'est un admin/cabine/livreur
|
||||
if user.Role != "admin" && user.Role != "cabine" && user.Role != "livreur" {
|
||||
log.Printf("❌ [LOGIN_ADMIN] Rôle invalide: %s", user.Role)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Accès non autorisé"})
|
||||
return
|
||||
}
|
||||
|
||||
// Vérifier le mot de passe
|
||||
if user.Password == "" {
|
||||
log.Printf("❌ [LOGIN_ADMIN] Mot de passe vide en base")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Identifiants invalides"})
|
||||
@@ -581,7 +445,6 @@ func LoginAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Générer le token
|
||||
token, _ := generateAdminToken(user)
|
||||
|
||||
expiresAt := time.Now().Add(adminTokenDuration)
|
||||
@@ -591,9 +454,7 @@ func LoginAdmin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [LOGIN_ADMIN] User authentifié: %s (role=%s)", user.Username, user.Role)
|
||||
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
c.JSON(http.StatusOK, models.LoginResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: int(adminTokenDuration.Seconds()),
|
||||
@@ -606,7 +467,6 @@ func LoginAdmin(c *gin.Context) {
|
||||
}
|
||||
|
||||
// LogoutAdmin déconnecte un admin/cabine/livreur
|
||||
// POST /api/v1/auth/admin/logout
|
||||
func LogoutAdmin(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
@@ -645,14 +505,14 @@ func GetCurrentClient(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"client": gin.H{
|
||||
"id": client.ID,
|
||||
"username": client.Username,
|
||||
"nom": client.Nom,
|
||||
"prenom": client.Prenom,
|
||||
"telephone": client.Telephone,
|
||||
"command": client.Command,
|
||||
"point": client.Point,
|
||||
"amende": client.Amende,
|
||||
"id": client.ID,
|
||||
"username": client.Username,
|
||||
"nom": client.Nom,
|
||||
"prenom": client.Prenom,
|
||||
"telephone": client.Telephone,
|
||||
"command": client.Command,
|
||||
"amende": client.Amende,
|
||||
"points_extra": client.PointsExtra,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -675,7 +535,7 @@ func GetCurrentAdmin(c *gin.Context) {
|
||||
log.Printf("✅ [GET_CURRENT_ADMIN] User récupéré: %s", user.Username)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": ProfileResponse{
|
||||
"user": models.ProfileResponse{
|
||||
Username: user.Username,
|
||||
Role: user.Role,
|
||||
},
|
||||
@@ -795,8 +655,7 @@ func GetAllClients(c *gin.Context) {
|
||||
"prenom": cl.Prenom,
|
||||
"telephone": cl.Telephone,
|
||||
"command": cl.Command,
|
||||
"point": cl.Point,
|
||||
"points_zipette": cl.PointZipette,
|
||||
"points_extra": cl.PointsExtra,
|
||||
"amende": cl.Amende,
|
||||
"cancellations_count": cl.CancellationsCount,
|
||||
"last_penalty_reason": cl.LastPenaltyReason,
|
||||
|
||||
Reference in New Issue
Block a user