fix(mfa): fix notification on email MFA resend (#18208)

* fix(mfa): fix notification on email MFA resend

- Prevent false success notifications when rate limited
- Enhance existing tests to verify UI notifications
- Modify sendCode() to return bool indicating success/failure
- Update resend actions in login, setup, and disable flows
- Add English translation keys for rate limited messages

* consistency

* cs

* fix tests

---------

Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
Kevin Yohanes
2025-10-15 10:29:30 +01:00
committed by GitHub
co-authored by Dan Harrin
parent db8d4556ca
commit e5cf9885f5
9 changed files with 123 additions and 21 deletions
@@ -30,6 +30,10 @@ return [
'title' => 'We\'ve sent you a new code by email',
],
'throttled' => [
'title' => 'Too many resend attempts. Please wait before requesting another code.',
],
],
],
@@ -30,6 +30,10 @@ return [
'title' => 'We\'ve sent you a new code by email',
],
'throttled' => [
'title' => 'Too many resend attempts. Please wait before requesting another code.',
],
],
],
@@ -41,6 +41,10 @@ return [
'title' => 'We\'ve sent you a new code by email',
],
'throttled' => [
'title' => 'Too many resend attempts. Please wait before requesting another code.',
],
],
],
@@ -43,7 +43,14 @@ class DisableEmailAuthenticationAction
/** @var HasEmailAuthentication $user */
$user = Filament::auth()->user();
$emailAuthentication->sendCode($user);
if (! $emailAuthentication->sendCode($user)) {
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.throttled.title'))
->danger()
->send();
return;
}
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.resent.title'))
@@ -44,7 +44,14 @@ class SetUpEmailAuthenticationAction
/** @var HasEmailAuthentication $user */
$user = Filament::auth()->user();
$emailAuthentication->sendCode($user);
if (! $emailAuthentication->sendCode($user)) {
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.throttled.title'))
->danger()
->send();
return;
}
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.resent.title'))
@@ -55,7 +55,7 @@ class EmailAuthentication implements HasBeforeChallengeHook, MultiFactorAuthenti
return $user->hasEmailAuthentication();
}
public function sendCode(HasEmailAuthentication $user): void
public function sendCode(HasEmailAuthentication $user): bool
{
if (! ($user instanceof Model)) {
throw new LogicException('The [' . $user::class . '] class must be an instance of [' . Model::class . '] to use email authentication.');
@@ -70,7 +70,7 @@ class EmailAuthentication implements HasBeforeChallengeHook, MultiFactorAuthenti
$rateLimitingKey = "filament_email_authentication.{$user->getKey()}";
if (RateLimiter::tooManyAttempts($rateLimitingKey, maxAttempts: 2)) {
return;
return false;
}
RateLimiter::hit($rateLimitingKey);
@@ -85,6 +85,8 @@ class EmailAuthentication implements HasBeforeChallengeHook, MultiFactorAuthenti
'code' => $code,
'codeExpiryMinutes' => $codeExpiryMinutes,
]));
return true;
}
public function enableEmailAuthentication(HasEmailAuthentication $user): void
@@ -197,7 +199,14 @@ class EmailAuthentication implements HasBeforeChallengeHook, MultiFactorAuthenti
->label(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.label'))
->link()
->action(function () use ($user): void {
$this->sendCode($user);
if (! $this->sendCode($user)) {
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.throttled.title'))
->danger()
->send();
return;
}
Notification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.resent.title'))
@@ -5,6 +5,7 @@ use Filament\Auth\MultiFactor\Email\EmailAuthentication;
use Filament\Auth\MultiFactor\Email\Notifications\VerifyEmailAuthentication;
use Filament\Auth\Pages\EditProfile;
use Filament\Facades\Filament;
use Filament\Notifications\Notification as FilamentNotification;
use Filament\Tests\Fixtures\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Arr;
@@ -70,7 +71,12 @@ it('can resend the code to the user', function (): void {
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
});
@@ -86,13 +92,23 @@ it('can resend the code to the user more than twice per minute', function (): vo
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.throttled.title'))
->danger()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
@@ -100,7 +116,12 @@ it('can resend the code to the user more than twice per minute', function (): vo
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/disable.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 3);
});
@@ -5,6 +5,7 @@ use Filament\Auth\MultiFactor\Email\EmailAuthentication;
use Filament\Auth\MultiFactor\Email\Notifications\VerifyEmailAuthentication;
use Filament\Auth\Pages\Login;
use Filament\Facades\Filament;
use Filament\Notifications\Notification as FilamentNotification;
use Filament\Tests\Fixtures\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Arr;
@@ -108,8 +109,14 @@ it('can resend the code to the user', function (): void {
$this->travelBack();
$livewire
->callAction(TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm'));
->callAction(
TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm')
)->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
});
@@ -133,22 +140,40 @@ it('can not resend the code to the user more than twice per minute', function ()
Notification::assertSentTimes(VerifyEmailAuthentication::class, 1);
$livewire
->callAction(TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm'));
->callAction(
TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm')
)->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
$livewire
->callAction(TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm'));
->callAction(
TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm')
)->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.throttled.title'))
->danger()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
$this->travelBack();
$livewire
->callAction(TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm'));
->callAction(
TestAction::make('resend')
->schemaComponent("{$emailAuthentication->getId()}.code", schema: 'multiFactorChallengeForm')
)->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/provider.login_form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 3);
});
@@ -5,6 +5,7 @@ use Filament\Auth\MultiFactor\Email\EmailAuthentication;
use Filament\Auth\MultiFactor\Email\Notifications\VerifyEmailAuthentication;
use Filament\Auth\Pages\EditProfile;
use Filament\Facades\Filament;
use Filament\Notifications\Notification as FilamentNotification;
use Filament\Tests\Fixtures\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Arr;
@@ -81,7 +82,12 @@ it('can resend the code to the user', function (): void {
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
});
@@ -97,13 +103,23 @@ it('can resend the code to the user more than twice per minute', function (): vo
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.throttled.title'))
->danger()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 2);
@@ -111,7 +127,12 @@ it('can resend the code to the user more than twice per minute', function (): vo
$livewire
->callAction(TestAction::make('resend')
->schemaComponent('code'));
->schemaComponent('code'))
->assertNotified(
FilamentNotification::make()
->title(__('filament-panels::auth/multi-factor/email/actions/set-up.modal.form.code.actions.resend.notifications.resent.title'))
->success()
);
Notification::assertSentTimes(VerifyEmailAuthentication::class, 3);
});