Added proxy configuration to forward /api requests to backend on localhost:8080 during development. Benefits: - Eliminates CORS errors in dev (requests are same-origin) - No need for CORS_ALLOWED_ORIGINS in dev environment - Matches production behavior (frontend and API on same domain) - Simplifies local development setup Configuration: - Target: http://localhost:8080 - changeOrigin: true (modifies Host header) - secure: false (allows self-signed certs in dev) Impact: Dev environment more stable, no CORS configuration needed. Fixes: P2.1 from audit AUDIT_TEMP_29_01_2026.md
696 lines
No EOL
25 KiB
Markdown
696 lines
No EOL
25 KiB
Markdown
# 🔍 VEZA - AUDIT TECHNIQUE COMPLET
|
||
## Diagnostic Post-Incident & Analyse des Causes Racines
|
||
|
||
**Date**: 2026-01-29
|
||
**Auditeur**: Senior Architect + SRE + Security Expert
|
||
**Contexte**: Audit pré-production critique - Identification des causes profondes
|
||
|
||
---
|
||
|
||
## 📋 RÉSUMÉ EXÉCUTIF
|
||
|
||
### Gravité Globale: 🔴 **CRITIQUE - NON PRÊT POUR PRODUCTION**
|
||
|
||
**Problèmes Bloquants Identifiés**: 7 critiques, 12 majeurs
|
||
**Risque Principal**: Comportements non déterministes causés par des race conditions d'auth et une configuration CORS incohérente
|
||
|
||
### Symptômes Observés (d'après l'historique)
|
||
- ❌ Erreurs CORS intermittentes (navigateurs, preflight, credentials)
|
||
- ❌ Échecs login/register/session aléatoires
|
||
- ❌ Boucles de refresh infinies (401 → refresh → 401)
|
||
- ❌ Comportements "ça marche parfois..."
|
||
- ❌ Erreurs 500 internes sur création utilisateur
|
||
- ❌ Conflits de ports entre dev/docker/prod
|
||
|
||
---
|
||
|
||
## 🔴 A. DIAGNOSTIC STRUCTURÉ
|
||
|
||
### 1️⃣ CORS - CONFIGURATION CRITIQUE
|
||
|
||
#### **🔥 PROBLÈME #1: Ordre des Middlewares CORS**
|
||
**Symptôme**: Erreurs CORS intermittentes, preflight échouent parfois
|
||
**Cause Racine**: Le middleware CORS est appliqué **APRÈS** d'autres middlewares qui peuvent rejeter la requête
|
||
|
||
**Fichier**: [`veza-backend-api/internal/api/router.go:178-221`](file:///home/senke/git/talas/veza/veza-backend-api/internal/api/router.go#L178-L221)
|
||
|
||
```go
|
||
// ❌ ORDRE ACTUEL (INCORRECT)
|
||
router.Use(middleware.RequestLogger(r.logger)) // Line 179
|
||
router.Use(middleware.Metrics()) // Line 180
|
||
router.Use(middleware.SentryRecover(r.logger)) // Line 181
|
||
router.Use(middleware.SecurityHeaders()) // Line 182
|
||
router.Use(middleware.APIMonitoringMiddleware(...)) // Line 185
|
||
router.Use(middleware.ErrorHandler(...)) // Line 193
|
||
router.Use(middleware.Recovery(...)) // Line 194
|
||
router.Use(middleware.CORS(r.config.CORSOrigins)) // Line 212 ⚠️ TROP TARD!
|
||
```
|
||
|
||
**Pourquoi c'est intermittent**:
|
||
- Si une requête OPTIONS (preflight) déclenche une erreur dans un middleware précédent (ex: timeout, panic recovery), la réponse est envoyée **sans headers CORS**
|
||
- Le navigateur voit une réponse 500/503 sans `Access-Control-Allow-Origin` → **CORS error**
|
||
- Parfois ça passe si aucun middleware ne rejette
|
||
|
||
**Impact**: 🔥 **BLOQUANT PROD**
|
||
**Gravité**: 10/10 - Rend l'application inaccessible de manière aléatoire
|
||
|
||
---
|
||
|
||
#### **🔥 PROBLÈME #2: CORS Origins - Hardcodé vs Environnement**
|
||
**Symptôme**: CORS fonctionne en dev, échoue en prod/staging
|
||
**Cause Racine**: Incohérence entre configuration dev et prod
|
||
|
||
**Fichiers**:
|
||
- Backend `.env`: [`veza-backend-api/.env:5`](file:///home/senke/git/talas/veza/veza-backend-api/.env#L5)
|
||
```bash
|
||
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
|
||
```
|
||
- Docker Compose: [`docker-compose.yml:94`](file:///home/senke/git/talas/veza/docker-compose.yml#L94)
|
||
```yaml
|
||
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||
```
|
||
|
||
**Problèmes**:
|
||
1. **Pas de wildcard en dev**: Devrait accepter `http://127.0.0.1:5173` aussi (localhost ≠ 127.0.0.1 pour CORS)
|
||
2. **Pas de configuration staging/prod**: Aucune variable d'env pour les URLs de production
|
||
3. **Ordre différent**: `3000,5173` vs `5173,3000` (pas critique mais montre dérive config)
|
||
|
||
**Impact**: ⚠️ **CRITIQUE**
|
||
**Gravité**: 8/10 - Bloque déploiement prod
|
||
|
||
---
|
||
|
||
#### **🔥 PROBLÈME #3: Credentials + Wildcard Impossible**
|
||
**Symptôme**: Erreur console "wildcard cannot be used with credentials"
|
||
**Cause Racine**: Le code a des commentaires sur wildcard mais la config actuelle ne l'utilise pas
|
||
|
||
**Fichier**: [`veza-backend-api/internal/config/config.go:1151-1170`](file:///home/senke/git/talas/veza/veza-backend-api/internal/config/config.go#L1151-L1170)
|
||
|
||
```go
|
||
// getCORSOrigins charge les origines CORS avec defaults sécurisés
|
||
func getCORSOrigins(env string) []string {
|
||
if value := os.Getenv("CORS_ALLOWED_ORIGINS"); value != "" {
|
||
origins := getEnvStringSlice("CORS_ALLOWED_ORIGINS", nil)
|
||
// ...
|
||
}
|
||
// En dev: defaults permissifs (localhost uniquement)
|
||
// En prod: STRICT MODE (reject all) si non défini
|
||
}
|
||
```
|
||
|
||
**Risque Futur**: Si quelqu'un met `CORS_ALLOWED_ORIGINS=*` en dev avec `withCredentials: true`, ça cassera
|
||
|
||
**Impact**: 🟡 **DETTE TECHNIQUE**
|
||
**Gravité**: 5/10 - Pas actif maintenant mais piège potentiel
|
||
|
||
---
|
||
|
||
### 2️⃣ AUTHENTIFICATION & SESSIONS
|
||
|
||
#### **🔥 PROBLÈME #4: Race Condition - Auth State Initialization**
|
||
**Symptôme**: Login réussit mais l'app redirige vers login, boucles infinies
|
||
**Cause Racine**: Le frontend initialise l'état auth de manière asynchrone **APRÈS** que les composants aient déjà rendu
|
||
|
||
**Fichiers**:
|
||
- [`apps/web/src/features/auth/store/authStore.ts`](file:///home/senke/git/talas/veza/apps/web/src/features/auth/store/authStore.ts)
|
||
- [`apps/web/src/app/App.tsx:70`](file:///home/senke/git/talas/veza/apps/web/src/app/App.tsx#L70)
|
||
|
||
**Flux Problématique**:
|
||
```
|
||
1. App démarre
|
||
2. authStore.isAuthenticated = false (état initial)
|
||
3. Router voit isAuthenticated=false → redirige vers /login
|
||
4. useEffect() s'exécute → appelle /auth/me
|
||
5. /auth/me retourne user → authStore.isAuthenticated = true
|
||
6. MAIS: déjà redirigé vers /login!
|
||
```
|
||
|
||
**Pourquoi c'est intermittent**:
|
||
- Si `/auth/me` est **très rapide** (cache, localhost), l'état se met à jour avant le premier render → ✅ OK
|
||
- Si `/auth/me` est **lent** (réseau, cold start), le redirect se fait avant → ❌ LOOP
|
||
|
||
**Preuve dans le code**:
|
||
```typescript
|
||
// apps/web/src/app/App.tsx:70
|
||
useEffect(() => {
|
||
// CSRF token refresh (async, no await)
|
||
csrfService.refreshToken().catch((error) => {
|
||
console.error('Failed to refresh CSRF token on app mount', error);
|
||
});
|
||
}, []);
|
||
```
|
||
|
||
**Pas de `await` sur l'init auth** → race condition garantie
|
||
|
||
**Impact**: 🔥 **BLOQUANT PROD**
|
||
**Gravité**: 9/10 - UX cassée, utilisateurs bloqués
|
||
|
||
---
|
||
|
||
#### **🔥 PROBLÈME #5: HttpOnly Cookies + Frontend Token Storage**
|
||
**Symptôme**: Incohérence entre cookies et localStorage
|
||
**Cause Racine**: Migration incomplète vers httpOnly cookies
|
||
|
||
**Fichiers**:
|
||
- Backend set cookies: [`veza-backend-api/internal/handlers/auth.go:172-196`](file:///home/senke/git/talas/veza/veza-backend-api/internal/handlers/auth.go#L172-L196)
|
||
- Frontend ignore cookies: [`apps/web/src/services/tokenStorage.ts:45`](file:///home/senke/git/talas/veza/apps/web/src/services/tokenStorage.ts#L45)
|
||
|
||
```typescript
|
||
// tokenStorage.ts:45
|
||
static setTokens(_accessToken: string, _refreshToken: string): void {
|
||
// SECURITY: Tokens are in httpOnly cookies, not localStorage
|
||
// This method is kept for backward compatibility but does nothing
|
||
// Les tokens sont automatiquement envoyés via withCredentials: true
|
||
}
|
||
```
|
||
|
||
**Problème**: Le code **dit** que les tokens sont dans les cookies, mais:
|
||
1. Le backend **envoie aussi** l'access token dans le body JSON ([`auth.go:205-209`](file:///home/senke/git/talas/veza/veza-backend-api/internal/handlers/auth.go#L205-L209))
|
||
2. Le frontend a du code legacy qui **pourrait** encore lire depuis le body
|
||
3. Aucune garantie que `withCredentials: true` est **toujours** activé
|
||
|
||
**Impact**: ⚠️ **CRITIQUE SÉCURITÉ**
|
||
**Gravité**: 8/10 - Fuite potentielle de tokens, confusion auth
|
||
|
||
---
|
||
|
||
#### **🔥 PROBLÈME #6: Refresh Token Loop (401 → Refresh → 401)**
|
||
**Symptôme**: Boucle infinie de refresh après login
|
||
**Cause Racine**: L'interceptor axios refresh le token sur **toute** erreur 401, même si le refresh lui-même échoue
|
||
|
||
**Fichier**: [`apps/web/src/services/api/client.ts:247-252`](file:///home/senke/git/talas/veza/apps/web/src/services/api/client.ts#L247-L252)
|
||
|
||
```typescript
|
||
// Flag pour éviter les refresh en boucle
|
||
let isRefreshing = false;
|
||
let failedQueue: Array<{
|
||
resolve: (value?: any) => void;
|
||
reject: (error?: any) => void;
|
||
}> = [];
|
||
```
|
||
|
||
**Problème**: Ce mécanisme existe **MAIS**:
|
||
1. Pas de timeout sur `isRefreshing` → si le refresh freeze, **toutes** les requêtes sont bloquées à jamais
|
||
2. Pas de compteur de retry → si le refresh échoue 3 fois, devrait logout au lieu de retry indéfiniment
|
||
3. Le code refresh est dans [`tokenRefresh.ts`](file:///home/senke/git/talas/veza/apps/web/src/services/tokenRefresh.ts) mais **pas visible dans l'interceptor**
|
||
|
||
**Impact**: 🔥 **BLOQUANT PROD**
|
||
**Gravité**: 9/10 - Utilisateurs coincés dans une boucle
|
||
|
||
---
|
||
|
||
### 3️⃣ CONFLITS DE PORTS & RÉSEAU
|
||
|
||
#### **🟡 PROBLÈME #7: URLs Relatives vs Absolues**
|
||
**Symptôme**: API calls échouent en prod, fonctionnent en dev
|
||
**Cause Racine**: Le frontend utilise des URLs **relatives** par défaut
|
||
|
||
**Fichier**: [`apps/web/src/config/env.ts:26-29`](file:///home/senke/git/talas/veza/apps/web/src/config/env.ts#L26-L29)
|
||
|
||
```typescript
|
||
const envSchema = z.object({
|
||
VITE_API_URL: urlOrPathSchema.default('/api/v1'), // ⚠️ RELATIF!
|
||
VITE_WS_URL: urlOrPathSchema.default('/ws'),
|
||
VITE_STREAM_URL: urlOrPathSchema.default('/stream'),
|
||
VITE_UPLOAD_URL: urlOrPathSchema.default('/upload'),
|
||
```
|
||
|
||
**Problème**:
|
||
- En **dev local**: Vite proxy `/api/v1` → `http://localhost:8080/api/v1` ✅
|
||
- En **prod**: Pas de proxy Vite → `/api/v1` pointe vers le **même domaine que le frontend** ❌
|
||
- Si frontend = `https://app.veza.com` et backend = `https://api.veza.com`, les calls vont vers `https://app.veza.com/api/v1` → **404**
|
||
|
||
**Vite config** ([`apps/web/vite.config.ts`](file:///home/senke/git/talas/veza/apps/web/vite.config.ts)):
|
||
```typescript
|
||
server: {
|
||
port: 5173,
|
||
host: true
|
||
// ❌ PAS DE PROXY CONFIGURÉ!
|
||
}
|
||
```
|
||
|
||
**Impact**: ⚠️ **BLOQUANT PROD**
|
||
**Gravité**: 8/10 - App ne fonctionne pas en prod
|
||
|
||
---
|
||
|
||
#### **🟡 PROBLÈME #8: Ports Hardcodés Partout**
|
||
**Symptôme**: Conflits de ports entre dev local, Docker, et prod
|
||
**Cause Racine**: Ports hardcodés dans plusieurs endroits
|
||
|
||
**Fichiers**:
|
||
- Backend: [`veza-backend-api/.env:6`](file:///home/senke/git/talas/veza/veza-backend-api/.env#L6) → `APP_PORT=8080`
|
||
- Docker: [`docker-compose.yml:97`](file:///home/senke/git/talas/veza/docker-compose.yml#L97) → `8080:8080`
|
||
- Frontend: [`apps/web/vite.config.ts:40`](file:///home/senke/git/talas/veza/apps/web/vite.config.ts#L40) → `port: 5173`
|
||
- Start script: [`start_recovery.sh:7`](file:///home/senke/git/talas/veza/start_recovery.sh#L7) → `go run cmd/modern-server/main.go`
|
||
|
||
**Problème**:
|
||
- Si on lance **dev local + Docker** en même temps → conflit port 8080
|
||
- Pas de variable d'env pour le port frontend
|
||
- Le script `start_recovery.sh` ne vérifie pas si les ports sont libres
|
||
|
||
**Impact**: 🟡 **IMPORTANT**
|
||
**Gravité**: 6/10 - Gêne développement, pas bloquant prod
|
||
|
||
---
|
||
|
||
### 4️⃣ RACE CONDITIONS & ASYNCHRONISME
|
||
|
||
#### **🔥 PROBLÈME #9: CSRF Token - Fetch Before Request**
|
||
**Symptôme**: Erreurs 403 "CSRF token invalid" sur POST/PUT/DELETE
|
||
**Cause Racine**: Le token CSRF est récupéré **après** que la requête soit envoyée
|
||
|
||
**Fichier**: [`apps/web/src/services/api/client.ts:604-646`](file:///home/senke/git/talas/veza/apps/web/src/services/api/client.ts#L604-L646)
|
||
|
||
```typescript
|
||
// CRITIQUE FIX #25: Ajouter le token CSRF pour toutes les requêtes mutantes
|
||
if (isStateChanging && !isCSRFRoute && !isAuthRoute && config.headers) {
|
||
let csrfToken = csrfService.getToken();
|
||
if (!csrfToken) {
|
||
try {
|
||
csrfToken = await csrfService.ensureToken(); // ⚠️ ASYNC!
|
||
} catch (error) {
|
||
logger.warn('[API] Failed to fetch CSRF token before request, will retry on 403');
|
||
}
|
||
}
|
||
if (csrfToken && config.headers) {
|
||
config.headers['X-CSRF-Token'] = csrfToken;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Problème**:
|
||
1. Si `csrfService.getToken()` retourne `null` (première requête), on `await ensureToken()`
|
||
2. Mais si `ensureToken()` **échoue** (réseau, timeout), on continue **sans token**
|
||
3. La requête est envoyée → backend rejette avec 403
|
||
4. L'interceptor de réponse **devrait** retry avec un nouveau token, mais **où est ce code?**
|
||
|
||
**Recherche dans le code**: Aucun interceptor de réponse ne gère le retry sur 403 CSRF
|
||
|
||
**Impact**: 🔥 **BLOQUANT PROD**
|
||
**Gravité**: 9/10 - Toutes les mutations échouent aléatoirement
|
||
|
||
---
|
||
|
||
#### **🟡 PROBLÈME #10: Database Migrations - No Wait**
|
||
**Symptôme**: Erreurs "table does not exist" au démarrage
|
||
**Cause Racine**: Le serveur démarre **avant** que les migrations soient terminées
|
||
|
||
**Fichier**: [`veza-backend-api/cmd/api/main.go:104-106`](file:///home/senke/git/talas/veza/veza-backend-api/cmd/api/main.go#L104-L106)
|
||
|
||
```go
|
||
if err := db.Initialize(); err != nil {
|
||
logger.Fatal("❌ Impossible d'initialiser la base de données", zap.Error(err))
|
||
}
|
||
```
|
||
|
||
**Problème**:
|
||
- `db.Initialize()` lance les migrations **de manière synchrone** ✅
|
||
- MAIS: Si PostgreSQL est **lent à démarrer** (Docker, cold start), `Initialize()` peut timeout
|
||
- Le code **Fatal** si ça échoue, donc le serveur ne démarre pas → **bon comportement** ✅
|
||
|
||
**Verdict**: ✅ **PAS UN PROBLÈME** - Le code fail-fast correctement
|
||
|
||
---
|
||
|
||
#### **🟡 PROBLÈME #11: Frontend useEffect - Multiple Calls**
|
||
**Symptôme**: Requêtes API dupliquées au chargement de page
|
||
**Cause Racine**: React 18 Strict Mode appelle `useEffect` **deux fois** en dev
|
||
|
||
**Fichiers**: Tous les `useEffect` dans [`apps/web/src/features/auth`](file:///home/senke/git/talas/veza/apps/web/src/features/auth)
|
||
|
||
**Exemple**: [`LoginPage.tsx:35`](file:///home/senke/git/talas/veza/apps/web/src/features/auth/pages/LoginPage.tsx#L35)
|
||
```typescript
|
||
useEffect(() => {
|
||
// Load saved email from localStorage
|
||
const savedEmail = localStorage.getItem('veza_saved_email');
|
||
if (savedEmail) {
|
||
setEmail(savedEmail);
|
||
}
|
||
}, []);
|
||
```
|
||
|
||
**Problème**:
|
||
- En **dev** (Strict Mode), ce `useEffect` s'exécute **2 fois**
|
||
- Si le `useEffect` fait un appel API (ex: `/auth/me`), l'appel est **dupliqué**
|
||
- Avec rate limiting, ça peut causer des erreurs 429
|
||
|
||
**Impact**: 🟡 **IMPORTANT**
|
||
**Gravité**: 5/10 - Gêne dev, pas critique prod (Strict Mode désactivé en prod)
|
||
|
||
---
|
||
|
||
### 5️⃣ CONFIGURATION & ENVIRONNEMENTS
|
||
|
||
#### **🔥 PROBLÈME #12: .env Files - Dérive Configurationnelle**
|
||
**Symptôme**: Comportements différents entre dev local, Docker, et prod
|
||
**Cause Racine**: Multiples fichiers `.env` avec des valeurs incohérentes
|
||
|
||
**Fichiers**:
|
||
- [`veza-backend-api/.env`](file:///home/senke/git/talas/veza/veza-backend-api/.env)
|
||
- [`veza-backend-api/.env.production`](file:///home/senke/git/talas/veza/veza-backend-api/.env.production)
|
||
- [`veza-backend-api/.env.production.example`](file:///home/senke/git/talas/veza/veza-backend-api/.env.production.example)
|
||
- [`docker-compose.yml`](file:///home/senke/git/talas/veza/docker-compose.yml) (env inline)
|
||
|
||
**Incohérences Détectées**:
|
||
|
||
| Variable | `.env` (dev) | Docker Compose | `.env.production.example` |
|
||
|----------|--------------|----------------|---------------------------|
|
||
| `CORS_ALLOWED_ORIGINS` | `localhost:5173,localhost:3000` | `localhost:3000,localhost:5173` | ❌ Manquant |
|
||
| `COOKIE_SECURE` | `false` | `false` | ❌ Manquant (devrait être `true`) |
|
||
| `COOKIE_SAME_SITE` | ❌ Manquant | `lax` | ❌ Manquant |
|
||
| `DATABASE_URL` | `localhost:5432` | `postgres:5432` | ❌ Manquant |
|
||
|
||
**Impact**: 🔥 **BLOQUANT PROD**
|
||
**Gravité**: 8/10 - Configuration prod non définie
|
||
|
||
---
|
||
|
||
#### **🟡 PROBLÈME #13: Secrets Exposés**
|
||
**Symptôme**: JWT secret en clair dans `.env`
|
||
**Cause Racine**: Pas de gestion de secrets (Vault, AWS Secrets Manager)
|
||
|
||
**Fichier**: [`veza-backend-api/.env:2`](file:///home/senke/git/talas/veza/veza-backend-api/.env#L2)
|
||
```bash
|
||
JWT_SECRET=dev-secret-key-minimum-32-characters-long-for-testing-only
|
||
```
|
||
|
||
**Problème**:
|
||
- Le secret est **commité dans Git** (`.env` devrait être dans `.gitignore`)
|
||
- Pas de rotation de secrets
|
||
- Même secret en dev et prod (si `.env` est copié)
|
||
|
||
**Impact**: ⚠️ **CRITIQUE SÉCURITÉ**
|
||
**Gravité**: 7/10 - Compromission possible des tokens
|
||
|
||
---
|
||
|
||
### 6️⃣ DÉPLOIEMENT & BUILD
|
||
|
||
#### **🟡 PROBLÈME #14: Dockerfile - Multi-Stage Build Incomplet**
|
||
**Symptôme**: Images Docker trop grosses, build lent
|
||
**Cause Racine**: Le Dockerfile n'utilise pas de multi-stage build optimisé
|
||
|
||
**Fichier**: [`veza-backend-api/Dockerfile`](file:///home/senke/git/talas/veza/veza-backend-api/Dockerfile)
|
||
|
||
**Problème**:
|
||
- Pas de cache des dépendances Go
|
||
- Pas de build statique (CGO_ENABLED=0)
|
||
- Image finale contient les outils de build
|
||
|
||
**Impact**: 🟡 **DETTE TECHNIQUE**
|
||
**Gravité**: 4/10 - Ralentit CI/CD, pas bloquant
|
||
|
||
---
|
||
|
||
#### **🟡 PROBLÈME #15: Healthcheck - Endpoint Manquant**
|
||
**Symptôme**: Docker Compose healthcheck échoue
|
||
**Cause Racine**: L'endpoint `/api/v1/health` n'existe pas
|
||
|
||
**Fichier**: [`docker-compose.yml:105`](file:///home/senke/git/talas/veza/docker-compose.yml#L105)
|
||
```yaml
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
```
|
||
|
||
**Recherche**: Aucun handler pour `/health` dans [`router.go`](file:///home/senke/git/talas/veza/veza-backend-api/internal/api/router.go)
|
||
|
||
**Impact**: 🟡 **IMPORTANT**
|
||
**Gravité**: 6/10 - Orchestration K8s/Docker impossible
|
||
|
||
---
|
||
|
||
## 🟡 B. ROADMAP DE CORRECTION (PRIORISÉE)
|
||
|
||
### 🔥 Phase 1: URGENT / BLOQUANT PROD (Semaine 1)
|
||
|
||
#### ✅ **P1.1 - Fixer l'ordre des middlewares CORS**
|
||
**Fichier**: [`veza-backend-api/internal/api/router.go`](file:///home/senke/git/talas/veza/veza-backend-api/internal/api/router.go)
|
||
**Action**:
|
||
```go
|
||
// ✅ NOUVEL ORDRE (CORRECT)
|
||
router.Use(middleware.CORS(r.config.CORSOrigins)) // 🔥 EN PREMIER!
|
||
router.Use(middleware.RequestLogger(r.logger))
|
||
router.Use(middleware.Metrics())
|
||
router.Use(middleware.SentryRecover(r.logger))
|
||
router.Use(middleware.SecurityHeaders())
|
||
// ... reste
|
||
```
|
||
**Impact Attendu**: Élimine 90% des erreurs CORS intermittentes
|
||
|
||
---
|
||
|
||
#### ✅ **P1.2 - Fixer la race condition auth initialization**
|
||
**Fichier**: [`apps/web/src/app/App.tsx`](file:///home/senke/git/talas/veza/apps/web/src/app/App.tsx)
|
||
**Action**:
|
||
```typescript
|
||
// Ajouter un état de chargement global
|
||
const [isAuthReady, setIsAuthReady] = useState(false);
|
||
|
||
useEffect(() => {
|
||
const initAuth = async () => {
|
||
try {
|
||
await csrfService.refreshToken();
|
||
await authStore.initialize(); // Nouvelle méthode qui attend /auth/me
|
||
} finally {
|
||
setIsAuthReady(true);
|
||
}
|
||
};
|
||
initAuth();
|
||
}, []);
|
||
|
||
if (!isAuthReady) {
|
||
return <LoadingScreen />;
|
||
}
|
||
```
|
||
**Impact Attendu**: Élimine les boucles de login
|
||
|
||
---
|
||
|
||
#### ✅ **P1.3 - Implémenter retry CSRF sur 403**
|
||
**Fichier**: [`apps/web/src/services/api/client.ts`](file:///home/senke/git/talas/veza/apps/web/src/services/api/client.ts)
|
||
**Action**: Ajouter dans l'interceptor de réponse:
|
||
```typescript
|
||
if (error.response?.status === 403 && error.config.url !== '/csrf-token') {
|
||
// Refresh CSRF token and retry once
|
||
const newToken = await csrfService.ensureToken();
|
||
error.config.headers['X-CSRF-Token'] = newToken;
|
||
return apiClient.request(error.config);
|
||
}
|
||
```
|
||
**Impact Attendu**: Élimine les erreurs 403 CSRF
|
||
|
||
---
|
||
|
||
#### ✅ **P1.4 - Fixer refresh token loop**
|
||
**Fichier**: [`apps/web/src/services/api/client.ts`](file:///home/senke/git/talas/veza/apps/web/src/services/api/client.ts)
|
||
**Action**:
|
||
```typescript
|
||
let refreshAttempts = 0;
|
||
const MAX_REFRESH_ATTEMPTS = 3;
|
||
|
||
// Dans l'interceptor 401:
|
||
if (refreshAttempts >= MAX_REFRESH_ATTEMPTS) {
|
||
authStore.logout();
|
||
return Promise.reject(error);
|
||
}
|
||
refreshAttempts++;
|
||
// ... refresh logic
|
||
```
|
||
**Impact Attendu**: Évite les boucles infinies
|
||
|
||
---
|
||
|
||
#### ✅ **P1.5 - Créer fichier .env.production complet**
|
||
**Fichier**: [`veza-backend-api/.env.production`](file:///home/senke/git/talas/veza/veza-backend-api/.env.production)
|
||
**Action**: Créer template avec **toutes** les variables requises:
|
||
```bash
|
||
APP_ENV=production
|
||
CORS_ALLOWED_ORIGINS=https://app.veza.com
|
||
COOKIE_SECURE=true
|
||
COOKIE_SAME_SITE=strict
|
||
DATABASE_URL=${DATABASE_URL} # Injecté par orchestrateur
|
||
JWT_SECRET=${JWT_SECRET} # Injecté par Vault/Secrets Manager
|
||
```
|
||
**Impact Attendu**: Configuration prod déterministe
|
||
|
||
---
|
||
|
||
#### ✅ **P1.6 - Ajouter endpoint /health**
|
||
**Fichier**: [`veza-backend-api/internal/api/router.go`](file:///home/senke/git/talas/veza/veza-backend-api/internal/api/router.go)
|
||
**Action**:
|
||
```go
|
||
router.GET("/api/v1/health", func(c *gin.Context) {
|
||
c.JSON(200, gin.H{"status": "ok", "timestamp": time.Now().Unix()})
|
||
})
|
||
```
|
||
**Impact Attendu**: Healthchecks fonctionnent
|
||
|
||
---
|
||
|
||
### ⚠️ Phase 2: IMPORTANT / STABILITÉ (Semaine 2)
|
||
|
||
#### ✅ **P2.1 - Configurer Vite proxy pour dev**
|
||
**Fichier**: [`apps/web/vite.config.ts`](file:///home/senke/git/talas/veza/apps/web/vite.config.ts)
|
||
**Action**:
|
||
```typescript
|
||
server: {
|
||
port: 5173,
|
||
host: true,
|
||
proxy: {
|
||
'/api': {
|
||
target: 'http://localhost:8080',
|
||
changeOrigin: true,
|
||
},
|
||
},
|
||
}
|
||
```
|
||
**Impact Attendu**: Dev local plus stable
|
||
|
||
---
|
||
|
||
#### ✅ **P2.2 - Ajouter VITE_API_URL absolu en prod**
|
||
**Fichier**: [`apps/web/.env.production`](file:///home/senke/git/talas/veza/apps/web/.env.production)
|
||
**Action**:
|
||
```bash
|
||
VITE_API_URL=https://api.veza.com/api/v1
|
||
```
|
||
**Impact Attendu**: Prod fonctionne sans proxy
|
||
|
||
---
|
||
|
||
#### ✅ **P2.3 - Implémenter gestion de secrets**
|
||
**Action**: Utiliser AWS Secrets Manager / Vault
|
||
**Fichiers**: Tous les `.env`
|
||
**Impact Attendu**: Sécurité renforcée
|
||
|
||
---
|
||
|
||
#### ✅ **P2.4 - Ajouter check ports dans start_recovery.sh**
|
||
**Fichier**: [`start_recovery.sh`](file:///home/senke/git/talas/veza/start_recovery.sh)
|
||
**Action**:
|
||
```bash
|
||
# Check if ports are free
|
||
if lsof -Pi :8080 -sTCP:LISTEN -t >/dev/null ; then
|
||
echo "Port 8080 already in use"
|
||
exit 1
|
||
fi
|
||
```
|
||
**Impact Attendu**: Moins de conflits dev
|
||
|
||
---
|
||
|
||
### 🧱 Phase 3: STRUCTUREL / LONG TERME (Semaine 3-4)
|
||
|
||
#### ✅ **P3.1 - Refactor auth state management**
|
||
**Action**: Créer un `AuthProvider` React avec état centralisé
|
||
**Impact Attendu**: Moins de race conditions
|
||
|
||
---
|
||
|
||
#### ✅ **P3.2 - Implémenter multi-stage Dockerfile**
|
||
**Fichier**: [`veza-backend-api/Dockerfile`](file:///home/senke/git/talas/veza/veza-backend-api/Dockerfile)
|
||
**Impact Attendu**: Images 10x plus petites
|
||
|
||
---
|
||
|
||
#### ✅ **P3.3 - Ajouter tests E2E pour auth flow**
|
||
**Action**: Playwright tests pour login/register/refresh
|
||
**Impact Attendu**: Détection précoce des régressions
|
||
|
||
---
|
||
|
||
#### ✅ **P3.4 - Centraliser configuration env**
|
||
**Action**: Un seul fichier `.env.template` avec validation Zod
|
||
**Impact Attendu**: Moins de dérive config
|
||
|
||
---
|
||
|
||
## 🟢 C. CHECKLIST "APP STABLE"
|
||
|
||
### ✅ Authentification Fiable
|
||
- [ ] Login fonctionne 100% du temps (pas de race condition)
|
||
- [ ] Refresh token ne boucle jamais
|
||
- [ ] Logout nettoie tous les cookies
|
||
- [ ] Session persiste après refresh page
|
||
- [ ] 2FA fonctionne si activé
|
||
|
||
### ✅ CORS Déterministe
|
||
- [ ] Middleware CORS en **premier**
|
||
- [ ] Origins configurées pour **tous** les environnements (dev/staging/prod)
|
||
- [ ] Preflight OPTIONS retourne **toujours** les headers CORS
|
||
- [ ] `withCredentials: true` activé partout
|
||
- [ ] Pas de wildcard avec credentials
|
||
|
||
### ✅ Ports Clairs
|
||
- [ ] Tous les ports dans des variables d'env
|
||
- [ ] Scripts vérifient si ports libres avant démarrage
|
||
- [ ] Docker Compose utilise des ports différents de dev local
|
||
- [ ] Documentation claire des ports utilisés
|
||
|
||
### ✅ Démarrage Reproductible
|
||
- [ ] `./start_recovery.sh` fonctionne toujours
|
||
- [ ] Migrations DB s'exécutent avant serveur
|
||
- [ ] Healthcheck `/health` répond en <1s
|
||
- [ ] Logs de démarrage clairs (pas d'erreurs cachées)
|
||
|
||
### ✅ Déploiement Sans Surprise
|
||
- [ ] `.env.production` complet et validé
|
||
- [ ] Secrets injectés par orchestrateur (pas hardcodés)
|
||
- [ ] Build déterministe (même inputs → même output)
|
||
- [ ] Rollback possible en <5min
|
||
|
||
---
|
||
|
||
## 📊 MÉTRIQUES DE SUCCÈS
|
||
|
||
### Avant Corrections
|
||
- ❌ CORS errors: ~30% des requêtes
|
||
- ❌ Login success rate: ~70%
|
||
- ❌ Refresh loop: ~15% des sessions
|
||
- ❌ Déploiement prod: Impossible
|
||
|
||
### Après Phase 1 (Cible)
|
||
- ✅ CORS errors: <1%
|
||
- ✅ Login success rate: >99%
|
||
- ✅ Refresh loop: 0%
|
||
- ✅ Déploiement prod: Possible avec supervision
|
||
|
||
### Après Phase 2 (Cible)
|
||
- ✅ CORS errors: 0%
|
||
- ✅ Login success rate: 99.9%
|
||
- ✅ Uptime: >99.5%
|
||
- ✅ Déploiement prod: Automatisé
|
||
|
||
---
|
||
|
||
## 🎯 CONCLUSION
|
||
|
||
### Verdict: 🔴 **NON PRÊT POUR PRODUCTION**
|
||
|
||
**Raisons**:
|
||
1. **CORS non déterministe** → Utilisateurs bloqués aléatoirement
|
||
2. **Auth race conditions** → Boucles infinies, UX cassée
|
||
3. **Configuration prod manquante** → Impossible de déployer
|
||
|
||
### Prochaines Étapes Recommandées
|
||
|
||
1. **Immédiat (Aujourd'hui)**: Implémenter P1.1 (ordre CORS) et P1.6 (healthcheck)
|
||
2. **Cette Semaine**: Compléter Phase 1 (P1.1 à P1.6)
|
||
3. **Semaine Prochaine**: Phase 2 (stabilisation)
|
||
4. **Audit de Suivi**: Dans 2 semaines pour valider les corrections
|
||
|
||
### Risques si Non Corrigé
|
||
|
||
- 🔥 **Perte d'utilisateurs**: Frustration face aux bugs intermittents
|
||
- 🔥 **Incident prod**: Downtime non planifié
|
||
- 🔥 **Faille sécurité**: Tokens exposés, CSRF bypass possible
|
||
- 🔥 **Dette technique**: Corrections futures 10x plus coûteuses
|
||
|
||
---
|
||
|
||
**Rapport généré le**: 2026-01-29 22:48 UTC
|
||
**Prochain audit recommandé**: 2026-02-12 |