diff --git a/android/PLAN.md b/android/PLAN.md index e51a29c..ed8b895 100644 --- a/android/PLAN.md +++ b/android/PLAN.md @@ -6,10 +6,10 @@ API changes it depends on can be landed in `website/` and `docs/` first. When we authoritative API reference is the committed OpenAPI spec at `website/server/swagger/swagger-output.json` (regenerated via `npm run swagger`). -**Prerequisite progress (§8):** ✅ **Password reset** — the "build FIRST, before app work" prerequisite -(§8 item 2) is **done** (RunicGateway/website#75 + docs#8, 2026-07-19). The remaining §8 items are still -open: role-agnostic `/auth/me/*` self surface (item 1), version/health for first-run (item 4), branding -confirmation (item 6); push (item 3) is post-v1. +**Prerequisite progress (§8):** ✅ **Password reset** (§8 item 2 — the "build FIRST" prerequisite; +website#75 + docs#8) and ✅ **role-agnostic `/auth/me/*` self surface** (§8 item 1) are **done** +(2026-07-19). Still open: version/health for first-run (item 4) and branding confirmation (item 6); +push (item 3) is post-v1. The workspace already holds `website/`, `link/`, `servuo-plugins/`, and `docs/`. `android-app/` is the fifth repo. It is **purely an API client of the website backend** — it never talks to the @@ -290,16 +290,18 @@ web client and the app both consume v1; a second parallel route tree + Swagger s maintenance cost. Reserve v2 for a real breaking re-shape if one ever arises. **To build (all additive, v1):** -1. **Role-agnostic self-service under `/auth/**` (§6.4, decided).** Mount the existing - `account.controller` self handlers behind `requireAuth` (any role), so the app has one self surface - and never touches `/admin`. Keep the old `/player/account/*` + `/admin/account/*` routes for web - back-compat. New canonical routes: - - `GET /auth/me` — current `{ id, username, role }` (already exists; the app's role source). +1. **Role-agnostic self-service under `/auth/**` (§6.4, decided).** + ✅ **DONE (2026-07-19, RunicGateway/website#76 (+ this docs PR)).** A `me.routes.js` + sub-router mounts the existing `account.controller` self handlers behind `requireAuth` (any role) at + `/auth/me/*`, so the app has one self surface and never touches `/admin`. The old + `/player/account/*` + `/admin/account/*` routes stay for web back-compat. Shipped routes: + - `GET /auth/me` — current `{ id, username, role }` (already existed; the app's role source). - `GET /auth/me/account` — full self account. - `PATCH /auth/me/account/username`, `PATCH /auth/me/account/password`. - `POST /auth/me/account/totp/setup|enable|disable`. - `GET /auth/me/account/identities`, `DELETE /auth/me/account/identities/:provider`. - - Regenerate Swagger; add `#swagger` annotations for each. + - Swagger regenerated with `#swagger` annotations; `test/authMe.test.js` guards the auth gate; and + an end-to-end smoketest confirmed both a player and an editor (staff) drive the same surface. 2. **Password reset — build on backend + web front end FIRST (a prerequisite, not app scope).** ✅ **DONE (2026-07-19, RunicGateway/website#75 + docs#8).** Full platform flow shipped in `website/`: request-reset (`POST /auth/password/forgot`, always a generic 200 — no account enumeration) emails a diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 2346b19..4bad38a 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -172,6 +172,18 @@ accepts `Authorization: Bearer` for API testing). | POST | `/password/forgot` | — (rate-limited) | `{email}` | email a single-use, ~1h reset link to **every active account** on the address; **always** returns the same generic 200 (no account enumeration). Email is non-unique, so several accounts may each get a link naming their username. Logs `account.password.reset.request`. | | GET | `/password/reset/:token` | — | — | validate a link → `{username}` for the form, else 404 (never distinguishes expired/used/never-existed) | | POST | `/password/reset/:token` | — (rate-limited) | `{password}` | consume the single-use link, rotate the hash, and revoke **all** sessions (web cutoff + mobile refresh tokens). Does **not** sign the user in — they log in fresh (so a 2FA account still passes TOTP). Logs `account.password.reset.complete`. | +| GET | `/me/account` | cookie / bearer | — | full self account (`id, username, role, email, status, totp_enabled, has_password`) | +| PATCH | `/me/account/username` | cookie / bearer (rate-limited) | `{username}` | change own username; re-issues the caller's session | +| PATCH | `/me/account/password` | cookie / bearer (rate-limited) | `{newPassword, currentPassword?}` | change/set own password (current required unless the account has none); revokes other sessions, keeps the caller's | +| POST | `/me/account/totp/setup` · `…/enable` · `…/disable` | cookie / bearer | `{code}` on enable/disable | self 2FA enrollment (disable needs a valid current code, not a password) | +| GET | `/me/account/identities` · DELETE `…/:provider` | cookie / bearer | — | list / unlink own SSO identities | + +**Role-agnostic self-service (`/auth/me/*`).** The canonical "me" surface for **every** authenticated +role. It reuses the exact `account.controller` handlers as `/player/account/*` and `/admin/account/*` +(no logic duplication) behind `requireAuth` **only** — any active account, never a specific role. This +lets a client (the Android app) manage its own account through one surface without ever touching +`/admin` (docs/android/PLAN.md §6.4). The older `/player/account/*` + `/admin/account/*` routes stay +for web back-compat. **Password reset.** Uses the same audited pattern as `user_invites`: an opaque 32-byte token whose **sha256 hash only** is stored in `password_resets`, single-use and short-lived (~1h). It