diff --git a/website/DEMO_MODE.md b/website/DEMO_MODE.md index c18c136..b851b0a 100644 --- a/website/DEMO_MODE.md +++ b/website/DEMO_MODE.md @@ -1,6 +1,6 @@ # Demo mode — `demo.runicgateway.com` -**Status:** design, **fully decided** 2026-10-23 (DM1-DM13, §9). Ready to build per §10. Nothing is +**Status:** design, **fully decided** 2026-10-23 (DM1-DM14, §9). Ready to build per §10. Nothing is built yet. **Issue:** `RunicGateway/website#222`. **Why now:** Google Play (`../android/PLAN.md` item 9, M8) needs a public HTTPS site with working sign-ins for its reviewers and for the 12 testers who @@ -49,8 +49,8 @@ The web client shows the message as a toast wherever an action fails with that c app maps the code to a new error kind with the same wording; today it would show the generic "Something went wrong", which is misleading for a reviewer. -Reads are never locked. Every page can be opened and every form filled in; only the save is -refused. That keeps the demo useful as a tour of the admin panel. +Reads are never locked, with one exception: the bridge's config (§3.5). Every page can be opened +and every form filled in; only the save is refused. That keeps the demo useful as a tour of the admin panel. **The server is the lock, never the UI (DM10).** Every lock is enforced at the route, so a request sent with `curl`, an old app build, or a hand-edited page is refused exactly like a click. The UI may @@ -104,7 +104,7 @@ wipe puts them back (§7). | Area | Routes | Why | | --- | --- | --- | | The link to the rig | `PUT`/`DELETE /admin/rust/servers/:id` | Repoint or delete the sidecar link; the site goes dark | -| Plugin config files | `POST /admin/rust/config/:serverId/file` | Writes a plugin's config file on the rig's disk | +| The bridge's config, **read or written** | `GET`/`POST /admin/rust/config/:serverId/file`, `GET …/writes…` for the `RunicGateway` plugin's files, which are also left out of `GET …/files` | It holds the bridge's link to the sidecar and its credentials. Read, it would let someone talk to the sidecar directly over HTTP (DM14, §3.5) | **Settings stay open (DM2).** Theme, navigation and the other `PUT /admin/settings` values can be changed and come back at the wipe. Brand images are uploads, so they fall under the upload lock. @@ -166,6 +166,41 @@ Commands that only change the world or the players (`server.writecfg`, `entity.* `weather.*`, `say`, `kick`, `ban`, `oxide.grant`) stay open. The baseline restore puts all of them back (§7.2). +### 3.5 The plugin config editor (DM14) + +Every plugin's config **except the bridge's** can be read and changed on the demo. A visitor can tune +RunicNPC, Kits, Economics and the rest and watch the change take effect. Three things make that safe: + +- **The save reloads through the bridge, not the console.** The bridge backs up the file, writes + it, reloads the plugin that owns it, and puts the old file back if that plugin fails to load (D178, + D179). The §3.4 deny list never sees this reload, and a config that breaks its plugin undoes + itself at once. +- **The wipe restores `oxide/config/`** from the baseline (§7.2), so a change lasts two hours at + most. +- **The bridge's own config is out of reach.** The editor's routes refuse every file the + `RunicGateway` plugin owns, for reading as well as writing, and the file list leaves those files + out. This is the one place where §3.1's "reads are never locked" gives way. The file holds the + bridge's link to the sidecar and its credentials, and anyone holding them could talk to the + sidecar directly over HTTP, outside every lock in this document. + +**Secrets in other plugins' configs.** A plugin config can hold a webhook URL or an API key. The +editor already flags those fields by key name (`SECRET_WORDS` in `configEdit.js`), but today it +returns the full text and masks them only **in the form**, behind a reveal button. Under DM10 that +mask is not a lock. On the demo, the server therefore: + +- **redacts** every flagged value in `GET …/file` and in the write history before it leaves the + module, replacing it with a placeholder; +- makes flagged fields **read-only**: a form save that edits one is refused; +- refuses **raw-JSON saves** (`text` instead of `edits`), because the raw document a visitor sends + back would hold the placeholders and overwrite the real values. Form saves splice only the fields + that changed, so untouched secrets stay as they are on disk. + +**The link on the Servers screen.** The module's admin view of a server never returns the sidecar +token; it returns `hasToken`. On the demo it also leaves out the sidecar's **URL**, so the screen +gives away nothing that helps someone reach the sidecar. Editing the link stays locked (§3.3). + +All of this is enforced in the module's server code behind `ctx.demoMode` (DM10), with tests. + ## 4. Static logins | Account | Role | Notes | @@ -307,7 +342,7 @@ down. | Piece | Repo | | --- | --- | | `DEMO_MODE` lock, banner field, `seed:demo` script | `website` (core) | -| The Rust module's `demoLocks`, the console-command deny list (§3.4), and `protocol` in its install manifest (§7.5) | `Module-Rust` | +| The Rust module's `demoLocks`, the console-command deny list (§3.4), the config-editor rules (§3.5), and `protocol` in its install manifest (§7.5) | `Module-Rust` | | `DEMO_LOCKED` error kind and the banner in the app | `Android-app` | | `deploy/demo/` (compose file, `.env.example`, seed fixture) and `.gitea/workflows/demo-wipe.yml` + `demo-baseline.yml` | `website` (DM8) | | `.env` (JWT secret, encryption key, the rig's sidecar token) | On the `demo-site` host only, never in a repo | @@ -333,6 +368,7 @@ down. | DM11 | (F1) Console-command steps on the demo | A **deny list** of the commands that would break the rig (`quit`, `oxide.unload RunicGateway` and the like), not an allowlist; everything else runs (§3.4) | | DM12 | (F2) What "latest" means across the Rust link | The latest **Rust bundle** for the rig's plugins and sidecar, the newest module release on the same protocol, and the website's `main` image; the wipe updates the rig when the bundle changes (§7.5) | | DM13 | (F3) DM2 read correctly? | Yes: only the settings stay open; every other Tier A row stays locked | +| DM14 | (after DM13) May visitors change plugin configs? | Yes, every plugin except the bridge. The bridge's config can be **neither read nor written**, because it holds credentials that would let someone talk to the sidecar directly (§3.5) | DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for @@ -354,9 +390,9 @@ through that proxy. `seed:demo`, `noindex`, the web banner and toast, the `MODULE_API` minor bump, and the OpenAPI note that a route may answer `DEMO_LOCKED`. Tests for every lock. One PR, plus `MODULE_API.md` and this doc. -2. **Module** (`Module-Rust`): `demoLocks` in `module.json`, the console-command deny list behind - `ctx.demoMode` (§3.4), and `protocol` in the release's install manifest (§7.5), each with - tests. One PR. +2. **Module** (`Module-Rust`): `demoLocks` in `module.json`; behind `ctx.demoMode`, the console-command + deny list (§3.4) and the config-editor rules (§3.5); and `protocol` in the release's install + manifest (§7.5). Each with tests. One PR. 3. **Kit** (`Integration-kit`): a paragraph on `demoLocks` and `ctx.demoMode`, and the `core-ref.json` re-pin. One PR. 4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR. @@ -364,6 +400,8 @@ through that proxy. `demo-baseline.yml`; the first deploy on `demo-site`; setting up the default set and capturing the baseline. Then a walk of every static login, every Tier A lock and the upload lock, in the browser and the app, plus one full wipe cycle that restores a changed permission, NPC and - event, and a denied console command refused at save and at run. One PR. + event; a denied console command refused at save and at run; and a plugin config changed and + reloaded through the editor, with the bridge's config refused for reading and another plugin's + secret field redacted. One PR. 6. **Play**: the sign-in details in the Play Console point at `play-review`; runicgateway.com `/beta` publishes the logins.