feat(m3): native auth — login+TOTP, token storage, refresh, access-level menu #8
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/m3-auth"
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?
M3 — Auth (functional pass)
Implements M3 of
docs/android/PLAN.md§4: the native auth surface. Functional Kotlin pass only — placeholder Compose UI; the design pass is M5. No backend/API change — the app is a pure consumer of the existing mobile bearer +/auth/mesurface, so no Swagger regeneration.What's in it
POST /auth/mobile/login(§4.1). A401 { totpRequired }reveals the code field (parsed from the error body) and a wrong code re-lands as a code error;429surfaces a friendly backoff message.TokenStore(interface + Tink/AES-256-GCM impl) behindSessionManager, the single source of truth for the in-memory bearer + the observableSession. Base URL stays in plain DataStore (§4.3).AuthInterceptorattaches the bearer;TokenAuthenticatordoes a one-shot, mutex-serialized refresh on a bearer401and replays the request. Refresh runs on its own bare client (no interceptor/authenticator) so it can never recurse; rotated single-use tokens are stored atomically; a dead refresh (401) signs out while a transient network error keeps the session.POST /auth/mobile/logout, this session or all devices) tears down locally even if the call fails.GET /auth/merole re-validation on every resume (LifecycleResumeEffect); a surviving401signs out, so a server-side demotion drops menu access promptly (role stays advisory — the backend is authority).visibleEntries(public / signed-in / player), a Sign in / Sign out toggle, and a My Account screen (identity + role + sign-out / sign-out-everywhere)./account/register), forgot-password (/account/forgot), and SSO (/account/login) — no native screens (§4.2).DTOs + the Retrofit interfaces are hand-authored + spec-aligned (as recorded for M1).
Decision: biometric app-lock deferred to M6
§4.3/§9 flag an optional biometric app-lock. Deferred to M6 (release hardening): tokens are already encrypted at rest, so it is opt-in UX, not a v1 requirement. Recorded in
docs/android/PLAN.md(docs PR:docs/android-m3-landed).Tests (18, all green)
Auth-DTO decode (incl.
totpRequiredvs a plain credential401), theSessionManagerlifecycle over a fake store, and the access-level menu filter + role mapping.Verification
./gradlew lint test assembleDebugpasses locally.AI-assisted: authored with Claude Code (Opus). AI-authored commit carries a
Co-Authored-By: Claudetrailer per org policy.🤖 Generated with Claude Code
Implements M3 (docs/android/PLAN.md §4): the functional Kotlin auth pass. - Native username/password (+ single-request TOTP) login over the existing POST /auth/mobile/login; a 401 { totpRequired } reveals the code field, 429 surfaces a backoff message (§4.1). - Token pair in EncryptedSharedPreferences (TokenStore behind SessionManager, the single source of truth for the in-memory bearer + observable Session); base URL stays in plain DataStore (§4.3). - OkHttp AuthInterceptor (bearer) + TokenAuthenticator: one-shot, mutex- serialized refresh-on-401 that replays the request, on its own bare client so it can never recurse; single-use rotation; dead refresh signs out, transient network keeps the session. - Logout (POST /auth/mobile/logout, this session or all devices) tears down locally even on failure. - GET /auth/me re-validates the role on every resume; a surviving 401 signs out (role stays advisory — backend is authority). - Declarative access-level menu (visibleEntries: public/signed-in/player) with a Sign in / Sign out toggle + a My Account screen. - Custom-Tab hand-offs (androidx.browser) to the website for register / forgot- password / SSO — no native screens (§4.2). - Settings → Server switch now also clears the stored session (§3). Biometric app-lock is deferred to M6 (tokens already encrypted at rest; it is opt-in UX, not a v1 requirement — decided at M3). JVM unit tests (18): auth-DTO decode (incl. totpRequired vs a plain credential 401), the SessionManager lifecycle over a fake store, and the menu access filter + role mapping. No backend/API change — a pure consumer of the existing mobile bearer + /auth/me surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr