Add email verification, organizer approval, and forgot password features

- Add isVerified, emailVerificationToken, emailVerifiedAt fields to User entity
- Send verification email on registration with token link
- Add /verification-email/{token} route to confirm email
- Send notification emails to organizer and staff on organizer email verification
- Add isApproved and offer fields to User entity for organizer approval workflow
- Auto-verify and auto-approve SSO Keycloak users with offer='custom'
- Add resetCode and resetCodeExpiresAt fields to User entity
- Create ForgotPasswordController with 2-step flow (email -> code + new password)
- Block forgot password for SSO users (no local password)
- Add "Mot de passe oublie" link on login page
- Create email templates: verification, reset_code, organizer_pending, organizer_request
- Add migrations for all new fields
- Add tests: ForgotPasswordControllerTest (9 tests), update RegistrationControllerTest,
  update UserTest with verification, approval, offer, and reset code fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-19 12:13:32 +01:00
parent 4577e06a19
commit 52e6e2c14c
16 changed files with 886 additions and 2 deletions

View File

@@ -88,6 +88,54 @@ class UserTest extends TestCase
self::assertNull($user->getPhone());
}
public function testResetCodeFields(): void
{
$user = new User();
self::assertNull($user->getResetCode());
self::assertNull($user->getResetCodeExpiresAt());
$expiry = new \DateTimeImmutable('+15 minutes');
$result = $user->setResetCode('123456')->setResetCodeExpiresAt($expiry);
self::assertSame($user, $result);
self::assertSame('123456', $user->getResetCode());
self::assertSame($expiry, $user->getResetCodeExpiresAt());
}
public function testApprovalAndOfferFields(): void
{
$user = new User();
self::assertFalse($user->isApproved());
self::assertNull($user->getOffer());
$result = $user->setIsApproved(true)->setOffer('custom');
self::assertSame($user, $result);
self::assertTrue($user->isApproved());
self::assertSame('custom', $user->getOffer());
}
public function testEmailVerificationFields(): void
{
$user = new User();
self::assertFalse($user->isVerified());
self::assertNull($user->getEmailVerificationToken());
self::assertNull($user->getEmailVerifiedAt());
$now = new \DateTimeImmutable();
$result = $user->setIsVerified(true)
->setEmailVerificationToken('abc123')
->setEmailVerifiedAt($now);
self::assertSame($user, $result);
self::assertTrue($user->isVerified());
self::assertSame('abc123', $user->getEmailVerificationToken());
self::assertSame($now, $user->getEmailVerifiedAt());
}
public function testEraseCredentialsDoesNotThrow(): void
{
$user = new User();