From 54503caf45837c87fdf9f52638a92619548a4704 Mon Sep 17 00:00:00 2001 From: Leon Morival Date: Wed, 20 May 2026 21:03:35 +0200 Subject: [PATCH] feat: resend email # Conflicts: # app/Http/Controllers/AuthController.php # lang/en/api.php # lang/fr/api.php # tests/Feature/EmailVerificationTest.php --- app/Http/Controllers/AuthController.php | 8 +++-- .../EmailVerificationNotificationRequest.php | 25 ++++++++++++++++ lang/en/api.php | 1 + lang/fr/api.php | 1 + routes/api.php | 6 ++-- tests/Feature/EmailVerificationTest.php | 29 ++++++++++++++++--- 6 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 app/Http/Requests/EmailVerificationNotificationRequest.php diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index c06a4ab..aa8c66c 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\EmailVerificationNotificationRequest; use App\Http\Requests\LoginRequest; use App\Http\Requests\RegisterRequest; use App\Http\Requests\UpdateUserRequest; @@ -107,9 +108,10 @@ class AuthController extends Controller ]); } - public function sendVerificationEmail(Request $request): JsonResponse + public function sendVerificationEmail(EmailVerificationNotificationRequest $request): JsonResponse { - $user = $request->user(); + $email = strtolower($request->validated('email')); + $user = User::where('email', $email)->first(); if ($user->hasVerifiedEmail()) { return response()->json([ @@ -120,7 +122,7 @@ class AuthController extends Controller $user->sendEmailVerificationNotification(); return response()->json([ - 'message' => __('api.auth.verification_sent'), + 'message' => __('api.auth.verification_sent_if_unverified'), ]); } diff --git a/app/Http/Requests/EmailVerificationNotificationRequest.php b/app/Http/Requests/EmailVerificationNotificationRequest.php new file mode 100644 index 0000000..338bfaa --- /dev/null +++ b/app/Http/Requests/EmailVerificationNotificationRequest.php @@ -0,0 +1,25 @@ +|string> + */ + public function rules(): array + { + return [ + 'email' => ['required', 'string', 'email'], + ]; + } +} diff --git a/lang/en/api.php b/lang/en/api.php index ecc1bc5..847ebba 100644 --- a/lang/en/api.php +++ b/lang/en/api.php @@ -8,6 +8,7 @@ return [ 'already_verified' => 'Account already verified.', 'verified' => 'Account verified successfully.', 'verification_sent' => 'Verification email sent.', + 'verification_sent_if_unverified' => 'If an unverified account exists for this address, a verification email has been sent.', 'logout' => 'Logged out successfully.', 'unauthenticated' => 'Unauthenticated.', 'deleted' => 'Account deleted successfully.', diff --git a/lang/fr/api.php b/lang/fr/api.php index 455147d..5e16808 100644 --- a/lang/fr/api.php +++ b/lang/fr/api.php @@ -8,6 +8,7 @@ return [ 'already_verified' => 'Compte déjà vérifié.', 'verified' => 'Compte vérifié avec succès.', 'verification_sent' => 'Email de vérification envoyé.', + 'verification_sent_if_unverified' => 'Si un compte non vérifié existe avec cette adresse, un email de vérification vient d’être envoyé.', 'logout' => 'Déconnecté avec succès.', 'unauthenticated' => 'Non authentifié.', 'deleted' => 'Compte supprimé avec succès.', diff --git a/routes/api.php b/routes/api.php index 6eb4d53..47374c0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,13 +16,13 @@ Route::prefix('auth')->group(function (): void { Route::get('email/verify/{id}/{hash}', [AuthController::class, 'verifyEmail']) ->middleware(['signed', 'throttle:6,1']) ->name('verification.verify'); + Route::post('email/verification-notification', [AuthController::class, 'sendVerificationEmail']) + ->middleware('throttle:6,1') + ->name('verification.send'); Route::middleware('auth:sanctum')->group(function (): void { Route::post('logout', [AuthController::class, 'logout']); Route::get('me', [AuthController::class, 'me']); - Route::post('email/verification-notification', [AuthController::class, 'sendVerificationEmail']) - ->middleware('throttle:6,1') - ->name('verification.send'); Route::patch('me', [AuthController::class, 'update'])->middleware('verified'); Route::delete('me', [AuthController::class, 'destroy']); }); diff --git a/tests/Feature/EmailVerificationTest.php b/tests/Feature/EmailVerificationTest.php index ae0d699..e7a77b6 100644 --- a/tests/Feature/EmailVerificationTest.php +++ b/tests/Feature/EmailVerificationTest.php @@ -101,19 +101,40 @@ it('allows login for verified users', function () { ->assertCookie('token'); }); -it('resends a verification email for an authenticated unverified user', function () { +it('resends a verification email for an unverified user without authentication', function () { Notification::fake(); $user = User::factory()->unverified()->create(); - Sanctum::actingAs($user); - $this->postJson('/api/auth/email/verification-notification') + $this->postJson('/api/auth/email/verification-notification', [ + 'email' => $user->email, + ]) ->assertOk() - ->assertJsonPath('message', __('api.auth.verification_sent')); + ->assertJsonPath('message', __('api.auth.verification_sent_if_unverified')); Notification::assertSentTo($user, VerifyEmail::class); }); +it('does not reveal whether an email can receive a verification email', function () { + Notification::fake(); + + $verifiedUser = User::factory()->create(); + + $this->postJson('/api/auth/email/verification-notification', [ + 'email' => $verifiedUser->email, + ]) + ->assertOk() + ->assertJsonPath('message', __('api.auth.verification_sent_if_unverified')); + + $this->postJson('/api/auth/email/verification-notification', [ + 'email' => 'missing@example.com', + ]) + ->assertOk() + ->assertJsonPath('message', __('api.auth.verification_sent_if_unverified')); + + Notification::assertNothingSent(); +}); + it('blocks verified routes for unverified users', function () { $user = User::factory()->unverified()->create(); Sanctum::actingAs($user);