feat(2fa): preserve QR + manual key when verification code is wrong #99

Merged
YousifShkara merged 1 commit from feat/bunyip-upgrade-04-2fa-preserve into main 2026-06-10 08:04:53 +02:00
Owner

The 2FA setup POST handler replaced the entire enrollment view with just
"Invalid verification code · Try again" when the user typed the wrong code. The QR image, the manual key, and the input field all disappeared, so a one-digit typo or a 30-second clock-drift turned into "scan a brand new QR from scratch." Auditor finding 6.

Refactor:

  • Extract the enrollment view (QR + manual key + verification-code form + "Verify & Enable" button) into twofa_setup_view(setup, error). The optional error argument renders a red banner above the code input. Both the GET handler (no error) and the POST error path (banner-with-message) call this helper, so the QR + manual key on a retry render are byte-identical to the entry render.
  • The POST handler's Err branch re-fetches the in-progress secret via auth_api::setup_2fa and feeds it to twofa_setup_view with Some(err_msg). This relies on the bunyip-api /v1/auth/2fa/setup endpoint returning the SAME pending secret while an enrollment is in flight - if the upstream mints a fresh secret per call, the user has to rescan after every wrong code (same UX bug, different mechanism). The API-side note in docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md flags that prerequisite.
  • If the re-fetch itself fails (network blip, session timeout, secret already promoted), the handler falls through to the legacy banner-only error + "Try again" link so the user can restart enrollment manually rather than seeing a blank page.
  • The verification-code input now carries autocomplete="one-time-code" so iOS / Chrome can surface a freshly-arrived TOTP from a sibling tab.

Imports: TwoFactorSetupResponse is added to the crate::api::types use block so the new view helper can name the type without crate-rooting.

Closes audit finding 6. See docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md for the full spec and verification.

The 2FA setup POST handler replaced the entire enrollment view with just "Invalid verification code · Try again" when the user typed the wrong code. The QR image, the manual key, and the input field all disappeared, so a one-digit typo or a 30-second clock-drift turned into "scan a brand new QR from scratch." Auditor finding 6. Refactor: - Extract the enrollment view (QR + manual key + verification-code form + "Verify & Enable" button) into `twofa_setup_view(setup, error)`. The optional `error` argument renders a red banner above the code input. Both the GET handler (no error) and the POST error path (banner-with-message) call this helper, so the QR + manual key on a retry render are byte-identical to the entry render. - The POST handler's `Err` branch re-fetches the in-progress secret via `auth_api::setup_2fa` and feeds it to `twofa_setup_view` with `Some(err_msg)`. This relies on the bunyip-api `/v1/auth/2fa/setup` endpoint returning the SAME pending secret while an enrollment is in flight - if the upstream mints a fresh secret per call, the user has to rescan after every wrong code (same UX bug, different mechanism). The API-side note in `docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md` flags that prerequisite. - If the re-fetch itself fails (network blip, session timeout, secret already promoted), the handler falls through to the legacy banner-only error + "Try again" link so the user can restart enrollment manually rather than seeing a blank page. - The verification-code input now carries `autocomplete="one-time-code"` so iOS / Chrome can surface a freshly-arrived TOTP from a sibling tab. Imports: `TwoFactorSetupResponse` is added to the `crate::api::types` use block so the new view helper can name the type without crate-rooting. Closes audit finding 6. See `docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md` for the full spec and verification.
feat(2fa): preserve QR + manual key when verification code is wrong
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt / clippy / build / test (pull_request) Successful in 1m6s
ec77829b1e
The 2FA setup POST handler replaced the entire enrollment view with just
"Invalid verification code · Try again" when the user typed the wrong code. The QR image, the manual key, and the input field all disappeared, so a one-digit typo or a 30-second clock-drift turned into "scan a brand new QR from scratch." Auditor finding 6.

Refactor:

- Extract the enrollment view (QR + manual key + verification-code form + "Verify & Enable" button) into `twofa_setup_view(setup, error)`. The optional `error` argument renders a red banner above the code input. Both the GET handler (no error) and the POST error path (banner-with-message) call this helper, so the QR + manual key on a retry render are byte-identical to the entry render.
- The POST handler's `Err` branch re-fetches the in-progress secret via `auth_api::setup_2fa` and feeds it to `twofa_setup_view` with `Some(err_msg)`. This relies on the bunyip-api `/v1/auth/2fa/setup` endpoint returning the SAME pending secret while an enrollment is in flight - if the upstream mints a fresh secret per call, the user has to rescan after every wrong code (same UX bug, different mechanism). The API-side note in `docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md` flags that prerequisite.
- If the re-fetch itself fails (network blip, session timeout, secret already promoted), the handler falls through to the legacy banner-only error + "Try again" link so the user can restart enrollment manually rather than seeing a blank page.
- The verification-code input now carries `autocomplete="one-time-code"` so iOS / Chrome can surface a freshly-arrived TOTP from a sibling tab.

Imports: `TwoFactorSetupResponse` is added to the `crate::api::types` use block so the new view helper can name the type without crate-rooting.

Closes audit finding 6. See `docs/bunyip-upgrade/04-2fa-error-state-preserves-form.md` for the full spec and verification.
YousifShkara deleted branch feat/bunyip-upgrade-04-2fa-preserve 2026-06-10 08:04:54 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!99
No description provided.