Skip to content

fix(desktop): isolate profile auth schemes - #484

Draft
xiaoland wants to merge 1 commit into
masterfrom
yihong/profile-deep-link-isolation
Draft

fix(desktop): isolate profile auth schemes#484
xiaoland wants to merge 1 commit into
masterfrom
yihong/profile-deep-link-isolation

Conversation

@xiaoland

Copy link
Copy Markdown
Member

Summary

  • suffix the desktop OAuth scheme with LINKCODE_PROFILE
  • vendor @linkcode/cloud 0.1.1 so profile-specific billing return targets remain strictly validated
  • document the profile-specific native callback contract

Depends on arcboxlabs/linkcodehq#52.

Validation

  • pnpm check:ci
  • pnpm exec vitest run apps/desktop/src/main/__tests__/cloud-hosted-billing.test.ts (3 passed)
  • pnpm test: 3002 passed, 5 skipped; 3 pre-existing release-artifact.test.ts failures caused by macOS /var vs /private/var canonicalization

Copilot AI lite review requested due to automatic review settings August 27, 2026 05:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

The PR gives each desktop profile a distinct OAuth callback scheme and updates the vendored cloud package so hosted-billing return targets accept it.

  • Appends the resolved profile to the desktop OAuth scheme.
  • Upgrades desktop and webview consumers to vendored @linkcode/cloud 0.1.1.
  • Adds profile-specific hosted-billing coverage and documents the native callback contract.

Confidence Score: 4/5

The packaged profile callback path should be fixed before merging because its dynamically generated OAuth scheme is absent from the native protocol declarations.

Profile-specific OAuth and billing URLs are generated consistently inside the application, but packaged macOS, Windows, and branded builds still register only their unsuffixed schemes, preventing reliable native callback routing.

Files Needing Attention: apps/desktop/src/main/constants.ts and desktop packaging protocol configurations

Important Files Changed

Filename Overview
apps/desktop/src/main/constants.ts Introduces profile-specific OAuth schemes without corresponding packaged protocol declarations.
apps/desktop/src/main/tests/cloud-hosted-billing.test.ts Correctly verifies URL generation for a profile, but does not exercise native operating-system protocol registration.
apps/desktop/package.json Consistently upgrades the desktop application to the new vendored cloud package.
apps/webview/package.json Keeps the webview cloud dependency aligned with the desktop application.
pnpm-lock.yaml Consistently records the vendored package upgrade and integrity metadata.

Sequence Diagram

sequenceDiagram
  participant Profile as Packaged profile app
  participant Provider as OAuth/Billing provider
  participant OS as Operating system
  Profile->>Provider: Request callback to linkcode-profile://
  Provider->>OS: Open linkcode-profile:// callback
  Note over OS: Package declares only linkcode://
  OS--xProfile: Profile-specific scheme is not registered
Loading

Reviews (1): Last reviewed commit: "fix(desktop): isolate profile auth schem..." | Re-trigger Greptile

Comment on lines +85 to +86
export const CLOUD_AUTH_SCHEME =
PROFILE === undefined ? CLOUD_AUTH_SCHEME_BASE : `${CLOUD_AUTH_SCHEME_BASE}-${PROFILE}`;

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 Packaged schemes omit profiles

When a packaged release, development-shell, or branded app runs with a profile, CLOUD_AUTH_SCHEME gains a profile suffix while the package metadata still registers only the unsuffixed scheme, causing OAuth sign-in and hosted-billing callbacks to use a native URL scheme the operating system cannot reliably route back to that app.

@pullfrog pullfrog 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.

Important

On macOS the profile-suffixed scheme cannot be registered at all — Electron can only claim schemes declared in the bundle's Info.plist, and the protocols: blocks are build-time and profile-blind. Worth deciding the intended macOS behavior before merging. There is also a separator collision that reintroduces exactly the clash this PR removes.

