diff --git a/.gitignore b/.gitignore
index 0cca1328..a56c12bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ easpip
ansible/
dist/
monitoring
+docker-prod/
diff --git a/README.md b/README.md
index dc67aedc..200dca63 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,26 @@
# 📚 Documentation API - Plateforme de Gestion de Commandes
-**Version:** 5.3.0
-**Date:** 2026-05-15
+**Version:** 5.4.0
+**Date:** 2026-06-11
**Base URL prod:** `https://mln-uber.club` (HTTPS via WAF nginx + ModSecurity)
**Base URL dev:** `http://localhost:8080`
**Technologies:** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19 + Vite, Expo 54 (React Native), TomTom API
----
## 📋 Table des Matières
1. [Vue d'ensemble](#vue-densemble)
-2. [Déploiement Production](#-déploiement-production)
-3. [Authentication](#authentication)
-4. [API Client (v1)](#api-client-v1)
-5. [API Admin (v2)](#api-admin-v2)
-6. [API Cabine (v1)](#api-cabine-v1)
-7. [API Livreur (v1)](#api-livreur-v1)
-8. [Notifications Push & Telegram](#-notifications-push--telegram)
-9. [Paiements Crypto](#-paiements-crypto)
-10. [Systeme GPS Integre](#-systeme-gps-integre)
-11. [Codes d'Erreur](#codes-derreur)
+2. [Infrastructure Serveurs](#-infrastructure-serveurs)
+3. [Déploiement Production](#-déploiement-production)
+4. [Authentication](#authentication)
+5. [API Client (v1)](#api-client-v1)
+6. [API Admin (v2)](#api-admin-v2)
+7. [API Cabine (v1)](#api-cabine-v1)
+8. [API Livreur (v1)](#api-livreur-v1)
+9. [Notifications Push & Telegram](#-notifications-push--telegram)
+10. [Paiements Crypto](#-paiements-crypto)
+11. [Systeme GPS Integre](#-systeme-gps-integre)
+12. [Codes d'Erreur](#codes-derreur)
---
@@ -657,6 +657,86 @@ graph LR
---
+## 🖥️ Infrastructure Serveurs
+
+### Réseau VPN (WireGuard)
+
+Tous les serveurs backend communiquent via un réseau WireGuard privé `10.0.0.0/24`. Le SSH est restreint à l'IP VPN uniquement sur les serveurs sensibles — il faut être connecté au VPN pour s'y connecter.
+
+| Serveur | IP Publique | IP VPN | RĂ´le |
+|---------|-------------|--------|------|
+| **vpn-uber** | `45.150.111.158` | `10.0.0.1` | Serveur WireGuard — point d'entrée VPN et jump host SSH |
+| **monitoring-uber** | `185.103.167.138` | `10.0.0.2` | Wazuh · Dozzle · Beszel hub · SSH via VPN uniquement |
+| **backup-mln** | `85.121.176.241` | `10.0.0.4` | MinIO S3 · ClamAV · Beszel agent · SSH via VPN uniquement |
+| **bdd-redis-prod** | `132.243.162.62` | `10.0.0.5` | PostgreSQL 16 · Redis 7 · Beszel agent · SSH via VPN uniquement |
+| **prod-uber** | `185.103.166.119` | `10.0.0.6` | Backend Go + WAF nginx · accessible publiquement sur 80/443 |
+| **pre-prod-uber** | `185.103.166.112` | — | Environnement de pré-production |
+| **s3-uber** | `80.96.58.164` | — | Stockage S3 externe · Dozzle agent |
+
+### Architecture DMZ / LAN
+
+```
+Internet
+ │
+ â–Ľ
+prod-uber (DMZ — 185.103.166.119)
+ │ 80/443 public
+ │ ──── VPN (10.0.0.6) ────► bdd-redis-prod (LAN — 10.0.0.5)
+ │ ├── PostgreSQL :5432
+ │ └── Redis :6379
+ │
+vpn-uber (10.0.0.1) — jump host SSH pour accès aux autres serveurs
+```
+
+L'application prod-uber se connecte Ă PostgreSQL et Redis via les IP VPN :
+- `DB_HOST=10.0.0.5` (PostgreSQL sur bdd-redis-prod)
+- `REDIS_HOST=10.0.0.5` (Redis sur bdd-redis-prod)
+
+### Monitoring
+
+Toutes les ressources monitoring sont accessibles via VPN (`10.0.0.2`) :
+
+| Outil | URL | Description |
+|-------|-----|-------------|
+| **Wazuh** | `https://10.0.0.2` | SIEM — alertes sécurité, logs agents |
+| **Dozzle** | `https://10.0.0.2:8080` | Logs Docker de tous les serveurs en temps réel |
+| **Beszel** | `https://10.0.0.2:8090` | Métriques système (CPU, RAM, disque, réseau) |
+
+Dozzle agrège les logs de : `pre-prod-uber`, `prod-uber`, `backup-mln (VPN)`, `s3-uber`.
+Beszel surveille : `monitoring-uber`, `backup-mln` (agent Docker port 10001), `bdd-redis-prod` (agent binaire systemd port 10001).
+
+Nettoyage automatique des logs Wazuh : cron tous les dimanches Ă 3h00 sur monitoring-uber (`/usr/local/bin/clean-wazuh-logs.sh`).
+
+### Sécurité réseau
+
+- **UFW** activé sur monitoring-uber et backup-mln : SSH bloqué depuis IP publique, accessible uniquement via VPN
+- **UFW** activé sur bdd-redis-prod : SSH, PostgreSQL et Redis accessibles uniquement depuis le réseau VPN (`10.0.0.0/24`)
+- Ports Docker liés à l'IP VPN (`10.0.0.4:port:port`) pour ne pas bypasser UFW
+- **ClamAV** sur backup-mln : scan antivirus quotidien de `/mnt/data`
+
+### Accès SSH aux serveurs VPN-only
+
+```bash
+# Via le jump host vpn-uber
+ssh -J root@45.150.111.158 root@10.0.0.2 # monitoring-uber
+ssh -J root@45.150.111.158 root@10.0.0.4 # backup-mln
+ssh -J root@45.150.111.158 root@10.0.0.5 # bdd-redis-prod
+```
+
+### Sauvegarde S3 (backup-mln)
+
+MinIO S3 tourne sur backup-mln avec nginx SSL proxy :
+
+| Endpoint | Adresse |
+|----------|---------|
+| API S3 | `https://10.0.0.4:9000` (via VPN) |
+| Console MinIO | `https://10.0.0.4:9001` (via VPN) |
+| Console nginx | `https://10.0.0.4:8080` (via VPN) |
+
+Les données sont montées sur `/mnt/data`.
+
+---
+
## 🚀 Déploiement Production
### Prérequis
@@ -703,31 +783,44 @@ docker compose -f docker/docker-compose-prod.yml up -d --build
docker compose -f docker/docker-compose-prod.yml logs -f waf
```
-### Services Docker
+### Services Docker (prod-uber)
| Service | Image | RĂ´le |
|---------|-------|------|
| `waf` | owasp/modsecurity-crs:nginx-alpine | Point d'entrée HTTPS (ports 80/443) |
| `backend` | Go 1.24 alpine | API REST (port 8080 interne) |
| `frontend` | nginx:alpine | SPA React (port 80 interne) |
-| `postgres` | postgres:16-alpine | Base de données |
-| `redis` | redis:7-alpine | Cache + sessions + queues |
+
+> PostgreSQL et Redis **ne tournent plus sur prod-uber**. Ils sont hébergés sur le serveur dédié `bdd-redis-prod` (`10.0.0.5`) et accessibles via le VPN WireGuard. Voir la section [Infrastructure Serveurs](#-infrastructure-serveurs).
### Variables d'environnement requises
```bash
+# Base de données (bdd-redis-prod via VPN)
+DB_HOST=10.0.0.5 # IP VPN de bdd-redis-prod
+DB_PORT=5432
DB_PASSWORD= # Mot de passe PostgreSQL
+
+# Redis (bdd-redis-prod via VPN)
+REDIS_HOST=10.0.0.5 # IP VPN de bdd-redis-prod
+REDIS_PORT=6379
+REDIS_PASSWORD= # Mot de passe Redis
+
+# JWT
USER_JWT_SECRET= # Secret JWT clients (min 32 chars)
ADMIN_JWT_SECRET= # Secret JWT admin/livreur/cabine (min 32 chars)
-REDIS_PASSWORD= # Mot de passe Redis
-TOMTOM_API_KEY= # Clé API TomTom (principale / legacy)
-TOMTOM_API_KEY_1= # Clé TomTom #1 (rotation automatique)
-TOMTOM_API_KEY_2= # Clé TomTom #2 (rotation automatique)
-TOMTOM_API_KEY_3= # Clé TomTom #3 (rotation automatique)
-SESSION_SECRET= # Secret sessions
-TELEGRAM_WEBHOOK_URL= # URL webhook Telegram
-TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram
-NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments
+
+# TomTom (rotation automatique entre les 3 clés)
+TOMTOM_API_KEY= # Clé TomTom principale / legacy
+TOMTOM_API_KEY_1= # Clé TomTom #1
+TOMTOM_API_KEY_2= # Clé TomTom #2
+TOMTOM_API_KEY_3= # Clé TomTom #3
+
+# Divers
+SESSION_SECRET= # Secret sessions
+TELEGRAM_WEBHOOK_URL= # URL webhook Telegram
+TELEGRAM_WEBHOOK_SECRET= # Secret webhook Telegram
+NOWPAYMENTS_IPN_SECRET= # Secret IPN NowPayments
```
---
@@ -3034,8 +3127,9 @@ Le système tente d'abord toutes les clés disponibles en rotation, puis bascule
---
-**Documentation mise Ă jour le :** 2026-05-18
+**Documentation mise Ă jour le :** 2026-06-11
**Version API :** 5.4.0
**Technologies :** Go 1.24, Gin, PostgreSQL 16, Redis 7, React 19, Expo 54, TomTom API, ModSecurity WAF
**Déploiement :** Docker Compose · Nginx + ModSecurity OWASP CRS · TLS 1.2/1.3
-**Base URL prod :** `https://mln-uber.club`
+**Base URL prod :** `https://mln-uber.club`
+**Infrastructure :** WireGuard VPN · Wazuh SIEM · Dozzle · Beszel · ClamAV · MinIO S3
diff --git a/docker-pre-prod/.env.example b/docker-pre-prod/.env.example
new file mode 100644
index 00000000..389dfacb
--- /dev/null
+++ b/docker-pre-prod/.env.example
@@ -0,0 +1,26 @@
+DB_HOST=postgres
+DB_PORT=5432
+DB_USER=postgres
+DB_PASSWORD=Ia3JWjw3Y0HzlEXH6QH3pqEu09Fap5C420
+DB_NAME=gestion_db
+DB_SSLMODE=disable
+SESSION_SECRET=GwDgqYn7Tn4x6Hs9ZjUD6HP8B7pQWK
+USER_JWT_SECRET=69F5ujM1YZ6JBh3pXczc3j0JzBuAvU
+ADMIN_JWT_SECRET=RwPxdzSzAR7HcrufA6kEXHFdIiEX87
+REDIS_HOST=redis
+REDIS_PORT=6379
+REDIS_PASSWORD=m3hQyr4BgF0Paer1H4a5iUnzXqjUji
+TOMTOM_API_KEY=MERY8I7LMeYVSLKO5WuV73W9rKJpBLoB
+TOMTOM_API_KEY_1=6F7HHk8GT6WGlZ22W4gfAbRiQk5lJoGV
+TELEGRAM_BOT_TOKEN=7419967935:AAEeNIzlK6DqcQTL8q63zQ-Ted5W5VOd-LI
+TELEGRAM_BOT_USERNAME=rezsssnfdsjfdsfbot
+TELEGRAM_WEBHOOK_SECRET=vGB8n5H2fJTUx6jy6iYgYlqLz1mfSv9htF
+TELEGRAM_WEBHOOK_URL=https://uber-demo.club/webhook/telegram
+BACKEND_LINK_SECRET=QxAEEGUGRMtWbNvC2REo27haN78Rl5c5EQ
+LBTELEGRAM_URL=http://lbtelegram:8081
+LBTELEGRAM_BOT1_USERNAME=GetRezStealer_bot
+LBTELEGRAM_BOT2_USERNAME=rezDJDFJSFUltraFast_bot
+BACKEND_LINK_SECRET=change_me_internal_secret
+API_PORT=8080
+FRONTEND_PORT=5173
+GIN_MODE=release
diff --git a/docker-pre-prod/.env.lbtelegram b/docker-pre-prod/.env.lbtelegram
new file mode 100644
index 00000000..c6af6483
--- /dev/null
+++ b/docker-pre-prod/.env.lbtelegram
@@ -0,0 +1,30 @@
+# Gateway
+PORT=8081
+ENV=production
+BOT_COUNT=2
+
+# Bots Telegram
+BOT1_TOKEN=8336841145:AAHPfHdgqLctEC_Zet5mT8D7ZXiwp8BQ1io
+BOT1_USERNAME=GetRezStealer_bot
+BOT1_WEBHOOK_SECRET=cVxqtea9s078ozDSlX57MWe6bjoLAK3ra7Zq
+
+BOT2_TOKEN=8325503969:AAGffm9Q-oYr5ySf8cR4Wr-e2CA2p_xOrbg
+BOT2_USERNAME=rezDJDFJSFUltraFast_bot
+BOT2_WEBHOOK_SECRET=591aVEu1kj3YUVCNWAOU2xGdFNCVWqElzXGi
+
+# URL publique de la gateway (pour setWebhook Telegram)
+GATEWAY_URL=https://demo-uber.club
+
+# JWT
+JWT_SECRET=IxGF36s14J0ZNeQCF2Of0APc4kpNd5PlsJ
+JWT_TTL_SECONDS=300
+
+# Load balancer: roundrobin | leastconn | failover
+LB_STRATEGY=failover
+
+# Health check interval en secondes
+HEALTH_CHECK_INTERVAL=30
+
+# URL interne du backend pour valider les tokens de liaison
+BACKEND_LINK_URL=http://backend:8080/api/internal/telegram/link
+BACKEND_LINK_SECRET=QxAEEGUGRMtWbNvC2REo27haN78Rl5c5EQ
diff --git a/docker-pre-prod/backend/Dockerfile b/docker-pre-prod/backend/Dockerfile
new file mode 100644
index 00000000..6464ae4f
--- /dev/null
+++ b/docker-pre-prod/backend/Dockerfile
@@ -0,0 +1,76 @@
+FROM golang:1.24-alpine AS builder
+WORKDIR /app
+
+# Installer les dépendances système
+RUN apk add --no-cache git ca-certificates tzdata gcc musl-dev
+
+# Copier go mod files
+COPY backend/gestion/go.mod backend/gestion/go.sum ./
+
+# Configurer Go et télécharger les dépendances
+ENV GOPROXY=https://proxy.golang.org,direct
+ENV GOSUMDB=sum.golang.org
+ENV CGO_ENABLED=0
+
+# Télécharger les dépendances
+RUN go mod download
+
+# Copier tout le code source
+COPY backend/gestion/ .
+
+# Build le binaire
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
+ -ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
+ -o /app/server .
+
+# =========================================================
+# Stage 2: Runtime
+# =========================================================
+FROM alpine:latest AS runtime
+
+# Installer les dépendances runtime
+RUN apk --no-cache add ca-certificates tzdata wget
+
+# Créer l'utilisateur avec UID/GID fixes
+RUN addgroup -g 101 app && adduser -u 101 -S app -G app
+
+
+WORKDIR /app
+
+# Copier le binaire et les fichiers nécessaires
+COPY --from=builder /app/server .
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+
+# Copier l'entrypoint
+COPY docker/backend/entrypoint.sh .
+RUN chmod +x entrypoint.sh
+
+RUN mkdir -p /app/uploads/images /app/uploads/videos && \
+ chown -R app:app /app
+
+# Passer Ă l'utilisateur non-root
+USER app
+
+EXPOSE 8080
+
+ENTRYPOINT ["./entrypoint.sh"]
+
+# =========================================================
+# Stage 3: WAF (Nginx + ModSecurity)
+# =========================================================
+FROM owasp/modsecurity-crs:nginx-alpine AS waf
+
+# Toutes les opérations privilégiées en root
+USER root
+
+RUN mkdir -p /var/log/modsec /etc/nginx/certs && \
+ chown -R nginx:nginx /var/log/modsec /etc/nginx/certs /usr/share/nginx/html
+
+COPY docker/backend/nginx.conf /etc/nginx/conf.d/app.conf
+COPY docker/backend/custom-rules.conf /etc/nginx/modsec/custom-rules.conf
+RUN echo "Include /etc/nginx/modsec/custom-rules.conf" > /etc/nginx/modsec/custom-includes.conf && \
+ rm -f /etc/nginx/templates/conf.d/default.conf.template || true
+
+USER nginx
+EXPOSE 80 443
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docker-pre-prod/backend/custom-rules.conf b/docker-pre-prod/backend/custom-rules.conf
new file mode 100644
index 00000000..c2aaeb37
--- /dev/null
+++ b/docker-pre-prod/backend/custom-rules.conf
@@ -0,0 +1,77 @@
+# Exclure le corps des requêtes/réponses de l'audit log pour garder les lignes < 6KB (limite Wazuh)
+SecAuditLogParts ABIFHZ
+
+SecRuleRemoveById 932235
+SecRuleRemoveById 911100
+
+SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \
+ "id:399002,phase:2,nolog,pass,\
+ ctl:ruleRemoveById=920120,\
+ ctl:ruleRemoveById=920121"
+
+SecRule REQUEST_URI "@beginsWith /uploads/" \
+ "id:1000,\
+ phase:1,\
+ pass,\
+ nolog,\
+ ctl:ruleEngine=Off"
+
+SecRule IP:BANNED "@eq 1" \
+ "id:100000,phase:1,deny,status:403,log,\
+ msg:'IP is banned'"
+
+SecRule IP:REPUTATION_SCORE "@ge 100" \
+ "id:100099,phase:1,deny,status:403,log,\
+ msg:'Critical reputation score',\
+ setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'"
+
+SecRule TX:SQL_INJECTION_SCORE "@ge 5" \
+ "id:100001,phase:2,deny,status:403,log,\
+ msg:'SQL Injection detected',\
+ setvar:'ip.banned=1',expirevar:'ip.banned=172800'"
+
+SecRule TX:XSS_SCORE "@ge 5" \
+ "id:100010,phase:2,deny,status:403,log,\
+ msg:'XSS detected',\
+ setvar:'ip.banned=1',expirevar:'ip.banned=172800'"
+
+SecRule TX:RCE_SCORE "@ge 5" \
+ "id:100020,phase:2,deny,status:403,log,\
+ msg:'RCE detected',\
+ setvar:'ip.banned=1',expirevar:'ip.banned=259200'"
+
+SecRule TX:LFI_SCORE "@ge 5" \
+ "id:100030,phase:2,deny,status:403,log,\
+ msg:'LFI detected',\
+ setvar:'ip.banned=1',expirevar:'ip.banned=172800'"
+
+SecRule TX:INBOUND_ANOMALY_SCORE "@ge 20" \
+ "id:100060,phase:2,deny,status:403,log,\
+ msg:'Critical anomaly score',\
+ setvar:'ip.banned=1',expirevar:'ip.banned=172800'"
+
+SecRule REQUEST_URI "@streq /api/v1/panier/remove" \
+ "id:399010,phase:1,nolog,pass,\
+ ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350"
+
+SecRule REQUEST_URI "@streq /api/v1/panier/clear" \
+ "id:399011,phase:1,nolog,pass,\
+ ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350"
+
+SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \
+ "id:399001,phase:2,nolog,pass,\
+ ctl:ruleRemoveById=932235"
+
+SecAction \
+ "id:400161,phase:1,nolog,pass,\
+ setvar:'ip.request_window_1sec=+1',\
+ expirevar:'ip.request_window_1sec=1'"
+
+SecRule IP:REQUEST_WINDOW_1SEC "@gt 20" \
+ "id:400160,phase:1,deny,status:429,log,\
+ msg:'Too many requests'"
+
+SecRule IP:REPUTATION_SCORE "@ge 100" \
+ "id:409999,phase:1,deny,status:403,log,\
+ msg:'Critical reputation score',\
+ setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'"
diff --git a/docker-pre-prod/backend/entrypoint.sh b/docker-pre-prod/backend/entrypoint.sh
new file mode 100755
index 00000000..4c1b857e
--- /dev/null
+++ b/docker-pre-prod/backend/entrypoint.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+set -e
+
+# Créer le dossier uploads s'il n'existe pas (le volume le crée en root)
+# Puis créer les sous-dossiers
+if [ ! -d "/app/uploads" ]; then
+ mkdir -p /app/uploads
+fi
+
+# Créer les sous-répertoires
+mkdir -p /app/uploads/images /app/uploads/videos 2>/dev/null || true
+
+echo "✅ Répertoires uploads prêts"
+
+# Lancer l'application
+exec ./server
diff --git a/docker-pre-prod/backend/nginx.conf b/docker-pre-prod/backend/nginx.conf
new file mode 100644
index 00000000..557db88f
--- /dev/null
+++ b/docker-pre-prod/backend/nginx.conf
@@ -0,0 +1,215 @@
+# =========================================================
+# Request ID correlation
+# =========================================================
+map $http_x_request_id $req_id {
+ default $http_x_request_id;
+ "" $request_id;
+}
+
+# =========================================================
+# HTTP → HTTPS redirect
+# =========================================================
+server {
+ listen 80;
+ listen [::]:80;
+ server_name _;
+
+ return 301 https://$host$request_uri;
+}
+
+# =========================================================
+# HTTPS — Hardened
+# =========================================================
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ http2 on;
+ server_name _;
+
+ server_tokens off;
+
+ # ---------------------------------------------------
+ # TLS
+ # ---------------------------------------------------
+ ssl_certificate /etc/nginx/certs/fullchain.pem;
+ ssl_certificate_key /etc/nginx/certs/privkey.pem;
+
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
+ ssl_prefer_server_ciphers off;
+
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 1d;
+ ssl_session_tickets off;
+
+ ssl_stapling on;
+ ssl_stapling_verify on;
+ ssl_trusted_certificate /etc/nginx/certs/fullchain.pem;
+ resolver 127.0.0.11 valid=10s ipv6=off;
+ resolver_timeout 5s;
+
+ # ---------------------------------------------------
+ # En-têtes de sécurité
+ # ---------------------------------------------------
+ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-XSS-Protection "0" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+ add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self';" always;
+
+ # ---------------------------------------------------
+ # Limites et timeouts
+ # ---------------------------------------------------
+ client_max_body_size 100M;
+ client_body_buffer_size 128k;
+ client_header_buffer_size 1k;
+ large_client_header_buffers 4 8k;
+
+ client_body_timeout 30s;
+ client_header_timeout 30s;
+ send_timeout 30s;
+ keepalive_timeout 65s;
+
+ # ---------------------------------------------------
+ # Gzip (statique uniquement — évite BREACH sur l'API)
+ # ---------------------------------------------------
+ gzip on;
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 5;
+ gzip_min_length 1000;
+ gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml;
+
+ # ---------------------------------------------------
+ # ModSecurity WAF
+ # ---------------------------------------------------
+ modsecurity on;
+ modsecurity_rules_file /etc/nginx/modsec/custom-rules.conf;
+
+ # ---------------------------------------------------
+ # API backend Go
+ # ---------------------------------------------------
+ location /api/ {
+ limit_except GET POST PUT PATCH DELETE OPTIONS { deny all; }
+
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always;
+ add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Request-ID' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Max-Age' 86400 always;
+ add_header 'Content-Length' 0;
+ add_header 'Content-Type' 'text/plain charset=UTF-8';
+ return 204;
+ }
+
+ set $upstream_backend http://backend:8080;
+ proxy_pass $upstream_backend;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Request-ID $req_id;
+ proxy_hide_header X-Powered-By;
+
+ proxy_connect_timeout 60s;
+ proxy_send_timeout 300s;
+ proxy_read_timeout 300s;
+
+ proxy_buffer_size 128k;
+ proxy_buffers 4 256k;
+ proxy_busy_buffers_size 256k;
+ }
+
+ # ---------------------------------------------------
+ # Webhook Telegram principal → backend Go
+ # ---------------------------------------------------
+ location = /webhook/telegram {
+ limit_except POST { deny all; }
+
+ set $upstream_backend http://backend:8080;
+ proxy_pass $upstream_backend;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # ---------------------------------------------------
+ # Webhooks LBTelegram (/webhook/bot1, /webhook/bot2…)
+ # ---------------------------------------------------
+ location /webhook/ {
+ limit_except POST { deny all; }
+
+ set $upstream_lbtelegram http://lbtelegram:8081;
+ proxy_pass $upstream_lbtelegram;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # ---------------------------------------------------
+ # Fichiers uploadés (images / vidéos)
+ # ---------------------------------------------------
+ location /uploads/ {
+ alias /usr/share/nginx/html/uploads/;
+ expires 7d;
+ add_header Cache-Control "public, no-transform" always;
+ add_header X-Content-Type-Options "nosniff" always;
+
+ location ~* \.(php|pl|py|cgi|sh|rb|exe)$ {
+ deny all;
+ }
+ }
+
+ # ---------------------------------------------------
+ # Frontend React SPA
+ # ---------------------------------------------------
+ location / {
+ set $upstream_frontend http://frontend:80;
+ proxy_pass $upstream_frontend;
+ proxy_http_version 1.1;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # ---------------------------------------------------
+ # Blocage fichiers sensibles
+ # ---------------------------------------------------
+ location ~ /\. {
+ deny all;
+ access_log off;
+ log_not_found off;
+ }
+
+ location ~* \.(env|git|sql|bak|log|conf|ini|sh)$ {
+ deny all;
+ access_log off;
+ log_not_found off;
+ }
+
+ location ~* (package\.json|package-lock\.json|yarn\.lock|Dockerfile|docker-compose)$ {
+ deny all;
+ access_log off;
+ log_not_found off;
+ }
+
+ # Bloquer scans WordPress / PHP courants
+ location ~* (wp-admin|wp-login|wp-content|xmlrpc|\.php)$ {
+ deny all;
+ access_log off;
+ log_not_found off;
+ }
+}
diff --git a/docker-pre-prod/docker-compose-pre-prod.yml b/docker-pre-prod/docker-compose-pre-prod.yml
new file mode 100644
index 00000000..0a4c84d8
--- /dev/null
+++ b/docker-pre-prod/docker-compose-pre-prod.yml
@@ -0,0 +1,212 @@
+services:
+ # =========================================================
+ # Backend Go
+ # =========================================================
+ backend:
+ image: xor1234/backend-mln:pre-prod
+ container_name: gestion-backend
+ restart: unless-stopped
+ environment:
+ - DB_HOST=${DB_HOST:-postgres}
+ - DB_PORT=${DB_PORT:-5432}
+ - DB_USER=${DB_USER:-postgres}
+ - DB_PASSWORD=${DB_PASSWORD}
+ - DB_NAME=${DB_NAME:-gestion_db}
+ - DB_SSLMODE=${DB_SSLMODE:-disable}
+ - SESSION_SECRET=${SESSION_SECRET}
+ - USER_JWT_SECRET=${USER_JWT_SECRET}
+ - USER_JWT_SECRET_OLD=${USER_JWT_SECRET_OLD}
+ - ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
+ - ADMIN_JWT_SECRET_OLD=${ADMIN_JWT_SECRET_OLD}
+ - REDIS_HOST=${REDIS_HOST:-redis}
+ - REDIS_PORT=${REDIS_PORT:-6379}
+ - REDIS_PASSWORD=${REDIS_PASSWORD}
+ - TOMTOM_API_KEY=${TOMTOM_API_KEY}
+ - TOMTOM_API_KEY_1=${TOMTOM_API_KEY_1}
+ - TOMTOM_API_KEY_2=${TOMTOM_API_KEY_2}
+ - TOMTOM_API_KEY_3=${TOMTOM_API_KEY_3}
+ - API_PORT=${API_PORT:-8080}
+ - NOWPAYMENTS_IPN_SECRET=${NOWPAYMENTS_IPN_SECRET}
+ - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
+ - TELEGRAM_BOT_USERNAME=${TELEGRAM_BOT_USERNAME}
+ - TELEGRAM_WEBHOOK_SECRET=${TELEGRAM_WEBHOOK_SECRET}
+ - TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL}
+ - LBTELEGRAM_URL=http://lbtelegram:8081
+ - LBTELEGRAM_BOT1_USERNAME=${LBTELEGRAM_BOT1_USERNAME:-GetRezStealer_bot}
+ - LBTELEGRAM_BOT2_USERNAME=${LBTELEGRAM_BOT2_USERNAME:-rezDJDFJSFUltraFast_bot}
+ - BACKEND_LINK_SECRET=${BACKEND_LINK_SECRET:-change_me_internal_secret}
+ volumes:
+ - backend_uploads:/app/uploads
+ networks:
+ - gestion-network
+ depends_on:
+ postgres:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+
+ # =========================================================
+ # Frontend Web (React/Vite — servi en HTTP interne)
+ # =========================================================
+ frontend:
+ image: xor1234/frontend-mln:pre-prod
+ container_name: gestion-frontend
+ restart: unless-stopped
+ networks:
+ - gestion-network
+ depends_on:
+ - backend
+
+ waf:
+ image: xor1234/backend-mln:waf-pre-prod
+ container_name: gestion-waf
+ restart: unless-stopped
+ environment:
+ - DISABLE_MODSEC_ENV_SUBST=true
+ - PARANOIA=2
+ - ANOMALY_INBOUND=5
+ - ANOMALY_OUTBOUND=4
+ - MODSEC_AUDIT_LOG=/var/log/modsec/modsec_audit.log
+ ports:
+ - "80:80"
+ - "443:443"
+ volumes:
+ - backend_uploads:/usr/share/nginx/html/uploads:ro
+ - ./certs:/etc/nginx/certs:ro
+ - ./backend/nginx.conf:/etc/nginx/conf.d/app.conf:ro
+ - ./backend/custom-rules.conf:/etc/nginx/modsec/custom-rules.conf:ro
+ - /var/log/waf/nginx:/var/log/nginx
+ - /var/log/waf/modsec:/var/log/modsec
+ networks:
+ - gestion-network
+ depends_on:
+ - backend
+ - frontend
+
+ # =========================================================
+ # PostgreSQL
+ # =========================================================
+ postgres:
+ image: postgres:16-alpine
+ container_name: gestion-postgres
+ restart: unless-stopped
+ environment:
+ - POSTGRES_USER=${DB_USER:-postgres}
+ - POSTGRES_PASSWORD=${DB_PASSWORD}
+ - POSTGRES_DB=${DB_NAME:-gestion_db}
+ - PGDATA=/var/lib/postgresql/data/pgdata
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ networks:
+ - gestion-network
+ healthcheck:
+ test:
+ [
+ "CMD-SHELL",
+ "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-gestion_db}",
+ ]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
+
+ # =========================================================
+ # Redis
+ # =========================================================
+ redis:
+ image: redis:7-alpine
+ container_name: gestion-redis
+ restart: unless-stopped
+ command: >
+ redis-server
+ --requirepass ${REDIS_PASSWORD}
+ --appendonly yes
+ --appendfsync everysec
+ --maxmemory 256mb
+ --maxmemory-policy allkeys-lru
+ volumes:
+ - redis_data:/data
+ networks:
+ - gestion-network
+ healthcheck:
+ test:
+ [
+ "CMD",
+ "redis-cli",
+ "--no-auth-warning",
+ "-a",
+ "${REDIS_PASSWORD}",
+ "ping",
+ ]
+ interval: 10s
+ timeout: 3s
+ retries: 5
+ start_period: 10s
+
+ # =========================================================
+ # LBTelegram — Gateway Telegram load balancer
+ # =========================================================
+ lbtelegram:
+ image: xor1234/load-balancer-tlg:latest
+ container_name: gestion-lbtelegram
+ restart: unless-stopped
+ env_file: ./.env.lbtelegram
+ environment:
+ - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
+ - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}?sslmode=disable
+ networks:
+ - gestion-network
+ depends_on:
+ postgres:
+ condition: service_healthy
+ redis:
+ condition: service_healthy
+ backend:
+ condition: service_started
+
+ clamav:
+ deploy:
+ resources:
+ limits:
+ memory: 1g
+ cpus: 0.5
+ image: clamav/clamav:latest
+ container_name: gestion-clamav
+ restart: unless-stopped
+ volumes:
+ - backend_uploads:/app/uploads:ro
+ - clamav_data:/var/lib/clamav
+ networks:
+ - gestion-network
+ healthcheck:
+ test: ["CMD", "clamdcheck.sh"]
+ interval: 60s
+ timeout: 10s
+ retries: 3
+ start_period: 120s
+
+ dozzle-agent:
+ image: amir20/dozzle:latest
+ command: agent
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ ports:
+ - "7007:7007"
+ restart: unless-stopped
+
+networks:
+ gestion-network:
+ driver: bridge
+ internal: false
+ driver_opts:
+ com.docker.network.bridge.name: gestion-br0
+
+volumes:
+ postgres_data:
+ driver: local
+ redis_data:
+ driver: local
+ backend_uploads:
+ driver: local
+ clamav_data:
+ driver: local
diff --git a/docker-pre-prod/frontend/Dockerfile b/docker-pre-prod/frontend/Dockerfile
new file mode 100644
index 00000000..8594b8ff
--- /dev/null
+++ b/docker-pre-prod/frontend/Dockerfile
@@ -0,0 +1,34 @@
+# =========================================================
+# Stage 1: Build React/Vite
+# =========================================================
+FROM node:22-alpine AS builder
+
+WORKDIR /app
+
+COPY frontend-prep/package.json frontend-prep/package-lock.json* ./
+RUN npm ci --ignore-scripts
+
+COPY frontend-prep/ .
+RUN npm run build
+
+# =========================================================
+# Stage 2: Nginx HTTP (TLS terminé par le WAF)
+# =========================================================
+FROM nginx:alpine AS runtime
+
+COPY docker/frontend/nginx.conf /etc/nginx/conf.d/default.conf
+
+COPY --from=builder /app/dist /usr/share/nginx/html
+
+RUN chown -R nginx:nginx /usr/share/nginx/html && \
+ mkdir -p /var/cache/nginx && \
+ chown -R nginx:nginx /var/cache/nginx && \
+ chown -R nginx:nginx /var/log/nginx && \
+ touch /var/run/nginx.pid && \
+ chown nginx:nginx /var/run/nginx.pid
+
+USER nginx
+
+EXPOSE 80
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docker-pre-prod/frontend/nginx.conf b/docker-pre-prod/frontend/nginx.conf
new file mode 100644
index 00000000..6d95b8c4
--- /dev/null
+++ b/docker-pre-prod/frontend/nginx.conf
@@ -0,0 +1,16 @@
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+}
diff --git a/docker-pre-prod/test-rules.sh b/docker-pre-prod/test-rules.sh
new file mode 100755
index 00000000..3f0fd0c9
--- /dev/null
+++ b/docker-pre-prod/test-rules.sh
@@ -0,0 +1,610 @@
+#!/bin/bash
+
+# =============================================================================
+# Script de Test - ModSecurity Rules (XSS, SQL Injection, RCE, LFI, RFI)
+# =============================================================================
+# Description: Teste les règles WAF pour XSS, SQL, RCE, LFI et RFI
+# Usage: ./test-rules.sh
+# =============================================================================
+
+# Couleurs pour l'affichage
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+PURPLE='\033[0;35m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+BOLD='\033[1m'
+
+# Configuration
+API_BASE_URL="http://172.20.167.237"
+
+# Credentials Client
+CLIENT_USERNAME="salut"
+CLIENT_PASSWORD="salut1234_"
+CLIENT_TOKEN=""
+
+# Credentials Admin
+ADMIN_USERNAME="admin_1768505094"
+ADMIN_PASSWORD="AdminPass123!"
+ADMIN_TOKEN=""
+
+TOTAL_TESTS=0
+PASSED_TESTS=0
+FAILED_TESTS=0
+LOG_FILE="modsec_test_$(date +%Y%m%d_%H%M%S).log"
+
+# =============================================================================
+# Fonctions Utilitaires
+# =============================================================================
+
+print_header() {
+ echo -e "\n${BOLD}${CYAN}========================================${NC}"
+ echo -e "${BOLD}${CYAN}$1${NC}"
+ echo -e "${BOLD}${CYAN}========================================${NC}\n"
+}
+
+print_section() {
+ echo -e "\n${BOLD}${BLUE}>>> $1${NC}\n"
+}
+
+print_test() {
+ echo -e "${YELLOW}[TEST] $1${NC}"
+}
+
+print_success() {
+ ((PASSED_TESTS++))
+ ((TOTAL_TESTS++))
+ echo -e "${GREEN}âś“ PASS${NC} - $1" | tee -a "$LOG_FILE"
+}
+
+print_fail() {
+ ((FAILED_TESTS++))
+ ((TOTAL_TESTS++))
+ echo -e "${RED}âś— FAIL${NC} - $1" | tee -a "$LOG_FILE"
+}
+
+print_info() {
+ echo -e "${CYAN}ℹ INFO${NC} - $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}âš WARNING${NC} - $1"
+}
+
+print_response() {
+ echo -e "${PURPLE}đź“„ Response:${NC} $1"
+}
+
+# Fonction pour effectuer une requĂŞte HTTP avec token client
+http_test_client() {
+ local method=$1
+ local endpoint=$2
+ local data=$3
+ local expected_code=$4
+ local description=$5
+ local extra_headers=$6
+
+ print_test "$description"
+
+ if [ -z "$data" ]; then
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Authorization: Bearer $CLIENT_TOKEN" \
+ -H "Content-Type: application/json" \
+ $extra_headers \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ else
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Authorization: Bearer $CLIENT_TOKEN" \
+ -H "Content-Type: application/json" \
+ $extra_headers \
+ -d "$data" \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ fi
+
+ http_code=$(echo "$response" | tail -n1)
+ body=$(echo "$response" | sed '$d')
+
+ if [ "$http_code" -eq "$expected_code" ]; then
+ print_success "$description (HTTP $http_code)"
+ else
+ print_fail "$description - Expected: $expected_code, Got: $http_code"
+ print_response "$body"
+ echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE"
+ echo "Response: $body" >> "$LOG_FILE"
+ fi
+
+ sleep 0.5
+}
+
+# Fonction pour effectuer une requĂŞte HTTP avec token admin
+http_test_admin() {
+ local method=$1
+ local endpoint=$2
+ local data=$3
+ local expected_code=$4
+ local description=$5
+ local extra_headers=$6
+
+ print_test "$description"
+
+ if [ -z "$data" ]; then
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
+ -H "Content-Type: application/json" \
+ $extra_headers \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ else
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
+ -H "Content-Type: application/json" \
+ $extra_headers \
+ -d "$data" \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ fi
+
+ http_code=$(echo "$response" | tail -n1)
+ body=$(echo "$response" | sed '$d')
+
+ if [ "$http_code" -eq "$expected_code" ]; then
+ print_success "$description (HTTP $http_code)"
+ echo "$body"
+ else
+ print_fail "$description - Expected: $expected_code, Got: $http_code"
+ print_response "$body"
+ echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE"
+ echo "Response: $body" >> "$LOG_FILE"
+ fi
+
+ sleep 0.5
+}
+
+# Fonction pour effectuer une requĂŞte HTTP sans authentification
+http_test_no_auth() {
+ local method=$1
+ local endpoint=$2
+ local data=$3
+ local expected_code=$4
+ local description=$5
+
+ print_test "$description"
+
+ if [ -z "$data" ]; then
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Content-Type: application/json" \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ else
+ response=$(curl -s -w "\n%{http_code}" -X "$method" \
+ -H "Content-Type: application/json" \
+ -d "$data" \
+ "${API_BASE_URL}${endpoint}" 2>&1)
+ fi
+
+ http_code=$(echo "$response" | tail -n1)
+ body=$(echo "$response" | sed '$d')
+
+ if [ "$http_code" -eq "$expected_code" ]; then
+ print_success "$description (HTTP $http_code)"
+ else
+ print_fail "$description - Expected: $expected_code, Got: $http_code"
+ print_response "$body"
+ echo "$description - Expected: $expected_code, Got: $http_code" >> "$LOG_FILE"
+ echo "Response: $body" >> "$LOG_FILE"
+ fi
+
+ sleep 0.5
+}
+
+# =============================================================================
+# Authentification
+# =============================================================================
+
+authenticate() {
+ print_header "AUTHENTIFICATION"
+
+ # ==================== CLIENT LOGIN ====================
+ print_section "1. Login Client"
+ response=$(curl -s -w "\n%{http_code}" -X POST \
+ -H "Content-Type: application/json" \
+ -d "{\"username\":\"$CLIENT_USERNAME\",\"password\":\"$CLIENT_PASSWORD\"}" \
+ "${API_BASE_URL}/api/v1/auth/login")
+
+ http_code=$(echo "$response" | tail -n1)
+ body=$(echo "$response" | sed '$d')
+
+ if [ "$http_code" -eq 200 ]; then
+ CLIENT_TOKEN=$(echo "$body" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
+ if [ -n "$CLIENT_TOKEN" ]; then
+ print_success "Login Client réussi - Token obtenu"
+ print_info "Token Client: ${CLIENT_TOKEN:0:50}..."
+ else
+ print_fail "Login Client réussi mais token non trouvé"
+ print_response "$body"
+ exit 1
+ fi
+ else
+ print_fail "Échec du login Client (HTTP $http_code)"
+ print_response "$body"
+ exit 1
+ fi
+
+ # ==================== ADMIN LOGIN ====================
+ print_section "2. Login Admin"
+ response=$(curl -s -w "\n%{http_code}" -X POST \
+ -H "Content-Type: application/json" \
+ -d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\"}" \
+ "${API_BASE_URL}/api/v2/admin/auth/login")
+
+ http_code=$(echo "$response" | tail -n1)
+ body=$(echo "$response" | sed '$d')
+
+ if [ "$http_code" -eq 200 ]; then
+ ADMIN_TOKEN=$(echo "$body" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
+ if [ -n "$ADMIN_TOKEN" ]; then
+ print_success "Login Admin réussi - Token obtenu"
+ print_info "Token Admin: ${ADMIN_TOKEN:0:50}..."
+ else
+ print_fail "Login Admin réussi mais token non trouvé"
+ print_response "$body"
+ exit 1
+ fi
+ else
+ print_fail "Échec du login Admin (HTTP $http_code)"
+ print_response "$body"
+ exit 1
+ fi
+}
+
+# =============================================================================
+# Tests SQL Injection
+# =============================================================================
+
+test_sql_injection() {
+ print_header "TESTS SQL INJECTION"
+
+ print_section "1. SQL Injection - Login"
+
+ # Test 1: SQL Injection classique dans login client
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' OR '\''1'\''='\''1","password":"test"}' \
+ 403 "SQLi - Login Client OR 1=1"
+
+ # Test 2: SQL Injection dans login admin
+ http_test_no_auth "POST" "/api/v2/admin/auth/login" \
+ '{"username":"admin'\'' OR '\''1'\''='\''1","password":"test"}' \
+ 403 "SQLi - Login Admin OR 1=1"
+
+ # Test 3: SQL Injection avec UNION
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' UNION SELECT * FROM users--","password":"test"}' \
+ 403 "SQLi - UNION SELECT"
+
+ # Test 4: SQL Injection avec DROP TABLE
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\''; DROP TABLE users;--","password":"test"}' \
+ 403 "SQLi - DROP TABLE"
+
+ # Test 5: SQL Injection avec commentaire
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\''--","password":"test"}' \
+ 403 "SQLi - Commentaire SQL --"
+
+ print_section "2. SQL Injection - Panier"
+
+ # Test 6: SQL Injection dans name_product
+ http_test_client "POST" "/api/v1/panier/add" \
+ '{"name_product":"Pizza'\'' OR 1=1--","category":"pizza","quantity":1}' \
+ 403 "SQLi - Panier name_product"
+
+ # Test 7: SQL Injection dans category
+ http_test_client "POST" "/api/v1/panier/add" \
+ '{"name_product":"Pizza","category":"pizza'\'' OR '\''1'\''='\''1","quantity":1}' \
+ 403 "SQLi - Panier category"
+
+ print_section "3. SQL Injection - Admin"
+
+ # Test 8: SQL Injection dans username pénalité
+ http_test_admin "POST" "/api/v2/admin/protected/penalty" \
+ "{\"username\":\"admin' OR '1'='1\",\"amount\":50.0,\"reason\":\"Test\"}" \
+ 403 "SQLi - Username pénalité"
+
+ # Test 9: SQL Injection dans paramètres commandes
+ http_test_admin "GET" "/api/v2/admin/protected/orders?status=pending' OR '1'='1" \
+ "" \
+ 403 "SQLi - Paramètres commandes"
+
+ # Test 10: SQL Injection dans ID commande
+ http_test_admin "POST" "/api/v2/admin/protected/orders/1' OR '1'='1/auto-assign" \
+ "" \
+ 403 "SQLi - ID commande"
+
+ # Test 11: SQL Injection dans username livreur
+ http_test_admin "GET" "/api/v2/admin/protected/delivery-persons/john' OR '1'='1/location" \
+ "" \
+ 403 "SQLi - Username livreur"
+
+ print_section "4. SQL Injection - Commandes Client"
+
+ # Test 12: SQL Injection dans adresse checkout
+ http_test_client "POST" "/api/v1/checkout" \
+ '{"delivery_address":"1'\'' OR '\''1'\''='\''1"}' \
+ 403 "SQLi - Adresse checkout"
+
+ # Test 13: SQL Injection nom produit admin
+ http_test_admin "POST" "/api/v2/admin/protected/products" \
+ '{"nom":"Pizza'\'' OR '\''1'\''='\''1","category":"pizza","stock":10,"prix":12.99}' \
+ 403 "SQLi - Nom produit admin"
+
+ print_section "5. SQL Injection - Variantes avancées"
+
+ # Test 14: SQL Injection avec AND
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' AND '\''1'\''='\''1","password":"test"}' \
+ 403 "SQLi - AND condition"
+
+ # Test 15: SQL Injection avec encodage hex
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' OR 0x31=0x31--","password":"test"}' \
+ 403 "SQLi - Encodage hex"
+
+ # Test 16: SQL Injection avec SLEEP (Time-based)
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' AND SLEEP(5)--","password":"test"}' \
+ 403 "SQLi - Time-based SLEEP"
+
+ # Test 17: SQL Injection avec BENCHMARK
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' AND BENCHMARK(10000000,SHA1('\''test'\''))--","password":"test"}' \
+ 403 "SQLi - BENCHMARK"
+
+ # Test 18: SQL Injection avec sous-requĂŞte
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"admin'\'' AND (SELECT COUNT(*) FROM users)>0--","password":"test"}' \
+ 403 "SQLi - Sous-requĂŞte"
+}
+
+# =============================================================================
+# Tests XSS (Cross-Site Scripting)
+# =============================================================================
+
+test_xss() {
+ print_header "TESTS XSS (CROSS-SITE SCRIPTING)"
+
+ print_section "1. XSS - Login"
+
+ # Test 1: XSS basique avec script tag
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"","password":"test"}' \
+ 403 "XSS - Script tag basique"
+
+ # Test 2: XSS avec event handler
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"
","password":"test"}' \
+ 403 "XSS - Event handler onerror"
+
+ # Test 3: XSS avec SVG
+ http_test_no_auth "POST" "/api/v1/auth/login" \
+ '{"username":"