Skip to content

fix(desktop): stop the OAuth connect callback from failing on a bare-path callback URL - #7005

Merged
TheodoreSpeaks merged 4 commits into
stagingfrom
fix/desktop-oauth-connect-callback
Aug 23, 2026
Merged

fix(desktop): stop the OAuth connect callback from failing on a bare-path callback URL#7005
TheodoreSpeaks merged 4 commits into
stagingfrom
fix/desktop-oauth-connect-callback

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

  • Connecting an account from the desktop app failed with a 500 on /api/auth/oauth2/callback/<provider> after the provider had already authorized — the browser landed on "This page isn't working" and no credential was created.
  • The desktop connect launcher passed better-auth a same-origin path as its callbackURL. Better Auth stores that value verbatim in the OAuth state, and the callback's credential-draft reader parsed it with a bare new URL(), which rejects a path. The throw happened inside the account.create.before database hook, which better-auth's OAuth callback does not guard, so it escaped as a 500 instead of a failed connection.
  • Fixed at both levels: the connect page now sends an absolute URL, matching its own workspace-scoped branch and every other connect surface; and the draft reader accepts a path-absolute callback URL so this shape can never fail the callback again. Protocol-relative and malformed values still throw, so an unreadable binding stays loud.
  • Scope was the desktop modal/reconnect flow only. Web is unaffected (it already built an absolute URL), and so was the desktop chat-chip flow, which routes through /api/auth/oauth2/authorize with an absolute callback.

Type of Change

  • Bug fix

Testing

  • New app/desktop/connect/page.test.tsx pins the launcher's complete URL as absolute (with and without a credential draft) and keeps the workspace-scoped authorize redirect covered. Verified both new tests fail against the old code with the exact TypeError: Invalid URL.
  • Extended lib/credentials/draft-processor.test.ts for the path-absolute callback URL, plus the protocol-relative value that must still throw.
  • bun run test lib/credentials app/desktop lib/auth — 662 passing; bun run type-check; bun run lint; bun run check:audits (33/33, incl. check:api-validation:strict and check:client-boundary).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 23, 2026 2:25am

Request Review

@cursor

cursor Bot commented Aug 23, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches OAuth callback/draft binding and global base-URL normalization used for redirects and internal APIs. Behavior is defensive and well-tested, but a parse or trim mistake would affect connect and other URL construction.

Overview
Desktop OAuth connect no longer 500s after the provider authorizes. The launcher was handing Better Auth a same-origin path as callbackURL; the credential-draft reader then called new URL() on it inside an unguarded account.create.before hook.

ConnectLauncher now gets an absolute complete URL (same as other connect surfaces). parseCredentialDraftIdFromCallbackUrl also accepts path-absolute callbacks (resolved against a dummy origin) so a path can never fail the callback again. Protocol-relative and malformed values still throw.

Separately, getBaseUrl / getInternalApiBaseUrl strip trailing slashes so ${base}/path cannot become //path and miss the complete route. Tests cover the launcher URL, trailing-slash bases, and path-form draft parsing.

Reviewed by Cursor Bugbot for commit e57f284. Configure here.

…path callback URL

The desktop connect launcher passed better-auth a same-origin path as its
callbackURL. Better Auth stores that value verbatim in the OAuth state, and
the callback's credential-draft reader parsed it with a bare `new URL()`,
which rejects a path. That throw happened inside the `account.create.before`
database hook, which better-auth's OAuth callback does not guard, so the
provider redirect landed on a 500 after authorization had already succeeded.

Send an absolute URL from the connect page, matching the workspace-scoped
branch and every other connect surface, and accept a path-absolute callback
URL in the draft reader so the shape can never fail the callback again.
Protocol-relative and malformed values still throw, keeping an unreadable
binding loud.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321
icecrasher321 force-pushed the fix/desktop-oauth-connect-callback branch from 94fdf84 to fbd094d Compare August 23, 2026 01:38
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

bugbot run

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR fixes the desktop OAuth connection callback by passing an absolute completion URL and accepting path-absolute callback state while rejecting malformed and protocol-relative values.

  • Composes desktop completion URLs through the URL API for launcher and workspace-scoped flows.
  • Normalizes public and internal base URLs by removing trailing slashes.
  • Adds regression coverage for absolute callbacks, trailing-slash configurations, draft extraction, and malformed callback values.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/desktop/connect/page.tsx Both desktop connection branches now compose a root-scoped absolute completion URL without duplicate slashes.
