From a8071c582d9cb786b0344d271fa7c0c9e2b83788 Mon Sep 17 00:00:00 2001 From: David Palomares Date: Fri, 24 Sep 2021 11:04:35 +0200 Subject: [PATCH 01/10] Move User::sendPasswordResetNotification to IsFilamentUserTrait That way we have that function available when not using Filament's user Model --- src/Models/Concerns/IsFilamentUser.php | 20 ++++++++++++++++++++ src/Models/User.php | 19 ------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Models/Concerns/IsFilamentUser.php b/src/Models/Concerns/IsFilamentUser.php index a3a9403607..bca9625242 100644 --- a/src/Models/Concerns/IsFilamentUser.php +++ b/src/Models/Concerns/IsFilamentUser.php @@ -2,6 +2,9 @@ namespace Filament\Models\Concerns; +use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; +use Illuminate\Support\Facades\URL; + trait IsFilamentUser { public function canAccessFilament() @@ -75,4 +78,21 @@ trait IsFilamentUser $this->{$column} : false; } + + public function sendPasswordResetNotification($token) + { + $notification = new ResetPasswordNotification($token); + $notification->createUrlUsing(function ($notifiable, $token) { + return URL::signedRoute( + 'filament.auth.password.reset', + [ + 'email' => $notifiable->getEmailForPasswordReset(), + 'token' => $token, + ], + now()->addMinutes(config('auth.passwords.filament_users.expire')), + ); + }); + + $this->notify($notification); + } } diff --git a/src/Models/User.php b/src/Models/User.php index 1e3b67d966..30a50c00bc 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -5,11 +5,9 @@ namespace Filament\Models; use Filament\Database\Factories\UserFactory; use Filament\Models\Concerns\IsFilamentUser; use Filament\Models\Contracts\FilamentUser; -use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Illuminate\Support\Facades\URL; class User extends Authenticatable implements FilamentUser { @@ -37,23 +35,6 @@ class User extends Authenticatable implements FilamentUser protected $table = 'filament_users'; - public function sendPasswordResetNotification($token) - { - $notification = new ResetPasswordNotification($token); - $notification->createUrlUsing(function ($notifiable, $token) { - return URL::signedRoute( - 'filament.auth.password.reset', - [ - 'email' => $notifiable->getEmailForPasswordReset(), - 'token' => $token, - ], - now()->addMinutes(config('auth.passwords.filament_users.expire')), - ); - }); - - $this->notify($notification); - } - protected static function newFactory() { return UserFactory::new(); From 14a807fa8c52029363d12e801898f689bf7c94f9 Mon Sep 17 00:00:00 2001 From: David Palomares Date: Fri, 24 Sep 2021 11:36:43 +0200 Subject: [PATCH 02/10] Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default This solves #601 --- src/Http/Livewire/Auth/RequestPassword.php | 5 ++++- src/Http/Livewire/Auth/ResetPassword.php | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Http/Livewire/Auth/RequestPassword.php b/src/Http/Livewire/Auth/RequestPassword.php index 85f42c575f..f36df52e72 100644 --- a/src/Http/Livewire/Auth/RequestPassword.php +++ b/src/Http/Livewire/Auth/RequestPassword.php @@ -36,7 +36,10 @@ class RequestPassword extends Component return; } - $requestStatus = Password::broker('filament_users')->sendResetLink($this->validate()); + // Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default + $broker = config('filament.auth.guard') === 'filament' ? 'filament_users' : null; + + $requestStatus = Password::broker($broker)->sendResetLink($this->validate()); if (Password::RESET_LINK_SENT !== $requestStatus) { $this->addError('email', __("filament::auth/request-password.messages.{$requestStatus}")); diff --git a/src/Http/Livewire/Auth/ResetPassword.php b/src/Http/Livewire/Auth/ResetPassword.php index 6fcb585969..a7848ed504 100644 --- a/src/Http/Livewire/Auth/ResetPassword.php +++ b/src/Http/Livewire/Auth/ResetPassword.php @@ -43,7 +43,10 @@ class ResetPassword extends Component { $this->validate(); - $resetStatus = Password::broker('filament_users') + // Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default + $broker = config('filament.auth.guard') === 'filament' ? 'filament_users' : null; + + $resetStatus = Password::broker($broker) ->reset( $this->only(['email', 'password', 'token']), function ($user, $password) { From a4dff9c1596177620542e0267724b77d856cb1ff Mon Sep 17 00:00:00 2001 From: David Palomares Date: Fri, 24 Sep 2021 13:31:52 +0200 Subject: [PATCH 03/10] Added tests --- tests/Feature/Auth/RequestPasswordTest.php | 24 +++++++++++++++ tests/Feature/Auth/ResetPasswordTest.php | 36 +++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/tests/Feature/Auth/RequestPasswordTest.php b/tests/Feature/Auth/RequestPasswordTest.php index 76fa7ba329..1d4d6996a8 100644 --- a/tests/Feature/Auth/RequestPasswordTest.php +++ b/tests/Feature/Auth/RequestPasswordTest.php @@ -6,6 +6,7 @@ use Filament\Http\Livewire\Auth\RequestPassword; use Filament\Models\User; use Filament\Tests\TestCase; use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Notification; use Livewire\Livewire; @@ -27,6 +28,29 @@ class RequestPasswordTest extends TestCase Notification::assertSentTo($user, ResetPasswordNotification::class); } + /** @test */ + public function can_request_password_reset_with_web_guard() + { + Notification::fake(); + + // Configure filament to use default Laravel's auth guard + Config::set('filament.auth.guard', 'web'); + + // Set Laravel's default user model to filament's one, so it has filament's attributes + // and uses 'IsFilamentUser' trait (needed to send the notification) + Config::set('auth.providers.users.model', User::class); + + $user = User::factory()->create(); + + Livewire::test(RequestPassword::class) + ->set('email', $user->email) + ->call('submit') + ->assertHasNoErrors() + ->assertDispatchedBrowserEvent('notify'); + + Notification::assertSentTo($user, ResetPasswordNotification::class); + } + /** @test */ public function can_view_password_reset_request_page() { diff --git a/tests/Feature/Auth/ResetPasswordTest.php b/tests/Feature/Auth/ResetPasswordTest.php index 3270517489..aed9b76a46 100644 --- a/tests/Feature/Auth/ResetPasswordTest.php +++ b/tests/Feature/Auth/ResetPasswordTest.php @@ -6,6 +6,7 @@ use Filament\Filament; use Filament\Http\Livewire\Auth\ResetPassword; use Filament\Models\User; use Filament\Tests\TestCase; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\URL; use Illuminate\Support\Str; @@ -37,6 +38,36 @@ class ResetPasswordTest extends TestCase ])); } + /** @test */ + public function can_reset_password_with_web_guard() + { + // Configure filament to use default Laravel's auth guard + Config::set('filament.auth.guard', 'web'); + + // Set Laravel's default user model to filament's one, so it has filament's attributes + Config::set('auth.providers.users.model', User::class); + + $user = User::factory()->create(); + $newPassword = Str::random(); + + Livewire::test(ResetPassword::class, [ + 'token' => $this->generateToken($user) + ]) + ->set('email', $user->email) + ->set('password', $newPassword) + ->set('passwordConfirmation', $newPassword) + ->call('submit') + ->assertHasNoErrors() + ->assertRedirect(route('filament.dashboard')); + + $this->assertAuthenticatedAs($user, config('filament.auth.guard')); + + $this->assertTrue(Filament::auth()->attempt([ + 'email' => $user->email, + 'password' => $newPassword, + ])); + } + /** @test */ public function can_view_password_reset_page() { @@ -137,6 +168,9 @@ class ResetPasswordTest extends TestCase $user = User::factory()->create(); } - return Password::broker('filament_users')->createToken($user); + // Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default + $broker = config('filament.auth.guard') === 'filament' ? 'filament_users' : null; + + return Password::broker($broker)->createToken($user); } } From e7d7950653a7cccbd5d66b1bbdcc53ae8e459a8c Mon Sep 17 00:00:00 2001 From: David Palomares Date: Fri, 24 Sep 2021 13:36:18 +0200 Subject: [PATCH 04/10] Fix incorrect file being used for reset password errors --- src/Http/Livewire/Auth/ResetPassword.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Livewire/Auth/ResetPassword.php b/src/Http/Livewire/Auth/ResetPassword.php index a7848ed504..74b631a26f 100644 --- a/src/Http/Livewire/Auth/ResetPassword.php +++ b/src/Http/Livewire/Auth/ResetPassword.php @@ -58,7 +58,7 @@ class ResetPassword extends Component ); if (Password::PASSWORD_RESET !== $resetStatus) { - $this->addError('email', __("filament::auth/request-password.messages.{$resetStatus}")); + $this->addError('email', __("filament::auth/reset-password.messages.{$resetStatus}")); return; } From 258f5ab903449ea303be9fc1f5d3c4322b80abbc Mon Sep 17 00:00:00 2001 From: David Palomares Date: Sat, 25 Sep 2021 12:42:48 +0200 Subject: [PATCH 05/10] Moved sendPasswordResetNotification to own trait --- src/Models/Concerns/IsFilamentUser.php | 20 -------------- .../SendsPasswordResetNotification.php | 26 +++++++++++++++++++ src/Models/User.php | 2 ++ 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 src/Models/Concerns/SendsPasswordResetNotification.php diff --git a/src/Models/Concerns/IsFilamentUser.php b/src/Models/Concerns/IsFilamentUser.php index bca9625242..a3a9403607 100644 --- a/src/Models/Concerns/IsFilamentUser.php +++ b/src/Models/Concerns/IsFilamentUser.php @@ -2,9 +2,6 @@ namespace Filament\Models\Concerns; -use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification; -use Illuminate\Support\Facades\URL; - trait IsFilamentUser { public function canAccessFilament() @@ -78,21 +75,4 @@ trait IsFilamentUser $this->{$column} : false; } - - public function sendPasswordResetNotification($token) - { - $notification = new ResetPasswordNotification($token); - $notification->createUrlUsing(function ($notifiable, $token) { - return URL::signedRoute( - 'filament.auth.password.reset', - [ - 'email' => $notifiable->getEmailForPasswordReset(), - 'token' => $token, - ], - now()->addMinutes(config('auth.passwords.filament_users.expire')), - ); - }); - - $this->notify($notification); - } } diff --git a/src/Models/Concerns/SendsPasswordResetNotification.php b/src/Models/Concerns/SendsPasswordResetNotification.php new file mode 100644 index 0000000000..4fa7abfa1c --- /dev/null +++ b/src/Models/Concerns/SendsPasswordResetNotification.php @@ -0,0 +1,26 @@ +createUrlUsing(function ($notifiable, $token) { + return URL::signedRoute( + 'filament.auth.password.reset', + [ + 'email' => $notifiable->getEmailForPasswordReset(), + 'token' => $token, + ], + now()->addMinutes(config('auth.passwords.filament_users.expire')), + ); + }); + + $this->notify($notification); + } +} diff --git a/src/Models/User.php b/src/Models/User.php index 30a50c00bc..d8e6f91a1b 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -4,6 +4,7 @@ namespace Filament\Models; use Filament\Database\Factories\UserFactory; use Filament\Models\Concerns\IsFilamentUser; +use Filament\Models\Concerns\SendsPasswordResetNotification; use Filament\Models\Contracts\FilamentUser; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -14,6 +15,7 @@ class User extends Authenticatable implements FilamentUser use HasFactory; use IsFilamentUser; use Notifiable; + use SendsPasswordResetNotification; public static $filamentAdminColumn = 'is_admin'; From fc755be20f79aedba0ef7e59679560d8867fa47d Mon Sep 17 00:00:00 2001 From: David Palomares Date: Sat, 25 Sep 2021 12:49:45 +0200 Subject: [PATCH 06/10] Update documentation --- docs/01-getting-started.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index e96ba26151..a110fb98f6 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -96,6 +96,8 @@ public function isFilamentAdmin() } ``` +Finally, for the reset password procedure to work correctly your user should implement a `sendPasswordResetNotification($token)` function. You can use the trait `Filament\Models\Concerns\SendsPasswordResetNotification` to add this functionality to your User model, fully integrated with Filament login and routes. + ### Disabling the Default Migrations You may wish to prevent the migration for the default users table from being registered. You may do this by calling: From 8b284a7b67bfcfbf144f6d0d6a17f63ee712696f Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Sun, 26 Sep 2021 11:16:08 +0100 Subject: [PATCH 07/10] Simplify test to use existing procedure --- tests/Feature/Auth/RequestPasswordTest.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/Feature/Auth/RequestPasswordTest.php b/tests/Feature/Auth/RequestPasswordTest.php index 1d4d6996a8..3ceba82160 100644 --- a/tests/Feature/Auth/RequestPasswordTest.php +++ b/tests/Feature/Auth/RequestPasswordTest.php @@ -31,24 +31,10 @@ class RequestPasswordTest extends TestCase /** @test */ public function can_request_password_reset_with_web_guard() { - Notification::fake(); - - // Configure filament to use default Laravel's auth guard Config::set('filament.auth.guard', 'web'); - - // Set Laravel's default user model to filament's one, so it has filament's attributes - // and uses 'IsFilamentUser' trait (needed to send the notification) Config::set('auth.providers.users.model', User::class); - $user = User::factory()->create(); - - Livewire::test(RequestPassword::class) - ->set('email', $user->email) - ->call('submit') - ->assertHasNoErrors() - ->assertDispatchedBrowserEvent('notify'); - - Notification::assertSentTo($user, ResetPasswordNotification::class); + $this->can_request_password_reset(); } /** @test */ From ef80ea67b76b7ac071ea0002f6fd1041e84d496c Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Sun, 26 Sep 2021 11:18:10 +0100 Subject: [PATCH 08/10] Simplify other test --- tests/Feature/Auth/RequestPasswordTest.php | 2 +- tests/Feature/Auth/ResetPasswordTest.php | 27 +++------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/tests/Feature/Auth/RequestPasswordTest.php b/tests/Feature/Auth/RequestPasswordTest.php index 3ceba82160..46cf11d426 100644 --- a/tests/Feature/Auth/RequestPasswordTest.php +++ b/tests/Feature/Auth/RequestPasswordTest.php @@ -29,7 +29,7 @@ class RequestPasswordTest extends TestCase } /** @test */ - public function can_request_password_reset_with_web_guard() + public function can_request_password_reset_with_custom_user_model() { Config::set('filament.auth.guard', 'web'); Config::set('auth.providers.users.model', User::class); diff --git a/tests/Feature/Auth/ResetPasswordTest.php b/tests/Feature/Auth/ResetPasswordTest.php index aed9b76a46..d07af07e8d 100644 --- a/tests/Feature/Auth/ResetPasswordTest.php +++ b/tests/Feature/Auth/ResetPasswordTest.php @@ -30,7 +30,7 @@ class ResetPasswordTest extends TestCase ->assertHasNoErrors() ->assertRedirect(route('filament.dashboard')); - $this->assertAuthenticatedAs($user); + $this->assertAuthenticatedAs($user, config('filament.auth.guard')); $this->assertTrue(Filament::auth()->attempt([ 'email' => $user->email, @@ -39,33 +39,12 @@ class ResetPasswordTest extends TestCase } /** @test */ - public function can_reset_password_with_web_guard() + public function can_reset_password_with_custom_user_model() { - // Configure filament to use default Laravel's auth guard Config::set('filament.auth.guard', 'web'); - - // Set Laravel's default user model to filament's one, so it has filament's attributes Config::set('auth.providers.users.model', User::class); - $user = User::factory()->create(); - $newPassword = Str::random(); - - Livewire::test(ResetPassword::class, [ - 'token' => $this->generateToken($user) - ]) - ->set('email', $user->email) - ->set('password', $newPassword) - ->set('passwordConfirmation', $newPassword) - ->call('submit') - ->assertHasNoErrors() - ->assertRedirect(route('filament.dashboard')); - - $this->assertAuthenticatedAs($user, config('filament.auth.guard')); - - $this->assertTrue(Filament::auth()->attempt([ - 'email' => $user->email, - 'password' => $newPassword, - ])); + $this->can_reset_password(); } /** @test */ From 5d981fc9407630b5811313880937cbc7780cfd1e Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Sun, 26 Sep 2021 11:19:51 +0100 Subject: [PATCH 09/10] Simplify comments --- src/Http/Livewire/Auth/RequestPassword.php | 2 +- src/Http/Livewire/Auth/ResetPassword.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Livewire/Auth/RequestPassword.php b/src/Http/Livewire/Auth/RequestPassword.php index f36df52e72..d159f0e058 100644 --- a/src/Http/Livewire/Auth/RequestPassword.php +++ b/src/Http/Livewire/Auth/RequestPassword.php @@ -36,7 +36,7 @@ class RequestPassword extends Component return; } - // Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default + // Use the `filament_users` broker only with the `filament` guard. $broker = config('filament.auth.guard') === 'filament' ? 'filament_users' : null; $requestStatus = Password::broker($broker)->sendResetLink($this->validate()); diff --git a/src/Http/Livewire/Auth/ResetPassword.php b/src/Http/Livewire/Auth/ResetPassword.php index 74b631a26f..426519d4bf 100644 --- a/src/Http/Livewire/Auth/ResetPassword.php +++ b/src/Http/Livewire/Auth/ResetPassword.php @@ -43,7 +43,7 @@ class ResetPassword extends Component { $this->validate(); - // Use filament_users broker only when we're using filament guard. Otherwise, use Laravel's default + // Use the `filament_users` broker only with the `filament` guard. $broker = config('filament.auth.guard') === 'filament' ? 'filament_users' : null; $resetStatus = Password::broker($broker) From b6f9db91a8776430ce090db0d490ab95587b56b8 Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Sun, 26 Sep 2021 11:23:01 +0100 Subject: [PATCH 10/10] Rename trait and improve docs --- docs/01-getting-started.md | 21 ++++++++++++++++++- ...endsFilamentPasswordResetNotification.php} | 2 +- src/Models/User.php | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) rename src/Models/Concerns/{SendsPasswordResetNotification.php => SendsFilamentPasswordResetNotification.php} (94%) diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index a110fb98f6..5d82de72ef 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -96,7 +96,26 @@ public function isFilamentAdmin() } ``` -Finally, for the reset password procedure to work correctly your user should implement a `sendPasswordResetNotification($token)` function. You can use the trait `Filament\Models\Concerns\SendsPasswordResetNotification` to add this functionality to your User model, fully integrated with Filament login and routes. +Finally, for the correct reset password URL to be sent, you should implement a `sendPasswordResetNotification($token)` function. You can use the `Filament\Models\Concerns\SendsFilamentPasswordResetNotification` trait to add this functionality to your User model, fully integrated with Filament: + +```php +