diff --git a/packages/panels/src/Auth/Pages/Login.php b/packages/panels/src/Auth/Pages/Login.php index 36d0ee52a0..1a24e1f9e4 100644 --- a/packages/panels/src/Auth/Pages/Login.php +++ b/packages/panels/src/Auth/Pages/Login.php @@ -27,14 +27,17 @@ use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Schema; use Filament\Support\Enums\Alignment; use Filament\View\PanelsRenderHook; +use Illuminate\Auth\Events\Failed; use Illuminate\Auth\SessionGuard; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Blade; use Illuminate\Support\HtmlString; use Illuminate\Validation\ValidationException; use Livewire\Attributes\Locked; +use SensitiveParameter; /** * @property-read Action $registerAction @@ -74,7 +77,10 @@ class Login extends SimplePage $data = $this->form->getState(); - $authProvider = Filament::auth()->getProvider(); /** @phpstan-ignore-line */ + /** @var SessionGuard $authGuard */ + $authGuard = Filament::auth(); + + $authProvider = $authGuard->getProvider(); /** @phpstan-ignore-line */ $credentials = $this->getCredentialsFromFormData($data); $user = $authProvider->retrieveByCredentials($credentials); @@ -82,6 +88,7 @@ class Login extends SimplePage if ((! $user) || (! $authProvider->validateCredentials($user, $credentials))) { $this->userUndertakingMultiFactorAuthentication = null; + $this->fireFailedEvent($authGuard, $user, $credentials); $this->throwFailureValidationException(); } @@ -112,9 +119,6 @@ class Login extends SimplePage } } - /** @var SessionGuard $authGuard */ - $authGuard = Filament::auth(); - if (! $authGuard->attemptWhen($credentials, function (Authenticatable $user): bool { if (! ($user instanceof FilamentUser)) { return true; @@ -122,6 +126,7 @@ class Login extends SimplePage return $user->canAccessPanel(Filament::getCurrentOrDefaultPanel()); }, $data['remember'] ?? false)) { + $this->fireFailedEvent($authGuard, $user, $credentials); $this->throwFailureValidationException(); } @@ -144,6 +149,14 @@ class Login extends SimplePage ->danger(); } + /** + * @param array $credentials + */ + protected function fireFailedEvent(Guard $guard, ?Authenticatable $user, #[SensitiveParameter] array $credentials): void + { + event(app(Failed::class, ['guard' => property_exists($guard, 'name') ? $guard->name : '', 'user' => $user, 'credentials' => $credentials])); + } + protected function throwFailureValidationException(): never { throw ValidationException::withMessages([ @@ -352,7 +365,7 @@ class Login extends SimplePage * @param array $data * @return array */ - protected function getCredentialsFromFormData(array $data): array + protected function getCredentialsFromFormData(#[SensitiveParameter] array $data): array { return [ 'email' => $data['email'], diff --git a/tests/src/Panels/Auth/LoginTest.php b/tests/src/Panels/Auth/LoginTest.php index 1ec460a32c..e086632220 100644 --- a/tests/src/Panels/Auth/LoginTest.php +++ b/tests/src/Panels/Auth/LoginTest.php @@ -4,6 +4,8 @@ use Filament\Auth\Pages\Login; use Filament\Facades\Filament; use Filament\Tests\Fixtures\Models\User; use Filament\Tests\TestCase; +use Illuminate\Auth\Events\Failed; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Str; use function Filament\Tests\livewire; @@ -61,6 +63,8 @@ it('can redirect unauthenticated app requests', function (): void { }); it('cannot authenticate with incorrect credentials', function (): void { + Event::fake([Failed::class]); + $userToAuthenticate = User::factory()->create(); livewire(Login::class) @@ -72,9 +76,30 @@ it('cannot authenticate with incorrect credentials', function (): void { ->assertHasFormErrors(['email']); $this->assertGuest(); + + Event::assertDispatched(function (Failed $event) use ($userToAuthenticate) { + if ($event->guard !== 'web') { + return false; + } + + if (! $event->user->is($userToAuthenticate)) { + return false; + } + + if ($event->credentials !== [ + 'email' => $userToAuthenticate->email, + 'password' => 'incorrect-password', + ]) { + return false; + } + + return true; + }); }); it('cannot authenticate on unauthorized panel', function (): void { + Event::fake([Failed::class]); + $userToAuthenticate = User::factory()->create(); Filament::setCurrentPanel('custom'); @@ -88,6 +113,25 @@ it('cannot authenticate on unauthorized panel', function (): void { ->assertHasFormErrors(['email']); $this->assertGuest(); + + Event::assertDispatched(function (Failed $event) use ($userToAuthenticate) { + if ($event->guard !== 'web') { + return false; + } + + if (! $event->user->is($userToAuthenticate)) { + return false; + } + + if ($event->credentials !== [ + 'email' => $userToAuthenticate->email, + 'password' => 'password', + ]) { + return false; + } + + return true; + }); }); it('can throttle authentication attempts', function (): void {