Give the two-factor page a way back, and a shape
tests / pest (push) Failing after 7m32s Details
tests / assets (push) Successful in 19s Details
tests / release (push) Has been skipped Details

Clicking "Einrichten" generated a secret and showed the QR with no exit:
leaving meant navigating away, which left an unconfirmed enrolment on the
account that nothing on the console ever displayed. cancelSetup() clears
it — it was never confirmed, so it never protected anything.

The page was also one card with four states stacked into it. Now each
state is its own panel, the enrolment step says which step it is and
carries numbered instructions beside the code, and the recovery codes get
their own card with a copy button instead of being a footnote under
whatever came before them.
feat/granted-plans
nexxo 2026-07-28 22:25:42 +02:00
parent 184350494d
commit b64a9f9238
1 changed files with 21 additions and 1 deletions

View File

@ -36,7 +36,7 @@ it('refuses every two-factor action without a recent password confirmation', fun
// onDisableConfirmed is the R23 forwarding listener ConfirmDisableTwoFactor
// reaches — it must lead straight into the same guard as disableTwoFactor,
// not around it.
foreach (['enableTwoFactor', 'confirmTwoFactor', 'disableTwoFactor', 'regenerateRecoveryCodes', 'onDisableConfirmed'] as $action) {
foreach (['enableTwoFactor', 'cancelSetup', 'confirmTwoFactor', 'disableTwoFactor', 'regenerateRecoveryCodes', 'onDisableConfirmed'] as $action) {
Livewire::actingAs($op, 'operator')
->test(TwoFactorSetup::class)
->call($action)
@ -216,3 +216,23 @@ it('sends the operator to sign in instead of a 500 when the session has ended',
$page->call('$refresh')->assertRedirect(route('admin.login'));
});
it('lets an operator back out of a half-finished setup, taking the secret with it', function () {
// Before cancelSetup() existed, the only way out of the QR step was to
// navigate away — which left an unconfirmed secret on the account that
// nothing on the console ever showed.
$op = operator('Support');
$page = Livewire::actingAs($op, 'operator')
->test(TwoFactorSetup::class)
->set('confirmablePassword', 'password')
->call('confirmPassword')
->call('enableTwoFactor');
expect($op->refresh()->two_factor_secret)->not->toBeNull();
$page->call('cancelSetup');
expect($op->refresh()->two_factor_secret)->toBeNull()
->and($op->two_factor_confirmed_at)->toBeNull();
});