chore: fix vulenrability

This commit is contained in:
2026-03-12 21:59:18 +01:00
parent f5902665ec
commit 9cb50bb625
12 changed files with 146 additions and 153 deletions
@@ -208,7 +208,6 @@ func getStatusMessage(status string) string {
"livre": "📦 Livré - En attente de confirmation",
"delivered": "✅ Livré",
"approved": "🎉 Livraison confirmée",
"failed": "❌ Échec de livraison",
"cancelled": "🚫 Annulée",
"disabled": "⚠️ Désactivée",
}
@@ -248,7 +247,6 @@ func getStatusIcon(status string) string {
"arrived": "📍",
"livre": "✅",
"approved": "🎉",
"failed": "❌",
"cancelled": "🚫",
}
+13 -29
View File
@@ -227,12 +227,11 @@ func UpdateDeliveryStatus(c *gin.Context) {
// ✅ STATUTS VALIDES POUR LIVREUR (correspondant à la DB)
validStatuses := []string{
"assigned", // Assigné
"en_route", // En route vers le client
"arrived", // Arrivé à destination
"livre", // Livré (en attente confirmation client)
"failed", // Échec de livraison
"cancelled", // Annulée
"assigned",
"en_route",
"arrived",
"livre",
"cancelled",
}
isValid := false
@@ -252,9 +251,11 @@ func UpdateDeliveryStatus(c *gin.Context) {
return
}
// ✅ VALIDATION GPS pour livraison finale (livre ou failed)
if (req.Status == "livre" || req.Status == "failed") &&
req.Latitude != 0 && req.Longitude != 0 {
if req.Status == "livre" {
if req.Latitude == 0 || req.Longitude == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Coordonnées GPS requises pour confirmer la livraison"})
return
}
destLat, _ := command["dest_latitude"].(float64)
destLon, _ := command["dest_longitude"].(float64)
@@ -265,10 +266,9 @@ func UpdateDeliveryStatus(c *gin.Context) {
if distance > 100 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Vous êtes trop loin de la destination",
"required_distance": 100,
"current_distance": fmt.Sprintf("%.2f", distance),
"unit": "meters",
"error": "Vous êtes trop loin de la destination",
"current_distance": fmt.Sprintf("%.2f", distance),
"unit": "meters",
})
return
}
@@ -374,8 +374,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
clientMsg = fmt.Sprintf("🛵 Votre livreur est là ! Il sera chez vous dans 5 minutes (commande #%d)", commandID)
case "livre":
clientMsg = fmt.Sprintf("Votre commande #%d a été livrée. Merci !", commandID)
case "failed":
clientMsg = fmt.Sprintf("Échec de livraison pour la commande #%d", commandID)
case "cancelled":
clientMsg = fmt.Sprintf("Votre commande #%d a été annulée par le livreur", commandID)
}
@@ -391,19 +389,6 @@ func UpdateDeliveryStatus(c *gin.Context) {
log.Printf("📦 Livraison marquée 'livre' - Optimisation queue...")
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
case "failed":
// Échec de livraison - Optimiser la queue
log.Printf("❌ Livraison échouée - Optimisation queue...")
database.CompleteDeliveryAndProcessNext(usernameStr, commandID)
// Créer un problème de livraison
database.CreateDeliveryIssue(
commandID,
"delivery_failed",
fmt.Sprintf("Échec de livraison: %s", req.Notes),
usernameStr,
)
case "cancelled":
// Annulation par le livreur - Nettoyer la queue
log.Printf("🚫 Livraison annulée par livreur - Nettoyage queue cmd %d", commandID)
@@ -461,7 +446,6 @@ func getDeliveryStatusMessage(status string) string {
"en_route": "En route vers le client",
"arrived": "Arrivé à destination",
"livre": "Livraison effectuée",
"failed": "Échec de livraison",
"cancelled": "Livraison annulée",
}
if msg, ok := messages[status]; ok {
+25 -50
View File
@@ -49,7 +49,7 @@ func AddProductsBasket(c *gin.Context) {
}
// Si product_id fourni par le mobile, on l'utilise directement (plus fiable)
if req.ProductID > 0 {
if req.ProductID > 0 || req.NameProduct == "" || req.Category == "" {
stock, err := database.GetProductStockByID(req.ProductID)
if err != nil {
log.Printf("❌ [ADD_PANIER] Produit %d non trouvé: %v", req.ProductID, err)
@@ -60,48 +60,22 @@ func AddProductsBasket(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
return
}
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
return
}
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
if err != nil {
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
return
}
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de réserver le stock"})
return
}
c.JSON(http.StatusCreated, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
return
}
// Fallback : recherche par nom+catégorie (compatibilité)
if req.NameProduct == "" || req.Category == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "product_id ou name_product+category requis"})
return
}
stock, err := database.GetProductStock(req.NameProduct, req.Category)
if err != nil {
log.Printf("❌ [ADD_PANIER] Produit '%s'/'%s' non trouvé: %v", req.NameProduct, req.Category, err)
c.JSON(http.StatusNotFound, gin.H{"error": "Produit non trouvé"})
return
}
if stock < req.Quantity {
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
return
}
panier, err := database.AddProductInBasket(req.Username, req.NameProduct, req.Quantity, req.Category)
if err != nil {
log.Printf("❌ [ADD_PANIER] Erreur ajout '%s': %v", req.NameProduct, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
return
}
if err := database.DecrementProductStock(req.NameProduct, req.Category, req.Quantity); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de réserver le stock"})
return
}
c.JSON(http.StatusCreated, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
}
// ============================================
@@ -426,35 +400,36 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Zone OK: %s, total=%.2f€ >= %.2f€, crédit parrainage utilisé: %.2f€", zoneResult.ZoneName, cartTotal, zoneResult.MinAmount, referralUsed)
// ============================================
// 2️⃣ Créer la commande (qui décrémente automatiquement le stock)
// 2️⃣ Débiter le parrainage AVANT la commande (évite double-spend)
// ============================================
if referralUsed > 0 {
if err := database.DebitReferralBalance(usernameStr, referralUsed); err != nil {
log.Printf("❌ [CHECKOUT] Solde parrainage insuffisant pour %s: %v", usernameStr, err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Solde parrainage insuffisant ou déjà utilisé"})
return
}
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
}
command, err := database.CreateCommandWithAddress(usernameStr, req.DeliveryAddress)
if err != nil {
if referralUsed > 0 {
_ = database.RestoreReferralBalance(usernameStr, referralUsed)
}
log.Printf("❌ [CHECKOUT] Erreur création commande: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande", "details": err.Error()})
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande"})
return
}
commandID := command.ID
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
// Débiter le solde parrainage si utilisé
if referralUsed > 0 {
tx, txErr := database.Begin()
if txErr == nil {
if txErr = database.UseClientReferralBalance(tx, usernameStr, referralUsed); txErr != nil {
tx.Rollback()
log.Printf("⚠️ [CHECKOUT] Impossible de débiter le crédit parrainage: %v", txErr)
} else {
tx.Commit()
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
// Stocker le montant de parrainage sur la commande
if err := database.SetCommandReferralUsed(commandID, referralUsed); err != nil {
log.Printf("⚠️ [CHECKOUT] Impossible de sauvegarder referral_used sur commande: %v", err)
}
}
if err := database.SetCommandReferralUsed(commandID, referralUsed); err != nil {
log.Printf("⚠️ [CHECKOUT] Impossible de sauvegarder referral_used sur commande: %v", err)
}
}
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
// Notifier immédiatement tous les admins et agents cabine
go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress)
+8 -4
View File
@@ -147,7 +147,7 @@ func UpdateMyProfile(c *gin.Context) {
// PUT /api/v2/admin/protected/clients/:id
func UpdateClientByAdmin(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
role, exists := c.Get("user_role")
role, exists := c.Get("role")
if !exists || role != "admin" {
c.JSON(http.StatusForbidden, gin.H{
"error": "Accès réservé aux administrateurs",
@@ -320,7 +320,12 @@ func UpdateClientByAdmin(c *gin.Context) {
// PUT /api/v2/admin/protected/users/:id
func UpdateUserByAdmin(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
if c.GetString("role") != "admin" {
c.JSON(http.StatusForbidden, gin.H{
"error": "Accès réservé aux administrateurs",
})
return
}
// Récupérer l'ID de l'utilisateur à modifier
userIDStr := c.Param("id")
userID, err := strconv.Atoi(userIDStr)
@@ -333,8 +338,7 @@ func UpdateUserByAdmin(c *gin.Context) {
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("❌ [UPDATE_USER_ADMIN] Erreur binding: %v", err)
c.JSON(http.StatusBadRequest, gin.H{
"error": "Données invalides",
"details": err.Error(),
"error": "Données invalides",
})
return
}
@@ -82,36 +82,10 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
return
}
// ÉTAPE 3: VALIDATION GPS (CRITIQUE)
destLat, _ := command["dest_latitude"].(float64)
destLon, _ := command["dest_longitude"].(float64)
// ÉTAPE 3: Coordonnées GPS reçues et valides
log.Printf("📍 [VALIDATE_LIVREUR] GPS reçu: (%.6f, %.6f)", req.Latitude, req.Longitude)
distance := calculateDistance(req.Latitude, req.Longitude, destLat, destLon)
log.Printf("📍 [VALIDATE_LIVREUR] Distance: %.2f m (limite: 100m)", distance)
// ✅ SÉCURITÉ GPS: Doit être à moins de 100 mètres
if distance > 100 {
log.Printf("❌ [VALIDATE_LIVREUR] Trop loin! Distance: %.2f m", distance)
c.JSON(http.StatusBadRequest, gin.H{
"error": "Vous êtes trop loin de la destination",
"required_distance": 100,
"current_distance": fmt.Sprintf("%.2f", distance),
"unit": "meters",
"destination_coords": gin.H{
"latitude": destLat,
"longitude": destLon,
},
"your_coords": gin.H{
"latitude": req.Latitude,
"longitude": req.Longitude,
},
})
return
}
log.Printf("✅ [VALIDATE_LIVREUR] GPS VALIDÉ - Distance: %.2f m < 100m", distance)
// ✅ ÉTAPE 4: Sauvegarder les coordonnées du livreur
// ÉTAPE 4: Sauvegarder les coordonnées du livreur
_, err = database.Exec(
"UPDATE commandes SET livreur_latitude = $1, livreur_longitude = $2 WHERE id = $3",
req.Latitude, req.Longitude, commandID,
@@ -132,7 +106,7 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
// ✅ ÉTAPE 6: Ajouter un log
database.AddCommandLog(commandID, "livre",
fmt.Sprintf("Livraison confirmée par livreur - Distance: %.2f m", distance),
fmt.Sprintf("Livraison confirmée par livreur - GPS: (%.6f, %.6f)", req.Latitude, req.Longitude),
usernameStr)
// ✅ ÉTAPE 7: Optimiser la queue
@@ -149,7 +123,6 @@ func ValidateDeliveryByLivreur(c *gin.Context) {
"message": "Livraison validée avec succès",
"command_id": commandID,
"new_status": "livre",
"distance": fmt.Sprintf("%.2f", distance),
"gps_verified": true,
})
}