Let clients without a managed backend manage pairing - #4496
Let clients without a managed backend manage pairing#4496colonelpanic8 wants to merge 7 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces new authentication and access management capabilities, enabling clients without managed backends to manage pairing links and sessions on external servers. Changes to auth/session handling and the introduction of new user-facing features warrant careful human review. You can customize Macroscope's approvability policy. Learn more. |
b38c6b5 to
f145096
Compare
…nage pairing) # Conflicts: # apps/web/src/components/settings/ConnectionsSettings.logic.test.ts # apps/web/src/components/settings/ConnectionsSettings.logic.ts # apps/web/src/components/settings/ConnectionsSettings.tsx
…nage pairing) # Conflicts: # apps/web/src/components/settings/ConnectionsSettings.logic.test.ts # apps/web/src/components/settings/ConnectionsSettings.logic.ts # apps/web/src/components/settings/ConnectionsSettings.tsx
…nage pairing) # Conflicts: # apps/web/src/components/settings/ConnectionsSettings.logic.test.ts # apps/web/src/components/settings/ConnectionsSettings.logic.ts # apps/web/src/components/settings/ConnectionsSettings.tsx
…nage pairing) # Conflicts: # apps/web/src/components/settings/ConnectionsSettings.logic.test.ts # apps/web/src/components/settings/ConnectionsSettings.logic.ts # apps/web/src/components/settings/ConnectionsSettings.tsx
…nage pairing) # Conflicts: # apps/web/src/components/settings/ConnectionsSettings.logic.test.ts # apps/web/src/components/settings/ConnectionsSettings.logic.ts # apps/web/src/components/settings/ConnectionsSettings.tsx
b824828 to
c2b774f
Compare
Access management was bound to the primary environment, so it was reachable only from a client that owns a local backend. A hosted-app client has no primary at all — `platformConnectionSourceLayer` emits no registrations for `isHostedStaticApp()` — so the Connections page hid the whole "Authorized clients" section. A user who had paired the hosted app to their own server could not mint a pairing link, revoke a client, or see a pairing QR code for that server. Route the access-management endpoints through the environment supervisor's prepared connection instead of the primary, and administer the active environment when there is no primary. A desktop that manages its own backend keeps administering that backend, so managed mode is unchanged. Pairing also silently lost administrative scope: onboarding always requested the standard client scopes, and the server rejects a wider request rather than clamping it, so a client bootstrapped from a server's own startup token was downgraded to a standard session and could not manage the access it had just been handed. Request no scopes and take exactly what the credential carries. Only a directly addressable HTTP origin yields a shareable pairing URL; relay and SSH environments are reached through a broker or a local tunnel whose address means nothing on another device, so those fall back to showing the bare pairing code.
`t3 serve` already prints a QR code next to its startup pairing URL, but a link minted later with `t3 pair` printed only text, so pairing a phone meant transcribing a URL by hand. Encode the pair URL when there is one. A bare token is not actionable on the device that scans it, and JSON output stays machine-readable.
When the Connections page administers an environment it is not served by — a relay or SSH environment, or a bearer environment whose profile is missing — that environment has no addressable base URL, and the pairing row is meant to fall back to the bare pairing code. It instead fell through to the current page's `/pair` URL whenever this page was itself on a non-loopback host, so the QR code and the copy button handed out a link to this client app rather than to the server the link belongs to. The page's own origin only reaches the administered server when that server is the one that served the page, which is the managed-backend case. Make that a stated input of the fallback rather than an accident of the page's hostname, and move the choice into a tested helper.
c2b774f to
9f1395b
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fc202bc. Configure here.
|
Folded into #9361, which carries this change on a current |

