Files
visitor-signin/.claude-context/01-architecture.md
T
2026-09-23 01:52:28 +10:00

72 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# visitor-signin — architecture
Current state of the design. Established 2026-09-23 (first recorded session). When a later
session changes something here, edit the relevant section and note the date and reason.
## Process layout
One Node process in one container:
- HTTPS listener on container port 3000 (kiosk, admin, APIs).
- Plain-HTTP helper on 3001: serves the CA certificate and redirects everything else to HTTPS.
- Background jobs in-process: photo retention purge, optional nightly auto sign-out, daily
certificate renewal check, Google Sheet sync retries.
## Data
- SQLite at `/data/visitors.db` (bind-mounted `./data`), WAL mode.
- Photos on disk under `/data/photos/YYYY/MM/`; banners under `/data/branding/`; certs under
`/data/certs/`.
- Migrations are additive and idempotent (`addColumn` in `src/db.js`) — never destructive.
- `visits` denormalises name, contact, host and site name, so history survives deletion of the
related records.
## Identity and access
- Admin accounts: email + scrypt password + TOTP (RFC 6238), eight one-shot recovery codes.
Roles `owner` (manages admins and sites) and `admin` (optionally scoped to one site).
Optional email-domain restriction. First account created once from `ADMIN_BOOTSTRAP_*`.
- Recurring visitors: mobile number is the username; PIN is 4 digits, stored AES-256-GCM
encrypted with a keyed HMAC `pin_lookup` for uniqueness. Phone, email and PIN are each unique.
- `APP_SECRET` keys sessions, PIN encryption and `pin_lookup`. Changing it breaks every PIN.
## Multi-site
- `sites` owns: people to visit (`hosts`), badge settings, printer settings, branding.
- A kiosk is bound to a site by `/?site=<slug>` (remembered in the tablet's local storage) or a
first-run picker; a single-site install never asks.
- Sign in, sign out and duplicate detection are scoped per site.
## TLS
- Local CA (10 years) + server certificate (398 days), generated by the container with openssl.
- Server cert renews inside 30 days and is hot-swapped with `setSecureContext`; the CA is never
replaced except by an explicit `--force`, so devices trust it once.
- Reissue is triggered by a change in `HTTPS_HOSTNAMES` (tracked in `certs/.hostnames.json`),
not by the full SAN list, because the container's own bridge IP changes between restarts.
- CA distributed as PEM `.crt`, DER `.cer` (required by Jamf Pro) and a zip bundle (browsers
block bare certificate downloads).
## Printing
- The server renders the badge (`src/printer.js`, `@napi-rs/canvas`) at 300 dpi, 696 dots across
(QL-820NWB head width on a 62 mm roll), and sends it with `brother_ql` over TCP 9100.
- Photos are halftoned in the renderer (Floyd–Steinberg by default) so preview equals print.
- The loaded roll is an explicit per-site setting (`printer_label`: `62` or `62red`). `62red`
always adds `--red`; without it the job is monochrome and the printer refuses it.
- Sign in waits up to `PRINT_SIGNIN_WAIT_MS` for the print; on failure the kiosk falls back to a
browser print of the HTML badge (`src/sites.js`).
- `brother_ql`'s network backend never reads the socket and the QL-820NWB does not answer status
requests on 9100, so the server cannot confirm a label came out. Brother Status Monitor on a
connected PC is the source of truth for refusals.
## Google Sheet
- Evacuation mirror only: one tab ("On site now"), rewritten in full on every sign in/out and
every 15 minutes. No history is sent to Google; history lives in the app (Visit log, CSV).
- A failed write marks the tab stale and is retried; it never blocks the kiosk.
## Front end
- Three separate documents: kiosk (`index.html`), login (`login.html`), console (`admin.html`).
- Kiosk theming is per site: four chosen colours, the rest derived with WCAG contrast rules.
- HTML/JS/CSS served with `Cache-Control: no-cache` so an upgrade is never masked by a cached
console.
## Deployment
- Author on Windows, deploy on an Ubuntu docker host from the `deploy` branch with
`git reset --hard origin/deploy` (the host is a mirror, never merges).
- `docker-entrypoint.sh` fixes ownership of the bind-mounted `/data`, then drops to `node`.
- `pull_policy: build` — the image is always built locally.