feat: platform AI names behind Sign in with OTM (v0.3.0)

- OTM SSO: /api/auth/otm-sso verifies the control-plane ticket (vendored
  verifier), in-memory jti claim, mints an HMAC cookie (src/lib/session.ts);
  /api/auth/session + /api/auth/signout. No accounts, no DB.
- /api/ai-names: server-side call to the platform's metered gateway
  (ANTHROPIC_BASE_URL + per-app gateway token), 401 without a session,
  ~20 req/h per OTM account. Prompt/parser shared with the BYO-key path
  via src/lib/ai-prompt.ts.
- Finder: 'Sign in with OTM for AI names' link → 'AI names' toggle when
  signed in; own key still overrides.

Pairs with platform 0.116.0 (needsAnthropic + needsAuthSecret on names).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XStQKxPfEjrTvWo83KFCxG
This commit is contained in:
2026-08-16 18:27:38 -05:00
parent c7d6812466
commit c307a75880
17 changed files with 666 additions and 60 deletions

View File

@@ -4,7 +4,7 @@ Free business-name generator with live domain availability, at
**names.poweredbyotm.com**. A first-party **Powered by OTM** app.
- **Stack:** Next.js 15 (App Router, `output: "standalone"`), React 18, no DB,
no auth, public. Generation runs **client-side**; availability is a server
no local accounts, public. Sign in with OTM (SSO) exists only to gate AI. Generation runs **client-side**; availability is a server
route (browsers can't do DNS, and RDAP servers send no CORS headers).
- **Repo:** `tonym/names` on `git.poweredbyotm.com`.
- **Deploy:** platform MCP — `build_app("names")` then `deploy_app("names")`.
@@ -15,8 +15,9 @@ Free business-name generator with live domain availability, at
## Deploy
Registered in `platform/apps/otm-admin/lib/first-party-apps.ts` as
`publicAccess: true`, `needsDb: false`, `needsAuthSecret: false`,
`needsSuggestions: true`, `uploads: "/app/public/uploads"`.
`publicAccess: true`, `needsDb: false`, `needsAuthSecret: true` (only for
`OTM_SSO_SECRET`/`AUTH_SECRET` — see below), `needsSuggestions: true`,
`needsAnthropic: true`, `uploads: "/app/public/uploads"`.
Adding this app to the registry required an otm-admin release (0.113.0) — and
because the auto-deploy webhook is **gated on green CI**, `names` does not
@@ -94,14 +95,36 @@ snapshots the last-seen status per domain; the Finder keeps snapshots fresh from
live lookups and **Re-check** re-queries through the normal availability route.
No server state, on purpose.
## Keys stay in the browser
## AI — two paths, one brief
Optional AI names use the **visitor's own** Anthropic/Gemini key from
`localStorage` (`names.aiKey`), called browser-direct — same pattern as `rpo`'s
Google Vision key, for the same reason: the platform has no per-app env tool,
and a free public tool must not hold a metered credential. The
`anthropic-dangerous-direct-browser-access` header is correct *here* and would be
wrong in a product that owns the key.
`src/lib/ai-prompt.ts` holds the prompt + parser both paths share; change the
brief there, never in one path.
1. **Platform gateway (default, needs OTM sign-in).** The platform registers
this app `needsAnthropic` and injects `ANTHROPIC_BASE_URL` (its metered
gateway) + `ANTHROPIC_API_KEY` (this app's *gateway token* `otmapp_names_…`,
NOT an sk-ant key — see `platform/apps/otm-admin/lib/first-party-ai.ts`).
`app/api/ai-names` calls it server-side, **only with a session cookie**, plus
~20 requests/hour per OTM account. Usage is metered against the "Powered By
OTM" customer, so the platform's monthly cap on `/settings/ai` is the hard
stop. **The login gate is what makes it safe to hold a metered credential on
a free public tool — do not add an anonymous path to `/api/ai-names`.**
2. **Bring your own key (override).** `localStorage` `names.aiKey`, called
browser-direct — same pattern as `rpo`'s Google Vision key. The
`anthropic-dangerous-direct-browser-access` header is correct *here* and
would be wrong in a product that owns the key. When a key is saved, the UI
shows only this path.
## Sign in with OTM — no accounts here
`src/lib/otm.ts` (`app:names` audience, bounce URLs) → OTM's `/sso/authorize`
`app/api/auth/otm-sso` verifies the 60s ticket (`src/lib/sso-ticket.ts`,
vendored verbatim from `@otm/account-panel` — keep in sync) and mints our own
HMAC cookie (`src/lib/session.ts`, `names.session`, 12h, signed with
`AUTH_SECRET`). jti replay guard is an in-memory Map — fine for one container
and 60s tickets. No user rows, no DB. `/api/auth/session` tells the UI who's
signed in and whether platform AI is wired (`platformAi`); if the env is
missing the feature is simply absent.
## Registrar links