Core half of the public demo (docs/website/DEMO_MODE.md §10 step 1, website#222).
Nothing here runs unless DEMO_MODE=true.
- server/src/demo/: the lock at /api/v1, ahead of every router. It covers every
Tier A row of §3.3, the shared-login conditions, POST /auth/register (DM16),
DELETE /auth/me/sessions/:id on a demo account (DM17), and the PUT
/admin/settings keys that reach a Tier A lock. Any multipart request is refused
app-wide. noindex and robots.txt. A condition that cannot decide refuses.
- Loader: demoLocks in module.json (writes only, under the module's own mounts),
ctx.demoMode, and api.onDemoSeed (DM15). MODULE_API 1.18.0 on both halves.
- GET /public/settings carries demo: { resetsAt, logins } on the demo only.
- npm run seed:demo -- <fixture>: upserts the four accounts, sets the site live
with registration disabled, seeds content on the first run only, calls each
module's onDemoSeed, and stamps demo_state. It resolves $env: strings and
refuses to run off the demo.
- Client: the banner, one DEMO_LOCKED toast fed from the request primitive (so
module screens get it too), and the published logins on both sign-in pages.
Dashboard and register say the demo message instead of blaming a role.
- OpenAPI: DemoLocked schema, the demo block, and a note that any write may
answer DEMO_LOCKED on the demo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
27 lines
1.3 KiB
JavaScript
27 lines
1.3 KiB
JavaScript
// ── Demo mode: the flag and the four shared accounts ───────────────────────
|
|
//
|
|
// `DEMO_MODE=true` on a deployment turns it into the public demo
|
|
// (docs/website/DEMO_MODE.md). Read per call rather than once at require time,
|
|
// so a test can flip it without re-requiring every file that asks — and so
|
|
// there is exactly one spelling of "is this the demo" in the codebase.
|
|
//
|
|
// Only the literal string `true` turns it on. A stray `DEMO_MODE=1` or `yes` on
|
|
// a real shard must not lock its admin panel, and the opposite mistake — a demo
|
|
// that forgot the flag — is caught by `seed:demo`, which refuses to run without it.
|
|
|
|
function isDemo() {
|
|
return process.env.DEMO_MODE === 'true'
|
|
}
|
|
|
|
// The static logins (DEMO_MODE.md §4). Fixed here, in core, rather than read
|
|
// from the seed fixture: the lock has to recognise them on every request, and a
|
|
// fixture that dropped one would quietly unlock the account everybody shares.
|
|
// The seed creates exactly these; their passwords come from the fixture.
|
|
const DEMO_ACCOUNTS = Object.freeze(['demo-admin', 'demo-moderator', 'demo-player', 'play-review'])
|
|
|
|
function isDemoUsername(username) {
|
|
return typeof username === 'string' && DEMO_ACCOUNTS.includes(username.toLowerCase())
|
|
}
|
|
|
|
module.exports = { isDemo, DEMO_ACCOUNTS, isDemoUsername }
|