Reviewed changes — the single commit 6d6bdf3b, extending the desktop channel × profile identity fork to the OAuth deep-link scheme.

  • Profile-suffixed auth schemeapps/desktop/src/main/constants.ts splits out a private CLOUD_AUTH_SCHEME_BASE and exports CLOUD_AUTH_SCHEME as `${BASE}-${PROFILE}` when a profile is active. Consumers in cloud-auth/client.ts (sign-in ?scheme=, electronClient protocol, setAsDefaultProtocolClient, billing returnTarget) are unchanged.
  • Vendored @linkcode/cloud 0.1.0 → 0.1.1 — the only behavioral delta is the return-target validator: an exact Set(['linkcode:', 'linkcode-dev:']) becomes /^linkcode(?:-dev)?(?:-[a-z0-9][a-z0-9-]{0,31})?:$/, whose profile group mirrors PROFILE_NAME_PATTERN exactly. Tarball sha512 matches the lockfile integrity, and the 0.1.0 tarball is removed cleanly.
  • New testcloud-hosted-billing.test.ts gains a profile case plus vi.resetModules() / delete process.env.LINKCODE_PROFILE in beforeEach. I verified it is real coverage: neutering constants.ts back to the base scheme makes it fail, and the whole apps/desktop/src/main/__tests__ directory plus the webview billing test stay green with 0.1.1 installed.
  • Docsdocs/DEVELOPMENT.md profile bullet and shared-across-channels paragraph, docs/ENVIRONMENT.md LINKCODE_PROFILE row.

⚠️ The cloud-side half of this contract can't be verified from this repo

The vendored @linkcode/cloud bump only widens the client-side URL builder — it proves nothing about what console.linkcode.ai accepts, and the PR body scopes arcboxlabs/linkcodehq#52 to billing. Sign-in is a separate path: cloud-auth/client.ts:33 puts the suffixed scheme on https://linkcode.ai/sign-in?scheme=…, and if that page has its own scheme allowlist, profiled sign-in breaks in a new way the moment this merges ahead of the cloud deploy.

Technical details
# Server-side acceptance of profile-suffixed schemes

## Affected sites
- `apps/desktop/src/main/cloud-auth/client.ts:33``?scheme=<base>-<profile>` on the HQ sign-in URL; server-side allowlist unknown from this repo.
- `apps/desktop/src/main/cloud-auth/client.ts:96``returnTarget=<base>-<profile>://billing/return`; the vendored `createHostedBillingUrl` validates it locally, but `console.linkcode.ai` re-validates on receipt.

## Required outcome
- Confirm the sign-in page and the billing console both accept the `-<profile>` suffix, not just the vendored client builder.
- Confirm the merge/deploy ordering: this PR must not ship to users before the linkcodehq side is live.

## Open questions for the human
- Does `arcboxlabs/linkcodehq#52` cover the sign-in `scheme` param, or billing only?

ℹ️ Nitpicks

  • apps/desktop/src/main/cloud-auth/client.ts:24-26 still describes the scheme as "channel-split so a development build never fights the release app" — the sibling doc comment in constants.ts was updated to say "channel and profile", this one was not.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

BRAND_BASE?.authScheme ?? (CHANNEL === 'development' ? 'linkcode-dev' : 'linkcode');

export const CLOUD_AUTH_SCHEME =
PROFILE === undefined ? CLOUD_AUTH_SCHEME_BASE : `${CLOUD_AUTH_SCHEME_BASE}-${PROFILE}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On macOS this scheme can never be registered. Electron's docs are explicit: "you can only register protocols that have been added to your app's info.plist, which cannot be modified at runtime" — and the protocols: blocks in electron-builder.release.yml / electron-builder.devshell.yml are build-time and profile-blind. So a packaged macOS build launched with a profile emits linkcode-<profile>:// on both the sign-in ?scheme= and the billing returnTarget, Launch Services has no handler, and the callback dead-ends silently — where before it at least reached the base-scheme app. @better-auth/electron's registerProtocolScheme only console.errors the failed registration, which is invisible in a packaged app.

Technical details
# Profile-suffixed deep-link scheme is unregistrable on macOS

## Affected sites
- `apps/desktop/src/main/constants.ts:85-86` — derives `<base>-<profile>` at launch time; nothing declares it to the OS.
- `apps/desktop/electron-builder.release.yml` / `electron-builder.devshell.yml``protocols:` lists only `linkcode` / `linkcode-dev`; these are baked at package time and cannot know the runtime `--profile`.
- `apps/desktop/src/main/cloud-auth/client.ts:59-62` (`claimDeepLink`) and `node_modules/@better-auth/electron/dist/client.mjs` (`registerProtocolScheme`) — both call `app.setAsDefaultProtocolClient`; the latter only `console.error`s on a `false` return.
- `docs/DEVELOPMENT.md:325` — the added claim "OAuth callbacks return to the matching app" does not hold on macOS.

## Required outcome
- A packaged macOS build launched with `--profile=<name>` must either receive its OAuth/billing callback, or fail loudly and predictably rather than dead-ending.
- The docs must not claim per-profile callback routing on a platform where it cannot work.

## Suggested approach (optional)
- Confirm the behavior on a packaged macOS build (`pnpm run package:devshell`, launch with a profile, watch the `setAsDefaultProtocolClient` return value) before choosing a fix — Windows registers at runtime and is unaffected, so this is macOS-specific.
- Options, roughly in increasing cost: keep the base scheme on `darwin` and disambiguate the profile inside the callback URL; declare a small reserved set of profile schemes in the `protocols:` blocks; or scope the suffix to the platforms where runtime registration works and document the macOS limitation.

## Open questions for the human
- Is a packaged macOS build with a profile a supported configuration, or is the profile flow dev-shell/Windows only?

const CLOUD_AUTH_SCHEME_BASE =
BRAND_BASE?.authScheme ?? (CHANNEL === 'development' ? 'linkcode-dev' : 'linkcode');

export const CLOUD_AUTH_SCHEME =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The - separator is inside the profile charset, so it reintroduces the collision this change exists to remove: release channel + --profile=dev yields linkcode-dev, byte-identical to the development channel's default scheme (--profile=dev-alpha likewise aliases dev-channel-alpha). Both names pass PROFILE_NAME_PATTERN. Every other identity axis picks a separator the profile charset forbids — . for the state dir and APP_ID, (name) for APP_NAME and the keyring service — for exactly this reason.

Technical details
# Profile separator `-` aliases the channel suffix

## Affected sites
- `apps/desktop/src/main/constants.ts:85-86``` `${CLOUD_AUTH_SCHEME_BASE}-${PROFILE}` ``; `linkcode` + `dev` == `linkcode-dev`.
- `packages/foundation/schema/src/daemon-runtime.ts:42``PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/` admits `dev` and `dev-alpha`.
- `packages/foundation/schema/src/product.ts:52-57` — the existing comment stating that the state dir's dot separator is chosen precisely so `--profile=development` cannot reach the development channel's directory.
- Vendored `@linkcode/cloud` 0.1.1 — `/^linkcode(?:-dev)?(?:-[a-z0-9][a-z0-9-]{0,31})?:$/` is ambiguous the same way, so it cannot disambiguate server-side either.

## Required outcome
- A channel-plus-profile scheme must be unambiguous: no (channel, profile) pair may produce the same scheme string as a different pair.

## Suggested approach (optional)
- RFC 3986 allows `+`, `-`, and `.` in a scheme after the first character, so a `.` separator (`linkcode-dev.alpha`) mirrors the state-dir discipline and is unambiguous because profile names forbid dots. This changes the wire-visible scheme, so it needs a matching `@linkcode/cloud` release and a linkcodehq-side update — worth confirming before the vendored 0.1.1 regex is treated as settled.

@xiaoland
xiaoland marked this pull request as draft August 27, 2026 07:20
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