feat(cli): create organization access tokens through the dashboard
Summary
polar auth token creates an organization access token (OAT) for an app and prints it on stdout, for the caller to store wherever it belongs.
What
- CLI โ
polar auth token [--scope โฆ]- Takes the organization and environment from the current session (
auth whoami), including--local. - Opens
/dashboard/<slug>/cli/token, waits on the existing loopback listener (127.0.0.1:3333), and prints the token, nothing else, on stdout. - The browser prompt goes to stderr, as does a hint for sandbox and local tokens on how to point the SDK at that environment. So
echo "POLAR_ACCESS_TOKEN=$(polar auth token)" >> .env.localandpolar auth token | gh secret set POLAR_ACCESS_TOKENcapture only the token.--jsonis refused.
- Takes the organization and environment from the current session (
- Web โ
/dashboard/[organization]/cli/token: full-screen page with the existing token form, prefilled with the CLI's name and scopes. Cancel notifies the CLI. Opened without a CLI key, it shows the token once to copy. - Refactors
utils/loopback.ts: the callback server is shared byauth loginandauth token.dashboardOrigininservices/api.ts: OAuthauthorizationUrland the token page resolve the dashboard the same way, includingPOLAR_DASHBOARD_URLand the guard against the hosted dashboard whenPOLAR_API_URLis set.useSubmitOrganizationAccessToken: create-token logic, including the fresh-session retry, is shared by the settings modal and the new page.
Why
Creating an OAT requires a fresh web session. That stays: an OAuth session must not be able to mint an OAT, because the OAT can outlive it. But developers and agents setting up Polar in an app need a token from the CLI. The token prints on stdout, so an agent should redirect or pipe it rather than read it, to keep it out of its history.
How
The CLI generates a one-time RSA-OAEP key pair and a state, and passes the public key to the page. After the user creates the token in their web session, the page encrypts it with WebCrypto and redirects to the fixed loopback URL. The CLI checks state and decrypts.
- The page only sends to
127.0.0.1:3333, so a crafted link cannot redirect the token. - The token travels encrypted, so browser history only holds ciphertext.
- Delivery is a top-level navigation, like the OAuth login, so no CORS or CSP changes are needed.
Checklist
- This PR addresses a single concern (one bug fix, one feature, one refactor)
- The diff is reasonably sized and easy to review
- New functionality is covered by tests
- Linting and type checking pass
- No unrelated changes or drive-by fixes are included
Verification
- Run end to end in Chrome against a local stack:
dev seed,bun src/cli.ts auth login --local, thenbun src/cli.ts auth token. - The local stack is HTTP throughout, so the redirect from an HTTPS dashboard to
http://127.0.0.1:3333can only be checked on sandbox after deploy.auth loginalready makes the same jump in production (polar.shโhttp://127.0.0.1:3333/oauth/callback); this page navigates withwindow.locationrather than a server redirect, which browsers also treat as a top-level navigation. - If the CLI has stopped listening when the page redirects, the token is created but never delivered and has to be revoked in settings.
๐ค Generated with Claude Code