summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-23 11:36:11 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-23 11:36:11 +0200
commit1abb7649b930d35d1f5a76fd72856659b1ee8275 (patch)
tree50d1d3dd905056b75749e22a58e7247e4a4bb0e2 /docs
parent71a702f375750829c634b552217c9925d549828b (diff)
downloadbokf-0.1.69.tar.gz
bokf-0.1.69.zip
web: bokftui in the browser (ttyd + bokfweb login gate); per-user login limitv0.1.69
New image bokf-web (Dockerfile target "web", compose service "web" on 127.0.0.1:8790): Caddy routing with forward_auth, the bokfweb login gate (C, authenticates with bokfd's session.open, per-address limit, cookie + terminal handle, one login handed to the TUI via /redeem) and ttyd running bokftui in web mode in an isolated throwaway HOME. TLS stays with the host's reverse proxy. BOKF_WEB=1 blocks every local file and viewer path in the TUI. bokfd's login limiter is now per user name instead of one global counter (5 wrong guesses from anyone locked out everybody), and a full counter table no longer disables it. The cross build and deploy.sh build and ship both images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/DECISIONS.md14
-rw-r--r--docs/DEPLOY.md60
-rw-r--r--docs/PROTOCOL.md13
-rw-r--r--docs/STATE.md10
-rw-r--r--docs/TUI-GUIDELINES.md7
5 files changed, 101 insertions, 3 deletions
diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md
index c18f49b..e317758 100644
--- a/docs/DECISIONS.md
+++ b/docs/DECISIONS.md
@@ -275,6 +275,20 @@ kept verbatim from the STATE.md they were pruned from (2026-09-21).
the key parts of #11 and #28; `make check` rejects F-keys, `^N` and
`^Enter` in `clients/`.
+30. **Web frontend (2026-09-23)**: bokftui runs in the browser through
+ ttyd in its own container (`bokf-web`), behind a login gate in C
+ (`bokfweb`) that authenticates with bokfd's `session.open` — no second
+ password store — and one login: the gate hands the bokfd session to
+ the TUI through a terminal handle that only works with the login's
+ cookie. Caddy in the container does the routing and `forward_auth`;
+ TLS stays with the host's existing Caddy (port 443 was taken), which
+ proxies `bokf.makandra.eu` to `127.0.0.1:8790`. Every terminal is an
+ isolated process (private HOME, limits, `BOKF_WEB=1`: no local files or
+ programs). bokfd's login limiter became per user name (it was one
+ global counter, so any 5 wrong guesses locked out everybody) and the
+ gate limits per client address. Audience: the owner and Petter first,
+ prepared for more users.
+
## Completed work formerly listed under "Pending decisions"
- Attachments are complete: download (voucher detail `f`, Underlag `Enter`,
diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md
index 6fe6a53..99b7661 100644
--- a/docs/DEPLOY.md
+++ b/docs/DEPLOY.md
@@ -224,6 +224,66 @@ Agents use `token.create` instead of a password; tokens are scoped and
revocable. Only the TLS port is forwarded, and every command still requires
authentication (`meta` and `health` excepted).
+## Web frontend (`https://bokf.makandra.eu/web`)
+
+The `web` service (image `bokf-web`, built from the `web` target of the
+same Dockerfile and shipped by `scripts/deploy.sh` with the daemon) serves
+bokftui in the browser:
+
+```
+browser ──443──▶ host Caddy (TLS) ──▶ 127.0.0.1:8790 bokf-web container
+ Caddy: routing + forward_auth
+ ├─ /web, /web/login, /web/logout ─▶ bokfweb (login gate)
+ └─ /web/tty/* (gate OK only) ────▶ ttyd ─▶ bokftui-web ─▶ bokftui
+ │
+ bokfd ◀── unix socket ┘
+```
+
+- **Login gate** (`bokfweb`, `clients/bokfweb.c`): the login page checks
+ the credentials with bokfd's own `session.open` — there is no second
+ password store, and bokfd's audit and per-user lockout apply. The gate
+ limits failed logins per client address (5 per 15 min) before bokfd's
+ limit is reached. A login sets a cookie (`HttpOnly`, `Secure`,
+ `SameSite=Strict`, 12 h) and redirects to `/web/tty/?arg=<handle>`.
+- **One login**: the terminal wrapper (`deploy/bokftui-web`) trades the
+ handle for the bokfd session on the gate's internal `/redeem` (never
+ routed by Caddy) and starts `bokftui` with `BOKFD_SESSION`, so the TUI
+ opens logged in. Caddy's `forward_auth` lets a request reach ttyd only
+ with a live cookie whose session owns the handle in the URL; a leaked
+ URL is useless without the cookie. Quitting the TUI (or `/web/logout`)
+ closes the bokfd session and with it the web session.
+- **Isolation**: every browser terminal is its own bokftui process with a
+ private throwaway `HOME` (config, drafts, log), memory/CPU/file limits,
+ and `BOKF_WEB=1`: the TUI refuses everything that would read or write
+ files or start programs on the frontend (file browser, save prompts,
+ downloads, the PDF viewer). No shell is ever offered. The container runs
+ as uid 10001, read-only root, `/tmp` tmpfs, all capabilities dropped,
+ `no-new-privileges`, pids/memory limits, and it sees bokfd only through
+ the protocol socket (no database, no secrets, no certificates).
+- **TLS** is the host's reverse proxy. On the NAS, add to the existing
+ Caddy (`/mnt/data/caddy/Caddyfile`) a site block like the others and
+ reload it:
+
+ ```
+ bokf.makandra.eu {
+ tls {
+ dns inwx { ... } # as for the other sites
+ }
+ reverse_proxy 127.0.0.1:8790
+ }
+ ```
+
+ Port 443 is already forwarded for the other sites. The container
+ publishes 8790 on the host's loopback only, so it is unreachable until
+ that block exists.
+- `BOKF_WEB_MAX_SESSIONS` (default 20) caps concurrent terminals.
+ `docker compose logs web` shows logins, logouts and failed attempts
+ (never passwords).
+
+Not yet in the web version: uploading and downloading files (attachments,
+bank files, SRU/eSKD/årsredovisning files, PDFs). The TUI says so where it
+applies; use bokftui on a computer for those.
+
## Mock company
```sh
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 709a475..3c8ba54 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -85,7 +85,11 @@ and returns an opaque, high-entropy session id:
intentional. API tokens survive restarts.
- Sliding TTL, `session_ttl` default 8 h. `session.close` ends one explicitly.
- Passwords are stored as Argon2id hashes. Failed logins are rate limited per
- peer (default: 5 failures per 15 minutes, then `RATE_LIMITED`).
+ user name (5 failures per 15 minutes, then `RATE_LIMITED` for that name
+ only): guessing one account never locks out the others. The table of
+ counters replaces expired or least-failed entries when full, so flooding
+ it with names neither disables the limiter nor lifts a block. The web
+ gate (`bokfweb`) adds its own limit per client address in front of this.
- A user changes their own password with `user.set_password` from a
password session (a token session gets `FORBIDDEN`). The current password
is required — 5 wrong ones per 15 minutes give `RATE_LIMITED` — and the
@@ -946,7 +950,10 @@ commands. Implemented screens (0.1.0-dev):
- **Inloggning** — server, user, password; org picker when several exist.
**Byt bolag** in the main menu reopens the picker during the session;
- **Byt lösenord** calls `user.set_password`.
+ **Byt lösenord** calls `user.set_password`. The same TUI runs in the
+ browser at `/web` (DEPLOY.md "Web frontend"): the login page opens the
+ session with `session.open` and the TUI reuses it; file features are
+ off there.
- **Dashboard** — status line with org, fiscal year, role and user.
- **Verifikat** — list and detail view (rows with column headers, an
underlag section separated by a rule, hash, link to corrected voucher); `c`
@@ -1122,7 +1129,7 @@ beyond the session and calls nothing but public commands.
| `session_ttl` | `8h` | sliding session lifetime |
| `max_line_bytes` | `1048576` | NDJSON line limit |
| `max_attachment_bytes` | `10485760` (10 MiB) | decoded attachment limit |
-| `auth_fail_limit` | `5/15m` | login rate limit per peer |
+| `auth_fail_limit` | `5/15m` | login rate limit per user name (fixed in the code) |
| `synchronous` | `FULL` | SQLite durability (`FULL`/`NORMAL`) |
| `audit_reads` | `false` | log read commands too |
| `allow_org_create` | `true` | any user may create an org |
diff --git a/docs/STATE.md b/docs/STATE.md
index 77ef9a4..93ade1e 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -14,6 +14,16 @@ unit tests and the docs consistency check.
## Resume here (2026-09-23)
+- **Web frontend (2026-09-23, branch `feat/web-frontend`, not deployed)**:
+ image `bokf-web` (Caddy routing + `bokfweb` login gate + ttyd +
+ bokftui in web mode) as compose service `web` on `127.0.0.1:8790`; see
+ DEPLOY.md "Web frontend" and decision #30. Tested end to end locally
+ (login, one-login handoff, foreign handle 403, logout, per-address
+ limit) and the aarch64 static build. **To go live**: deploy, then add
+ the `bokf.makandra.eu` block to the NAS Caddy
+ (`/mnt/data/caddy/Caddyfile`) and reload it. bokfd's login limiter is
+ now per user name (was global: 5 wrong guesses locked out everyone).
+ Next: file upload/download through the browser for the web mode.
- **Context menu and web-safe keys (2026-09-23, branch
`eff/context-menu`)**: `→` (or `^O`, also in table cells) opens
"Åtgärder", a box at the right edge with every action of the view and
diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md
index 650f026..c223514 100644
--- a/docs/TUI-GUIDELINES.md
+++ b/docs/TUI-GUIDELINES.md
@@ -145,6 +145,13 @@ terminal and browser delivers (checked by `make check`):
reload — `^R` is a developer convenience and never the only way).
- Every action is reachable with arrows + `Enter` through the menu; a
letter accelerator is a shortcut, never the only path.
+- **Web mode** (`BOKF_WEB=1`, set by the browser-terminal wrapper): the TUI
+ runs on the web frontend, not on the user's machine, so a screen never
+ reads or writes a local file or starts a program there. Every such path
+ goes through `ui_web_block(title)` (`clients/ui.c`), which shows why and
+ returns 1: the file browser, `save_cache_and_open` (PDF viewer), the
+ attachment download and the SRU/eSKD/årsredovisning save prompts. New
+ file features must call it too (pty scenario `web-mode`).
### Implementation status