docs(android): record M3 (auth) landed, M4 next
Records the M3 functional auth pass in docs/android/PLAN.md: native username/password (+TOTP) login, EncryptedSharedPreferences token storage, the refresh-on-401 authenticator, /auth/me resume re-validation, the declarative access-level menu + My Account, and the Custom-Tab hand-offs for register / forgot-password / SSO. Also records the decision to defer the optional biometric app-lock to M6 (tokens are already encrypted at rest). No backend/API change accompanied M3 — the app is a pure consumer of the existing mobile bearer + /auth/me surface — so no Swagger regeneration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Android App — Plan
|
# Android App — Plan
|
||||||
|
|
||||||
Status: **M0–M2 landed; M3 (auth) next.** This document is the
|
Status: **M0–M3 landed; M4 (player self-service & game data) next.** This document is the
|
||||||
design contract for the `RunicGateway/Android-app` repo. It was written before implementation so the
|
design contract for the `RunicGateway/Android-app` repo. It was written before implementation so the
|
||||||
API changes it depends on could be landed in `website/` and `docs/` first. The authoritative API
|
API changes it depends on could be landed in `website/` and `docs/` first. The authoritative API
|
||||||
reference is the committed OpenAPI spec at `website/server/swagger/swagger-output.json` (regenerated
|
reference is the committed OpenAPI spec at `website/server/swagger/swagger-output.json` (regenerated
|
||||||
@@ -47,7 +47,35 @@ DTOs. Wired into the shared drawer (§5), all strings externalized (§2). JVM un
|
|||||||
SSE frame parsing. No backend/API change — the app is a pure consumer of the existing public shard
|
SSE frame parsing. No backend/API change — the app is a pure consumer of the existing public shard
|
||||||
surface.
|
surface.
|
||||||
|
|
||||||
**M3–M4 (functional pass) and M5 (design pass) remain.**
|
✅ **M3 — auth** (2026-07-19, `RunicGateway/Android-app#8`, functional Kotlin pass): native
|
||||||
|
**username/password (+ single-request TOTP) login** over the existing `POST /auth/mobile/login` — a
|
||||||
|
`401 { totpRequired }` reveals the code field and a wrong code re-lands as a code error; `429` surfaces
|
||||||
|
a friendly backoff message (§4.1). The token pair lives in **EncryptedSharedPreferences** (a
|
||||||
|
`TokenStore` behind `SessionManager`, the single source of truth for the in-memory bearer + the
|
||||||
|
observable `Session`); the base URL stays in plain DataStore (§4.3). An OkHttp `AuthInterceptor`
|
||||||
|
attaches the bearer and a `TokenAuthenticator` does a **one-shot, mutex-serialized refresh** on a
|
||||||
|
bearer `401` and 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, and a dead
|
||||||
|
refresh (`401`) signs out while a transient network error keeps the session. Logout
|
||||||
|
(`POST /auth/mobile/logout`, this session or all devices) tears down locally even if the call fails.
|
||||||
|
`GET /auth/me` **re-validates the role on every resume** (`LifecycleResumeEffect`); a surviving `401`
|
||||||
|
signs out, so a server-side demotion drops menu access promptly (role stays advisory — the backend is
|
||||||
|
authority, §4.3). The **access-level menu** is one declarative list (`visibleEntries` filters by
|
||||||
|
session — public / signed-in / player) with a Sign in / Sign out toggle and a **My Account** screen
|
||||||
|
(identity + role + sign-out / sign-out-everywhere). Registration, forgot-password, and SSO are
|
||||||
|
**Custom-Tab hand-offs** (androidx.browser) to the website's own pages (`/account/register`,
|
||||||
|
`/account/forgot`, `/account/login`) — no native screens (§4.2). The Settings → Server switch now also
|
||||||
|
clears the stored session (§3). JVM unit tests (18) cover 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** — the app is a pure consumer of the existing mobile bearer +
|
||||||
|
`/auth/me` surface.
|
||||||
|
|
||||||
|
> **Biometric app-lock — deferred (decided at M3).** §4.3/§9 flag an *optional* biometric app-lock;
|
||||||
|
> it is **deferred to M6 (release hardening)**. Tokens are already encrypted at rest (Tink/AES-256-GCM),
|
||||||
|
> so an app-lock is a UX layer, not a security requirement, and adding it in the functional pass would
|
||||||
|
> widen scope without changing the data flow. Revisit as an opt-in setting during M6.
|
||||||
|
|
||||||
|
**M4 (functional pass) and M5 (design pass) remain.**
|
||||||
|
|
||||||
**Prerequisite progress (§8):** all v1 prerequisites are **done** (2026-07-19) — ✅ password reset
|
**Prerequisite progress (§8):** all v1 prerequisites are **done** (2026-07-19) — ✅ password reset
|
||||||
(item 2; website#75 + docs#8), ✅ role-agnostic `/auth/me/*` self surface (item 1; website#76 + docs#10),
|
(item 2; website#75 + docs#8), ✅ role-agnostic `/auth/me/*` self surface (item 1; website#76 + docs#10),
|
||||||
@@ -527,7 +555,8 @@ password+TOTP only, with registration/invite/reset/SSO **handled by the website*
|
|||||||
reset built on backend + web first**, before app work (§8); minSdk 29, compile/target 35 (§2); no
|
reset built on backend + web first**, before app work (§8); minSdk 29, compile/target 35 (§2); no
|
||||||
telemetry in v1 (§2); strings externalized from day one, English-only bundled (§2); **text-only** game
|
telemetry in v1 (§2); strings externalized from day one, English-only bundled (§2); **text-only** game
|
||||||
data in v1, pretty paperdoll is future (§6.3); **no offline cache in v1** (§7); push via self-hosted
|
data in v1, pretty paperdoll is future (§6.3); **no offline cache in v1** (§7); push via self-hosted
|
||||||
ntfy / UnifiedPush (§11).
|
ntfy / UnifiedPush (§11); **biometric app-lock deferred to M6** (tokens already encrypted at rest, so
|
||||||
|
it is opt-in UX, not a v1 requirement — decided at M3).
|
||||||
|
|
||||||
**Still open:**
|
**Still open:**
|
||||||
- **App identity / domain.** Target application ID **`com.runicgateway.app`** — pending securing the
|
- **App identity / domain.** Target application ID **`com.runicgateway.app`** — pending securing the
|
||||||
|
|||||||
Reference in New Issue
Block a user