diff --git a/website/ARCHITECTURE.md b/website/ARCHITECTURE.md index 284e56e..dfd43ea 100644 --- a/website/ARCHITECTURE.md +++ b/website/ARCHITECTURE.md @@ -100,7 +100,13 @@ flowchart TB talks to it. Every backend→sidecar call carries `Authorization: Bearer ` and an `X-UOLink-Version` header (a protocol mismatch fails fast with `409`). The REST client (`uoLinkClient.js`) never throws — every call returns `{ ok, data, status }` — so the site degrades - gracefully when the shard is down. + gracefully when the shard is down. That guarantee covers **reading the config too**: resolving the + admin-managed config decrypts the stored auth token, which throws when the ciphertext can't be + authenticated (`SECRET_ENC_KEY` rotated, or a DB dump restored under a different key). This is + handled inside the client and reported as `{ ok: false, status: 0, error: 'uo-link config + unreadable' }` plus a distinct `ERROR`-level log, so a wrong key degrades the shard surface to + "unavailable" instead of 500ing it — and `GET /admin/uo-link/config` keeps working, which is the + screen an admin needs to re-enter the token and recover. - **Two ways in from the sidecar.** Live game events arrive over an outbound **WebSocket** and are routed by the `shardIngest.js` dispatcher (state-changing kinds update `shard_*` tables, notable kinds append to `shard_events`, high-frequency kinds only update state). Point-in-time reads and diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index c1eea89..3d0f433 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -561,7 +561,13 @@ Public content GETs pass through the **siteMode** gate (§5). the whole gate. The ops/config capabilities — `uo-link`, `email`, `discord-bot`, `settings`, and `PUT /site-mode` — are `adminOnly`; `shard` is the one mixed prefix, where self-service account linking carries no extra gate and the in-game staff operations carry `modAccess`. There is no residual -file: every admin route is declared in a capability router. The URLs below are unaffected by which +file: every admin route is declared in a capability router. + +`GET /dashboard` and `PUT /site-mode` are the one place where a **single screen spans two tiers**: the +dashboard is staff-wide, but the site-mode toggle on it is `adminOnly`. The client must therefore gate +that control on its own (`Dashboard.jsx` renders it only for `role === 'admin'`) rather than relying on +the route gate that admitted them to the page — the same rule the sidebar follows, so a non-admin is +never shown a control that would 403. The URLs below are unaffected by which file a route sits in — that is the property the route manifest freezes. | Method | Path | Purpose | |---|---|---|