fix(web): preserve ?redirect= through the 2FA hop #45

Merged
YousifShkara merged 1 commit from fix/preserve-redirect-through-2fa into main 2026-06-03 02:53:45 +02:00
Owner

When LoginOutcome::TwoFactorRequired fires, the post-verify redirect was
hardcoded to /dashboard. That broke the OIDC return-url for any user who
has 2FA enabled: signing in via /oauth2/authorize -> bunyip-web /login ->
/login/2fa -> /dashboard never bounced back to api.a8n.systems for the
code-exchange. The user lands on bunyip's dashboard instead of mokosh.

Carry the original ?redirect= forward through the 2FA hop:

  1. login_post (2FA branch): on the redirect to /login/2fa, append
    ?redirect=.
  2. twofa_verify_get: read RedirectQuery::redirect, pass to twofa_card.
  3. twofa_card: render the redirect as a hidden form field.
  4. TwoFactorForm: pluck the redirect out of the form POST.
  5. twofa_verify_post: run it through safe_redirect (same allowlist as the
    plain login_post), redirect_cookies to the result on success, render
    error card with redirect preserved on failure.

Adds urlencoding="2" as a tiny dep so we don't hand-roll percent-encoding
for the OIDC URL. The unrelated magic-link 2FA branch at line 363 still
hardcodes /login/2fa - magic link is its own flow with no caller-supplied
redirect today, untouched here.

When LoginOutcome::TwoFactorRequired fires, the post-verify redirect was hardcoded to /dashboard. That broke the OIDC return-url for any user who has 2FA enabled: signing in via /oauth2/authorize -> bunyip-web /login -> /login/2fa -> /dashboard never bounced back to api.a8n.systems for the code-exchange. The user lands on bunyip's dashboard instead of mokosh. Carry the original ?redirect= forward through the 2FA hop: 1. login_post (2FA branch): on the redirect to /login/2fa, append ?redirect=<percent-encoded original redirect>. 2. twofa_verify_get: read RedirectQuery::redirect, pass to twofa_card. 3. twofa_card: render the redirect as a hidden form field. 4. TwoFactorForm: pluck the redirect out of the form POST. 5. twofa_verify_post: run it through safe_redirect (same allowlist as the plain login_post), redirect_cookies to the result on success, render error card with redirect preserved on failure. Adds urlencoding="2" as a tiny dep so we don't hand-roll percent-encoding for the OIDC URL. The unrelated magic-link 2FA branch at line 363 still hardcodes /login/2fa - magic link is its own flow with no caller-supplied redirect today, untouched here.
fix(web): preserve ?redirect= through the 2FA hop
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 1m11s
ff262ced67
When LoginOutcome::TwoFactorRequired fires, the post-verify redirect was
hardcoded to /dashboard. That broke the OIDC return-url for any user who
has 2FA enabled: signing in via /oauth2/authorize -> bunyip-web /login ->
/login/2fa -> /dashboard never bounced back to api.a8n.systems for the
code-exchange. The user lands on bunyip's dashboard instead of mokosh.

Carry the original ?redirect= forward through the 2FA hop:

1. login_post (2FA branch): on the redirect to /login/2fa, append
   ?redirect=<percent-encoded original redirect>.
2. twofa_verify_get: read RedirectQuery::redirect, pass to twofa_card.
3. twofa_card: render the redirect as a hidden form field.
4. TwoFactorForm: pluck the redirect out of the form POST.
5. twofa_verify_post: run it through safe_redirect (same allowlist as the
   plain login_post), redirect_cookies to the result on success, render
   error card with redirect preserved on failure.

Adds urlencoding="2" as a tiny dep so we don't hand-roll percent-encoding
for the OIDC URL. The unrelated magic-link 2FA branch at line 363 still
hardcodes /login/2fa - magic link is its own flow with no caller-supplied
redirect today, untouched here.
YousifShkara deleted branch fix/preserve-redirect-through-2fa 2026-06-03 02:53:45 +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!45
No description provided.