diff --git a/app/Livewire/Auth/TwoFactorChallenge.php b/app/Livewire/Auth/TwoFactorChallenge.php index 3ab0331..3e946e0 100644 --- a/app/Livewire/Auth/TwoFactorChallenge.php +++ b/app/Livewire/Auth/TwoFactorChallenge.php @@ -25,6 +25,12 @@ class TwoFactorChallenge extends Component } } + /** Whether the PENDING user has TOTP — the authenticator code field renders only then. */ + public function getPendingHasTotpProperty(): bool + { + return (bool) User::find(session('2fa.user'))?->hasTotp(); + } + public function verify() { $this->validate(); @@ -44,8 +50,9 @@ class TwoFactorChallenge extends Component throw ValidationException::withMessages(['code' => __('auth.session_expired')]); } - // Accept the TOTP code, or fall back to a one-time backup (recovery) code. - $valid = (new Google2FA)->verifyKey($user->two_factor_secret, preg_replace('/\s+/', '', $this->code)) + // Accept the TOTP code only when the pending user actually has TOTP (otherwise a null + // secret would break verifyKey); always allow a one-time backup (recovery) code. + $valid = ($user->hasTotp() && (new Google2FA)->verifyKey($user->two_factor_secret, preg_replace('/\s+/', '', $this->code))) || $user->useRecoveryCode($this->code); if (! $valid) { diff --git a/lang/de/auth.php b/lang/de/auth.php index da4ae13..7b9a66b 100644 --- a/lang/de/auth.php +++ b/lang/de/auth.php @@ -74,6 +74,10 @@ return [ // ── 2FA backup (recovery) codes ────────────────────────────────────── 'challenge_recovery_hint' => 'Authenticator verloren? Gib einen deiner Backup-Codes ein.', + 'challenge_key_subtitle' => 'Bestätige mit deinem Security-Key oder einem Backup-Code.', + 'challenge_backup_label' => 'Backup-Code', + 'challenge_backup_placeholder' => 'xxxxxxxxxx-xxxxxxxxxx', + 'challenge_backup_only_hint' => 'Kein Authenticator nötig — Security-Key oben oder ein Backup-Code.', 'recovery_heading' => 'Backup-Codes', 'recovery_subtitle' => 'Mit diesen Einmal-Codes meldest du dich an, wenn du keinen Authenticator hast.', 'recovery_warning' => 'Speichere diese Codes jetzt sicher ab — jeder Code funktioniert genau einmal. Lade sie herunter, solange sie angezeigt werden.', diff --git a/lang/en/auth.php b/lang/en/auth.php index 3567f8a..9d040a8 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -74,6 +74,10 @@ return [ // ── 2FA backup (recovery) codes ────────────────────────────────────── 'challenge_recovery_hint' => 'Lost your authenticator? Enter one of your backup codes.', + 'challenge_key_subtitle' => 'Confirm with your security key or a backup code.', + 'challenge_backup_label' => 'Backup code', + 'challenge_backup_placeholder' => 'xxxxxxxxxx-xxxxxxxxxx', + 'challenge_backup_only_hint' => 'No authenticator needed — use the security key above or a backup code.', 'recovery_heading' => 'Backup codes', 'recovery_subtitle' => 'Use these one-time codes to sign in when you do not have your authenticator.', 'recovery_warning' => 'Save these codes somewhere safe now — each works exactly once. Download them while they are shown.', diff --git a/resources/views/livewire/auth/two-factor-challenge.blade.php b/resources/views/livewire/auth/two-factor-challenge.blade.php index b64e731..c474f60 100644 --- a/resources/views/livewire/auth/two-factor-challenge.blade.php +++ b/resources/views/livewire/auth/two-factor-challenge.blade.php @@ -1,5 +1,6 @@ @php $field = 'h-14 w-full rounded-md border border-line bg-inset text-center font-mono text-2xl font-semibold tracking-[0.5em] text-ink caret-accent placeholder:text-ink-4 focus:border-accent/40 focus:outline-none'; + $fieldText = 'h-12 w-full rounded-md border border-line bg-inset px-3 text-center font-mono text-sm text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none'; $label = 'mb-1.5 block font-mono text-[11px] uppercase tracking-wider text-ink-3'; $err = 'mt-1.5 flex items-center gap-1.5 font-mono text-[11px] text-offline'; @endphp @@ -8,16 +9,29 @@
{{ __('auth.two_factor') }}
{{ __('auth.challenge_subtitle') }}
+{{ $this->pendingHasTotp ? __('auth.challenge_subtitle') : __('auth.challenge_key_subtitle') }}