app: OTM integrations — operator-magic + account SSO + suggestion jots + self-update (v0.4.0)

Three new doors, all wire-contract ports of the shared @otm/account-panel
factories (this backbone is plain Node — the Next factories can't mount):

- GET /api/auth/operator-magic — OTM 'Log in as admin'. HS256 verify against
  OPERATOR_SHARED_SECRET (alg allowlist, constant-time, action-claim rejected
  for flow separation), single-use jti via OperatorMagicConsumed (+ who/when
  audit), 1-HOUR session with cookie Max-Age derived from the payload, 🔑
  support-session banner in the app, every failure a 302 reason redirect.
- GET /api/auth/otm-sso — the OTM /account tile. Stricter sso-ticket verify
  (exp mandatory, audience compared), email-then-role actor mapping, safeNext.
- 💡 suggestion jot chip — files a Gitea issue (shared attribution footer,
  8k cap, 10s timeout); on any failure the jot is kept as a note instead.
  Success also leaves a '#N — …' note so she has her own record.
- Footer version chip + one-tap self-update (HMAC-signed OTM proxy with
  explicit field picking — foreign JSON can never reach the _setSession/_redirect
  control keys), owner-gated, 10-min sessionStorage cache on the check.

Hardening that rode along: session key now derived from OPERATOR_SHARED_SECRET
(managed boot REFUSES the old derivable DATABASE_URL fallback), 1MB JSON body
cap, login.html prototype-lookup fix. NOTE: server.mjs previously contained a
literal NUL byte that made git treat it as binary — this commit re-encodes it
as an escape (behavior identical) and adds .gitattributes so source diffs can
never go blind again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmybqyQmZWfcqA1vMP4jbQ
This commit is contained in:
2026-08-09 22:31:34 -05:00
parent 3269032525
commit 9e21895232
10 changed files with 122 additions and 11 deletions

View File

@@ -16,11 +16,30 @@ is captured in [VISION.md](VISION.md). The `site` instance is the storefront lay
**The backbone is LIVE (2026-08-06): https://bms.poweredbyotm.com** — template
`bms-backbone-next` (otm-admin 0.111.0), container `bms-backbone-bms` on anvil,
built from this repo's `app/` (Postgres + Prisma port of the prototype, OTM
owner-seed auth). Deploy sequence: bump `app/lib/version.mjs` + push here → in
OTM admin, Build template `bms-backbone-next` → update/recreate the instance.
owner-seed auth). Deploy sequence: bump `app/lib/version.mjs` **and
`app/package.json`** + push here → the push webhook now AUTO-BUILDS the image
(`autoBuildOnly`, otm-admin 0.111.1) → deploying it stays a manual click on
OTM `/instances` (or `redeploy_service` via the MCP).
Bonna's laptop data was migrated; `prototype/` is now legacy reference (the
laptop `.command` still works but the hosted notebook is the daily driver).
## v0.4.0 — OTM integrations (what needs which env)
All three are provision-injected via the template's flags; a hand-provision
that misses one degrades silently, so here's the lookup:
| Feature | Env it needs | Symptom when unset |
|---|---|---|
| OTM "Log in as admin" (`GET /api/auth/operator-magic`) + OTM account SSO (`GET /api/auth/otm-sso`) | `OPERATOR_SHARED_SECRET`; SSO also `OTM_SSO_SECRET` + `OTM_SSO_AUDIENCE` | `/login?reason=not_enabled` |
| Suggestion jots → Gitea issues (the 💡 chip) | `GITEA_URL` + `GITEA_TOKEN` + `GITEA_REPO` | jot saved as a note tagged `suggestion` instead (`fallback: true`) — never lost |
| Self-update (footer ✨ chip) | `OTM_API_BASE` + `OTM_SERVICE_ID` + `OPERATOR_SHARED_SECRET` | footer shows no update chip; API answers 503 `not_configured` |
Session-signing note: the cookie key is derived from `OPERATOR_SHARED_SECRET`
(domain-separated HMAC) unless an explicit `SESSION_SECRET` is set; a managed
instance (`OTM_MANAGED=true`) with neither refuses to boot. OTM sign-ins are
recorded in `OperatorMagicConsumed` (jti + who + when); operator sessions last
1h and show a 🔑 support-session banner.
## The repo
- Gitea: `https://git.poweredbyotm.com/bonna61/Bonna-Moon-Studio.git`