Skip to content

Docs browser login + Kapa Agent session endpoint (Ask AI phase 2) - #190

Open
JakeSCahill wants to merge 11 commits into
feature/mcp-email-authfrom
feature/docs-login-ask-ai-session
Open

Docs browser login + Kapa Agent session endpoint (Ask AI phase 2)#190
JakeSCahill wants to merge 11 commits into
feature/mcp-email-authfrom
feature/docs-login-ask-ai-session

Conversation

@JakeSCahill

@JakeSCahill JakeSCahill commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Stacked on #181 — targets feature/mcp-email-auth and 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/callback route). Retarget to main once #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). /login reuses the exact Auth0 federation leg the MCP flow already uses — same public client, same allow-listed /mcp/callback redirect URI — so no Auth0 config changes are needed. Auth requests are tagged flow: 'docs-login'.
  • The shared /callback in mcp-oauth.mjs branches on that flag: instead of minting an OAuth code, it sets a browser session and redirects back to return_to.
  • Sessions are stateless: an RS256 JWT signed with the AS's existing key, audience-separated (${origin}/docs-session) so it can never be replayed as an MCP access token or vice-versa. Stored in an HttpOnly rp_docs_session cookie, with a JS-readable rp_docs_auth hint cookie so the widget/header can cheaply detect login state. safeReturnTo guards 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, salted external_owner_id from the docs session (so Kapa can store conversation history per user); anonymous requests get 401 auth_required — the agent tier is signed-in only.
  • Returns { session_token, expires_at, authenticated, user: { email, domain } }; CORS allowlist covers localhost + *.netlify.app previews (safe only because the cookie is SameSite=Lax — commented in code).

Also: the api-feedback Netlify form + mcp.mjs gain source (mcp vs ask-ai-widget) and thread-id fields so the docs team can tell submissions apart and follow the conversation without storing raw text. The login interstitial (pages.mjs) took heading/lead params, switched to the indigo AI-surface palette, and got dark mode (follows the docs theme localStorage key).

New env vars

  • KAPA_PROJECT_ID (required for /kapa/session) — 97f44223-f930-4fb9-ae1e-ecd436a4d85c
  • KAPA_OWNER_SALT (recommended) — the Kapa external_owner_id is sha256(salt + auth0_sub). Why salt: so we never hand Kapa the raw Auth0 sub (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 with openssl rand -hex 32; store in the secret manager; mark do-not-rotate.
  • DOCS_LOGIN_URL (optional) — overrides the sign-in target; defaults to /login

Testing

  • Unit: tests/docs-session.test.ts (8 — mint/verify round-trip, cross-origin audience rejection, open-redirect guard) + full existing suite green.
  • Local end-to-end verified with 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.
  • A focused security review of the cookie/JWT/OAuth-callback/CORS surface found no HIGH/MEDIUM issues; two low hardenings applied (source-URL scheme guard, CORS/SameSite coupling comment).

Deploy note

/kapa/session must 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

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]>
@JakeSCahill
JakeSCahill requested a review from a team as a code owner July 6, 2026 16:08
@netlify

netlify Bot commented Jul 6, 2026

Copy link
Copy Markdown

Deploy Preview for redpanda-documentation ready!

Name Link
🔨 Latest commit b2f4b07
🔍 Latest deploy log https://app.netlify.com/projects/redpanda-documentation/deploys/6a689c46ab6dcf0008c5067e
😎 Deploy Preview https://deploy-preview-190--redpanda-documentation.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 73 (🔴 down 12 from production)
Accessibility: 93 (🔴 down 1 from production)
Best Practices: 92 (no change from production)
SEO: 83 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

JakeSCahill and others added 5 commits July 7, 2026 11:14
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]>
JakeSCahill and others added 2 commits July 17, 2026 16:33
…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]>
@JakeSCahill
JakeSCahill requested a review from sago2k8 July 21, 2026 13:13
JakeSCahill and others added 3 commits July 21, 2026 17:09
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant