diff --git a/packages/panels/resources/lang/en/pages/auth/password-reset/request-password-reset.php b/packages/panels/resources/lang/en/pages/auth/password-reset/request-password-reset.php index 97c276f265..a26eaa4e23 100644 --- a/packages/panels/resources/lang/en/pages/auth/password-reset/request-password-reset.php +++ b/packages/panels/resources/lang/en/pages/auth/password-reset/request-password-reset.php @@ -32,6 +32,10 @@ return [ 'notifications' => [ + 'sent' => [ + 'body' => 'If your account doesn\'t exist, you will not receive the email.', + ], + 'throttled' => [ 'title' => 'Too many requests', 'body' => 'Please try again in :seconds seconds.', diff --git a/packages/panels/src/Pages/Auth/PasswordReset/RequestPasswordReset.php b/packages/panels/src/Pages/Auth/PasswordReset/RequestPasswordReset.php index c9f2059eff..dbb6b6403e 100644 --- a/packages/panels/src/Pages/Auth/PasswordReset/RequestPasswordReset.php +++ b/packages/panels/src/Pages/Auth/PasswordReset/RequestPasswordReset.php @@ -11,6 +11,7 @@ use Filament\Facades\Filament; use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; +use Filament\Models\Contracts\FilamentUser; use Filament\Notifications\Auth\ResetPassword as ResetPasswordNotification; use Filament\Notifications\Notification; use Filament\Pages\Concerns\InteractsWithFormActions; @@ -62,6 +63,13 @@ class RequestPasswordReset extends SimplePage $status = Password::broker(Filament::getAuthPasswordBroker())->sendResetLink( $this->getCredentialsFromFormData($data), function (CanResetPassword $user, string $token): void { + if ( + ($user instanceof FilamentUser) && + (! $user->canAccessPanel(Filament::getCurrentPanel())) + ) { + return; + } + if (! method_exists($user, 'notify')) { $userClass = $user::class; @@ -111,6 +119,7 @@ class RequestPasswordReset extends SimplePage { return Notification::make() ->title(__($status)) + ->body(($status === Password::RESET_LINK_SENT) ? __('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent.body') : null) ->success(); } diff --git a/packages/panels/src/Pages/Auth/PasswordReset/ResetPassword.php b/packages/panels/src/Pages/Auth/PasswordReset/ResetPassword.php index 65d7dbfe56..84beb935fe 100644 --- a/packages/panels/src/Pages/Auth/PasswordReset/ResetPassword.php +++ b/packages/panels/src/Pages/Auth/PasswordReset/ResetPassword.php @@ -11,6 +11,7 @@ use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Http\Responses\Auth\Contracts\PasswordResetResponse; +use Filament\Models\Contracts\FilamentUser; use Filament\Notifications\Notification; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Pages\SimplePage; @@ -76,18 +77,33 @@ class ResetPassword extends SimplePage $data['email'] = $this->email; $data['token'] = $this->token; + $hasPanelAccess = true; + $status = Password::broker(Filament::getAuthPasswordBroker())->reset( $this->getCredentialsFromFormData($data), - function (CanResetPassword | Model | Authenticatable $user) use ($data) { + function (CanResetPassword | Model | Authenticatable $user) use ($data, &$hasPanelAccess) { + if ( + ($user instanceof FilamentUser) && + (! $user->canAccessPanel(Filament::getCurrentPanel())) + ) { + $hasPanelAccess = false; + + return; + } + $user->forceFill([ 'password' => Hash::make($data['password']), 'remember_token' => Str::random(60), ])->save(); event(new PasswordReset($user)); - }, + } ); + if ($hasPanelAccess === false) { + $status = Password::INVALID_USER; + } + if ($status === Password::PASSWORD_RESET) { Notification::make() ->title(__($status)) diff --git a/tests/src/Panels/Auth/PasswordReset/RequestPasswordResetTest.php b/tests/src/Panels/Auth/PasswordReset/RequestPasswordResetTest.php index 21447fe176..a5c9f07015 100644 --- a/tests/src/Panels/Auth/PasswordReset/RequestPasswordResetTest.php +++ b/tests/src/Panels/Auth/PasswordReset/RequestPasswordResetTest.php @@ -2,6 +2,7 @@ use Filament\Facades\Filament; use Filament\Notifications\Auth\ResetPassword; +use Filament\Notifications\Notification as FilamentNotification; use Filament\Pages\Auth\PasswordReset\RequestPasswordReset; use Filament\Tests\Models\User; use Filament\Tests\TestCase; @@ -39,11 +40,40 @@ it('can request password reset', function () { 'email' => $userToResetPassword->email, ]) ->call('request') - ->assertNotified(); + ->assertNotified( + FilamentNotification::make() + ->success() + ->title(__('passwords.sent')) + ->body(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent.body')) + ); Notification::assertSentTo($userToResetPassword, ResetPassword::class); }); +it('cannot request password reset without panel access', function () { + Notification::fake(); + + $this->assertGuest(); + + $userToResetPassword = User::factory()->create(); + + Filament::setCurrentPanel(Filament::getPanel('custom')); + + livewire(RequestPasswordReset::class) + ->fillForm([ + 'email' => $userToResetPassword->email, + ]) + ->call('request') + ->assertNotified( + FilamentNotification::make() + ->success() + ->title(__('passwords.sent')) + ->body(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent.body')) + ); + + Notification::assertNotSentTo($userToResetPassword, ResetPassword::class); +}); + it('can throttle requests', function () { Notification::fake(); diff --git a/tests/src/Panels/Auth/PasswordReset/ResetPasswordTest.php b/tests/src/Panels/Auth/PasswordReset/ResetPasswordTest.php index 4ad3ee225b..f4f83b8719 100644 --- a/tests/src/Panels/Auth/PasswordReset/ResetPasswordTest.php +++ b/tests/src/Panels/Auth/PasswordReset/ResetPasswordTest.php @@ -1,6 +1,7 @@ set('password', 'new-password') ->set('passwordConfirmation', 'new-password') ->call('resetPassword') - ->assertNotified() + ->assertNotified( + Notification::make() + ->success() + ->title(__('passwords.reset')) + ) ->assertRedirect(Filament::getLoginUrl()); Event::assertDispatched(PasswordReset::class); @@ -69,6 +74,37 @@ it('can reset password', function () { ]); }); +it('cannot reset password without panel access', function () { + Event::fake(); + + $this->assertGuest(); + + $userToResetPassword = User::factory()->create(); + $token = Password::createToken($userToResetPassword); + + Filament::setCurrentPanel(Filament::getPanel('custom')); + + livewire(ResetPassword::class, [ + 'email' => $userToResetPassword->email, + 'token' => $token, + ]) + ->set('password', 'new-password') + ->set('passwordConfirmation', 'new-password') + ->call('resetPassword') + ->assertNotified( + Notification::make() + ->danger() + ->title(__('passwords.user')) + ); + + Event::assertNotDispatched(PasswordReset::class); + + $this->assertCredentials([ + 'email' => $userToResetPassword->email, + 'password' => 'password', + ]); +}); + it('requires request signature', function () { $userToResetPassword = User::factory()->make(); $token = Password::createToken($userToResetPassword);