veza/veza-backend-api/docs/AUTH_PASSWORD_RESET.md
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
Backend Go:
- Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN.
- Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError).
- Sécurisation de config.go, CORS, statuts de santé et monitoring.
- Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles).
- Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés.
- Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*.

Chat server (Rust):
- Refonte du pipeline JWT + sécurité, audit et rate limiting avancé.
- Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing).
- Nettoyage des panics, gestion d’erreurs robuste, logs structurés.
- Migrations chat alignées sur le schéma UUID et nouvelles features.

Stream server (Rust):
- Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core.
- Transactions P0 pour les jobs et segments, garanties d’atomicité.
- Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION).

Documentation & audits:
- TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services.
- Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3).
- Scripts de reset et de cleanup pour la lab DB et la V1.

Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 11:14:38 +01:00

394 lines
12 KiB
Markdown

# AUTH_PASSWORD_RESET.md
## 📋 Vue d'ensemble
Ce document décrit le système complet de réinitialisation de mot de passe (password reset) implémenté dans `veza-backend-api`. Le système permet aux utilisateurs de réinitialiser leur mot de passe de manière sécurisée via un flux en deux étapes : demande de reset et confirmation avec token.
## 🎯 Objectifs
- Permettre aux utilisateurs de réinitialiser leur mot de passe en cas d'oubli
- Garantir la sécurité via des tokens à usage unique avec expiration
- Prévenir l'énumération d'emails (email enumeration)
- Invalider automatiquement les sessions existantes après reset
## 🔄 Flux global
```
1. User → POST /api/v1/auth/password/reset-request
└─> Email fourni
└─> Si email existe → Génération token + Stockage DB + Envoi email
└─> Réponse générique (toujours succès pour sécurité)
2. User → Email reçu avec lien contenant token
└─> Clic sur lien → Frontend avec token en paramètre
3. User → POST /api/v1/auth/password/reset
└─> Token + Nouveau mot de passe
└─> Vérification token (valide, non expiré, non utilisé)
└─> Hash nouveau mot de passe
└─> Mise à jour password_hash en DB
└─> Invalidation token (marqué comme utilisé)
└─> Invalidation sessions utilisateur (revoke refresh tokens)
```
## 📡 Contrat API
### Endpoint 1 : Request Password Reset
**Route** : `POST /api/v1/auth/password/reset-request`
**Request Body** :
```json
{
"email": "user@example.com"
}
```
**Response (200 OK)** :
```json
{
"message": "If the email exists, a reset link has been sent"
}
```
**Comportement** :
- Si l'email existe : génération token, stockage DB, envoi email
- Si l'email n'existe pas : même réponse (prévention énumération)
- Toujours retourne 200 OK avec message générique
**Codes d'erreur** :
- `400 Bad Request` : Email invalide (format)
- `500 Internal Server Error` : Erreur serveur (génération token, stockage DB)
---
### Endpoint 2 : Confirm Password Reset
**Route** : `POST /api/v1/auth/password/reset`
**Request Body** :
```json
{
"token": "base64-url-safe-token-here",
"new_password": "NewSecurePassword123!"
}
```
**Response (200 OK)** :
```json
{
"message": "Password reset successfully"
}
```
**Codes d'erreur** :
- `400 Bad Request` :
- Token invalide ou expiré
- Token déjà utilisé
- Mot de passe trop faible (validation)
- Format de requête invalide
**Comportement** :
- Vérifie token (existe, non expiré, non utilisé)
- Valide force du mot de passe
- Hash nouveau mot de passe (bcrypt, cost 12)
- Met à jour `password_hash` dans table `users`
- Marque token comme utilisé
- Invalide toutes les sessions utilisateur (revoke refresh tokens)
---
## 🔒 Sécurité
### Tokens
- **Génération** : 32 bytes aléatoires, encodés en base64 URL-safe
- **Expiration** : 1 heure (configurable via `PasswordResetService`)
- **Usage unique** : Token marqué comme `used = TRUE` après utilisation
- **Invalidation** : Tous les tokens précédents d'un utilisateur sont invalidés lors d'une nouvelle demande
### Prévention d'énumération
- **Réponse uniforme** : Toujours retourner le même message, même si l'email n'existe pas
- **Pas de timing attack** : Même temps de traitement pour email existant/non existant
- **Logs sécurisés** : Jamais logger le token complet, seulement un preview (8 premiers caractères)
### Invalidation des sessions
Après un reset de mot de passe réussi :
- Tous les refresh tokens de l'utilisateur sont révoqués
- Les sessions actives sont invalidées
- L'utilisateur doit se reconnecter avec le nouveau mot de passe
### Hash des mots de passe
- **Algorithme** : bcrypt
- **Cost** : 12 (équilibre sécurité/performance)
- **Stockage** : Champ `password_hash` dans table `users`
---
## 🗄️ Modèle de données
### Table `password_reset_tokens`
```sql
CREATE TABLE public.password_reset_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
-- Token
token VARCHAR(255) NOT NULL UNIQUE,
token_hash VARCHAR(255) NOT NULL, -- Pour future amélioration
-- Status
used BOOLEAN NOT NULL DEFAULT false,
used_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ NOT NULL,
-- Metadata
ip_address INET,
user_agent TEXT,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_password_reset_expires CHECK (expires_at > created_at)
);
```
**Indexes** :
- `idx_password_reset_tokens_user_id` sur `user_id`
- `idx_password_reset_tokens_token_hash` sur `token_hash`
- `idx_password_reset_tokens_expires_at` sur `expires_at`
**Règles** :
- Un token est valide si : `used = FALSE` ET `expires_at > NOW()`
- Sur nouvelle demande, tous les tokens précédents (`used = FALSE`) sont invalidés
---
## 🏗️ Architecture
### Services
#### `PasswordResetService` (`internal/services/password_reset_service.go`)
Méthodes principales :
- `GenerateToken() (string, error)` : Génère un token aléatoire sécurisé
- `StoreToken(userID uuid.UUID, token string) error` : Stocke le token en DB
- `VerifyToken(token string) (uuid.UUID, error)` : Vérifie et retourne userID
- `MarkTokenAsUsed(token string) error` : Marque le token comme utilisé
- `InvalidateOldTokens(userID uuid.UUID) error` : Invalide tous les tokens précédents
#### `PasswordService` (`internal/services/password_service.go`)
Méthodes utilisées :
- `GetUserByEmail(email string) (*UserInfo, error)` : Récupère utilisateur par email
- `ValidatePassword(password string) error` : Valide la force du mot de passe
- `UpdatePassword(userID uuid.UUID, newPassword string) error` : Met à jour le mot de passe
#### `EmailService` (`internal/services/email_service.go`)
Méthodes utilisées :
- `SendPasswordResetEmail(userID uuid.UUID, email string, token string) error` : Envoie l'email de reset
#### `AuthService` (`internal/core/auth/service.go`)
Méthodes principales :
- `RequestPasswordReset(ctx context.Context, email string) error` : Orchestre la demande de reset
- `ResetPassword(ctx context.Context, token string, newPassword string) error` : Orchestre la confirmation de reset
### Handlers
#### `RequestPasswordReset` (`internal/handlers/password_reset_handler.go`)
Handler HTTP pour la demande de reset :
- Valide l'email
- Trouve l'utilisateur (ou retourne succès générique)
- Génère et stocke le token
- Envoie l'email
- Retourne réponse générique
#### `ResetPassword` (`internal/handlers/password_reset_handler.go`)
Handler HTTP pour la confirmation de reset :
- Valide le token
- Valide le nouveau mot de passe
- Met à jour le mot de passe
- Marque le token comme utilisé
- Invalide les sessions utilisateur
---
## ⚙️ Configuration
### Variables d'environnement
```bash
# URL du frontend (pour construire le lien de reset)
FRONTEND_URL=http://localhost:5173 # Défaut si non défini
# Configuration SMTP (pour envoi emails)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-password
FROM_EMAIL=noreply@veza.com
FROM_NAME=Veza
```
### Configuration du service
Le `PasswordResetService` utilise une expiration de **1 heure** par défaut (non configurable actuellement, hardcodé dans `StoreToken`).
---
## 🧪 Tests
### Tests unitaires
**Fichier** : `internal/core/auth/service_test.go` (à créer)
Tests à implémenter :
- `TestAuthService_RequestPasswordReset_UserExists` : Token généré et stocké
- `TestAuthService_RequestPasswordReset_UserNotExists` : Retourne nil (pas d'erreur)
- `TestAuthService_ResetPassword_ValidToken` : Mot de passe mis à jour
- `TestAuthService_ResetPassword_ExpiredToken` : Erreur "token expired"
- `TestAuthService_ResetPassword_UsedToken` : Erreur "token already used"
- `TestAuthService_ResetPassword_InvalidToken` : Erreur "invalid token"
### Tests d'intégration
**Fichier** : `tests/integration/password_reset_test.go` (à créer)
Test complet du flux :
1. Créer un utilisateur en DB
2. Appeler `/api/v1/auth/password/reset-request`
3. Récupérer le token en DB
4. Appeler `/api/v1/auth/password/reset` avec le token
5. Vérifier que le nouveau mot de passe permet un login
**Note** : Peut être marqué comme `t.Skip` si l'infra de test n'est pas configurée.
### Lancer les tests
```bash
# Tests unitaires du service auth
cd veza-backend-api
go test ./internal/core/auth -run TestAuthService.*PasswordReset -v
# Tests d'intégration (si configurés)
go test ./tests/integration -run TestPasswordReset -v
```
---
## 📝 Logs
### Ce qui est loggé
- **RequestPasswordReset** :
- `Info` : "Password reset requested successfully" (avec email, user_id, token preview)
- `Error` : Erreurs de génération token, stockage, envoi email
- `Warn` : Échec invalidation anciens tokens (non bloquant)
- **ResetPassword** :
- `Info` : "Password reset completed successfully" (avec user_id)
- `Warn` : Token invalide/expiré/utilisé, validation mot de passe échouée
- `Error` : Erreurs de mise à jour mot de passe
- `Warn` : Échec marquage token comme utilisé (non bloquant)
- `Warn` : Échec invalidation sessions (non bloquant)
### Ce qui n'est JAMAIS loggé
- **Token complet** : Seulement un preview (8 premiers caractères + "...")
- **Nouveau mot de passe** : Jamais loggé, même hashé
- **Email utilisateur** : Loggé uniquement pour debugging (peut être masqué en production)
---
## 🔧 Maintenance
### Nettoyage des tokens expirés
Les tokens expirés peuvent être nettoyés périodiquement via un job de maintenance :
```sql
DELETE FROM password_reset_tokens
WHERE expires_at < NOW() - INTERVAL '7 days'
AND used = TRUE;
```
**Note** : Un job de cleanup n'est pas encore implémenté, mais peut être ajouté dans `internal/jobs/`.
### Monitoring
Métriques à surveiller :
- Nombre de demandes de reset par jour
- Taux d'échec de vérification token (tokens expirés/invalides)
- Taux de succès de reset (token utilisé avec succès)
- Temps moyen entre demande et confirmation
---
## 🐛 Dépannage
### Problème : Token invalide ou expiré
**Causes possibles** :
- Token déjà utilisé
- Token expiré (> 1h)
- Token incorrect (copie/collage partiel)
**Solution** : Demander un nouveau token via `/api/v1/auth/password/reset-request`
### Problème : Email non reçu
**Causes possibles** :
- Configuration SMTP incorrecte
- Email dans spam
- Email invalide
**Vérifications** :
- Logs serveur pour erreurs SMTP
- Vérifier `SMTP_*` variables d'environnement
- Vérifier que l'utilisateur existe en DB
### Problème : Sessions non invalidées après reset
**Cause** : Échec de `refreshTokenService.RevokeAll()`
**Solution** : Vérifier les logs, le mot de passe est déjà mis à jour (non bloquant)
---
## 📚 Références
- Migration : `migrations/010_auth_and_users.sql` (table `password_reset_tokens`)
- Service : `internal/services/password_reset_service.go`
- Handler : `internal/handlers/password_reset_handler.go`
- Auth Service : `internal/core/auth/service.go`
- Router : `internal/api/router.go` (routes `/api/v1/auth/password/*`)
---
## ✅ Checklist de validation
- [x] Endpoints fonctionnels (`/reset-request` et `/reset`)
- [x] Tokens stockés en DB avec expiration
- [x] Tokens invalidés après usage
- [x] Prévention énumération emails (réponse uniforme)
- [x] Invalidation sessions après reset
- [x] Validation force mot de passe
- [x] Logs sécurisés (pas de token complet)
- [x] Documentation complète
- [ ] Tests unitaires complets (à compléter)
- [ ] Test d'intégration (à compléter si infra disponible)
---
**Dernière mise à jour** : 2025-01-XX
**Version** : 1.0.0
**Auteur** : Équipe Veza Backend