Files
coolify/app/Providers/EventServiceProvider.php
T
Aditya Tripathi 7bed78c81e feat(auth): add passkey sign-in
Enable Fortify passkeys (laravel/passkeys) with the @laravel/passkeys
client. Users can sign in with a passkey, confirm sensitive actions with
one, and add, rename, and delete passkeys on the Profile page.

- Resolve the relying party for each request from the instance URL, then
  APP_URL, then localhost. Passkey routes return 404 on other hosts.
- Add the is_passkey_enabled instance setting (Settings > Advanced).
- Use a hex user handle (laravel/passkeys-server#41).
- Rate limit the passkey routes by client IP.
- Record audit events for registered, verified, and deleted passkeys.
2026-10-13 04:01:40 +00:00

118 lines
5.0 KiB
PHP

<?php
namespace App\Providers;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Laravel\Fortify\Events\RecoveryCodesGenerated;
use Laravel\Fortify\Events\TwoFactorAuthenticationConfirmed;
use Laravel\Fortify\Events\TwoFactorAuthenticationDisabled;
use Laravel\Fortify\Events\TwoFactorAuthenticationEnabled;
use Laravel\Passkeys\Events\PasskeyDeleted;
use Laravel\Passkeys\Events\PasskeyRegistered;
use Laravel\Passkeys\Events\PasskeyVerified;
use SocialiteProviders\Authentik\AuthentikExtendSocialite;
use SocialiteProviders\Azure\AzureExtendSocialite;
use SocialiteProviders\Clerk\ClerkExtendSocialite;
use SocialiteProviders\Discord\DiscordExtendSocialite;
use SocialiteProviders\Google\GoogleExtendSocialite;
use SocialiteProviders\Infomaniak\InfomaniakExtendSocialite;
use SocialiteProviders\Manager\SocialiteWasCalled;
use SocialiteProviders\Zitadel\ZitadelExtendSocialite;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
SocialiteWasCalled::class => [
AzureExtendSocialite::class.'@handle',
AuthentikExtendSocialite::class.'@handle',
ClerkExtendSocialite::class.'@handle',
DiscordExtendSocialite::class.'@handle',
GoogleExtendSocialite::class.'@handle',
InfomaniakExtendSocialite::class.'@handle',
ZitadelExtendSocialite::class.'@handle',
],
];
public function boot(): void
{
Event::listen(Login::class, function (Login $event): void {
auditLog('auth.user.login_succeeded', $this->authContext($event->user));
});
Event::listen(Failed::class, function (Failed $event): void {
auditLog('auth.user.login_failed', [
'attempted_email' => $this->attemptedEmailForAudit(data_get($event->credentials, 'email')),
'guard' => $event->guard,
], 'warning');
});
Event::listen(Logout::class, function (Logout $event): void {
auditLog('auth.user.logged_out', $this->authContext($event->user));
});
Event::listen(Registered::class, function (Registered $event): void {
auditLog('auth.user.registered', $this->authContext($event->user));
});
Event::listen(Verified::class, function (Verified $event): void {
auditLog('auth.user.email_verified', $this->authContext($event->user));
});
Event::listen(PasswordReset::class, function (PasswordReset $event): void {
auditLog('auth.user.password_reset', $this->authContext($event->user));
});
Event::listen(TwoFactorAuthenticationEnabled::class, function (TwoFactorAuthenticationEnabled $event): void {
auditLog('auth.user.two_factor_enabled', $this->authContext($event->user));
});
Event::listen(TwoFactorAuthenticationConfirmed::class, function (TwoFactorAuthenticationConfirmed $event): void {
auditLog('auth.user.two_factor_confirmed', $this->authContext($event->user));
});
Event::listen(TwoFactorAuthenticationDisabled::class, function (TwoFactorAuthenticationDisabled $event): void {
auditLog('auth.user.two_factor_disabled', $this->authContext($event->user));
});
Event::listen(RecoveryCodesGenerated::class, function (RecoveryCodesGenerated $event): void {
auditLog('auth.user.recovery_codes_regenerated', $this->authContext($event->user));
});
Event::listen(PasskeyRegistered::class, function (PasskeyRegistered $event): void {
auditLog('auth.user.passkey_registered', $this->authContext($event->user));
});
Event::listen(PasskeyVerified::class, function (PasskeyVerified $event): void {
auditLog('auth.user.passkey_verified', $this->authContext($event->user));
});
Event::listen(PasskeyDeleted::class, function (PasskeyDeleted $event): void {
auditLog('auth.user.passkey_deleted', $this->authContext($event->user));
});
}
/**
* Users sometimes type a password into the email field, so only a valid email address is stored.
*/
private function attemptedEmailForAudit(mixed $email): ?string
{
if (blank($email)) {
return null;
}
return is_string($email) && filter_var($email, FILTER_VALIDATE_EMAIL) !== false ? $email : '[invalid]';
}
private function authContext(?object $user): array
{
return [
'team_id' => $user?->currentTeam()?->id,
'resource' => 'user',
'user_name' => $user?->name,
'actor_id' => $user?->id,
'actor_name' => $user?->name,
'actor_email' => $user?->email,
];
}
public function shouldDiscoverEvents(): bool
{
return true;
}
}