fuzzyworld

fuzzyworld-tools / files / docs / admin.md

admin.md

4.7 KB · history · blame

Admin panel (fw admin)

fw admin [--port N] [--no-open] [--code-dir DIR]

lib/admin.py (Python standard library only) runs on the laptop. It starts a small web page on 127.0.0.1 for bug reports, blog comments and what is deployed where, prints a one-time login URL and opens it in the browser.

FW_CODE_DIR (or --code-dir, default ~/code) is the folder holding the apps' working copies.

The server-side fw must know issues list --json, issues show --json, issues record, comments list --json and deploys --json, so deploy the server scripts first (npm run deploy).

Security model

Threat Defence
Other machines binds to 127.0.0.1 only
Strangers on this machine the login URL carries a random key that works once and sets a session cookie fwadmin-<port> (HttpOnly, SameSite=Strict); any other request without the cookie gets 403
DNS rebinding the Host header must be exactly 127.0.0.1:<port>, else 421
Cross-site requests every POST needs a random per-launch token in its form (a separate secret from the cookie), an Origin (if sent) equal to the page's own, and Sec-Fetch-Site (if sent) same-origin or none; otherwise 403 and no command runs
Script injection strict Content-Security-Policy (no scripts, forms only to itself, no framing), nosniff, no referrer, no caching
Command injection mail text never reaches a command line: only a validated repo name, issue number, comment id and a fixed set of action words, each checked again before ssh is run
Mistakes hide, reject, untrust, remove and every mode change ask for confirmation first
Leaks query strings are never logged

Pages

Reports (/reports)

One table across repos: repo, number, title, state, public, mode, record, messages shown/total, age. Filters: Waiting (the default when anything waits), Has replay and All.

A report (/reports/<repo>/<n>)

Watch (on a report with a run record)

It works only while npm run dev runs in that repo. The record folder is kept, because the dev server needs it: one fw-admin-<repo>-<n>-... folder per replay under the system temp dir, mode 700. Delete them when you like.

Comments (/comments)

Pending comments in full, with the post link, author, date and source. Buttons: Approve, Approve + trust (email comments only), Reject, and Untrust (trusted senders only). Below, "Recently approved" (the 20 newest) with a Remove button each.

Deploy (/deploy, read-only for now)

Uses fw deploys --json. There is no button yet: deploy with npm run deploy.

Behaviour

The server handles one request at a time (an idle connection cannot stall it). After each action the page reloads with the command's output (or its error) at the top. These messages are server-wide, not per browser tab.

Source
1# Admin panel (`fw admin`)
2
3```bash
4fw admin [--port N] [--no-open] [--code-dir DIR]
5```
6
7`lib/admin.py` (Python standard library only) runs **on the laptop**. It starts a small web page on `127.0.0.1` for
8bug reports, blog comments and what is deployed where, prints a one-time login URL and opens it in the browser.
9
10- The pages are server-rendered HTML with plain `POST` forms and **no JavaScript**.
11- Every button runs the matching `~/bin/fw ...` command on the server over ssh, exactly what you would type (`FW_HOST`, default `dh`).
12- The panel keeps no state and never shows a password.
13- It is local-only on purpose: it is never hosted on DreamHost.
14
15`FW_CODE_DIR` (or `--code-dir`, default `~/code`) is the folder holding the apps' working copies.
16
17The server-side `fw` must know `issues list --json`, `issues show --json`, `issues record`, `comments list --json`
18and `deploys --json`, so deploy the server scripts first (`npm run deploy`).
19
20## Security model
21
22| Threat | Defence |
23|---|---|
24| Other machines | binds to `127.0.0.1` only |
25| Strangers on this machine | the login URL carries a random key that works once and sets a session cookie `fwadmin-<port>` (`HttpOnly`, `SameSite=Strict`); any other request without the cookie gets 403 |
26| DNS rebinding | the `Host` header must be exactly `127.0.0.1:<port>`, else 421 |
27| Cross-site requests | every `POST` needs a random per-launch token in its form (a separate secret from the cookie), an `Origin` (if sent) equal to the page's own, and `Sec-Fetch-Site` (if sent) `same-origin` or `none`; otherwise 403 and no command runs |
28| Script injection | strict `Content-Security-Policy` (no scripts, forms only to itself, no framing), `nosniff`, no referrer, no caching |
29| Command injection | mail text never reaches a command line: only a validated repo name, issue number, comment id and a fixed set of action words, each checked again before ssh is run |
30| Mistakes | hide, reject, untrust, remove and every mode change ask for confirmation first |
31| Leaks | query strings are never logged |
32
33## Pages
34
35### Reports (`/reports`)
36
37One table across repos: repo, number, title, state, public, mode, record, messages shown/total, age. Filters:
38**Waiting** (the default when anything waits), **Has replay** and **All**.
39
40### A report (`/reports/<repo>/<n>`)
41
42- The details table and every message in full, **including reports that are not published**, with who sent it (Reporter A, B, ...), whether it passed authentication and whether it is shown.
43- Buttons: Approve, Hide, Close / Reopen.
44- Radio buttons for the repo's mode (public, reviewed, private) with Set.
45- Previous / next links, and a link to the public page when there is one.
46
47### Watch (on a report with a run record)
48
49- A link to the deployed replay viewer, when the record is public.
50- Always "Download record" (`issues record`).
51- When `<code dir>/<repo>/package.json` has a `report-replay` script, "Prepare dev replay":
52 1. it saves the record to a fresh temporary folder,
53 2. runs `npm run --silent report-replay -- <record.json>` in `<code dir>/<repo>` (with `--rules <commit>` when the report's details have a commit), 60 s at most,
54 3. shows the output, with the `http://localhost:5173/?replay=...` address as a link.
55
56 It works only while `npm run dev` runs in that repo. The record folder is kept, because the dev server needs it:
57 one `fw-admin-<repo>-<n>-...` folder per replay under the system temp dir, mode 700. Delete them when you like.
58
59### Comments (`/comments`)
60
61Pending comments in full, with the post link, author, date and source. Buttons: Approve, Approve + trust (email
62comments only), Reject, and Untrust (trusted senders only). Below, "Recently approved" (the 20 newest) with a Remove
63button each.
64
65### Deploy (`/deploy`, read-only for now)
66
67Uses `fw deploys --json`. There is no button yet: deploy with `npm run deploy`.
68
69- **Tools:** per repo that declares deploy targets (in `package.json` under `fuzzyworld.deploy`), each target's state (up to date, pending, never deployed), the commit and time it was deployed from, and the commits on the default branch since that touch its folders; per laptop for `perMachine` targets.
70- **Apps:** every deployed app against its repo: state, deployed commit, time, and the commits since.
71 - Commits that only touch Markdown, CI config, the licence, `.gitignore`, `docs/` or `screenshots/` are ignored, so a repo-wide CI-file rename does not mark every app pending.
72 - The state is "unknown" when the repo or the commit is not on the server.
73
74## Behaviour
75
76The server handles one request at a time (an idle connection cannot stall it). After each action the page reloads
77with the command's output (or its error) at the top. These messages are server-wide, not per browser tab.