diff --git a/docs/CLIENT_PORTAL.md b/docs/CLIENT_PORTAL.md index b74a94d..0fc2e88 100644 --- a/docs/CLIENT_PORTAL.md +++ b/docs/CLIENT_PORTAL.md @@ -132,6 +132,32 @@ live data, all from cache. magic-link (passwordless email) and/or accounts, proper sessions, password reset, and likely auth for the *internal* app too. Reverse-proxy + TLS posture. +## Going to prod (M1) + +1. **Run the migration on the prod DB** — `migrate_add_client_portal.py` adds + `projects.client_id` (the new tables auto-create via `create_all`). Skipping it + 500s anything that touches `Project.client_id`. This is the silent killer. + ```bash + docker compose exec web-app python3 backend/migrate_add_client_portal.py + ``` +2. **Set a real `SECRET_KEY`** in the prod env (compose). The portal signs session + cookies with it; the insecure dev default (it logs a warning at boot) is + forgeable. Non-negotiable for an internet-facing portal. +3. **SLMM_BASE_URL** — prod base compose already points at `:8100` (correct; the + `:9100` mismatch is a dev-only override quirk). For full live data (L1/L10 + + chart backfill) prod SLMM must be on the `dev` build with its migrations + (`migrate_add_ln_percentiles`, `migrate_add_monitor_enabled`) and **keepalive on** + for the client's units — otherwise the portal degrades gracefully (cards show + `--`, chart empty), it just isn't fully populated. +4. **Seed real clients** with the CLI (`backend/portal_admin.py`): `create-client` + → `link-project` (a real sound project with an active SLM assignment) → + `mint-link` → send the client the printed URL (shown once). +5. **Exposure** — portal routes are auth-gated, but port 8001 still serves the + whole *internal* app with no auth. Before real clients are on it, the portal + should sit behind the reverse proxy with only `/portal/*` exposed (or the app + restricted). This is the point where the parked reverse-proxy/TLS work becomes + load-bearing. + ## Security notes - Portal is auth-gated from day one (even the interim gate) — never wide-open like