diff --git a/packages/panels/resources/lang/en/auth/multi-factor/email/actions/disable.php b/packages/panels/resources/lang/en/auth/multi-factor/email/actions/disable.php index 24c4c47fd7..94ac321d2c 100644 --- a/packages/panels/resources/lang/en/auth/multi-factor/email/actions/disable.php +++ b/packages/panels/resources/lang/en/auth/multi-factor/email/actions/disable.php @@ -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.', + ], + ], ], diff --git a/packages/panels/resources/lang/en/auth/multi-factor/email/actions/set-up.php b/packages/panels/resources/lang/en/auth/multi-factor/email/actions/set-up.php index 3c24ab066a..e50d4c5d96 100644 --- a/packages/panels/resources/lang/en/auth/multi-factor/email/actions/set-up.php +++ b/packages/panels/resources/lang/en/auth/multi-factor/email/actions/set-up.php @@ -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.', + ], + ], ], diff --git a/packages/panels/resources/lang/en/auth/multi-factor/email/provider.php b/packages/panels/resources/lang/en/auth/multi-factor/email/provider.php index 377c792bf7..7f9578b42c 100644 --- a/packages/panels/resources/lang/en/auth/multi-factor/email/provider.php +++ b/packages/panels/resources/lang/en/auth/multi-factor/email/provider.php @@ -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.', + ], + ], ], diff --git a/packages/panels/src/Auth/MultiFactor/Email/Actions/DisableEmailAuthenticationAction.php b/packages/panels/src/Auth/MultiFactor/Email/Actions/DisableEmailAuthenticationAction.php index 7ae3e4f94e..daf9f75d24 100644 --- a/packages/panels/src/Auth/MultiFactor/Email/Actions/DisableEmailAuthenticationAction.php +++ b/packages/panels/src/Auth/MultiFactor/Email/Actions/DisableEmailAuthenticationAction.php @@ -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')) diff --git a/packages/panels/src/Auth/MultiFactor/Email/Actions/SetUpEmailAuthenticationAction.php b/packages/panels/src/Auth/MultiFactor/Email/Actions/SetUpEmailAuthenticationAction.php index e863d5dce5..f131c18754 100644 --- a/packages/panels/src/Auth/MultiFactor/Email/Actions/SetUpEmailAuthenticationAction.php +++ b/packages/panels/src/Auth/MultiFactor/Email/Actions/SetUpEmailAuthenticationAction.php @@ -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')) diff --git a/packages/panels/src/Auth/MultiFactor/Email/EmailAuthentication.php b/packages/panels/src/Auth/MultiFactor/Email/EmailAuthentication.php index d4dc86ea53..5cb647840a 100644 --- a/packages/panels/src/Auth/MultiFactor/Email/EmailAuthentication.php +++ b/packages/panels/src/Auth/MultiFactor/Email/EmailAuthentication.php @@ -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')) diff --git a/tests/src/Panels/Auth/MultiFactor/Email/DisableEmailAuthenticationActionTest.php b/tests/src/Panels/Auth/MultiFactor/Email/DisableEmailAuthenticationActionTest.php index 16c92b94aa..377236e20b 100644 --- a/tests/src/Panels/Auth/MultiFactor/Email/DisableEmailAuthenticationActionTest.php +++ b/tests/src/Panels/Auth/MultiFactor/Email/DisableEmailAuthenticationActionTest.php @@ -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); }); diff --git a/tests/src/Panels/Auth/MultiFactor/Email/EmailAuthenticationChallengeTest.php b/tests/src/Panels/Auth/MultiFactor/Email/EmailAuthenticationChallengeTest.php index b40d4f5648..823333f987 100644 --- a/tests/src/Panels/Auth/MultiFactor/Email/EmailAuthenticationChallengeTest.php +++ b/tests/src/Panels/Auth/MultiFactor/Email/EmailAuthenticationChallengeTest.php @@ -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); }); diff --git a/tests/src/Panels/Auth/MultiFactor/Email/SetUpEmailAuthenticationActionTest.php b/tests/src/Panels/Auth/MultiFactor/Email/SetUpEmailAuthenticationActionTest.php index 43a66fc4a1..9b59ae2ef7 100644 --- a/tests/src/Panels/Auth/MultiFactor/Email/SetUpEmailAuthenticationActionTest.php +++ b/tests/src/Panels/Auth/MultiFactor/Email/SetUpEmailAuthenticationActionTest.php @@ -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); });