1547 lines
43 KiB
Markdown
1547 lines
43 KiB
Markdown
# Audit Technique Exhaustif — Module Frontend Veza (`apps/web`)
|
|
|
|
**Date**: 2025-12-12
|
|
**Auditeur**: Auto (Cursor AI)
|
|
**Module**: Frontend React (Vite + TypeScript)
|
|
**Version**: 1.0.0
|
|
|
|
---
|
|
|
|
## Table des matières
|
|
|
|
1. [Module Overview + Runbook](#module-overview--runbook)
|
|
2. [Health/Build/Test Status](#healthbuildtest-status)
|
|
3. [Security Findings](#security-findings)
|
|
4. [Observability & Prod Readiness Gaps](#observability--prod-readiness-gaps)
|
|
5. [Performance Notes](#performance-notes)
|
|
6. [Issue List Priorisée (P0→P3)](#issue-list-priorisée-p0p3)
|
|
7. [Execution Plan](#execution-plan)
|
|
|
|
---
|
|
|
|
# Module Overview + Runbook
|
|
|
|
## But du module
|
|
|
|
Le module `apps/web` est l'application frontend React de la plateforme Veza, une plateforme audio collaborative avec streaming, chat en temps réel, bibliothèque musicale, playlists collaboratives, et marketplace.
|
|
|
|
**Rôle exact** : Interface utilisateur web (SPA) pour :
|
|
- Authentification (JWT, 2FA, OAuth)
|
|
- Streaming audio (HLS.js, WebRTC)
|
|
- Chat en temps réel (WebSocket)
|
|
- Gestion de bibliothèque et playlists
|
|
- Marketplace de tracks
|
|
- Profils utilisateurs et collaboration
|
|
|
|
## Entrées / Sorties
|
|
|
|
### APIs exposées (consommées)
|
|
|
|
**HTTP REST** :
|
|
- Base URL : `VITE_API_BASE_URL` (défaut: `http://localhost:8080/api/v1`)
|
|
- Client : Axios (`src/services/api.ts`, `src/services/api/client.ts`)
|
|
- Endpoints principaux :
|
|
- `/auth/*` : login, register, refresh, logout, me
|
|
- `/users/*` : CRUD utilisateurs
|
|
- `/tracks/*` : CRUD tracks, upload, download
|
|
- `/library/*` : bibliothèque utilisateur
|
|
- `/conversations/*`, `/messages/*` : chat
|
|
- `/playlists/*` : playlists
|
|
|
|
**WebSocket** :
|
|
- URL : `VITE_WS_BASE_URL` (défaut: `ws://localhost:8081/ws`)
|
|
- Service : `src/services/websocket.ts`
|
|
- Protocole : JSON messages avec types (`join_conversation`, `send_message`, `typing`, etc.)
|
|
|
|
**Streaming** :
|
|
- URL : `VITE_STREAM_URL` (défaut: `http://localhost:8082`)
|
|
- Protocole : HLS.js pour audio streaming
|
|
|
|
### Formats
|
|
|
|
- **JSON** : Toutes les APIs REST et WebSocket
|
|
- **FormData** : Upload de fichiers (tracks, avatars)
|
|
- **Schémas Zod** : Validation côté client (`src/services/api.ts`)
|
|
|
|
## Dépendances internes
|
|
|
|
- **Stores Zustand** : `src/features/auth/store/authStore.ts` (moved from stores/auth.ts), `src/features/chat/store/chatStore.ts` (moved from stores/chat.ts), `src/stores/library.ts`, `src/stores/ui.ts`, `src/stores/cartStore.ts`
|
|
- **Types partagés** : `src/types/api.ts`, `src/types/index.ts`
|
|
- **Utils** : `src/utils/*` (sanitize, token-manager, validation, etc.)
|
|
- **Hooks** : `src/hooks/*` (useAuth, useDebounce, usePWA, etc.)
|
|
|
|
## Dépendances externes
|
|
|
|
- **Base de données** : Aucune (consomme API backend Go)
|
|
- **Redis** : Aucune (consomme via backend)
|
|
- **Storage** : `localStorage` (tokens), `sessionStorage` (refresh tokens), IndexedDB (PWA cache)
|
|
- **Services externes** : Pas de dépendances directes (tout via backend)
|
|
|
|
## Exécution
|
|
|
|
### Commandes de build/run/dev
|
|
|
|
```bash
|
|
# Dev
|
|
npm run dev # Vite dev server (port 3000)
|
|
npm run dev:lab # Dev avec vraie BDD (via scripts/start_lab.sh)
|
|
npm run dev:mocks # Dev avec MSW mocks
|
|
|
|
# Build
|
|
npm run build # Production build → dist/
|
|
|
|
# Tests
|
|
npm test # Vitest (unit)
|
|
npm run test:e2e # Playwright E2E
|
|
npm run test:e2e:mocks # E2E avec MSW
|
|
|
|
# Qualité
|
|
npm run typecheck # TypeScript check
|
|
npm run lint # ESLint
|
|
npm run format # Prettier
|
|
```
|
|
|
|
### Configuration
|
|
|
|
**Variables d'environnement** (validées via Zod dans `src/config/env.ts`) :
|
|
- `VITE_API_BASE_URL` : URL API REST
|
|
- `VITE_WS_BASE_URL` : URL WebSocket
|
|
- `VITE_STREAM_URL` : URL streaming
|
|
- `VITE_UPLOAD_URL` : URL upload
|
|
- `VITE_APP_NAME` : Nom de l'app
|
|
- `VITE_DEBUG` : Mode debug (boolean)
|
|
- `VITE_USE_MSW` : Activer MSW mocks
|
|
- `VITE_FCM_VAPID_KEY` : Push notifications (optionnel)
|
|
|
|
**Fichiers de config** :
|
|
- `vite.config.ts` : Configuration Vite (aliases, plugins, CSP)
|
|
- `tsconfig.json` : TypeScript config
|
|
- `vitest.config.ts` : Tests config
|
|
- `eslint.config.js` : ESLint config
|
|
- `nginx.conf` : Config production (Docker)
|
|
- `Dockerfile` : Build production
|
|
|
|
### Docker
|
|
|
|
```bash
|
|
docker build -t veza-frontend .
|
|
docker run -p 80:80 veza-frontend
|
|
```
|
|
|
|
## Points d'intégration
|
|
|
|
### Contrats d'API
|
|
|
|
**Backend Go** (`veza-backend-api`) :
|
|
- Format réponse : `{ success: true, data: {...} }` ou `{ error: "...", message: "..." }`
|
|
- Auth : JWT dans header `Authorization: Bearer <token>`
|
|
- IDs : Backend retourne `int64`, frontend attend `string` (conversion nécessaire)
|
|
|
|
**Chat Server Rust** (`veza-chat-server`) :
|
|
- WebSocket sur `/ws`
|
|
- Token JWT en query param : `?token=<access_token>`
|
|
- Messages JSON : `{ type: string, data: any }`
|
|
|
|
**Stream Server Rust** (`veza-stream-server`) :
|
|
- HLS streaming via HTTP
|
|
- WebSocket pour synchronisation playback
|
|
|
|
### Auth
|
|
|
|
- **JWT** : Access token (15min) + Refresh token (30/90 jours selon `remember_me`)
|
|
- **Storage** : `localStorage` (access), `sessionStorage`/cookie (refresh)
|
|
- **Refresh automatique** : Intercepteur Axios (`src/services/api/client.ts`)
|
|
- **2FA** : Supporté via `src/services/2fa-service.ts`
|
|
- **OAuth** : Supporté via `src/features/auth/hooks/useOAuthCallback.ts`
|
|
|
|
### Headers / CORS
|
|
|
|
- CORS géré par backend
|
|
- Headers de sécurité : CSP, X-Frame-Options, etc. (via `vite.config.ts` et `nginx.conf`)
|
|
|
|
### Schéma DB / UUID
|
|
|
|
- **IDs** : Backend retourne `int64`, frontend utilise `string` (conversion dans `src/services/api.ts`)
|
|
- **UUID** : Pas utilisé (IDs numériques)
|
|
|
|
---
|
|
|
|
# Health/Build/Test Status
|
|
|
|
## Build Status
|
|
|
|
### TypeScript Errors
|
|
|
|
**Total** : ~100+ erreurs TypeScript (voir `ts_real_errors.log`)
|
|
|
|
**Catégories principales** :
|
|
1. **Modules manquants** (P0) :
|
|
- `@/hooks/use-toast` : utilisé mais non exporté/existant
|
|
- `@/stores/player` : utilisé mais non existant
|
|
- `@/components/ui/progress` : utilisé mais non existant
|
|
- `@/hooks/useIntersectionObserver` : utilisé mais non existant
|
|
- `@/features/settings/pages/SettingsPage` : importé mais chemin incorrect
|
|
- `@/features/profile/pages/UserProfilePage` : importé mais chemin incorrect
|
|
- `@/features/roles/pages/RolesPage` : importé mais chemin incorrect
|
|
- `@/features/tracks/pages/TrackDetailPage` : importé mais chemin incorrect
|
|
- `@/features/playlists/routes` : importé mais chemin incorrect
|
|
- `@/pages/marketplace/MarketplaceHome` : importé mais chemin incorrect
|
|
|
|
2. **Types incorrects** (P1) :
|
|
- `Track` : propriétés manquantes (`url`, `cover_url`, `album`, `file_size`, `is_playing`, `progress`)
|
|
- `User` : propriétés manquantes (`userId`, `username`)
|
|
- `PaginatedResponse<Track>` : propriété `tracks` manquante
|
|
- `ChatPage` : export par défaut manquant (lazy loading)
|
|
|
|
3. **React Query v5 incompatibilité** (P1) :
|
|
- `onSuccess` déprécié (remplacé par `useEffect` + `data`)
|
|
- `keepPreviousData` déprécié (remplacé par `placeholderData: keepPreviousData`)
|
|
|
|
4. **Types implicites `any`** (P2) : ~20 occurrences
|
|
|
|
### ESLint Warnings
|
|
|
|
**Total** : ~50+ warnings
|
|
|
|
**Principaux** :
|
|
- `no-console` : 121 occurrences (devrait être supprimé en prod)
|
|
- `@typescript-eslint/no-explicit-any` : ~30 occurrences
|
|
- `@typescript-eslint/no-unused-vars` : 2 erreurs (variables non utilisées)
|
|
- `react-hooks/exhaustive-deps` : 1 warning
|
|
|
|
## Test Status
|
|
|
|
### Coverage
|
|
|
|
**Configuré** : 80% threshold (branches, functions, lines, statements)
|
|
**État actuel** : Non mesuré (pas de rapport de coverage visible)
|
|
|
|
### Tests unitaires (Vitest)
|
|
|
|
**Total** : 189 fichiers de test (`.test.ts`, `.test.tsx`)
|
|
|
|
**Résultats** :
|
|
- ✅ La plupart des tests passent
|
|
- ❌ **2 tests échouent** :
|
|
1. `src/features/playlists/hooks/usePlaylistTrack.test.tsx` : `useAddTrackToPlaylist` — argument manquant
|
|
2. `src/features/playlists/hooks/usePlaylistTrack.test.tsx` : `useRemoveTrackFromPlaylist` — fonction non exportée
|
|
|
|
**Problèmes identifiés** :
|
|
- MSW handlers manquants pour certains endpoints WebSocket
|
|
- Mocks incomplets pour certains services
|
|
|
|
### Tests E2E (Playwright)
|
|
|
|
**Config** : `playwright.config.ts`
|
|
**Tests** : `e2e/qa-audit.spec.ts`
|
|
**État** : Non exécuté dans cet audit
|
|
|
|
## Gestion des erreurs
|
|
|
|
### Points positifs
|
|
|
|
- ErrorBoundary React (`src/components/ErrorBoundary.tsx`)
|
|
- Intercepteurs Axios pour refresh token automatique
|
|
- Validation Zod pour les réponses API
|
|
|
|
### Points à améliorer
|
|
|
|
- Pas de centralisation des erreurs (chaque composant gère ses erreurs)
|
|
- Pas de logging structuré (console.log partout)
|
|
- Pas de retry automatique pour les requêtes échouées (sauf refresh token)
|
|
|
|
## Cohérence des conventions
|
|
|
|
### Naming
|
|
|
|
- ✅ Cohérent : PascalCase pour composants, camelCase pour fonctions
|
|
- ⚠️ Incohérences :
|
|
- `useAuthStore` vs `useAuth` (deux hooks différents)
|
|
- `apiService` vs `apiClient` (deux clients API différents)
|
|
- `websocketService` vs `wsService` (aliases, confusion)
|
|
|
|
### Structure dossiers
|
|
|
|
- ✅ Feature-based : `src/features/*`
|
|
- ✅ Séparation claire : components, hooks, services, stores
|
|
- ⚠️ Duplication : `src/services/api.ts` et `src/services/api/client.ts` (deux implémentations)
|
|
|
|
### Séparation couches
|
|
|
|
- ✅ Services séparés des composants
|
|
- ✅ Stores Zustand pour state management
|
|
- ⚠️ Logique métier parfois dans les composants (ex: `src/features/library/components/LibraryManager.tsx`)
|
|
|
|
---
|
|
|
|
# Security Findings
|
|
|
|
## Top 10 Risques Sécurité
|
|
|
|
### P0 — Critiques (Exploitables immédiatement)
|
|
|
|
#### 1. **Tokens JWT stockés en localStorage (XSS vulnérable)**
|
|
|
|
**Fichiers** :
|
|
- `src/services/api.ts` : lignes 105-121
|
|
- `src/services/api/auth.ts` : lignes 64-68, 124-126
|
|
- `src/utils/token-manager.ts` : lignes 15-34, 40-54
|
|
|
|
**Problème** :
|
|
```typescript
|
|
// src/services/api.ts:105-106
|
|
private getAccessToken(): string | null {
|
|
return localStorage.getItem('access_token');
|
|
}
|
|
```
|
|
|
|
Les tokens JWT sont stockés dans `localStorage`, vulnérables aux attaques XSS. Un script malveillant injecté peut voler les tokens.
|
|
|
|
**Impact** : Vol de session, accès non autorisé aux comptes utilisateurs
|
|
|
|
**Fix minimal** :
|
|
- Option 1 : Utiliser `httpOnly` cookies (nécessite backend)
|
|
- Option 2 : Utiliser `sessionStorage` + rotation fréquente des tokens
|
|
- Option 3 : Implémenter un service worker pour isoler les tokens
|
|
|
|
**Plan de validation** :
|
|
- Test XSS : Injecter `<script>alert(localStorage.getItem('access_token'))</script>`
|
|
- Vérifier que les tokens ne sont pas accessibles via XSS
|
|
|
|
**Effet de bord** : Nécessite modification backend pour cookies httpOnly
|
|
|
|
**Effort** : M (2-4h)
|
|
|
|
---
|
|
|
|
#### 2. **CSP non-strict en production (`unsafe-inline`, `unsafe-eval`)**
|
|
|
|
**Fichiers** :
|
|
- `vite.config.ts` : lignes 62-75
|
|
|
|
**Problème** :
|
|
```typescript
|
|
'script-src': [
|
|
"'self'",
|
|
"'unsafe-inline' 'unsafe-eval' blob:", // ⚠️ Dangereux
|
|
],
|
|
'style-src': [
|
|
"'self'",
|
|
"'unsafe-inline'", // ⚠️ Nécessaire pour Tailwind mais risqué
|
|
],
|
|
```
|
|
|
|
La CSP en dev contient `unsafe-inline` et `unsafe-eval`, permettant l'exécution de scripts inline (XSS).
|
|
|
|
**Impact** : Attaques XSS facilitées
|
|
|
|
**Fix minimal** :
|
|
- Utiliser des nonces CSP pour les scripts inline (déjà partiellement implémenté dans `src/utils/csp.ts`)
|
|
- Supprimer `unsafe-eval` en production
|
|
- Utiliser `nonce-` pour les styles inline si possible
|
|
|
|
**Plan de validation** :
|
|
- Tester avec CSP strict en production
|
|
- Vérifier que tous les scripts ont des nonces
|
|
|
|
**Effet de bord** : Peut casser certains composants si nonces mal gérés
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
#### 3. **Utilisation de `dangerouslySetInnerHTML` sans sanitisation complète**
|
|
|
|
**Fichiers** :
|
|
- `src/features/chat/components/ChatMessages.tsx` : ligne 102
|
|
- `src/features/chat/components/VirtualizedChatMessages.tsx` : ligne 75
|
|
|
|
**Problème** :
|
|
```typescript
|
|
// src/features/chat/components/ChatMessages.tsx:102
|
|
dangerouslySetInnerHTML={{
|
|
__html: sanitizeChatMessage(message.content)
|
|
}}
|
|
```
|
|
|
|
Bien que `sanitizeChatMessage` soit utilisé, la fonction `sanitizeHTML` dans `src/utils/sanitize.ts` peut avoir des failles (regex-based, pas de parser HTML complet).
|
|
|
|
**Impact** : XSS via messages de chat
|
|
|
|
**Fix minimal** :
|
|
- Utiliser `DOMPurify` (déjà dans `package.json` : `dompurify@^3.3.0`) au lieu de la sanitisation custom
|
|
- Vérifier que `DOMPurify` est bien utilisé partout
|
|
|
|
**Plan de validation** :
|
|
- Test XSS : Envoyer `<img src=x onerror=alert(1)>` dans un message
|
|
- Vérifier que le script ne s'exécute pas
|
|
|
|
**Effet de bord** : Aucun (DOMPurify est déjà une dépendance)
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
#### 4. **CSRF Token non implémenté**
|
|
|
|
**Fichiers** :
|
|
- `src/services/csrf.ts` : lignes 1-34
|
|
|
|
**Problème** :
|
|
```typescript
|
|
// src/services/csrf.ts:14-17
|
|
public async refreshCsrfToken(): Promise<void> {
|
|
// Placeholder: fetch from backend if needed
|
|
// this.csrfToken = ...
|
|
}
|
|
```
|
|
|
|
Le service CSRF existe mais n'est pas implémenté. Aucune protection CSRF pour les requêtes POST/PUT/DELETE.
|
|
|
|
**Impact** : Attaques CSRF possibles (actions non autorisées)
|
|
|
|
**Fix minimal** :
|
|
- Implémenter `refreshCsrfToken()` pour récupérer le token depuis le backend
|
|
- Ajouter le header `X-CSRF-Token` dans tous les intercepteurs Axios
|
|
- Backend doit générer et valider les tokens CSRF
|
|
|
|
**Plan de validation** :
|
|
- Test CSRF : Créer une requête POST depuis un site externe
|
|
- Vérifier que la requête est rejetée sans token CSRF valide
|
|
|
|
**Effet de bord** : Nécessite modification backend
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
#### 5. **WebSocket token en query param (exposé dans logs/URLs)**
|
|
|
|
**Fichiers** :
|
|
- `src/services/api.ts` : ligne 380
|
|
- `src/services/websocket.ts` : ligne 45
|
|
|
|
**Problème** :
|
|
```typescript
|
|
// src/services/api.ts:378-381
|
|
getWebSocketUrl(): string {
|
|
const token = this.getAccessToken();
|
|
return `${WS_BASE_URL}?token=${token}`;
|
|
}
|
|
```
|
|
|
|
Le token JWT est passé en query param, exposé dans :
|
|
- Logs serveur
|
|
- Historique navigateur
|
|
- URLs partagées
|
|
|
|
**Impact** : Fuite de token via logs/historique
|
|
|
|
**Fix minimal** :
|
|
- Utiliser le header `Authorization` si le serveur WebSocket le supporte
|
|
- Sinon, utiliser un token temporaire (one-time token) généré par le backend
|
|
|
|
**Plan de validation** :
|
|
- Vérifier que le token n'apparaît pas dans les logs serveur
|
|
- Vérifier que l'historique navigateur ne contient pas le token
|
|
|
|
**Effet de bord** : Nécessite modification serveur WebSocket Rust
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### P1 — Haute priorité (Bugs fréquents / Dette bloquante)
|
|
|
|
#### 6. **Validation JWT côté client absente (expiration non vérifiée)**
|
|
|
|
**Fichiers** :
|
|
- `src/services/api/client.ts` : lignes 11-78
|
|
- `src/utils/token-manager.ts` : lignes 40-54
|
|
|
|
**Problème** :
|
|
Aucune vérification de l'expiration du token JWT côté client. Le token peut être expiré mais toujours utilisé jusqu'à ce que le backend renvoie 401.
|
|
|
|
**Impact** : Requêtes inutiles avec token expiré, UX dégradée
|
|
|
|
**Fix minimal** :
|
|
- Décoder le JWT (base64) et vérifier `exp` avant chaque requête
|
|
- Refresh automatique si expiration < 5min
|
|
|
|
**Plan de validation** :
|
|
- Test : Créer un token expiré, vérifier qu'il est refresh automatiquement
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
#### 7. **Pas de rate limiting côté client**
|
|
|
|
**Fichiers** :
|
|
- `src/services/api.ts` : toutes les méthodes
|
|
- `src/services/websocket.ts` : méthode `send()`
|
|
|
|
**Problème** :
|
|
Aucune protection contre le spam de requêtes (rate limiting). Un utilisateur malveillant peut spammer l'API.
|
|
|
|
**Impact** : DoS côté client, surcharge serveur
|
|
|
|
**Fix minimal** :
|
|
- Implémenter un rate limiter simple (ex: max 10 requêtes/seconde)
|
|
- Utiliser un debounce pour les requêtes répétées
|
|
|
|
**Plan de validation** :
|
|
- Test : Envoyer 100 requêtes simultanées, vérifier que seules 10 passent
|
|
|
|
**Effet de bord** : Peut ralentir certaines opérations légitimes
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
#### 8. **Secrets potentiels dans le code (VITE_FCM_VAPID_KEY)**
|
|
|
|
**Fichiers** :
|
|
- `src/config/env.ts` : ligne 18
|
|
- `vite.config.ts` : pas de validation
|
|
|
|
**Problème** :
|
|
`VITE_FCM_VAPID_KEY` est une clé publique (VAPID), mais elle est chargée côté client. Si elle est mal configurée, elle pourrait exposer une clé privée.
|
|
|
|
**Impact** : Fuite de clé si erreur de configuration
|
|
|
|
**Fix minimal** :
|
|
- Documenter que `VITE_FCM_VAPID_KEY` doit être la clé **publique** uniquement
|
|
- Ajouter une validation pour s'assurer que ce n'est pas une clé privée
|
|
|
|
**Plan de validation** :
|
|
- Vérifier que la clé dans `.env` est bien publique (commence par `BK` ou similaire)
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (30min)
|
|
|
|
---
|
|
|
|
#### 9. **Pas de validation de taille de fichier upload côté client**
|
|
|
|
**Fichiers** :
|
|
- `src/features/library/components/UploadModal.tsx`
|
|
- `src/features/tracks/components/TrackUpload.tsx`
|
|
|
|
**Problème** :
|
|
Aucune validation de la taille de fichier avant upload. Un utilisateur peut tenter d'uploader un fichier de 10GB.
|
|
|
|
**Impact** : Surcharge serveur, DoS
|
|
|
|
**Fix minimal** :
|
|
- Valider la taille max (ex: 100MB) avant upload
|
|
- Afficher une erreur claire si dépassement
|
|
|
|
**Plan de validation** :
|
|
- Test : Tenter d'uploader un fichier > 100MB, vérifier que c'est rejeté
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
#### 10. **CSP middleware non utilisé (créé mais jamais appelé)**
|
|
|
|
**Fichiers** :
|
|
- `src/utils/csp.ts` : lignes 185-202
|
|
|
|
**Problème** :
|
|
```typescript
|
|
// src/utils/csp.ts:185-202
|
|
export function createCSPMiddleware() {
|
|
return (_req: any, res: any, next: any) => {
|
|
// ... génère CSP header
|
|
}
|
|
}
|
|
```
|
|
|
|
Le middleware CSP est créé mais jamais utilisé (c'est du code Node.js dans un frontend React). La CSP est gérée via `vite.config.ts` uniquement.
|
|
|
|
**Impact** : CSP peut être inconsistante entre dev et prod
|
|
|
|
**Fix minimal** :
|
|
- Supprimer le middleware inutile (code mort)
|
|
- S'assurer que la CSP dans `vite.config.ts` est correcte pour prod
|
|
|
|
**Plan de validation** :
|
|
- Vérifier que la CSP en prod est stricte (pas d'`unsafe-inline`)
|
|
|
|
**Effet de bord** : Aucun (code mort)
|
|
|
|
**Effort** : S (30min)
|
|
|
|
---
|
|
|
|
## Autres problèmes sécurité (P2/P3)
|
|
|
|
### P2 — Moyenne priorité
|
|
|
|
- **Pas de validation de format de fichier upload** : Seule la validation backend existe
|
|
- **Pas de protection contre clickjacking** : `X-Frame-Options: SAMEORIGIN` au lieu de `DENY` dans `nginx.conf`
|
|
- **Pas de HSTS** : Header `Strict-Transport-Security` manquant dans `nginx.conf`
|
|
- **Logs console en production** : 121 occurrences de `console.log/error/warn` (peuvent exposer des infos sensibles)
|
|
|
|
### P3 — Basse priorité
|
|
|
|
- **Pas de validation de rate limiting pour WebSocket** : Spam de messages possible
|
|
- **Pas de chiffrement des données sensibles en localStorage** : Tokens non chiffrés (mais localStorage est déjà vulnérable)
|
|
|
|
---
|
|
|
|
# Observability & Prod Readiness Gaps
|
|
|
|
## Logs structurés
|
|
|
|
### État actuel
|
|
|
|
❌ **Aucun logging structuré** :
|
|
- Utilisation de `console.log/error/warn` partout (121 occurrences)
|
|
- Pas de corrélation (pas de `request_id` / `trace_id`)
|
|
- Pas de niveaux de log cohérents
|
|
- Logs en production (devrait être désactivé)
|
|
|
|
**Fichiers concernés** :
|
|
- `src/services/websocket.ts` : 6 occurrences
|
|
- `src/features/streaming/services/playbackAnalyticsService.ts` : 11 occurrences
|
|
- `src/app/App.tsx` : 1 occurrence
|
|
- Et 50+ autres fichiers
|
|
|
|
### Fix recommandé
|
|
|
|
- Implémenter un logger structuré (ex: `pino` ou `winston` côté client)
|
|
- Ajouter `request_id` pour chaque requête API
|
|
- Désactiver les logs en production (`if (import.meta.env.DEV)`)
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
## Metrics
|
|
|
|
### État actuel
|
|
|
|
❌ **Aucune métrique** :
|
|
- Pas de Prometheus / StatsD
|
|
- Pas de métriques custom (erreurs, latence, etc.)
|
|
- Pas de healthcheck détaillé
|
|
|
|
### Healthcheck
|
|
|
|
✅ **Basique** : `/health` endpoint dans `nginx.conf` (ligne 26-30)
|
|
- Retourne simplement `200 "healthy\n"`
|
|
- Pas de vérification de dépendances (API, WebSocket, etc.)
|
|
|
|
### Fix recommandé
|
|
|
|
- Ajouter un healthcheck frontend qui vérifie :
|
|
- Connectivité API
|
|
- Connectivité WebSocket
|
|
- État du storage (localStorage disponible)
|
|
- Exposer des métriques custom (optionnel, via API backend)
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
## Timeouts / Retries
|
|
|
|
### État actuel
|
|
|
|
⚠️ **Partiel** :
|
|
- Timeout Axios : 10s (`src/services/api.ts:58`)
|
|
- Retry : Seulement pour refresh token (intercepteur)
|
|
- Pas de retry pour autres erreurs réseau
|
|
- Pas de circuit breaker
|
|
|
|
### Fix recommandé
|
|
|
|
- Implémenter un retry avec backoff exponentiel pour les erreurs réseau (5xx, timeout)
|
|
- Ajouter un circuit breaker pour éviter de spammer un serveur down
|
|
- Configurer des timeouts différents selon le type de requête (upload: 60s, API: 10s)
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
## Gestion de charge
|
|
|
|
### État actuel
|
|
|
|
⚠️ **Basique** :
|
|
- Pas de pool de connexions (Axios gère automatiquement)
|
|
- Pas de limite de connexions WebSocket simultanées
|
|
- Pas de chunking pour les uploads volumineux
|
|
|
|
### Uploads
|
|
|
|
✅ **Chunked upload** : Implémenté dans `src/features/tracks/services/chunkedUploadService.ts`
|
|
- Upload par chunks pour fichiers volumineux
|
|
- Retry automatique en cas d'échec
|
|
|
|
### WebSocket
|
|
|
|
⚠️ **Pas de backpressure** :
|
|
- Pas de limite de messages en queue
|
|
- Pas de gestion de reconnexion intelligente (backoff exponentiel partiel)
|
|
|
|
**Fix recommandé** :
|
|
- Limiter la queue de messages WebSocket (max 100 messages)
|
|
- Implémenter un backpressure (pause si queue > 50)
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
## Migrations / Compatibilité
|
|
|
|
### État actuel
|
|
|
|
⚠️ **Conversion IDs** :
|
|
- Backend retourne `int64`, frontend attend `string`
|
|
- Conversion manuelle dans `src/services/api.ts` (lignes 339-344, 361-374)
|
|
|
|
**Problème** : Risque d'erreur si conversion oubliée
|
|
|
|
**Fix recommandé** :
|
|
- Créer un utilitaire centralisé pour la conversion
|
|
- Utiliser des types TypeScript stricts pour forcer la conversion
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
# Performance Notes
|
|
|
|
## Hotspots évidents
|
|
|
|
### 1. **Bundle size**
|
|
|
|
**État** : Non mesuré (bundle analyzer présent mais non exécuté)
|
|
|
|
**Fichiers** :
|
|
- `vite.config.ts` : lignes 34-39 (bundle analyzer configuré)
|
|
- `dist/bundle-analysis.html` : présent mais non analysé
|
|
|
|
**Recommandations** :
|
|
- Analyser le bundle (`npm run build` puis ouvrir `dist/bundle-analysis.html`)
|
|
- Identifier les dépendances lourdes
|
|
- Code splitting par route (déjà fait via lazy loading)
|
|
|
|
**Effort** : S (30min pour analyse)
|
|
|
|
---
|
|
|
|
### 2. **Re-renders inutiles**
|
|
|
|
**Problèmes identifiés** :
|
|
- `src/components/data/Table.tsx` : ligne 102 — `useCallback` avec dépendance manquante
|
|
- Pas de `React.memo` sur les composants lourds (liste de tracks, messages chat)
|
|
|
|
**Fix recommandé** :
|
|
- Ajouter `React.memo` sur les composants de liste
|
|
- Corriger les dépendances de `useCallback`/`useMemo`
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### 3. **JSON parsing répétitif**
|
|
|
|
**Fichiers** :
|
|
- `src/services/websocket.ts` : ligne 59 — `JSON.parse` à chaque message
|
|
|
|
**Impact** : Faible (WebSocket messages sont petits)
|
|
|
|
**Fix recommandé** : Aucun (optimisation prématurée)
|
|
|
|
---
|
|
|
|
### 4. **N+1 queries potentielles**
|
|
|
|
**Fichiers** :
|
|
- `src/features/library/components/LibraryManager.tsx` : chargement de tracks un par un possible
|
|
|
|
**Impact** : Moyen (si beaucoup de tracks)
|
|
|
|
**Fix recommandé** :
|
|
- Utiliser la pagination côté API
|
|
- Charger les tracks par batch
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
## Streaming
|
|
|
|
### HLS.js
|
|
|
|
✅ **Bien configuré** : Utilisation de `hls.js@^1.6.14`
|
|
- Buffering automatique
|
|
- Adaptation de bitrate (si backend supporte)
|
|
|
|
**Pas de problème identifié**
|
|
|
|
---
|
|
|
|
## React Query
|
|
|
|
### Cache
|
|
|
|
✅ **Bien configuré** : React Query v5 avec cache automatique
|
|
- `staleTime` : défaut (0, toujours stale)
|
|
- `cacheTime` : défaut (5min)
|
|
|
|
**Recommandation** :
|
|
- Augmenter `staleTime` pour les données peu changeantes (profils, tracks)
|
|
- Configurer `staleTime: 5 * 60 * 1000` (5min) pour les tracks
|
|
|
|
**Effort** : S (30min)
|
|
|
|
---
|
|
|
|
## WebSocket
|
|
|
|
### Reconnexion
|
|
|
|
✅ **Bien implémenté** : Backoff exponentiel dans `src/services/websocket.ts` (lignes 76-98)
|
|
- Max 5 tentatives
|
|
- Délai exponentiel : `3000 * 2^(attempt-1)`
|
|
|
|
**Pas de problème identifié**
|
|
|
|
---
|
|
|
|
# Issue List Priorisée (P0→P3)
|
|
|
|
## Format des issues
|
|
|
|
Chaque issue suit le format :
|
|
- **ID** : `WEB-<P0|P1|P2|P3>-###`
|
|
- **Titre**
|
|
- **Impact** (concret, scénarios)
|
|
- **Preuve** : fichiers + symboles + extrait court
|
|
- **Cause racine**
|
|
- **Fix minimal** (patch-level, pas de refonte totale)
|
|
- **Plan de validation** (tests + commande + cas limites)
|
|
- **Effet de bord / risque de régression**
|
|
- **Effort estimé** (S/M/L) + dépendances
|
|
|
|
---
|
|
|
|
## P0 — Build Breakers / Sécurité Critique
|
|
|
|
### WEB-P0-001 : Modules manquants (build cassé)
|
|
|
|
**Impact** : Build TypeScript échoue, application ne compile pas
|
|
|
|
**Preuve** :
|
|
- `src/components/player/AudioPlayer.tsx:6` : `Cannot find module '@/hooks/use-toast'`
|
|
- `src/components/player/AudioPlayer.tsx:2` : `Cannot find module '@/stores/player'`
|
|
- `src/components/ui/optimized-image.tsx:2` : `Cannot find module '@/hooks/useIntersectionObserver'`
|
|
- `src/components/ui/LazyComponent.tsx:39` : `Cannot find module '@/features/settings/pages/SettingsPage'`
|
|
- Et 5+ autres modules manquants
|
|
|
|
**Cause racine** :
|
|
- Fichiers supprimés/déplacés sans mise à jour des imports
|
|
- Exports manquants dans les modules
|
|
|
|
**Fix minimal** :
|
|
1. Créer les modules manquants ou corriger les imports
|
|
2. Vérifier que tous les exports sont corrects
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
npm run typecheck # Doit passer sans erreur
|
|
npm run build # Doit compiler
|
|
```
|
|
|
|
**Effet de bord** : Aucun (fix nécessaire pour build)
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P0-002 : Tokens JWT en localStorage (XSS vulnérable)
|
|
|
|
**Voir [Security Finding #1](#1-tokens-jwt-stockés-en-localstorage-xss-vulnérable)**
|
|
|
|
**Effort** : M (2-4h)
|
|
|
|
---
|
|
|
|
### WEB-P0-003 : CSP non-strict (`unsafe-inline`, `unsafe-eval`)
|
|
|
|
**Voir [Security Finding #2](#2-csp-non-strict-en-production-unsafe-inline-unsafe-eval)**
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
### WEB-P0-004 : `dangerouslySetInnerHTML` sans sanitisation complète
|
|
|
|
**Voir [Security Finding #3](#3-utilisation-de-dangerouslysetinnerhtml-sans-sanitisation-complète)**
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
### WEB-P0-005 : CSRF Token non implémenté
|
|
|
|
**Voir [Security Finding #4](#4-csrf-token-non-implémenté)**
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
## P1 — Bugs fréquents / Dette bloquante
|
|
|
|
### WEB-P1-001 : Types Track/User incomplets (100+ erreurs TypeScript)
|
|
|
|
**Impact** : Erreurs TypeScript partout, développement ralenti, bugs runtime possibles
|
|
|
|
**Preuve** :
|
|
- `src/components/player/AudioPlayer.tsx:90` : `Property 'url' does not exist on type 'Track'`
|
|
- `src/components/player/AudioPlayer.tsx:179` : `Property 'cover_url' does not exist on type 'Track'`
|
|
- `src/features/library/components/VirtualizedTrackList.tsx:56` : `Property 'album' does not exist on type 'Track'`
|
|
- `src/features/chat/hooks/useChat.ts:9` : `Property 'userId' does not exist on type 'AuthState'`
|
|
|
|
**Cause racine** :
|
|
- Types `Track` et `User` dans `src/types/api.ts` ne correspondent pas à l'usage réel
|
|
- Backend retourne plus de champs que définis dans les types
|
|
|
|
**Fix minimal** :
|
|
1. Mettre à jour `src/types/api.ts` avec tous les champs utilisés
|
|
2. Vérifier la cohérence avec le backend Go
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
npm run typecheck # Doit passer sans erreur
|
|
```
|
|
|
|
**Effet de bord** : Aucun (correction de types)
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P1-002 : React Query v5 incompatibilité (`onSuccess`, `keepPreviousData`)
|
|
|
|
**Impact** : Warnings React Query, comportement incorrect possible
|
|
|
|
**Preuve** :
|
|
- `src/features/chat/pages/ChatPage.tsx:28` : `'onSuccess' does not exist`
|
|
- `src/features/library/hooks/useMyTracks.ts:17` : `'keepPreviousData' does not exist`
|
|
|
|
**Cause racine** : Migration React Query v4 → v5 incomplète
|
|
|
|
**Fix minimal** :
|
|
1. Remplacer `onSuccess` par `useEffect` avec dépendance sur `data`
|
|
2. Remplacer `keepPreviousData` par `placeholderData: keepPreviousData`
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
npm run typecheck # Doit passer sans erreur
|
|
npm test # Tests doivent passer
|
|
```
|
|
|
|
**Effet de bord** : Aucun (migration standard)
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
### WEB-P1-003 : Tests échouent (usePlaylistTrack)
|
|
|
|
**Impact** : CI/CD peut échouer, confiance réduite dans les tests
|
|
|
|
**Preuve** :
|
|
- `src/features/playlists/hooks/usePlaylistTrack.test.tsx` : 2 tests échouent
|
|
- `useAddTrackToPlaylist` : argument manquant
|
|
- `useRemoveTrackFromPlaylist` : fonction non exportée
|
|
|
|
**Cause racine** :
|
|
- Signature de fonction changée sans mise à jour des tests
|
|
- Export manquant
|
|
|
|
**Fix minimal** :
|
|
1. Corriger les signatures de fonction
|
|
2. Ajouter les exports manquants
|
|
3. Mettre à jour les tests
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
npm test -- usePlaylistTrack # Doit passer
|
|
```
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
### WEB-P1-004 : Validation JWT côté client absente
|
|
|
|
**Voir [Security Finding #6](#6-validation-jwt-côté-client-absente-expiration-non-vérifiée)**
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
### WEB-P1-005 : Duplication de clients API (`apiService` vs `apiClient`)
|
|
|
|
**Impact** : Confusion, maintenance difficile, bugs possibles
|
|
|
|
**Preuve** :
|
|
- `src/services/api.ts` : classe `ApiService` (singleton `apiService`)
|
|
- `src/services/api/client.ts` : `apiClient` (Axios instance)
|
|
- `src/lib/apiClient.ts` : autre `apiClient` (Axios instance)
|
|
|
|
**Cause racine** : Refactoring incomplet, plusieurs implémentations coexistent
|
|
|
|
**Fix minimal** :
|
|
1. Unifier en un seul client API
|
|
2. Migrer tous les usages vers le client unifié
|
|
3. Supprimer les clients dupliqués
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
grep -r "apiService\|apiClient" src/ # Vérifier qu'un seul client est utilisé
|
|
npm run typecheck # Doit passer
|
|
npm test # Tests doivent passer
|
|
```
|
|
|
|
**Effet de bord** : Risque de régression si migration incomplète
|
|
|
|
**Effort** : M (3-4h)
|
|
|
|
---
|
|
|
|
### WEB-P1-006 : WebSocket token en query param
|
|
|
|
**Voir [Security Finding #5](#5-websocket-token-en-query-param-exposé-dans-logsurls)**
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P1-007 : Conversion IDs int64 → string manuelle (risque d'erreur)
|
|
|
|
**Impact** : Bugs possibles si conversion oubliée, maintenance difficile
|
|
|
|
**Preuve** :
|
|
- `src/services/api.ts:339-344` : conversion manuelle `String(conv.id)`
|
|
- `src/services/api.ts:361-374` : conversion manuelle `String(data.id)`
|
|
|
|
**Cause racine** : Backend retourne `int64`, frontend attend `string`
|
|
|
|
**Fix minimal** :
|
|
1. Créer un utilitaire `convertBackendId(id: number | string): string`
|
|
2. Utiliser partout où nécessaire
|
|
3. Ajouter des types stricts pour forcer la conversion
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
grep -r "String(.*\.id)" src/ # Vérifier que toutes les conversions utilisent l'utilitaire
|
|
npm run typecheck # Doit passer
|
|
```
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
## P2 — Qualité, maintenabilité, perf non critique
|
|
|
|
### WEB-P2-001 : 121 occurrences de `console.log/error/warn` (logs en production)
|
|
|
|
**Impact** : Performance légère, exposition d'infos sensibles possible, pollution console
|
|
|
|
**Preuve** :
|
|
```bash
|
|
grep -r "console\." src/ | wc -l # 121 occurrences
|
|
```
|
|
|
|
**Cause racine** : Pas de logger structuré, logs de debug laissés
|
|
|
|
**Fix minimal** :
|
|
1. Créer un logger wrapper (`src/utils/logger.ts`)
|
|
2. Remplacer tous les `console.*` par le logger
|
|
3. Désactiver les logs en production
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
grep -r "console\." src/ | wc -l # Doit être 0
|
|
npm run build && npm run preview # Vérifier que pas de logs en prod
|
|
```
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P2-002 : Pas de rate limiting côté client
|
|
|
|
**Voir [Security Finding #7](#7-pas-de-rate-limiting-côté-client)**
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
### WEB-P2-003 : Re-renders inutiles (pas de `React.memo`)
|
|
|
|
**Impact** : Performance dégradée sur les listes longues
|
|
|
|
**Preuve** :
|
|
- `src/components/data/Table.tsx:102` : `useCallback` avec dépendance manquante
|
|
- Pas de `React.memo` sur `TrackList`, `ChatMessages`, etc.
|
|
|
|
**Cause racine** : Optimisation non faite
|
|
|
|
**Fix minimal** :
|
|
1. Ajouter `React.memo` sur les composants de liste
|
|
2. Corriger les dépendances de `useCallback`
|
|
|
|
**Plan de validation** :
|
|
- Profiler avec React DevTools
|
|
- Vérifier que les re-renders sont réduits
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P2-004 : Pas de validation de taille de fichier upload
|
|
|
|
**Voir [Security Finding #9](#9-pas-de-validation-de-taille-de-fichier-upload-côté-client)**
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
### WEB-P2-005 : Pas de logging structuré
|
|
|
|
**Voir [Observability - Logs structurés](#logs-structurés)**
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P2-006 : Healthcheck basique (pas de vérification dépendances)
|
|
|
|
**Voir [Observability - Healthcheck](#healthcheck)**
|
|
|
|
**Effort** : S (1-2h)
|
|
|
|
---
|
|
|
|
### WEB-P2-007 : Pas de retry automatique (sauf refresh token)
|
|
|
|
**Voir [Observability - Timeouts / Retries](#timeouts--retries)**
|
|
|
|
**Effort** : M (2-3h)
|
|
|
|
---
|
|
|
|
### WEB-P2-008 : Bundle size non analysé
|
|
|
|
**Voir [Performance - Bundle size](#1-bundle-size)**
|
|
|
|
**Effort** : S (30min)
|
|
|
|
---
|
|
|
|
### WEB-P2-009 : CSP middleware inutile (code mort)
|
|
|
|
**Voir [Security Finding #10](#10-csp-middleware-non-utilisé-créé-mais-jamais-appelé)**
|
|
|
|
**Effort** : S (30min)
|
|
|
|
---
|
|
|
|
### WEB-P2-010 : Naming incohérent (`useAuthStore` vs `useAuth`, `apiService` vs `apiClient`)
|
|
|
|
**Impact** : Confusion, maintenance difficile
|
|
|
|
**Preuve** :
|
|
- `src/features/auth/store/authStore.ts` : exporte `useAuthStore` (moved from stores/auth.ts)
|
|
- `src/features/auth/hooks/useAuth.ts` : exporte `useAuth` (utilise `useAuthStore` en interne)
|
|
- `src/services/api.ts` : `apiService`
|
|
- `src/services/api/client.ts` : `apiClient`
|
|
|
|
**Cause racine** : Refactoring incomplet, conventions non suivies
|
|
|
|
**Fix minimal** :
|
|
1. Documenter les conventions de naming
|
|
2. Unifier progressivement (dans le cadre de P1-005)
|
|
|
|
**Plan de validation** :
|
|
- Revue de code pour vérifier cohérence
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h pour documentation)
|
|
|
|
---
|
|
|
|
## P3 — Cosmétique, refactors non urgents
|
|
|
|
### WEB-P3-001 : 30+ occurrences de `any` (types implicites)
|
|
|
|
**Impact** : Perte de type safety, bugs possibles
|
|
|
|
**Preuve** :
|
|
```bash
|
|
grep -r ": any\|as any" src/ | wc -l # ~30 occurrences
|
|
```
|
|
|
|
**Cause racine** : Types non définis, migration TypeScript incomplète
|
|
|
|
**Fix minimal** :
|
|
1. Remplacer progressivement les `any` par des types stricts
|
|
2. Utiliser `unknown` si type vraiment inconnu
|
|
|
|
**Plan de validation** :
|
|
```bash
|
|
npm run lint # Doit passer sans warnings @typescript-eslint/no-explicit-any
|
|
```
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : L (4-6h, peut être fait progressivement)
|
|
|
|
---
|
|
|
|
### WEB-P3-002 : Pas de HSTS header
|
|
|
|
**Impact** : Sécurité légèrement réduite (manque protection HTTPS forcé)
|
|
|
|
**Preuve** :
|
|
- `nginx.conf` : pas de header `Strict-Transport-Security`
|
|
|
|
**Fix minimal** :
|
|
```nginx
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
```
|
|
|
|
**Plan de validation** :
|
|
- Vérifier avec `curl -I` que le header est présent
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (5min)
|
|
|
|
---
|
|
|
|
### WEB-P3-003 : `X-Frame-Options: SAMEORIGIN` au lieu de `DENY`
|
|
|
|
**Impact** : Protection clickjacking partielle
|
|
|
|
**Preuve** :
|
|
- `nginx.conf:14` : `X-Frame-Options "SAMEORIGIN"`
|
|
|
|
**Fix minimal** :
|
|
```nginx
|
|
add_header X-Frame-Options "DENY" always;
|
|
```
|
|
|
|
**Plan de validation** :
|
|
- Vérifier avec `curl -I`
|
|
|
|
**Effet de bord** : Aucun (pas d'iframe dans l'app)
|
|
|
|
**Effort** : S (5min)
|
|
|
|
---
|
|
|
|
### WEB-P3-004 : Pas de validation de format de fichier upload
|
|
|
|
**Voir [Security - Autres problèmes P2](#p2--moyenne-priorité)**
|
|
|
|
**Effort** : S (1h)
|
|
|
|
---
|
|
|
|
### WEB-P3-005 : TODO/FIXME dans le code (38 occurrences)
|
|
|
|
**Impact** : Dette technique, fonctionnalités incomplètes
|
|
|
|
**Preuve** :
|
|
```bash
|
|
grep -ri "TODO\|FIXME\|XXX\|HACK" src/ | wc -l # 38 occurrences
|
|
```
|
|
|
|
**Exemples** :
|
|
- `src/features/chat/hooks/useChat.ts:114` : `// TODO: Add fetchHistory function`
|
|
- `src/features/library/components/LibraryManager.tsx:247` : `isLiked={() => false} // Todo`
|
|
|
|
**Fix minimal** :
|
|
1. Créer des issues GitHub pour chaque TODO
|
|
2. Supprimer les TODOs résolus
|
|
3. Documenter les TODOs non urgents
|
|
|
|
**Plan de validation** :
|
|
- Revue manuelle des TODOs
|
|
|
|
**Effet de bord** : Aucun
|
|
|
|
**Effort** : S (1h pour trier et documenter)
|
|
|
|
---
|
|
|
|
# Execution Plan
|
|
|
|
## Checklist P0 (ordre strict)
|
|
|
|
### Phase 1 : Build Fixes (Critique — 2-3h)
|
|
|
|
1. ✅ **WEB-P0-001** : Créer/corriger les modules manquants
|
|
- Créer `src/hooks/use-toast.ts` (ou corriger import)
|
|
- Créer `src/stores/player.ts` (ou corriger import)
|
|
- Créer `src/hooks/useIntersectionObserver.ts` (ou corriger import)
|
|
- Corriger les imports dans `src/components/ui/LazyComponent.tsx`
|
|
- Valider : `npm run typecheck` passe
|
|
|
|
2. ✅ **WEB-P1-001** : Corriger les types Track/User
|
|
- Mettre à jour `src/types/api.ts` avec tous les champs
|
|
- Valider : `npm run typecheck` passe
|
|
|
|
### Phase 2 : Sécurité Critique (4-6h)
|
|
|
|
3. ✅ **WEB-P0-004** : Utiliser DOMPurify pour sanitisation
|
|
- Remplacer `sanitizeChatMessage` par `DOMPurify.sanitize()`
|
|
- Valider : Test XSS avec `<img src=x onerror=alert(1)>`
|
|
|
|
4. ✅ **WEB-P0-003** : Renforcer CSP (supprimer `unsafe-eval`)
|
|
- Modifier `vite.config.ts` pour production
|
|
- Utiliser nonces pour scripts inline
|
|
- Valider : Build prod, vérifier CSP header
|
|
|
|
5. ✅ **WEB-P0-002** : Migrer tokens vers httpOnly cookies (nécessite backend)
|
|
- **Option alternative** : Utiliser `sessionStorage` + rotation fréquente
|
|
- Valider : Test XSS ne peut plus voler tokens
|
|
|
|
6. ✅ **WEB-P0-005** : Implémenter CSRF token (nécessite backend)
|
|
- Implémenter `refreshCsrfToken()` dans `src/services/csrf.ts`
|
|
- Ajouter header dans intercepteurs Axios
|
|
- Valider : Test CSRF échoue sans token
|
|
|
|
### Phase 3 : Tests (1h)
|
|
|
|
7. ✅ **WEB-P1-003** : Corriger tests usePlaylistTrack
|
|
- Corriger signatures et exports
|
|
- Valider : `npm test -- usePlaylistTrack` passe
|
|
|
|
---
|
|
|
|
## Checklist P1 (par lots cohérents)
|
|
|
|
### Lot 1 : React Query v5 Migration (1-2h)
|
|
|
|
8. ✅ **WEB-P1-002** : Migrer `onSuccess` → `useEffect`
|
|
- Remplacer dans `src/features/chat/pages/ChatPage.tsx`
|
|
- Remplacer `keepPreviousData` → `placeholderData`
|
|
- Valider : `npm run typecheck` + `npm test`
|
|
|
|
### Lot 2 : Unification Clients API (3-4h)
|
|
|
|
9. ✅ **WEB-P1-005** : Unifier `apiService` et `apiClient`
|
|
- Choisir un client (recommandé : `apiClient` de `src/services/api/client.ts`)
|
|
- Migrer tous les usages
|
|
- Supprimer `src/services/api.ts` (ou le garder comme wrapper)
|
|
- Valider : `npm run typecheck` + `npm test` + tests manuels
|
|
|
|
### Lot 3 : JWT & WebSocket (3-4h)
|
|
|
|
10. ✅ **WEB-P1-004** : Validation JWT expiration côté client
|
|
- Décoder JWT et vérifier `exp`
|
|
- Refresh automatique si < 5min
|
|
- Valider : Test avec token expiré
|
|
|
|
11. ✅ **WEB-P1-006** : WebSocket token en header (nécessite backend)
|
|
- **Option alternative** : Utiliser token temporaire (one-time)
|
|
- Valider : Token n'apparaît pas dans logs
|
|
|
|
### Lot 4 : Utilitaires (1h)
|
|
|
|
12. ✅ **WEB-P1-007** : Créer utilitaire conversion IDs
|
|
- Créer `src/utils/id-converter.ts`
|
|
- Remplacer toutes les conversions manuelles
|
|
- Valider : `grep -r "String(.*\.id)" src/` retourne 0
|
|
|
|
---
|
|
|
|
## Quick Wins (≤ 1h chacun)
|
|
|
|
### Sécurité
|
|
|
|
- ✅ **WEB-P2-009** : Supprimer CSP middleware inutile (30min)
|
|
- ✅ **WEB-P3-002** : Ajouter HSTS header (5min)
|
|
- ✅ **WEB-P3-003** : Changer `X-Frame-Options` à `DENY` (5min)
|
|
- ✅ **WEB-P2-004** : Validation taille fichier upload (1h)
|
|
|
|
### Qualité
|
|
|
|
- ✅ **WEB-P2-008** : Analyser bundle size (30min)
|
|
- ✅ **WEB-P3-005** : Trier et documenter TODOs (1h)
|
|
|
|
---
|
|
|
|
## Tests à ajouter en priorité
|
|
|
|
### P0 — Critiques
|
|
|
|
1. **Test XSS** : Injecter `<script>alert(1)</script>` dans chat, vérifier qu'il ne s'exécute pas
|
|
2. **Test CSRF** : Requête POST depuis site externe, vérifier rejet
|
|
3. **Test token expiration** : Vérifier refresh automatique
|
|
|
|
### P1 — Importants
|
|
|
|
4. **Test rate limiting** : Envoyer 100 requêtes, vérifier limitation
|
|
5. **Test upload taille max** : Tenter upload > 100MB, vérifier rejet
|
|
6. **Test WebSocket reconnexion** : Simuler déconnexion, vérifier reconnexion
|
|
|
|
---
|
|
|
|
## PR Plan
|
|
|
|
### PR 1 : Build Fixes (P0-001, P1-001)
|
|
**Titre** : `fix: resolve TypeScript build errors (missing modules, incomplete types)`
|
|
**Fichiers** :
|
|
- Créer modules manquants
|
|
- Mettre à jour `src/types/api.ts`
|
|
**Tests** : `npm run typecheck` passe
|
|
|
|
### PR 2 : Security - XSS & CSP (P0-003, P0-004)
|
|
**Titre** : `security: improve XSS protection (DOMPurify, strict CSP)`
|
|
**Fichiers** :
|
|
- `src/features/chat/components/ChatMessages.tsx`
|
|
- `src/features/chat/components/VirtualizedChatMessages.tsx`
|
|
- `vite.config.ts`
|
|
**Tests** : Test XSS manuel
|
|
|
|
### PR 3 : Security - Tokens & CSRF (P0-002, P0-005)
|
|
**Titre** : `security: migrate tokens to httpOnly cookies, implement CSRF protection`
|
|
**Fichiers** :
|
|
- `src/services/api/auth.ts`
|
|
- `src/services/csrf.ts`
|
|
- `src/services/api/client.ts`
|
|
**Tests** : Tests CSRF, test XSS token theft
|
|
**Note** : Nécessite coordination backend
|
|
|
|
### PR 4 : React Query v5 Migration (P1-002)
|
|
**Titre** : `chore: migrate to React Query v5 API (remove onSuccess, keepPreviousData)`
|
|
**Fichiers** :
|
|
- `src/features/chat/pages/ChatPage.tsx`
|
|
- `src/features/library/hooks/useMyTracks.ts`
|
|
**Tests** : `npm test` passe
|
|
|
|
### PR 5 : Unify API Clients (P1-005)
|
|
**Titre** : `refactor: unify API clients (remove duplication)`
|
|
**Fichiers** :
|
|
- `src/services/api.ts` (supprimer ou wrapper)
|
|
- `src/services/api/client.ts` (garder)
|
|
- Tous les fichiers utilisant `apiService`
|
|
**Tests** : Tests complets + tests manuels
|
|
|
|
### PR 6 : JWT & WebSocket Security (P1-004, P1-006)
|
|
**Titre** : `security: add JWT expiration validation, move WebSocket token to header`
|
|
**Fichiers** :
|
|
- `src/services/api/client.ts`
|
|
- `src/services/websocket.ts`
|
|
- `src/utils/token-manager.ts`
|
|
**Tests** : Tests expiration, tests WebSocket
|
|
**Note** : WebSocket nécessite backend
|
|
|
|
### PR 7 : Utilities & Tests (P1-007, P1-003)
|
|
**Titre** : `fix: add ID converter utility, fix usePlaylistTrack tests`
|
|
**Fichiers** :
|
|
- `src/utils/id-converter.ts`
|
|
- `src/services/api.ts` (utiliser converter)
|
|
- `src/features/playlists/hooks/usePlaylistTrack.test.tsx`
|
|
**Tests** : Tests unitaires
|
|
|
|
### PR 8 : Quick Wins (P2-009, P3-002, P3-003, P2-004, P2-008, P3-005)
|
|
**Titre** : `chore: quick wins (remove dead code, add security headers, validate uploads, analyze bundle, document TODOs)`
|
|
**Fichiers** :
|
|
- `src/utils/csp.ts` (supprimer middleware)
|
|
- `nginx.conf` (ajouter headers)
|
|
- `src/features/library/components/UploadModal.tsx` (validation taille)
|
|
- `package.json` (script bundle analysis)
|
|
- Documentation TODOs
|
|
**Tests** : Tests manuels
|
|
|
|
---
|
|
|
|
## Estimation totale
|
|
|
|
- **P0** : 8-12h (build fixes + sécurité critique)
|
|
- **P1** : 8-12h (bugs fréquents, dette bloquante)
|
|
- **P2** : 6-10h (qualité, maintenabilité)
|
|
- **P3** : 2-4h (cosmétique)
|
|
|
|
**Total** : **24-38h** de travail (3-5 jours développeur)
|
|
|
|
---
|
|
|
|
## Dépendances externes
|
|
|
|
### Backend Go (`veza-backend-api`)
|
|
|
|
- Support httpOnly cookies pour tokens (P0-002)
|
|
- Endpoint CSRF token (P0-005)
|
|
- Validation CSRF sur toutes les requêtes POST/PUT/DELETE (P0-005)
|
|
|
|
### Chat Server Rust (`veza-chat-server`)
|
|
|
|
- Support token JWT en header au lieu de query param (P1-006)
|
|
|
|
### Stream Server Rust (`veza-stream-server`)
|
|
|
|
- Aucune dépendance identifiée
|
|
|
|
---
|
|
|
|
## Notes finales
|
|
|
|
### Points positifs
|
|
|
|
✅ Architecture feature-based claire
|
|
✅ Tests unitaires nombreux (189 fichiers)
|
|
✅ Utilisation de Zod pour validation
|
|
✅ ErrorBoundary React
|
|
✅ Upload par chunks pour gros fichiers
|
|
✅ WebSocket avec reconnexion intelligente
|
|
✅ PWA support (service worker)
|
|
✅ i18n (français/anglais)
|
|
|
|
### Points à surveiller
|
|
|
|
⚠️ Duplication de code (clients API)
|
|
⚠️ Logs console partout (121 occurrences)
|
|
⚠️ Types incomplets (Track/User)
|
|
⚠️ Sécurité tokens (localStorage)
|
|
|
|
### Recommandations long terme
|
|
|
|
1. **Centraliser la gestion d'erreurs** : Créer un service d'erreurs global
|
|
2. **Logger structuré** : Implémenter un logger avec niveaux et corrélation
|
|
3. **Métriques** : Ajouter des métriques custom (erreurs, latence, etc.)
|
|
4. **Documentation** : Documenter les conventions de code et les patterns
|
|
5. **CI/CD** : Ajouter des checks automatiques (typecheck, lint, tests) dans CI
|
|
|
|
---
|
|
|
|
**Fin du rapport d'audit**
|
|
|