🤖 feat: Login with Coder from a browser on a remote Xum server - #4047
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
This comment has been minimized.
This comment has been minimized.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
This comment has been minimized.
This comment has been minimized.
The Coder OAuth flow only supported a desktop loopback listener, so the Settings UI disabled login when served from a remote Xum server. The authorization redirect now lands on the server's own /auth/coder/callback route in browser mode (like the Gateway/MCP server flows): the service gains a server-hosted callback channel behind the existing flow manager, the HTTP layer builds the redirect URI from the validated public host, and the UI keeps waiting/cancelling through oRPC.
Dogfooding the browser flow from a non-localhost host surfaced two insecure-context gaps: crypto.randomUUID is undefined there (the flow ID, which doubles as the OAuth state, now comes from getRandomValues), and navigator.clipboard is undefined too (open the authorization page before the best-effort copy so a throw cannot swallow the navigation).
ccc39d5 to
f89d97d
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
This comment has been minimized.
This comment has been minimized.
## Summary Version bump for the v0.28.4 patch release. The headline change since v0.28.3 is Gemini 3.8 Flash becoming the default Gemini Flash model (coder#4060). The release also carries browser Login with Coder on remote Xum servers (coder#4047), the opt-in project bundle for settings backup (coder#4043), the connection-indicator slow-response surfacing (coder#4059), send-queue and terminal-wake fixes (coder#4053, coder#4052), and the Effect Phase 11 runtime refactors. ## Implementation Bumped with `node ./scripts/set-package-version.js 0.28.4` so the root `package.json` and the legacy `packages/mux-compat` forwarding package stay version-locked (the v0.28.3 bump missed the compat package and broke `Test / Unit` on main, fixed in coder#4048). `src/common/compat/productIdentity.test.ts` passes locally. After this PR merges, the `v0.28.4` tag will be applied to the squash commit and the GitHub Release published to trigger the desktop/npm/docker pipelines. --- _Generated with `xum` • Model: `anthropic:claude-fable-5-1` • Thinking: `medium` • Cost: `$0.00`_ <!-- mux-attribution: model=anthropic:claude-fable-5-1 thinking=medium costs=0.00 -->
Summary
"Login with Coder" now works from a browser connected to a Xum server, including a remote one. The authorization redirect lands on the Xum server's own
/auth/coder/callbackroute instead of a desktop loopback listener, following the pattern the Gateway/MCP OAuth flows already use. The Settings UI no longer disables the login control on remote hosts.Background
The Coder OAuth flow (authorization-code + PKCE, RFC 7591 dynamic client registration with exact redirect-URI matching) only had a desktop implementation: it bound an ephemeral
127.0.0.1:<port>listener and registered that as the client's redirect URI. A browser talking to a remote Xum server can never reach that listener, soProvidersSectionhid the control behindisRemoteServerwith the message "Login with Coder requires the desktop app or a locally hosted Xum server".That limitation was self-imposed. Coder's OAuth2 provider accepts non-loopback redirect URIs (HTTPS required for non-localhost hosts — verified against
codersdk/oauth2_validation.go), and Xum already hosts server-side OAuth callbacks for other providers. Since DCR lets Xum register any redirect URI, the server can register its own public callback URL.Implementation
coderOauthService.ts— the callback source is abstracted as aCoderCallbackChannel: the existingLoopbackServer, or a newServerCallbackChannel(server: null) for browser mode.startServerFlow({ deploymentUrl, flowId, redirectUri })runs the same launch pipeline asstartDesktopFlowwith the server channel;handleServerCallback({ state, code, error, errorDescription })delivers the redirect to its flow (state = flow ID) and resolves with the login outcome for the route to render. Delivery is one-shot and raced against the flow's own completion so a Cancel/timeout mid-exchange settles the browser request instead of hanging it. Server flows live in the sameOAuthFlowManager, sowaitForDesktopFlow,cancelDesktopFlow,disconnect, the stored-client lease, the cross-process commit lock, and revocation apply unchanged.orpc/server.ts—GET /auth/coder/start?deploymentUrl&flowId(authenticated; the redirect URI is built server-side from the validated public host incl. app-proxy prefix and is never accepted from the client — there is deliberately no oRPC surface that takes a redirect URI) andALL /auth/coder/callback(unauthenticated navigation, added to the origin-bypass set, GET +form_post). The byte-identical Gateway/MCP callback HTML is extracted into onesendOAuthCallbackPagehelper rather than adding a fourth copy; Governor keeps its own template.ProvidersSection.tsx— branches onisDesktoplike the Gateway flow: desktop → oRPC loopback flow; browser (local or remote) →/auth/coder/start, then the existing oRPC wait/cancel. The remote-server paragraph and its gate on the palette hint are removed. The flow ID (which doubles as the OAuthstate) is now derived fromcrypto.getRandomValuesbecausecrypto.randomUUIDis undefined outside secure contexts (plain-HTTP remote origins), and "Copy & Open Coder" opens before the best-effort clipboard write for the same reason (navigator.clipboardis undefined there).providers.mdx+ regenerated built-in skill content) describe remote login and the HTTPS requirement.Validation
Dogfooded end-to-end in a
dev-server-sandboxserved at a non-localhost hostname (xum.127.0.0.1.nip.io) against a mock Coder deployment implementing buildinfo, RFC 8414 discovery, DCR, an explicit Allow/Deny consent page, token exchange, revocation, and the AI Gateway listings:http://xum.127.0.0.1.nip.io:<port>/auth/coder/callback(the Xum server's origin, built from the request host); the authorize URL carried that redirect URI plus an S256 challenge./auth/coder/callback→ PKCE exchange (verifier present) → catalog discovery → Settings shows Connected with Re-login / Refresh models / Disconnect; the callback tab auto-closed.randomUUID,navigator.clipboard), which would have silently broken login on plain-HTTP remote servers.New tests: service (full server flow with exact HTTPS redirect URI in DCR + exchange + persistence + replay refusal; OAuth error redirect; cancel mid-exchange settles the pending callback and revokes; invalid redirect URI rejected pre-network), routes (auth required on start, app-proxy-aware redirect URI, callback success/failure rendering, cross-origin POST on the new callback), UI (browser-mode hint starts via
/auth/coder/startand continues onwaitForDesktopFlow; remote host renders the login control and targets its own origin).Risks
Medium, scoped to the Coder provider login. The desktop path is only refactored to consume the channel interface (the loopback
LoopbackServeris structurally compatible; all 88 pre-existing service tests pass unchanged). The new unauthenticated callback route only looks up a live flow bystateand hands it an authorization code that is useless without this process's PKCE verifier and client secret. Gateway/MCP callback routes are refactored to the shared page helper with byte-identical output (existing route tests cover them).Known limitation: Coder rejects
http://redirect URIs for non-localhost hosts, so a remote Xum server reachable only over plain HTTP gets a clear DCR error from the deployment rather than a login.Generated with
xum• Model:anthropic:claude-fable-5-1• Thinking:xhigh• Cost:$29.72