feat(auth): merge the 2FA trust-device control into sign-in remember-me (BUNYIP-382) #376

Merged
nrupard merged 2 commits from feat/BUNYIP-382-merge-2fa-remember into main 2026-07-16 20:16:26 +02:00
Owner

Summary

Unifies the two "remember"-style controls into one. The sign-in "Remember me" checkbox now drives both:

  • a 30-day refresh session (else 1 day), and
  • on the 2FA path for subscribers, trusting the device (skip future TOTP prompts).

The separate 2FA "Trust this device for 30 days" checkbox is removed.

Stacked on #375 (BUNYIP-381) - it uses refresh_absolute_ttl(remember) + the remember threading from that PR. Base is the BUNYIP-381 branch; retarget to main after #375 merges.

Also fixes the BUNYIP-381 lifetime gap

A login deferred for a second factor or the suspicious-login approval gate previously lost remember (it was not carried in the challenge), so those sessions were always 1 day. remember is now a claim on the challenge JWT (TwoFactorChallengeClaims, #[serde(default)] for backward compatibility), set from the sign-in choice when the challenge is minted and read at completion.

Changes

  • jwt: create_2fa_challenge_token / create_login_approval_challenge_token take remember; the claim carries it.
  • complete_2fa_login: drops the trust_device param; claims.remember drives both the trusted-device creation (subscribers only; admins still always complete 2FA, BUNYIP-138) and the 30-day/1-day session.
  • complete_login_approval: claims.remember drives the session length.
  • login threads its remember into both challenge types; magic-link paths pass false.
  • bunyip-api: Verify2FARequest loses trust_device; handler no longer sends it.
  • bunyip-web: the 2FA form's "Trust this device" checkbox + TwoFactorForm.trust_device + the API client's trust_device are removed.

Behaviour matrix

  • Remember me + subscriber + 2FA: 30-day session, device trusted (next 2FA skipped).
  • Remember me + admin + 2FA: 30-day session, still completes 2FA every time (unchanged).
  • No remember me: 1-day session, no trusted device.
  • Remember me through the suspicious-login gate: 30-day session.

Testing

just check-container green (fmt + clippy -D warnings + cargo test --workspace --all-targets: 275 domain / 73 api / 104 web, 0 failed).

## Summary Unifies the two "remember"-style controls into one. The sign-in **"Remember me"** checkbox now drives both: - a **30-day** refresh session (else 1 day), and - on the 2FA path for subscribers, **trusting the device** (skip future TOTP prompts). The separate 2FA **"Trust this device for 30 days"** checkbox is removed. **Stacked on #375** (BUNYIP-381) - it uses `refresh_absolute_ttl(remember)` + the remember threading from that PR. Base is the BUNYIP-381 branch; retarget to `main` after #375 merges. ## Also fixes the BUNYIP-381 lifetime gap A login deferred for a second factor or the suspicious-login approval gate previously lost `remember` (it was not carried in the challenge), so those sessions were always 1 day. `remember` is now a claim on the challenge JWT (`TwoFactorChallengeClaims`, `#[serde(default)]` for backward compatibility), set from the sign-in choice when the challenge is minted and read at completion. ## Changes - `jwt`: `create_2fa_challenge_token` / `create_login_approval_challenge_token` take `remember`; the claim carries it. - `complete_2fa_login`: drops the `trust_device` param; `claims.remember` drives both the trusted-device creation (subscribers only; admins still always complete 2FA, BUNYIP-138) and the 30-day/1-day session. - `complete_login_approval`: `claims.remember` drives the session length. - `login` threads its `remember` into both challenge types; magic-link paths pass `false`. - bunyip-api: `Verify2FARequest` loses `trust_device`; handler no longer sends it. - bunyip-web: the 2FA form's "Trust this device" checkbox + `TwoFactorForm.trust_device` + the API client's `trust_device` are removed. ## Behaviour matrix - Remember me + subscriber + 2FA: 30-day session, device trusted (next 2FA skipped). - Remember me + admin + 2FA: 30-day session, still completes 2FA every time (unchanged). - No remember me: 1-day session, no trusted device. - Remember me through the suspicious-login gate: 30-day session. ## Testing `just check-container` green (fmt + clippy `-D warnings` + `cargo test --workspace --all-targets`: 275 domain / 73 api / 104 web, 0 failed).
The sign-in "Remember me" checkbox now drives everything: a 30-day refresh session AND, on the 2FA path for subscribers, trusting the device to skip future TOTP prompts. The separate 2FA "Trust this device for 30 days" checkbox is removed.

This also fixes the lifetime gap from BUNYIP-381: a login deferred for a second factor or the suspicious-login approval gate previously lost `remember` (it was not carried in the challenge), so those sessions were always 1 day. `remember` is now a claim on the challenge JWT (TwoFactorChallengeClaims, serde-default for backward compatibility), set from the sign-in choice when the challenge is minted, and read at completion.

- jwt: create_2fa_challenge_token / create_login_approval_challenge_token take `remember`; the claim carries it.
- complete_2fa_login: drops the `trust_device` param; claims.remember drives both the trusted-device creation (subscribers only; admins unchanged, BUNYIP-138) and the 30-day/1-day session length.
- complete_login_approval: claims.remember drives the session length.
- login threads its `remember` into both challenge types; the magic-link paths pass false (no remember concept).
- bunyip-api: Verify2FARequest loses trust_device; the handler no longer sends it.
- bunyip-web: the 2FA form's "Trust this device" checkbox + TwoFactorForm.trust_device + the API client's trust_device are removed.

check-container green.

#BUNYIP-382

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard changed target branch from feat/BUNYIP-381-token-lifetimes to main 2026-07-16 19:14:42 +02:00
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-16 19:19:09 +02:00
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-16 19:59:27 +02:00
nrupard canceled auto merging this pull request when all checks succeed 2026-07-16 19:59:49 +02:00
Merge branch 'main' into feat/BUNYIP-382-merge-2fa-remember
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 37s
Check / fmt + clippy + build + tests (pull_request) Successful in 16m26s
Create release / Create release from merged PR (pull_request) Has been skipped
ec5903bb29
nrupard scheduled this pull request to auto merge when all checks succeed 2026-07-16 20:00:23 +02:00
nrupard deleted branch feat/BUNYIP-382-merge-2fa-remember 2026-07-16 20:16:26 +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!376
No description provided.