Skip to content

Commit 93ba434

Browse files
author
David Courtey
authored
fix: handle DecryptException during 2FA and fix setup key copy button (#219) (#223)
- Catch DecryptException on two-factor-challenge route and redirect to login with a helpful error message instead of showing a 500 error - Replace custom navigator.clipboard copy logic with Mary UI x-clipboard directive for the manual setup key (fixes copy on non-HTTPS contexts)
1 parent 78a8c9b commit 93ba434

3 files changed

Lines changed: 55 additions & 38 deletions

File tree

bootstrap/app.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,11 @@
5555
]);
5656
})
5757
->withExceptions(function (Exceptions $exceptions) {
58-
//
58+
$exceptions->renderable(function (\Illuminate\Contracts\Encryption\DecryptException $e, $request) {
59+
if ($request->is('two-factor-challenge')) {
60+
return redirect()->route('login')->withErrors([
61+
'email' => __('Your session or encryption key has changed. Please log in again. If this persists, your APP_KEY may have changed since two-factor authentication was set up.'),
62+
]);
63+
}
64+
});
5965
})->create();

resources/views/livewire/settings/two-factor.blade.php

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -123,43 +123,25 @@ class="btn-primary w-full"
123123
<div class="space-y-4">
124124
<div class="divider text-sm">{{ __('or, enter the code manually') }}</div>
125125

126-
<div
127-
class="flex items-center space-x-2"
128-
x-data="{
129-
copied: false,
130-
async copy() {
131-
try {
132-
await navigator.clipboard.writeText('{{ $manualSetupKey }}');
133-
this.copied = true;
134-
setTimeout(() => this.copied = false, 1500);
135-
} catch (e) {
136-
console.warn('Could not copy to clipboard');
137-
}
138-
}
139-
}"
140-
>
141-
<div class="flex items-stretch w-full border rounded-xl border-base-300">
142-
@empty($manualSetupKey)
143-
<div class="flex items-center justify-center w-full p-3 bg-base-200">
144-
<span class="loading loading-spinner loading-sm"></span>
145-
</div>
146-
@else
147-
<input
148-
type="text"
149-
readonly
150-
value="{{ $manualSetupKey }}"
151-
class="input input-bordered w-full border-0"
152-
/>
153-
154-
<button
155-
@click="copy()"
156-
class="btn btn-ghost px-3"
157-
>
158-
<x-icon x-show="!copied" name="o-document-duplicate" class="w-5 h-5" />
159-
<x-icon x-show="copied" name="o-check" class="w-5 h-5 text-success" />
160-
</button>
161-
@endempty
162-
</div>
126+
<div class="flex items-stretch w-full border rounded-xl border-base-300">
127+
@empty($manualSetupKey)
128+
<div class="flex items-center justify-center w-full p-3 bg-base-200">
129+
<span class="loading loading-spinner loading-sm"></span>
130+
</div>
131+
@else
132+
<input
133+
type="text"
134+
readonly
135+
value="{{ $manualSetupKey }}"
136+
class="input input-bordered w-full border-0"
137+
/>
138+
139+
<x-button
140+
icon="o-clipboard-document"
141+
class="btn-ghost px-3"
142+
x-clipboard="$wire.manualSetupKey"
143+
/>
144+
@endempty
163145
</div>
164146
</div>
165147
@endif

tests/Feature/Auth/AuthenticationTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,35 @@
9292
$this->assertGuest();
9393
});
9494

95+
test('two factor challenge with invalid encryption key redirects to login', function () {
96+
if (! Features::canManageTwoFactorAuthentication()) {
97+
$this->markTestSkipped('Two-factor authentication is not enabled.');
98+
}
99+
100+
Features::twoFactorAuthentication([
101+
'confirm' => true,
102+
'confirmPassword' => true,
103+
]);
104+
105+
$user = User::factory()->create();
106+
107+
// Login to trigger 2FA challenge
108+
$this->post(route('login.store'), [
109+
'email' => $user->email,
110+
'password' => 'password',
111+
]);
112+
113+
// Corrupt the two_factor_secret to simulate an APP_KEY change
114+
$user->update(['two_factor_secret' => 'corrupted-encrypted-value']);
115+
116+
$response = $this->post(route('two-factor.login.store'), [
117+
'code' => '123456',
118+
]);
119+
120+
$response->assertRedirect(route('login'));
121+
$response->assertSessionHasErrors('email');
122+
});
123+
95124
test('login screen renders oauth provider button when enabled', function (string $provider, string $label) {
96125
User::factory()->create();
97126

0 commit comments

Comments
 (0)