Skip to content

🤖 fix: refactor & harden OAuth desktop flows#2248

Merged
ibetitsmike merged 22 commits intomainfrom
auth-7ete
Feb 15, 2026
Merged

🤖 fix: refactor & harden OAuth desktop flows#2248
ibetitsmike merged 22 commits intomainfrom
auth-7ete

Conversation

@ibetitsmike
Copy link
Contributor

@ibetitsmike ibetitsmike commented Feb 7, 2026

Summary

Extracts duplicated OAuth infrastructure (loopback server, flow lifecycle, cancellation, HTML rendering) from 5 service files into shared, tested utilities. Fixes several real-world edge cases found during testing.

Why

Each OAuth service (Gateway, Governor, Codex, Copilot, MCP) had its own copy of loopback server setup, callback HTML, state validation, and flow cancellation — ~775 lines of near-identical code with inconsistent error handling. None of it had tests.

What changed

Shared utilities (src/node/utils/oauth*.ts):

  • oauthLoopbackServer — configurable localhost callback server (port, host, state validation, deferred responses)
  • oauthFlowManager — flow registration, timeout, cancellation, completed-flow TTL for late waiters
  • oauthUtilscreateDeferred, closeServer, escapeHtml, callback HTML rendering

Service simplification: Gateway, Governor, and Codex desktop flows now delegate to the shared utilities. MCP partially migrated (uses helpers but keeps its own server due to @ai-sdk/mcp integration).

Bug fixes:

  • Invalid OAuth state returns 400 without killing the flow (keeps listening for the real callback)
  • Copilot device flow no longer cancelled on IPC reconnect (useEffect dep fix)
  • Duplicate flow registration is handled defensively (cleanup previous before replacing)

Line count

Added Removed Net
New tests (0 → 106 cases) +1,283 +1,283
Shared utilities +614 +614
Refactored services +201 −775 −574
Frontend fixes +21 −5 +16

Production code is net negative (−161). The entire +1,344 increase is test coverage that didn't exist before.


Generated with mux • Model: anthropic:claude-opus-4-6 • Thinking: xhigh • Cost: $12.84

Copy link

@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: 2ef2cdc075

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ibetitsmike
Copy link
Contributor Author

@codex review

Addressed the review feedback:

  • Preserve completed flows (TTL) so late waiters still get the result.
  • Loopback server: invalid state no longer ends the flow; redirectUri now honors host.

Please take another look.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. 👍

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ibetitsmike ibetitsmike changed the title 🤖 fix: harden OAuth desktop flows and add P2/P3 coverage 🤖 fix: reafactor & harden OAuth desktop flows Feb 8, 2026
@ibetitsmike ibetitsmike changed the title 🤖 fix: reafactor & harden OAuth desktop flows 🤖 fix: refactor & harden OAuth desktop flows Feb 10, 2026
@ibetitsmike
Copy link
Contributor Author

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Mux added 22 commits February 15, 2026 19:54
Create 3 new shared modules for the duplicated OAuth infrastructure
found across 5 service files (codexOauthService, copilotOauthService,
muxGatewayOauthService, muxGovernorOauthService, mcpOauthService):

- oauthUtils.ts: createDeferred, closeServer, escapeHtml,
  renderOAuthCallbackHtml — verbatim-duplicated utilities
- oauthLoopbackServer.ts: startLoopbackServer — shared local HTTP
  server pattern for receiving OAuth code redirects
- oauthFlowManager.ts: OAuthFlowManager class — shared desktop flow
  lifecycle (register/waitFor/cancel/finish/shutdownAll)

No existing files are modified. Follow-up work will wire each service
to use these shared modules.
Replace the inline deferred-promise pattern with the shared
createDeferred<T>() utility extracted into oauthUtils.
Replace duplicated OAuth infrastructure with the new shared utilities:

- Import createDeferred, renderOAuthCallbackHtml from oauthUtils
- Replace http.createServer block with startLoopbackServer
- Replace manual desktopFlows Map + lifecycle methods with OAuthFlowManager
- Remove local closeServer, createDeferred, escapeHtml helpers
- Remove DesktopFlow interface and handleDesktopCallback method
- Remove finishDesktopFlow (delegated to OAuthFlowManager.finish)
- Simplify dispose() to use shutdownAll()
- Preserve Gateway-specific branding via renderHtml + extraHead

Public API unchanged. File reduced from 443 to 239 lines (-46%).
Replace duplicated OAuth infrastructure with the new shared modules:
- Import createDeferred, renderOAuthCallbackHtml from oauthUtils
- Replace manual http.createServer with startLoopbackServer
- Replace desktopFlows Map + lifecycle methods with OAuthFlowManager
- Remove local closeServer, escapeHtml, createDeferred functions
- Remove DesktopFlow interface, handleDesktopCallback, finishDesktopFlow

