feat: Réinitialisation de mot de passe avec tokens sécurisés

Implémentation complète du flux de réinitialisation de mot de passe (Story 1.5):

Backend:
- Aggregate PasswordResetToken avec TTL 1h, UUID v7, usage unique
- Endpoint POST /api/password/forgot avec rate limiting (3/h par email, 10/h par IP)
- Endpoint POST /api/password/reset avec validation token
- Templates email (demande + confirmation)
- Repository Redis avec TTL 2h pour distinguer expiré/invalide

Frontend:
- Page /mot-de-passe-oublie avec message générique (anti-énumération)
- Page /reset-password/[token] avec validation temps réel des critères
- Gestion erreurs: token invalide, expiré, déjà utilisé

Tests:
- 14 tests unitaires PasswordResetToken
- 7 tests unitaires RequestPasswordResetHandler
- 7 tests unitaires ResetPasswordHandler
- Tests E2E Playwright pour le flux complet
This commit is contained in:
2026-02-01 23:15:01 +01:00
parent b7354b8448
commit affad287f9
71 changed files with 4829 additions and 222 deletions

View File

@@ -7,15 +7,15 @@ namespace App\Administration\Infrastructure\Security;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
/**
* Enrichit le payload JWT avec les claims métier.
* Enriches the JWT payload with business claims.
*
* Claims ajoutés:
* - sub: Email de l'utilisateur (identifiant Symfony Security)
* - user_id: UUID de l'utilisateur (pour les consommateurs d'API)
* - tenant_id: UUID du tenant pour l'isolation multi-tenant
* - roles: Liste des rôles Symfony pour l'autorisation
* Added claims:
* - sub: User email (Symfony Security identifier)
* - user_id: User UUID (for API consumers)
* - tenant_id: Tenant UUID for multi-tenant isolation
* - roles: List of Symfony roles for authorization
*
* @see Story 1.4 - Connexion utilisateur
* @see Story 1.4 - User login
*/
final readonly class JwtPayloadEnricher
{
@@ -29,7 +29,7 @@ final readonly class JwtPayloadEnricher
$payload = $event->getData();
// Claims métier pour l'isolation multi-tenant et l'autorisation
// Business claims for multi-tenant isolation and authorization
$payload['user_id'] = $user->userId();
$payload['tenant_id'] = $user->tenantId();
$payload['roles'] = $user->getRoles();