Docs browser login + Kapa Agent session endpoint (Ask AI phase 2) - #190
Open
JakeSCahill wants to merge 11 commits into
Open
Docs browser login + Kapa Agent session endpoint (Ask AI phase 2)#190JakeSCahill wants to merge 11 commits into
JakeSCahill wants to merge 11 commits into
Conversation
Builds on the OAuth 2.1 AS from #181 to add "phase 2": a human browser login for the docs site, plus the backend the Ask AI Agent SDK widget (docs-ui) needs. - docs-login.mjs — /login, /logout, /auth/me. /login reuses the existing Auth0 federation leg (same public client + allow-listed /mcp/callback); auth requests are tagged flow:'docs-login'. - mcp-oauth.mjs — the shared /callback now branches on flow:'docs-login' to set a browser session cookie and redirect back, instead of minting an OAuth code. - lib/docs-session.mjs — stateless RS256-JWT session (same signing key as MCP access tokens, audience-separated `${origin}/docs-session`), HttpOnly rp_docs_session cookie + JS-readable rp_docs_auth hint; open-redirect guard. - kapa-session.mjs — mints short-lived Kapa Agent SDK session tokens; holds the Kapa API key server-side, derives external_owner_id from the docs session (salted sub), returns { session_token, expires_at, authenticated, user }. 401 for anonymous visitors (agent tier is signed-in only). - api-feedback form + mcp.mjs — add `source` and `thread-id` fields so widget vs MCP submissions are distinguishable and carry conversation context. - pages.mjs — interstitial takes heading/lead params, switched to the indigo AI-surface palette, added dark mode (follows the docs `theme` key). New env: KAPA_PROJECT_ID (+ KAPA_OWNER_SALT for salting), optional DOCS_LOGIN_URL. Tests: tests/docs-session.test.ts (8) + existing suites pass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
✅ Deploy Preview for redpanda-documentation ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Changing it re-keys every external_owner_id and orphans all saved Kapa conversation history. Set once, back up, never rotate. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Point the preview playbook's UI bundle at the docs-ui prerelease v3.2.0-beta.1 (PR #395: Agent SDK widget + sign-in redesign) so this PR's deploy preview exercises the full login + agent flow. Temporary: revert to releases/latest before merge. Production antora-playbook.yml is unchanged and still tracks /latest/. Co-Authored-By: Claude Opus 4.8 <[email protected]>
beta.2 is docs-ui #395 merged with current main (#396/#397/#398), so the deploy preview now carries the latest main fixes plus the Agent SDK widget and sign-in redesign. Still temporary: revert to releases/latest before merge. Co-Authored-By: Claude Opus 4.8 <[email protected]>
docs-login reuses the shared auth_requests table but, unlike the MCP OAuth
flow, has no downstream OAuth client: it never sets client_id or
client_redirect_uri, and instead carries flow ('docs-login') + return_to
through the upstream leg for the shared /callback to branch on.
The Neon backend only had the MCP columns (client_id/client_redirect_uri
NOT NULL, no flow/return_to), so docs-login worked on the Blobs backend
(stores the whole object) but crashed on Neon with:
null value in column "client_id" ... violates not-null constraint
- Migration: relax client_id/client_redirect_uri to nullable, add
flow + return_to columns (idempotent, backward compatible with MCP).
- neon.mjs: persist and return flow + return_to; tolerate null client fields.
- test: apply all migrations (not just the first) before the DB-backed run.
NOTE: the migration must be applied to the Neon database before this takes
effect (no auto-runner). ADD COLUMN IF NOT EXISTS / DROP NOT NULL are safe
to run against the live table.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
beta.3 adds the sign-in modal early-click fix (docs-ui #395) on top of the beta.2 content. Still temporary: revert to releases/latest before merge. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…NC-2905) Logging out only cleared our session cookies while the shared Cloud Auth0 SSO session stayed live, so the next sign-in silently re-authenticated. To the user logout looked broken, and a retry loop bounced the browser through the Cloud authorize flow repeatedly — which made the Cloud console fire and cancel read RPCs, spiking the Public API error rate enough to page SEV-1 (INC-2905, judged a false positive, no customer impact). Fix (local logout, no cross-product side effect): - /logout sets a short-lived rp_docs_reauth flag alongside clearing the session. - The next /login sees the flag and requests Auth0 prompt=login (no silent SSO re-login), then consumes the flag so ordinary later sign-ins stay one-click. - buildAuthorizeUrl gains an optional `prompt` param. This deliberately does NOT do a federated Auth0 logout (which would also sign the user out of Redpanda Cloud + needs an Auth0 Allowed Logout URLs entry). If we want true SSO logout, that's a follow-up decision with Cloud/identity. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…y (Michele feedback) Replaces the vague 'open pages for you' in the sign-in interstitial lead, matching the drawer/modal wording. No em dashes. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…arer
The Ask AI widget's API reference tools call the docs MCP server from the
browser. mcp.mjs authenticates with Authorization: Bearer only (audience
${origin}/mcp) and never reads cookies, so once REQUIRE_AUTH is enforced
the widget needs a token source. This endpoint verifies the rp_docs_session
cookie (readDocsSession) and re-mints a 15-minute access token with the
same signing key and claims shape the OAuth AS produces, preserving the
docs-session/mcp audience separation.
Modeled on kapa-session.mjs: same CORS allowlist, rate limiting, 401
shape, and KAPA_TEST_OWNER_ID dev escape hatch.
Co-Authored-By: Claude Fable 5 <[email protected]>
…gout Cold, the docs-login function stalls ~6s (Lambda cold start + Neon scale-to-zero resume on the /login INSERT), and logout paid the same cold start for what is just cookie clearing. - /auth/warm: 204 after a best-effort store ping() (SELECT 1 on Neon, no-op on Blobs). The docs-ui header fetches it on sign-in/account-menu intent so the function and DB are warm when /login is clicked. - /login?disclosed=1 302s straight to Auth0: the referring docs-ui surface already showed the privacy note, so the interstitial would be a redundant second consent click. Bare /login keeps the interstitial. - Interstitial Continue button shows "Redirecting…" and blocks re-clicks (benefits the MCP flow too). - /logout moved to an edge function (near-zero cold start): cookie clearing, the INC-2905 rp_docs_reauth flag, and the safeReturnTo guard ported byte-identically from docs-login.mjs / lib/docs-session.mjs. Pairs with the docs-ui prewarm/disclosed=1 changes in redpanda-data/docs-ui#395. Co-Authored-By: Claude Fable 5 <[email protected]>
Picks up the docs-ui prewarm/disclosed=1/sign-out-feedback frontend so the full login flow can be tested end-to-end on this deploy preview. Still TEMPORARY - revert to /latest/ before merge. Co-Authored-By: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #181 — targets
feature/mcp-email-authand should merge after (or into) it, since it reuses that PR's OAuth 2.1 core (lib/oauth/*, the Auth0 federation, the signing key, the/mcp/callbackroute). Retarget tomainonce #181 lands.What this adds
This is the backend half of the docs Ask AI upgrade (the docs-ui widget PR is separate). Two things:
1. Docs browser login ("phase 2" from #181's description)
GET /login?return_to=…/GET /logout/GET /auth/me(docs-login.mjs)./loginreuses the exact Auth0 federation leg the MCP flow already uses — same public client, same allow-listed/mcp/callbackredirect URI — so no Auth0 config changes are needed. Auth requests are taggedflow: 'docs-login'./callbackinmcp-oauth.mjsbranches on that flag: instead of minting an OAuth code, it sets a browser session and redirects back toreturn_to.${origin}/docs-session) so it can never be replayed as an MCP access token or vice-versa. Stored in an HttpOnlyrp_docs_sessioncookie, with a JS-readablerp_docs_authhint cookie so the widget/header can cheaply detect login state.safeReturnToguards against open redirects. (lib/docs-session.mjs)2. Kapa Agent SDK session endpoint
POST /kapa/session(kapa-session.mjs) holds the Kapa API key server-side and mints short-lived Agent SDK session tokens. For signed-in users it derives a stable, saltedexternal_owner_idfrom the docs session (so Kapa can store conversation history per user); anonymous requests get401 auth_required— the agent tier is signed-in only.{ session_token, expires_at, authenticated, user: { email, domain } }; CORS allowlist covers localhost +*.netlify.apppreviews (safe only because the cookie isSameSite=Lax— commented in code).Also: the
api-feedbackNetlify form +mcp.mjsgainsource(mcp vs ask-ai-widget) andthread-idfields so the docs team can tell submissions apart and follow the conversation without storing raw text. The login interstitial (pages.mjs) tookheading/leadparams, switched to the indigo AI-surface palette, and got dark mode (follows the docsthemelocalStorage key).New env vars
KAPA_PROJECT_ID(required for/kapa/session) —97f44223-f930-4fb9-ae1e-ecd436a4d85cKAPA_OWNER_SALT(recommended) — the Kapaexternal_owner_idissha256(salt + auth0_sub). Why salt: so we never hand Kapa the raw Auth0sub(our cross-system identity key), and so the owner id can't be reversed or cross-linked to a user by a third party (an unsalted hash of a known id is globally reproducible). Set once and NEVER rotate/change — it keys every user's conversation history; changing it orphans all saved history. Generate withopenssl rand -hex 32; store in the secret manager; mark do-not-rotate.DOCS_LOGIN_URL(optional) — overrides the sign-in target; defaults to/loginTesting
tests/docs-session.test.ts(8 — mint/verify round-trip, cross-origin audience rejection, open-redirect guard) + full existing suite green.MCP_OAUTH_UPSTREAM=mock netlify dev(mock IdP, no Auth0) and against the integration Auth0 tenant (REDPANDA_OAUTH_CLIENT_ID+ issuer, localhost is allow-listed): sign-in screen →/login→ interstitial → IdP → session cookie → agent tier + history under the cookie-derived owner →/logout.Deploy note
/kapa/sessionmust be live before the docs-ui widget bundle ships (the widget calls it). Form-registration change should deploy with/before submissions carrying the new fields.🤖 Generated with Claude Code