Skip to content

Standalone login-gated Mindmap with GitHub Pages deploy - #1

Merged
nhyiramante1 merged 5 commits into
mainfrom
feat/standalone-oauth-login
Sep 29, 2026
Merged

nhyiramante1 merged 5 commits into
mainfrom
feat/standalone-oauth-login

Conversation

@nhyiramante1

Copy link
Copy Markdown
Contributor

Summary

Turns Mindmap into a standalone, login-gated web app at https://mindmap.thoughtful-ai.com, served from GitHub Pages. It no longer gets launched from the Writing Tools add-in and has no document access: users paste text in and copy results out. The only thing it gets from Writing Tools is AI calls billed to the signed-in user, through an OAuth login.

Companion backend change: AIToolsLab/writing-tools#654.

What changes

  • Login gate replaces the launch gate. In production the app shows a "Connect to Writing Tools" screen, and no env var can turn the gate off. npm run dev runs with no login (the backend's capped demo key pays).
  • OAuth 2.1 authorization code + PKCE (S256):
    • the verifier and a random state live in session storage
    • state is checked exactly on return, and the redirect URI and resource are re-checked
    • login attempts expire after 10 minutes
    • resource (the backend origin) is sent on both the authorize request and the token request
    • code/state/error are scrubbed from the URL before the exchange and on every failure path
  • Tokens:
    • stored in session storage only, and shape-checked when read back
    • honoured until the earliest of the JWT's exp, expires_in, or the stored expiry
    • sent with credentials: "omit" and only to the configured backend
    • logout clears Mindmap's token only; it never signs the user out of Writing Tools
  • 401 vs 403 at the AI proxy: 401 clears the token and offers Connect. 403 (signed in, but the account isn't enabled) shows a final message and never loops back to login. The X-Writing-Tools-Error: platform-auth header separates these from upstream model errors.
  • Local work always survives a token expiry, a 401, a logout, or a failed login.
  • Copy-out: "Copy draft" and "Copy map as outline/Markdown."
  • The old launch path is removed (grant parsing, /handoff/exchange, rooms, launchRequired).
  • Production config fails closed: the bundle refuses to start without an HTTPS VITE_BACKEND_URL and a VITE_OAUTH_CLIENT_ID.

Contract with writing-tools#654

Value Production
Backend URL https://app.thoughtful-ai.com/api
resource https://app.thoughtful-ai.com (origin only)
client_id writing-tools-mindmap
redirect_uri https://mindmap.thoughtful-ai.com/
scope openai:chat

Deployment

  • .github/workflows/deploy-pages.yml: pull requests build and verify but cannot publish. Deploy runs only on a manual workflow_dispatch from main, with pages: write/id-token: write scoped to the deploy job.
  • npm run verify:pages checks the artifact before upload:
    • the backend URL is present and HTTPS
    • dist/ isn't empty
    • no .env* files ship
    • the client id is compiled in
  • Order: this can go live before #654 is deployed. The Connect screen will load, but login only works once #654 is live on the backend.

Tests

  • npx tsc -b clean; Vitest 376/376.
  • Playwright against the production build 6/6:
    • Connect screen
    • login round trip
    • URL scrubbed even when the exchange fails
    • bearer attached to AI calls
    • 403 doesn't loop back to login
    • work survives a token clear
  • Playwright in dev mode 4/4.

Open question

Map export inserts card text as-is, so a card beginning with # or - changes the outline's structure if the output is rendered as Markdown. It's left unescaped on purpose: exports are mostly pasted into Word/Docs, where escape backslashes would show up in the user's own text. Easy to change if we'd rather escape.

🤖 Generated with Claude Code

nhyiramante1 and others added 5 commits August 5, 2026 18:19
Track A is implemented and tested, so several things this spec left to
inference are now facts worth stating:

- The resource value is the origin of VITE_BACKEND_URL, no path and no
  trailing slash, sent on both the authorize request and the token POST body.
  Track A has tests rejecting a missing resource and a trailing-slash variant,
  and Better Auth issues an unverifiable opaque token when it is absent.

- 401 and 403 need opposite handling. A non-allowlisted user signs in
  successfully, receives a real 12-hour token, and is then refused at the
  proxy. Treating that as a prompt to log in again loops forever, because
  signing in cannot fix it. Added a required test for the loop.

- Logout clears local state only. Calling sign-out would end the shared
  Writing Tools browser session that the add-in also uses.

- Every backend request sends credentials: omit. Mindmap must never rely on
  or transmit Writing Tools cookies.
- Production refuses a non-HTTPS VITE_BACKEND_URL at module load, in both
  platform-session.ts and the duplicated resolver in api.ts. The bearer token
  and every AI request go to that URL. (verify:pages already rejects it in the
  deploy workflow; this makes the bundle itself fail closed too.)
- Token expiry is the earliest of the JWT exp, the advertised expires_in, and
  the stored expiry, both at exchange time and when read back.
- Login transactions expire after 10 minutes (and are rejected if dated in the
  future), so an abandoned state/verifier can't complete a login later.
- Any of code, state, error or error_description marks the URL as a callback,
  so partial or forged callback material is scrubbed and rejected.

Tests cover each; all four fail without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nhyiramante1
nhyiramante1 merged commit 5f57eb4 into main Sep 29, 2026
2 checks passed
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