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

@@ -84,6 +84,7 @@
.matrow input:first-child { flex: 1; }
.matrow input:last-of-type { width: 80px; }
footer { text-align: center; color: #b1a0ae; font-size: 13px; margin-top: 32px; font-style: italic; font-family: Georgia, serif; }
footer .chip { font-family: "Avenir Next", "Nunito", sans-serif; font-style: normal; margin-left: 8px; }
</style>
</head>
<body>
@@ -106,7 +107,7 @@
<div class="sub" id="greeting">welcome back</div>
</header>
<main id="view"></main>
<footer>🌙 Studio Notebook · bms.poweredbyotm.com</footer>
<footer id="foot">🌙 Studio Notebook · bms.poweredbyotm.com</footer>
</div>
</div>
<script src="/app.js"></script>