diff --git a/docs/plans/2026-10-11_webui-preview.md b/docs/plans/2026-10-11_webui-preview.md new file mode 100644 index 000000000..f10206964 --- /dev/null +++ b/docs/plans/2026-10-11_webui-preview.md @@ -0,0 +1,106 @@ +# WebUI preview behind sign-in (2026-10-11) + +Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`. +One row. No code starts until Jason answers Q91 (amending lead decision +48 for a signed-in preview reachable beyond localhost). Mos relays the +answer. If the ruling narrows or changes what follows, this brief is +amended first. + +## WebUI preview: a signed-in, read-only Console behind the edge proxy + +### Problem + +Jason asked for an ETA for the refactor WebUI at mosaic.woltje.com. Today +the Console can't be reached from anywhere but the host it runs on, by +design: + +- `startServer` refuses to bind to a non-loopback address + (`packages/webui/src/serve.mjs:82`) and answers 403 to any request whose + Host isn't loopback on its own port (`serve.mjs:92`). A reverse proxy + that forwards the public Host is refused. +- The Origin check (`serve.mjs:96`) compares against `http://`, so a + browser on `https://` is refused as cross-origin. +- There is no sign-in. Anything that connects to the port reads what a + reader capability reads (`packages/webui/README.md`, the paragraph on + the bus views), and the README forbids a public proxy. +- Two routes write: POST `/api/seen` and `/api/reply` go to the board. +- Nothing runs the Console as a service. It is a foreground `serve`. + +Mos approved these cuts for the preview (2026-10-11): sign-in at the edge +through Pocket ID forward auth, not login code in the Console; a systemd +user unit beside the bus; a preview hostname, so the old build at +mosaic.woltje.com stays; read-only; not held for S7 (row 42) or Gate E +(row 5). ops-01 owns the Pocket ID client, the DNS name, TLS, the proxy +and the route from the edge to this host. Jason approves that route +before anything is served. + +### Owner and reviewer + +Owner: Dewey, who owns the WebUI, after their row 59 review. Reviewers: +Darkwing and Filbert. + +### Files owned + +- `packages/webui/src/serve.mjs`, `packages/webui/src/cli.mjs`, + `packages/webui/README.md` and `packages/webui/tests/` +- a new `scripts/webui-service.sh` and its unit template, beside + `scripts/bus-service.sh` and `packages/cli/systemd/mosaic-bus.service.in` +- the test for that script, in the package that holds the template +- `docs/TOOLS.md`, the new script's entry only. Row 70 also edits this + file; whichever lands second rebases on the first. +- `agents/dewey/work/queue-/` + +### What ships + +A preview mode, off by default. Without its flags the Console behaves +exactly as today, and the existing tests show it. + +- `serve --public-host NAME --auth-header HEADER --allow-user ID`, all + three or none. NAME is a hostname, no port or scheme. The bind stays + loopback; nothing here listens beyond it. +- In preview mode a request's Host must be NAME, or loopback on the + Console's port as today. An Origin, when sent, must be `https://NAME` + for a NAME request, and the loopback form for a loopback request. +- A NAME request must carry HEADER with a value equal to ID. Missing or + different answers 401 with no bus read made. Loopback requests are + unchanged, so the operator's own browser on the host still works. +- In preview mode every POST answers 403 `read-only`, whatever the Host. + `/api/seen` and `/api/reply` don't reach the board. +- Tests for each refusal: wrong Host, wrong Origin, missing header, wrong + user, a POST to each write route, and a partial flag set refused at + start. A test that the loopback path is unchanged in preview mode. +- `scripts/webui-service.sh render|install|uninstall|status` for a + `mosaic-webui.service` user unit, on the `bus-service.sh` pattern: a + template, write to a temp file then rename, `daemon-reload` only, no + start, no restart. It runs the board's `serve` and the Console's + `serve` with the preview flags (as one unit or two, the packet says + which and why), with values from flags or an env file the script names. + The env file holds no secret: the header name, the user id and the + hostname aren't secrets. +- The README replaces "Do not expose either unauthenticated server + through a public proxy" with what the preview mode requires of the + proxy: it strips HEADER from every client request and sets it only + after Pocket ID signs the user in, it preserves Host, it terminates TLS, + and it is the only thing that reaches the port from off the host. It + says plainly that any local process can still connect and send HEADER, + the same exposure the README already describes for the port. +- The packet runs the Console in preview mode with `curl` against + loopback, showing each refusal and one allowed read, with no network + beyond the host. + +### Out of scope + +- OIDC or login code in the Console; Pocket ID config; DNS, TLS, the + proxy and the route from the edge (ops-01). +- Resolving decisions or replying from the browser. REQ-DEC-3 keeps + resolution in the CLI; changing that is a ruling and its own row. +- Live-seat conversations (CHAT-07) and any board change. +- A container image, CI deploy, or changes to the bus unit. +- Installing or starting the unit on dragon-lin. Sage does that after the + row lands, only once Jason has ruled on Q91 and approved ops-01's route. + +### Gate + +Darkwing and Filbert approve on the row's issue naming the candidate +manifest. The webui, control-board and cli node suites and every +`scripts/test-*.sh` green on Sage's gate rerun.