102 lines
3.4 KiB
PHP
102 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Settings;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Services\WebauthnService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Component;
|
|
|
|
class WebauthnKeys extends Component
|
|
{
|
|
#[Validate('required|string|max:60')]
|
|
public string $newName = '';
|
|
|
|
/** JSON creation options for navigator.credentials.create (the JS posts the result to register). */
|
|
public function options(WebauthnService $webauthn): array
|
|
{
|
|
abort_unless($webauthn->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(),
|
|
]);
|
|
}
|
|
}
|