docs(website): demo mode core built — DM15-DM17, MODULE_API 1.18.0

- DEMO_MODE.md: DM15 (onDemoSeed), DM16 (POST /auth/register locked), DM17
  (sessions route locked outright on a demo account). It also covers the settings
  keys that reach a Tier A lock, how demo accounts are recognised, failing
  closed, the published logins, the toast, the seed's fixture shape and $env,
  and the fact that there is no seeded team (Teams come from the rig's clans).
  The §3.2 example drops the config-file POST that DM14 opened. Step 1 is
  marked built.
- MODULE_API.md: 1.18.0. demoLocks in module.json, ctx.demoMode, and
  api.onDemoSeed, which must upsert because it runs on every seed.
- BACKEND_DESIGN.md: §5.1 demo mode, and demo_state in the settings table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-23 18:52:12 -05:00
parent 6fae383255
commit 6ea268ea86
3 changed files with 149 additions and 26 deletions

View File

@@ -338,7 +338,9 @@ Seeded keys: `site_mode` (default `maintenance`), `site_mode_changed_at`,
`site_mode_changed_by`, `maintenance_message`, `status_message`, `homepage_teaser`,
`contact_email` (seeded from `BRAND_CONTACT_EMAIL`; e.g. `UOMysticmoon@gmail.com` on the first
instance), `site_title`, `player_registration`
(default `disabled`), `mobile_app_links_enabled`, `module_source_hosts`.
(default `disabled`), `mobile_app_links_enabled`, `module_source_hosts`. On the public demo only,
`demo_state` (JSON `{ resetsAt, logins }`, written by `seed:demo`, never writable through
`PUT /admin/settings`; §5.1).
`module_source_hosts` is the allowlist of hostnames a module may be installed from
(MODULE_SYSTEM.md §2.7.2 decision 6), edited in Admin → Modules and audited as
@@ -1808,6 +1810,34 @@ Admin "preview live" simply hits the content APIs with the admin cookie, which b
Dashboard reads `site_mode` + `site_mode_changed_at`/`_by` for "current mode + last change +
who"; `activity_log` provides the history feed.
### 5.1 Demo mode (`DEMO_MODE=true`)
The public demo, `demo.runicgateway.com`, runs the normal image with one env var. The design of
record is `DEMO_MODE.md`; this is the contract in brief. With the flag unset, `app.js` mounts none of
it.
- **The lock** (`server/src/demo/locks.js`). It is mounted at `/api/v1` ahead of every router, after the
body parser and `cookieParser`. Each `POST`/`PUT`/`PATCH`/`DELETE` is checked against core's list
(DEMO_MODE.md §3.3) and every installed module's `demoLocks` (`MODULE_API` 1.18.0). A match answers
`403 { message: "This is turned off on the demo site.", code: "DEMO_LOCKED" }` before the route's own
auth check. Reads are never locked. Matching follows express 4's defaults: case-insensitive, and the
trailing slash optional. A few rows are conditional:
- the target of `/admin/users/:id…` is a demo account;
- the caller of `/auth/me/…` is signed in as one;
- the `PUT /admin/settings` body names a protected key (`site_mode*`, `module_source_hosts`,
`teams_voice_*`, `demo_*`).
A condition that cannot decide refuses.
- **Uploads.** Any request with a `multipart/*` content type is refused app-wide, before a router or
multer sees it.
- **Search engines.** Every response carries `X-Robots-Tag: noindex, nofollow`. `/robots.txt` is
`Disallow: /`, served ahead of the SPA's static file.
- **`GET /public/settings`** gains `demo: { resetsAt, logins }`, from the `demo_state` settings row.
- **`npm run seed:demo -- <fixture>`** (`server/db/seedDemo.js`) refuses to run without the flag. It
upserts the four accounts (`demo-admin`, `demo-moderator`, `demo-player`, `play-review`, fixed in
`server/src/demo/mode.js`), sets the site live with registration disabled, and seeds content on a
database's first run only. It calls each module's `onDemoSeed`, then stamps `demo_state`.
---
## 6. Auth & security