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:
@@ -90,10 +90,12 @@ test.describe('Account Activation Flow', () => {
|
||||
const digitItem = page.locator('.password-requirements li').filter({ hasText: /chiffre/ });
|
||||
await expect(digitItem).not.toHaveClass(/valid/);
|
||||
|
||||
// Valid password should show all checkmarks
|
||||
// Valid password (without special char) should show 4/5 checkmarks
|
||||
// Requirements: minLength, uppercase, lowercase, digit, specialChar
|
||||
// "Abcdefgh1" satisfies: minLength(9>=8), uppercase(A), lowercase(bcdefgh), digit(1)
|
||||
await passwordInput.fill('Abcdefgh1');
|
||||
const validItems = page.locator('.password-requirements li.valid');
|
||||
await expect(validItems).toHaveCount(3);
|
||||
await expect(validItems).toHaveCount(4);
|
||||
});
|
||||
|
||||
test('requires password confirmation to match', async ({ page }) => {
|
||||
@@ -106,13 +108,13 @@ test.describe('Account Activation Flow', () => {
|
||||
const passwordInput = page.locator('#password');
|
||||
const confirmInput = page.locator('#passwordConfirmation');
|
||||
|
||||
await passwordInput.fill('SecurePass123');
|
||||
await confirmInput.fill('DifferentPass123');
|
||||
await passwordInput.fill('SecurePass123!');
|
||||
await confirmInput.fill('DifferentPass123!');
|
||||
|
||||
await expect(page.getByText(/mots de passe ne correspondent pas/i)).toBeVisible();
|
||||
|
||||
// Fix confirmation
|
||||
await confirmInput.fill('SecurePass123');
|
||||
await confirmInput.fill('SecurePass123!');
|
||||
await expect(page.getByText(/mots de passe ne correspondent pas/i)).not.toBeVisible();
|
||||
});
|
||||
|
||||
@@ -128,9 +130,9 @@ test.describe('Account Activation Flow', () => {
|
||||
// Initially disabled
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill valid password
|
||||
await page.locator('#password').fill('SecurePass123');
|
||||
await page.locator('#passwordConfirmation').fill('SecurePass123');
|
||||
// Fill valid password (must include special char)
|
||||
await page.locator('#password').fill('SecurePass123!');
|
||||
await page.locator('#passwordConfirmation').fill('SecurePass123!');
|
||||
|
||||
// Should now be enabled
|
||||
await expect(submitButton).toBeEnabled();
|
||||
@@ -194,9 +196,9 @@ test.describe('Account Activation Flow', () => {
|
||||
// Button should be disabled initially (no password yet)
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill valid password
|
||||
await page.locator('#password').fill('SecurePass123');
|
||||
await page.locator('#passwordConfirmation').fill('SecurePass123');
|
||||
// Fill valid password (must include special char)
|
||||
await page.locator('#password').fill('SecurePass123!');
|
||||
await page.locator('#passwordConfirmation').fill('SecurePass123!');
|
||||
|
||||
// Wait for validation to complete - button should now be enabled
|
||||
await expect(submitButton).toBeEnabled({ timeout: 2000 });
|
||||
|
||||
Reference in New Issue
Block a user