Governor-specific behavior preserved:
- Dynamic governorOrigin from user input for authorize/exchange URLs
- Custom 'Enrollment complete/failed' branding via renderHtml option
- policyService.refreshNow() callback after successful enrollment
- ServerFlow pattern (startServerFlow, handleServerCallbackAndExchange)
  left unchanged as it doesn't use loopback

All public method signatures remain identical.
Reduces file from 484 to 293 lines (-40%).
Replace local duplicated helpers with imports from @/node/utils/oauthUtils:
- createDeferred (~7 lines)
- closeServer (~4 lines)
- escapeHtml (~8 lines)
- Inline HTML template in handleDesktopCallback → renderOAuthCallbackHtml

MCP's unique patterns (DesktopFlow lifecycle, two-phase callback handling,
state-mismatch flow termination, telemetry) are preserved unchanged.

Net reduction: 26 lines (1492 → 1466).
- Replace local createDeferred, closeServer, escapeHtml, isLoopbackAddress
  with imports from shared oauthUtils/oauthLoopbackServer
- Replace DesktopFlow interface + Map with OAuthFlowManager
- Replace manual http.createServer with startLoopbackServer
- Remove handleDesktopCallback (logic moved to background task in startDesktopFlow)
- Remove finishDesktopFlow (delegated to OAuthFlowManager.finish)
- Simplify dispose() with shutdownAll()
- Keep DeviceFlow and all device flow methods unchanged

931 → 749 lines (-182)
Add 3 test files covering the shared OAuth utilities:

- oauthUtils.test.ts (21 tests): createDeferred, closeServer, escapeHtml,
  renderOAuthCallbackHtml (XSS escaping, auto-close, extraHead)
- oauthLoopbackServer.test.ts (9 tests): server start, valid callback,
  state mismatch, error params, missing code, 404, cancel, custom path/renderer
- oauthFlowManager.test.ts (13 tests): register/get/has, waitFor with
  resolve/timeout/missing, cancel, finish (idempotent, null server, timeout
  cleanup), shutdownAll

Uses real HTTP requests for loopback server tests and mock http.Server
for flow manager tests.
…flow timeouts

P1a (XSS regression): renderOAuthCallbackHtml now escapes the message
unconditionally for both success and failure paths. Previously, failure
messages (which can contain provider error_description values) were
rendered as raw HTML, allowing injection in the local callback page.

Also remove the now-redundant escapeHtml() wrapper in mcpOauthService's
handleDesktopCallback — renderOAuthCallbackHtml handles escaping itself.

P1b (leaked flows): Gateway, Governor, and Codex desktop flow registrations
now set a server-side timeout at register time. Previously, the timeout only
existed inside waitFor(), so flows started without a corresponding waitFor
call (e.g. MuxGatewaySessionExpiredDialog) would leak the loopback server
and map entry indefinitely.
Preserve existing OAuth flow test behavior while resolving lint/type failures in the new gateway and governor test files.
Bump pending-detection race timeout from 25ms to 100ms in new MuxGateway/MuxGovernor OAuth service tests.

Document that RenderOAuthCallbackHtmlOptions.extraHead is injected unescaped and must be trusted static HTML.
… cancel

Three fixes:

1. ProvidersSection: change useEffect dep from [api] to [] with apiRef
   so the cleanup only fires on true unmount, not on api identity changes
   (e.g. IPC reconnection). This was the root cause of the first flow
   being cancelled before it could succeed.

2. ProvidersSection: pre-emptively cancel previous copilot flow before
   starting a new one (matching the existing Codex pattern).

3. copilotOauthService: skip cancelDeviceFlow log+resolve when the flow
   already completed, avoiding misleading 'cancelled' logs after success.
- ProvidersSection: cancel Copilot device flow when start result returns for a stale attempt.
- OAuthFlowManager: on duplicate register(flowId), best-effort cleanup the previous active entry (timeout, deferred, server) before storing the new one.
- CodexOauthService: only log cancel when the desktop flow exists to avoid misleading logs.
- OAuthFlowManager: regression test for duplicate flowId register cleanup.
- oauthLoopbackServer: regression test asserting validateLoopback rejects non-loopback clients with 403.
@ibetitsmike
Copy link
Contributor Author

@codex review

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. You're on a roll.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ibetitsmike ibetitsmike merged commit 0789408 into main Feb 15, 2026
23 checks passed
@ibetitsmike ibetitsmike deleted the auth-7ete branch February 15, 2026 19:55
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