available() && Auth::user()->hasTwoFactorEnabled(), 404); // Validate the label BEFORE the browser ceremony, so an invalid name never // leaves an orphaned credential on the authenticator. $this->validate(); return $webauthn->registrationOptions(Auth::user()); } public function register(array $attestation, WebauthnService $webauthn): void { // Re-check 2FA enrollment (it may have been disabled in another tab since the // ceremony started) so a key is never created against a reset second factor. abort_unless($webauthn->available() && Auth::user()->hasTwoFactorEnabled(), 404); $this->validate(); $cred = $webauthn->verifyRegistration(Auth::user(), $attestation, trim($this->newName)); $this->reset('newName'); AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()->name ?? 'system', 'action' => 'webauthn.register', 'target' => $cred->name, 'ip' => request()->ip(), ]); $this->dispatch('notify', message: __('auth.webauthn_added')); } /** Destructive (R5): open the confirm modal, which re-dispatches `webauthnKeyRemoved`. */ public function confirmRemove(int $id): void { $cred = Auth::user()->webauthnCredentials()->whereKey($id)->first(); if (! $cred) { return; } $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => __('auth.webauthn_title'), 'body' => __('auth.webauthn_remove_confirm'), 'confirmLabel' => __('common.remove'), 'danger' => true, 'icon' => 'trash', 'event' => 'webauthnKeyRemoved', 'params' => ['id' => $id], 'notify' => '', ], ); } #[On('webauthnKeyRemoved')] public function remove(int $id, WebauthnService $webauthn): void { abort_unless($webauthn->available(), 404); $cred = Auth::user()->webauthnCredentials()->whereKey($id)->first(); if ($cred) { $name = $cred->name; $cred->delete(); AuditEvent::create([ 'user_id' => Auth::id(), 'actor' => Auth::user()->name ?? 'system', 'action' => 'webauthn.remove', 'target' => $name, 'ip' => request()->ip(), ]); $this->dispatch('notify', message: __('auth.webauthn_removed')); } } public function render() { return view('livewire.settings.webauthn-keys', [ 'available' => app(WebauthnService::class)->available() && Auth::user()->hasTwoFactorEnabled(), 'keys' => Auth::user()->webauthnCredentials()->latest()->get(), ]); } }