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:
@@ -22,11 +22,11 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
|
||||
/**
|
||||
* Gère les échecs de login : rate limiting Fibonacci, audit, messages user-friendly.
|
||||
* Handles login failures: Fibonacci rate limiting, audit, user-friendly messages.
|
||||
*
|
||||
* Important: Ne jamais révéler si l'email existe ou non (AC2).
|
||||
* Important: Never reveal whether the email exists or not (AC2).
|
||||
*
|
||||
* @see Story 1.4 - T5: Endpoint Login Backend
|
||||
* @see Story 1.4 - T5: Backend Login Endpoint
|
||||
*/
|
||||
final readonly class LoginFailureHandler implements AuthenticationFailureHandlerInterface
|
||||
{
|
||||
@@ -46,10 +46,10 @@ final readonly class LoginFailureHandler implements AuthenticationFailureHandler
|
||||
$ipAddress = $request->getClientIp() ?? 'unknown';
|
||||
$userAgent = $request->headers->get('User-Agent', 'unknown');
|
||||
|
||||
// Enregistrer l'échec et obtenir le nouvel état
|
||||
// Record the failure and get the new state
|
||||
$result = $this->rateLimiter->recordFailure($request, $email);
|
||||
|
||||
// Émettre l'événement d'échec
|
||||
// Dispatch the failure event
|
||||
$this->eventBus->dispatch(new ConnexionEchouee(
|
||||
email: $email,
|
||||
ipAddress: $ipAddress,
|
||||
@@ -58,7 +58,7 @@ final readonly class LoginFailureHandler implements AuthenticationFailureHandler
|
||||
occurredOn: $this->clock->now(),
|
||||
));
|
||||
|
||||
// Si l'IP vient d'être bloquée
|
||||
// If the IP was just blocked
|
||||
if ($result->ipBlocked) {
|
||||
$this->eventBus->dispatch(new CompteBloqueTemporairement(
|
||||
email: $email,
|
||||
@@ -72,7 +72,7 @@ final readonly class LoginFailureHandler implements AuthenticationFailureHandler
|
||||
return $this->createBlockedResponse($result);
|
||||
}
|
||||
|
||||
// Réponse standard d'échec avec infos sur le délai et CAPTCHA
|
||||
// Standard failure response with delay and CAPTCHA info
|
||||
return $this->createFailureResponse($result);
|
||||
}
|
||||
|
||||
@@ -106,13 +106,13 @@ final readonly class LoginFailureHandler implements AuthenticationFailureHandler
|
||||
'attempts' => $result->attempts,
|
||||
];
|
||||
|
||||
// Ajouter le délai si applicable
|
||||
// Add delay if applicable
|
||||
if ($result->delaySeconds > 0) {
|
||||
$data['delay'] = $result->delaySeconds;
|
||||
$data['delayFormatted'] = $result->getFormattedDelay();
|
||||
}
|
||||
|
||||
// Indiquer si CAPTCHA requis pour la prochaine tentative
|
||||
// Indicate if CAPTCHA is required for the next attempt
|
||||
if ($result->requiresCaptcha) {
|
||||
$data['captchaRequired'] = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user