feat(web): login-gated BFF auth — sealed session cookie + server-side token injection
ci / rust (push) Has been cancelled
ci / rust (push) Has been cancelled
Single-user, LAN-reachable-but-gated. The web server is a backend-for-frontend:
- Login: POST /_auth/login {password} checks PUNKTFUNK_UI_PASSWORD (constant-time) and
sets a SEALED session cookie (h3 useSession / AES-GCM). server/middleware/auth.ts gates
every request — pages 302 → /login, /api → 401 — and FAILS CLOSED (503) when
PUNKTFUNK_UI_PASSWORD is unset, so a misconfigured LAN-exposed server admits no one.
- The management API stays loopback-only + token (never LAN-exposed). The proxy
(server/routes/api/[...].ts) injects PUNKTFUNK_MGMT_TOKEN server-side and drops the
browser's cookie before forwarding — the token never reaches the browser, which only
holds the session cookie.
Nitro doesn't auto-scan a server/ dir, so the Nitro plugin gets an explicit scanDirs to
pick up middleware + routes. Client: removed the localStorage token (server injects it);
the fetcher bounces to /login on 401; new /login page (bare, no shell); Settings drops the
token field and gains a Sign-out button; en/de strings.
Validated live end to end: unauth /→302, /api→401; wrong pw→401; right pw→200+cookie;
authed /api/v1/status→200 (proxied, mgmt token injected — the host required it); logout→
session cleared→401. tsc + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-5
@@ -35,14 +35,32 @@ If the host runs with `--mgmt-token`, set it under **Settings → API token** (s
|
||||
|
||||
```sh
|
||||
bun run build # → .output/ (Nitro server, `bun` preset, + .output/public assets)
|
||||
PORT=3000 HOST=0.0.0.0 bun run start # = bun run .output/server/index.mjs
|
||||
PORT=3000 HOST=0.0.0.0 \
|
||||
PUNKTFUNK_UI_PASSWORD=… PUNKTFUNK_MGMT_TOKEN=… \
|
||||
bun run start # = bun run .output/server/index.mjs
|
||||
bun run lint # tsc --noEmit
|
||||
```
|
||||
|
||||
The built **Nitro Bun server** SSR-renders the app and proxies `/api/**` to the management
|
||||
host (a Nitro `routeRules` proxy → `PUNKTFUNK_MGMT_URL`, default `127.0.0.1:47990`), so the
|
||||
browser stays same-origin (bearer token rides along, no CORS). Run it on the same box as
|
||||
the host; it serves the console on `:3000` (or `$PORT`).
|
||||
The built **Nitro Bun server** SSR-renders the app and is the only thing exposed on the LAN.
|
||||
Run it on the same box as the host; it serves the console on `:3000` (or `$PORT`).
|
||||
|
||||
## Auth (backend-for-frontend)
|
||||
|
||||
Single-user, login-gated. Config via env (see `.env.example`):
|
||||
|
||||
- The console requires a **login** (`PUNKTFUNK_UI_PASSWORD`). On success the server sets a
|
||||
**sealed session cookie** (h3 `useSession`, AES-GCM). `server/middleware/auth.ts` gates
|
||||
*every* request — pages redirect to `/login`, `/api` returns 401 — and **fails closed**
|
||||
(503) if `PUNKTFUNK_UI_PASSWORD` is unset, so a misconfigured LAN server admits no one.
|
||||
- The **management API stays loopback-only + token** — never LAN-exposed. The web server
|
||||
holds `PUNKTFUNK_MGMT_TOKEN` server-side and injects it when proxying `/api/**` →
|
||||
`PUNKTFUNK_MGMT_URL` (`server/routes/api/[...].ts`). **The token never reaches the
|
||||
browser**; the browser only ever holds the session cookie.
|
||||
|
||||
So: `browser ──password──▶ web server (session cookie) ──mgmt token, server-side──▶ mgmt API`.
|
||||
Run the host with a matching token: `cargo run -rp punktfunk-host -- serve` +
|
||||
`PUNKTFUNK_MGMT_TOKEN=…` (or `--mgmt-token …`). `vite dev` has no gate (localhost-only) and
|
||||
proxies straight to the loopback mgmt API.
|
||||
|
||||
> Toolchain notes (load-bearing): TanStack Start's `start-plugin-core` peer-requires
|
||||
> **Vite ≥ 7** — on Vite 6 the build's prerender/post-build hook silently doesn't run.
|
||||
|
||||
Reference in New Issue
Block a user