feat(auth): trusted devices & recovery codes on the mobile client #23
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/trusted-devices-mfa"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Consumes the merged backend trusted-device + MFA feature (RunicGateway/website#93, docs#32) on the Android client, per
docs/android/PLAN.md §4.1.1. The backend is fully additive; this PR just uses the new endpoints. No existing behavior changes for accounts that don't opt in.Login (
POST /auth/mobile/login)401 { totpRequired }step: a "Trust this device" checkbox and a "use a recovery code instead" toggle (sendsrecoveryCodeinstead ofcode).trustDevice/device_nameand replays a storedX-Trust-Token; a valid token skips the TOTP prompt entirely (straight200).trustTokenis stored in a dedicated, username-scoped EncryptedSharedPreferences file (runic_trust, AES-256-GCM) — separate from the session store.Account → Security
GET /auth/me/trusted-devices), revoke one (DELETE …/:id), untrust all (DELETE …/trusted-devices), and trust the current device (POST …/trusted-devices, persists the returned token).GET …/recovery-codes/status) + password-stepped regenerate (POST …/recovery-codes/generate) with a show-once copy/share display. The one-time batch returned by enabling 2FA is also surfaced on the account screen.Two deliberate design decisions (documented in docs PR RunicGateway/docs#…)
rg_trustcookie, which per the canonical design survives logout so the next login skips 2FA. On mobile the token is only ever consulted at a fresh login — i.e. exactly after a logout / dead-refresh sign-out — so clearing it there would make the whole feature a no-op. It's therefore kept in its own store, untouched bySessionManager.onSignedOut, and cleared only on a Settings → Server switch, an untrust-all, or server-side revocation (password change/reset, TOTP disable, which make any surviving token inert). This supersedes the earlier handoff note that said clear-on-logout.trustLimitReached) is surfaced + resolved on the Trusted Devices screen rather than a blocking login-time modal, because the native login has already issued the session by that point (unlike the web cookie step).How it was tested
:app:testDebugUnitTest→ 154 pass / 0 fail (15 new: DTO decode for every new wire shape incl. the login trust/cap fields and one-time recovery codes, plusAccountRepositorylogic — the409cap-body parse into a typed outcome, revoke, and recovery status/generate).:app:assembleDebugclean (JDK 21,-Pksp.incremental=false).mobile.controller.js,account.controller.js,session.service/tokenfor theX-Trust-Tokenheader).Checklist
AI-assisted contributions (required)
Claude Code (Opus 4.8). I have reviewed and understand every change, and take responsibility for it. AI-authored commits carry aCo-Authored-Bytrailer.License
🤖 Generated with Claude Code
https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr