Updates the panel password reset request and password reset flow so that they fully

support the canAccessPanel gating checks.
This commit is contained in:
Robin Malburn
2025-05-09 11:07:31 +01:00
parent 9b409550e0
commit 07122f504d
5 changed files with 97 additions and 5 deletions
@@ -37,6 +37,8 @@ return [
'body' => 'Please try again in :seconds seconds.',
],
'sent' => 'If that email address is valid in our records, we will send you an email to reset your password.',
],
];
@@ -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;
@@ -68,6 +69,10 @@ class RequestPasswordReset extends SimplePage
throw new Exception("Model [{$userClass}] does not have a [notify()] method.");
}
if (!($user instanceof FilamentUser) || !$user->canAccessPanel(Filament::getCurrentPanel())) {
return;
}
$notification = app(ResetPasswordNotification::class, ['token' => $token]);
$notification->url = Filament::getResetPasswordUrl($token, $user);
@@ -81,7 +86,7 @@ class RequestPasswordReset extends SimplePage
return;
}
$this->getSentNotification($status)?->send();
$this->getSentNotification('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent')?->send();
$this->form->fill();
}
@@ -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,30 @@ 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))
@@ -3,9 +3,11 @@
use Filament\Facades\Filament;
use Filament\Notifications\Auth\ResetPassword;
use Filament\Pages\Auth\PasswordReset\RequestPasswordReset;
use Filament\Panel;
use Filament\Tests\Models\User;
use Filament\Tests\TestCase;
use Illuminate\Support\Facades\Notification;
use Filament\Notifications\Notification as FilamentNotification;
use function Filament\Tests\livewire;
@@ -39,11 +41,41 @@ it('can request password reset', function () {
'email' => $userToResetPassword->email,
])
->call('request')
->assertNotified();
->assertNotified(
FilamentNotification::make()
->success()
->title(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent'))
);
Notification::assertSentTo($userToResetPassword, ResetPassword::class);
});
it('can gate password resets based on panel access', function () {
Notification::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
$testPanel = Panel::make();
$testPanel->id('test');
Filament::setCurrentPanel($testPanel);
livewire(RequestPasswordReset::class)
->fillForm([
'email' => $userToResetPassword->email,
])
->call('request')
->assertNotified(
FilamentNotification::make()
->success()
->title(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.sent'))
);
Notification::assertNotSentTo($userToResetPassword, ResetPassword::class);
});
it('can throttle requests', function () {
Notification::fake();
@@ -8,6 +8,8 @@ use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Filament\Panel;
use Filament\Notifications\Notification;
use function Filament\Tests\livewire;
@@ -58,7 +60,11 @@ it('can reset password', function () {
->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 +75,40 @@ it('can reset password', function () {
]);
});
it('can gate reset password based on panel access', function () {
Event::fake();
$this->assertGuest();
$userToResetPassword = User::factory()->create();
$token = Password::createToken($userToResetPassword);
$testPanel = Panel::make();
$testPanel->id('test');
Filament::setCurrentPanel($testPanel);
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);