Skip to content

feat: browser-login flow for newapi CLI (server side)#1

Open
Xbang0222 wants to merge 4 commits into
mainfrom
feat/cli-login-exchange
Open

feat: browser-login flow for newapi CLI (server side)#1
Xbang0222 wants to merge 4 commits into
mainfrom
feat/cli-login-exchange

Conversation

@Xbang0222
Copy link
Copy Markdown
Owner

Summary

Adds the server-side half of a browser-login flow for the newapi CLI. The CLI starts a local 127.0.0.1 loopback, opens the browser here, and a new /cli-login page swaps the signed-in session for a 30-day access token. The CLI side ships in a separate PR.

Changes

  • controller/user.go: extract issueAccessTokenForUser(userID) from the inline body of GenerateAccessToken so the new endpoint can mint identical tokens.
  • controller/cli_login.go (new): CliLoginExchange handler.
  • router/api-router.go: register POST /api/user/cli-login/exchange on the existing selfRoute group (inherits middleware.UserAuth()).
  • web/src/components/auth/CliLogin.jsx (new) + web/src/App.jsx: React page at /cli-login that reads port+state from the URL, polls /api/user/self until logged in (5-min cap), calls the exchange API, then cross-origin POSTs the token to the loopback.

Security

  • Session-only — the new endpoint is mounted under the auth-protected /api/user group; unauthenticated requests get 401 from middleware before reaching the handler.
  • State CSRF — the CLI generates a 32-byte nonce, server echoes it back; the actual state check happens at the CLI loopback (server is stateless here). state field has binding:"required,max=128" so protocol mistakes surface immediately.
  • Token never lands in URL, history, or referer — only in the cross-origin POST body to 127.0.0.1.

Test Plan

  • go build ./... clean (verified locally)
  • go vet ./controller/... ./router/... clean (verified locally)
  • Deploy to a test gateway and run the CLI side against it
  • Confirm GET /api/user/token behavior is byte-identical to pre-PR (refactor preserved the exact response envelope and i18n keys)

Notes

  • No tests added to this repo (per repo convention).
  • Design doc kept in the CLI repo at docs/superpowers/specs/2026-05-25-web-login-design.md.

Xbang0222 added 4 commits May 25, 2026 18:58
Extract the inline access-token minting logic from GenerateAccessToken
into a package-level helper, so a follow-up CliLoginExchange handler
can mint tokens for the new browser-login flow without duplicating
the logic. No behavior change to GET /api/user/token.
Browser-side companion to the new CLI login flow: takes the caller's
session cookie, mints a 30-day access token via the helper extracted
in 3ff63d9, echoes the CLI-supplied state nonce back to the browser
so the CLI's loopback can verify it. Session-auth middleware on the
/api/user group handles the unauthenticated case before reaching the
handler.
Address code-review feedback on 51296f5:
- session-missing branch now uses common.ApiErrorI18n(c, i18n.MsgAuthNotLoggedIn)
  to match middleware/auth.go behavior and house style
- state field gains binding:"required,max=128" so protocol mistakes
  surface immediately and the echoed value can't grow unbounded
Companion page for the newapi CLI's browser-login handshake: reads
port + state from the URL, polls /api/user/self until the user has
a live session, calls /api/user/cli-login/exchange to mint a 30-day
token, then cross-origin POSTs the result to the CLI's local
loopback. Self-contained — no LoginForm redirect coupling.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f97759a53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


setStatus(STATUS.RETURNING);
try {
const cbResp = await fetch(`http://127.0.0.1:${params.port}/callback`, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict callback target to a numeric loopback port

The port query parameter is interpolated directly into a URL authority string, so crafted values like 80@evil.com are parsed with evil.com as the host (http://127.0.0.1:80@evil.com/callback), causing the freshly issued access token to be POSTed off-host instead of to the CLI loopback. This is exploitable when a user opens a malicious /cli-login?... link while logged in (or logs in during the flow), especially on HTTP deployments where mixed-content blocking does not mitigate the request. Validate port as digits in [1,65535] and construct the callback URL via new URL() with fixed hostname 127.0.0.1.

Useful? React with 👍 / 👎.

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