apps/sim/app/desktop/connect/connect-launcher.tsx The launcher consistently passes the absolute completion URL as both its success and error callback.
apps/sim/lib/core/utils/urls.ts Public and internal base URLs now match the no-trailing-slash form assumed by existing URL-concatenation callers.
apps/sim/lib/credentials/draft-processor.ts Credential-draft parsing now accepts documented path-absolute callbacks while continuing to reject protocol-relative and malformed values.
apps/sim/app/desktop/connect/page.test.tsx New tests cover launcher and workspace callback composition, including trailing-slash base configuration.
apps/sim/lib/credentials/draft-processor.test.ts Tests pin valid relative callback parsing and rejection of protocol-relative input.
packages/testing/src/mocks/urls.mock.ts The shared URL mock mirrors production trailing-slash normalization.

Sequence Diagram

sequenceDiagram
  participant Desktop as Desktop app
  participant Page as Connect page
  participant Auth as OAuth service
  participant Provider as OAuth provider
  participant Complete as Completion page
  Desktop->>Page: Open connect request
  Page->>Auth: Start link with absolute completion URL
  Auth->>Provider: Authorize account
  Provider-->>Auth: OAuth callback
  Auth->>Auth: Read optional credential draft binding
  Auth-->>Complete: Redirect to completion URL
  Complete-->>Desktop: Forward result to loopback
Loading

Reviews (5): Last reviewed commit: "chore(urls): stop claiming path-prefixed..." | Re-trigger Greptile

Comment thread apps/sim/app/desktop/connect/page.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fbd094d. Configure here.

Comment thread apps/sim/app/desktop/connect/page.tsx Outdated
Concatenating `getBaseUrl()` with the completion path leaves the result
dependent on how the deployment spelled `NEXT_PUBLIC_APP_URL`: the helper only
adds a missing protocol, so a trailing slash produced `//desktop/connect/complete`,
a pathname that matches no route. The completion page is what bounces the OAuth
result to the desktop app's loopback, so that typo would have stranded the flow
just past the callback it was meant to fix.

Both callback URLs in the page — the launcher's and the workspace-scoped
authorize redirect's — now go through one helper that resolves the path against
the base with `new URL`, matching how the same function already builds the
authorize URL, with coverage for a trailing-slash base.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/desktop/connect/page.tsx
… assume

`getBaseUrl()` returned `NEXT_PUBLIC_APP_URL` as the operator spelled it, while
almost every consumer builds `${base}/path`. A base configured with a trailing
slash therefore produced a `//path` pathname that matches no route, and broke
the `startsWith(`${base}/`)` prefix checks that decide whether a redirect target
is our own — the OAuth authorize route rejected its own completion callback and
fell back to the workspace page, so the desktop handoff never ran on those
deployments. The previous commit fixed one such URL; this fixes the reason it
was wrong, for the ~30 concatenation sites that share the assumption.

`normalizeBaseUrl` now strips trailing slashes alongside the protocol it already
added, which is the invariant SITE_URL has always documented. A path-prefixed
base keeps its path. `getInternalApiBaseUrl` gets the same treatment, since its
callers concatenate identically.

`@sim/testing`'s urls mock is a hand-written mirror of this module, so it moves
in step. `internal-api-base-url.test.ts` now unmocks the module it names —
otherwise it asserts against that mirror and any drift between the two passes
unnoticed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/desktop/connect/page.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5e8e10b. Configure here.

The previous commit's doc and test said a path-prefixed base keeps its path.
That reads as support for a deployment shape the app does not have: there is no
Next `basePath`, so routes are served at the origin root and such a value could
not address them however the base were normalized. Every documented example is
origin-only.

Says only what is true — trailing slashes are the one spelling absorbed — and
reframes the test as pinning the trim's shape rather than asserting a
path-prefixed deployment works. No behavior change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit e57f284. Configure here.

@TheodoreSpeaks
TheodoreSpeaks merged commit 43aba98 into staging Aug 23, 2026
30 checks passed
@TheodoreSpeaks
TheodoreSpeaks deleted the fix/desktop-oauth-connect-callback branch August 23, 2026 03:28
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.

2 participants