71 lines
2.4 KiB
PHP
71 lines
2.4 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Modals;
|
|
|
|
use App\Models\AuditEvent;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Livewire\Attributes\Locked;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
|
|
/**
|
|
* Backup (recovery) codes, shown once in a modal. Opened on first-factor enrollment and
|
|
* from Settings → Security ("Backup-Codes verwalten"). The download endpoint stays a route.
|
|
*/
|
|
class RecoveryCodes extends ModalComponent
|
|
{
|
|
/**
|
|
* Codes are revealed ONLY when freshly generated in this flow — never re-viewable from
|
|
* "Backup-Codes verwalten". Set true by a one-time server flag on mount (first-factor
|
|
* enrollment) or by regenerate(). A client cannot tamper this into showing stored codes:
|
|
* render() only emits codes when $revealed, and $revealed is only set server-side.
|
|
* #[Locked] rejects any client-side update of this prop, so a crafted /livewire/update
|
|
* cannot flip it to re-view stored codes.
|
|
*/
|
|
#[Locked]
|
|
public bool $revealed = false;
|
|
|
|
public static function modalMaxWidth(): string
|
|
{
|
|
return 'lg';
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
// One-time, server-set flag (put on first-factor enrollment). pull() reads + forgets,
|
|
// so a refresh of the manage view will no longer reveal the codes.
|
|
if (session()->pull('2fa.codes_fresh', false)) {
|
|
$this->revealed = true;
|
|
}
|
|
}
|
|
|
|
/** Regenerate the current user's backup codes (invalidates the old set). */
|
|
public function regenerate(): void
|
|
{
|
|
Auth::user()->replaceRecoveryCodes();
|
|
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'actor' => Auth::user()->name ?? 'system',
|
|
'action' => 'two_factor.recovery_regenerate',
|
|
'target' => Auth::user()->email,
|
|
'ip' => request()->ip(),
|
|
]);
|
|
|
|
// The freshly generated set may be shown once in this same modal instance.
|
|
$this->revealed = true;
|
|
|
|
// One-time download grant for the freshly regenerated set (consumed by the
|
|
// download route). Without it a later direct hit of the route 403s.
|
|
session()->put('2fa.download_grant', true);
|
|
|
|
$this->dispatch('notify', message: __('auth.recovery_regenerated'));
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.modals.recovery-codes', [
|
|
'codes' => $this->revealed ? Auth::user()->recoveryCodes() : [],
|
|
]);
|
|
}
|
|
}
|