What Changed
Access management (pairing links, authorized clients, and the pairing QR code) is now scoped to whichever server the Connections page is administering, instead of always the primary environment.
resolveAccessEnvironment()administers the primary when one exists, and the active environment otherwise. A desktop that manages its own backend is unaffected.packages/client-runtime/src/state/authHttp.tsroutes/api/auth/session,/api/auth/pairing-token,/api/auth/pairing-links/revoke,/api/auth/clients/revoke, and/api/auth/clients/revoke-othersthrough the environment supervisor's prepared connection.environmentPairingBaseUrl()produces a shareable pairing URL only for a directly addressable HTTP origin. Relay and SSH environments fall back to showing the bare pairing code.t3 pairprints the same scannable QR code thatt3 servealready prints at startup.Why
platformConnectionSourceLayerreturns no registrations whenisHostedStaticApp(), so a hosted-app client has no primary environment at all.canManageLocalBackendreads scopes from the primary session, which isnullin that case, so the entire "Authorized clients" section was hidden. A user who had paired the hosted app to their own server could not create a pairing link, revoke a client, or display a pairing QR code for that server.Worse, the page rendered a contradictory "Administrative access — pairing links and client-session management require the access:write scope for this backend" row even when the client had paired using the server's own administrative startup token.
That second part had its own cause: onboarding always requested the standard client scopes, and the server rejects a wider request rather than clamping it, so a client bootstrapped from a server's startup token was silently downgraded to a standard session and could not manage the access it had just been handed. Requesting no scopes and accepting what the credential carries fixes it.
The access-management endpoints were already per-environment; only the client bound them to the primary.
UI Changes
Hosted web client (no primary environment, served from a non-loopback origin on
:53595) paired to astandalone
t3 serveon:53141, atSettings → Connections. Same viewport, theme, and backend forevery shot; only the
apps/webbuild was swapped between commits.Before (
38cfc25, main) — no "Authorized clients" section at all, so there is no way to create apairing link or revoke a client, plus a misleading scope message despite having paired with the
server's administrative startup token:
After (
b38c6b5) — the section renders against the saved environment, withCreate link, revokecontrols, and the pairing link's QR affordance:
After (
b38c6b5) — the pairing link and its QR resolve to the administered backend's own origin(
:53141), not to the client app's origin (:53595). The token is redacted and the QR blurred; thelink was left to expire and the disposable backend was destroyed after capture:
Control for the
resolveShareablePairingUrlreview fix (unchanged rendering)The review fix only changes the case where the administered environment has no directly
addressable HTTP origin — a relay environment, an SSH environment, or a bearer entry with no profile.
Those states are not reachable from a locally runnable web client (relay needs managed-relay
infrastructure to connect; SSH needs
window.desktopBridge; a connected bearer environment always hasa base URL, since the resolver reads it from the same profile
environmentPairingBaseUrl()reads), sothat branch is covered by the unit tests in
ConnectionsSettings.logic.test.tsrather than by ascreenshot. For the case that is reachable here — an environment with a usable HTTP base URL — the
rendering is unchanged, as it should be:
Before the fix (
dcd149d):After the fix (
b38c6b5):Only the expiry countdown differs.
Checklist
Validation
Integrated verification: hosted-mode web client (
VITE_HOSTED_APP_CHANNELset, noVITE_HTTP_URL/VITE_WS_URL) paired to an isolatedt3 serve, then created a pairing link, opened its QR popover, and confirmed the link resolves to the environment's own base URL. Also confirmed a client holding only standard scopes still sees no access-management section.Note for reviewers
The section's visibility is gated on
access:writewhile its data stream requiresaccess:read, so a credential granted one but not the other renders an error state. That mismatch already exists onmainfor the primary path, so it is left unchanged here.Note
Medium Risk
Touches auth pairing, session scopes, and shareable URL selection across web and client-runtime; mistakes could pair the wrong device or hide admin capabilities, but logic is heavily unit-tested.
Overview
Connections now administers pairing links and authorized clients on whichever server the page is targeting—primary when the desktop manages a backend, otherwise the active saved environment—so hosted and client-only apps can mint links and revoke sessions without a primary.
New client-runtime HTTP helpers route
/api/auth/*mutations through each environment’s prepared connection, with session-scope queries gating UI on the credential for that server. Shareable pairing URLs are chosen viaresolveShareablePairingUrl(loopback and wrong-origin links filtered); relay/SSH entries show the bare code only.Pairing onboarding no longer sends an explicit OAuth scope list, so sessions inherit exactly what the pairing credential grants (fixing silent downgrade when bootstrapping from an admin startup token). CLI human output adds a terminal QR when a pair URL exists; JSON stays machine-readable. Chat and root bootstrap sync active environment id with the current route without overwriting an existing selection.
Reviewed by Cursor Bugbot for commit 3496404. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Let clients without a managed backend manage pairing for saved environments
authHttp.tsfor reading session state and mutating access (pairing links, client sessions) using the active environment's prepared connection.createAuthEnvironmentAtomsinauth.tsto expose these per-environment access-management queries and commands.resolveShareablePairingUrllogic inConnectionsSettings.logic.ts; human-readable CLI output gains a terminal QR code when a shareable URL is available.Macroscope summarized 3496404.