Skip to content

fix(platform): support native shell auth callbacks#372

Merged
HamedMP merged 2 commits into
mainfrom
087-native-auth-callback
Jun 5, 2026
Merged

fix(platform): support native shell auth callbacks#372
HamedMP merged 2 commits into
mainfrom
087-native-auth-callback

Conversation

@HamedMP
Copy link
Copy Markdown
Owner

@HamedMP HamedMP commented Jun 5, 2026

Summary

  • add an allowlisted matrixos://auth redirect handoff to the device approval flow for the macOS shell client
  • preserve the native redirect through the Clerk approval page and success page so the browser can return control to the desktop app
  • allow generated zellij session names with underscores in gateway terminal session validation while keeping profile/layout names stricter

Invariants

  • Source of truth: the device-code flow state remains the existing platform auth flow store; the native callback is only an allowlisted handoff URL attached to verification and approval pages.
  • Lock/transaction scope: this change does not add database writes or multi-step persistence. Device approval continues to use the existing flow approval path.
  • Acceptable orphan states: if the custom URL handoff is blocked or unsupported, the approval still succeeds and the page shows a manual Return to Matrix OS link. The desktop app can keep polling device status.
  • Auth source of truth: Clerk remains the source of truth for approving device codes. The custom redirect never authenticates by itself and is only accepted for clientId=matrix-os-macos with matrixos://auth.
  • Deferred scope: this PR does not ship the native macOS UI changes or deploy the platform/app-shell service; those are separate follow-up slices.

Validation

  • pnpm test tests/platform/device-routes.test.ts
  • pnpm test tests/gateway/shell-names.test.ts
  • pnpm run check:patterns (0 violations; existing warning buckets only)

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

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

Project Deployment Actions Updated (UTC)
matrix-os-www Ready Ready Preview, Comment Jun 5, 2026 10:03pm

Request Review

Copy link
Copy Markdown
Owner Author

HamedMP commented Jun 5, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@HamedMP HamedMP changed the title fix(shell): harden account menu sign out (#365) fix(platform): support native shell auth callbacks Jun 5, 2026
@HamedMP HamedMP marked this pull request as ready for review June 5, 2026 21:02
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 5, 2026

Greptile Summary

This PR adds a signed native redirect handoff (matrixos://auth) for the macOS shell client in the device-code approval flow and widens the gateway session-name regex to allow underscores (for zellij auto-generated names) and a 64-character maximum.

  • Native redirect signing: a new HMAC-SHA256 signature (keyed by jwtSecret, bound to the userCode) is embedded in the verification URI at code issuance and re-verified at both the approval-page GET and the POST approve handler before any native URL is rendered or followed.
  • Session slug regex: SESSION_SLUG now allows [a-z0-9_-] up to 64 characters; PROFILE_SLUG and LONG_SLUG (used for layout/profile names) are unchanged, and the test suite explicitly checks the split rejection paths.
  • Tests: new cases cover the full macOS roundtrip (issuance → approval page → success page with meta-refresh), unsigned-URI rejection, and both session/layout/profile boundary behaviour.

Confidence Score: 5/5

Safe to merge; the HMAC binding is consistent at signing and verification, HTML escaping is applied at every embedding point, and the regex change is narrowly scoped to session names.

The signing and verification functions both normalize the redirect URI before computing the HMAC, so there is no canonicalization mismatch. The approval page re-validates the signature on GET before embedding hidden fields, and the POST handler re-validates it again before rendering the success page. The only finding is a swapped argument order in the timingSafeTokenEquals call that is functionally harmless today.

No files require special attention.

Important Files Changed

Filename Overview
packages/platform/src/auth-routes.ts Adds HMAC-SHA256-signed native redirect URI support for macOS device-code flow; signing and verification are consistent, HTML escaping is applied throughout, and the signature gate is enforced at both the approval page GET and the POST approve handler.
packages/gateway/src/shell/names.ts SESSION_SLUG regex updated to allow underscores and extend max length from 31 to 64 chars for zellij auto-generated session names; PROFILE_SLUG and LONG_SLUG unchanged.
tests/platform/device-routes.test.ts New tests cover macOS native callback issuance, scheme/clientId filtering, full approval-page roundtrip with meta-refresh verification, and rejection of unsigned manually-appended redirect URIs.
tests/gateway/shell-names.test.ts Tests updated to cover underscore acceptance in session names, exact 64-char length boundary, and separate rejection paths for layout/profile names that still ban underscores.

Sequence Diagram

sequenceDiagram
    participant App as macOS Shell App
    participant Platform as Platform API
    participant Browser as User Browser
    participant Clerk as Clerk Auth

    App->>Platform: POST /api/auth/device/code
    Platform->>Platform: normalizeNativeRedirectUri + signNativeRedirectUri
    Platform-->>App: verificationUri with redirect_uri and redirect_sig

    App->>Browser: Open verificationUri
    Browser->>Platform: GET /auth/device with redirect_uri and redirect_sig
    Platform->>Platform: verifiedNativeRedirectUri HMAC check
    Platform-->>Browser: Approval HTML with hidden redirect fields

    Browser->>Clerk: Sign-in
    Clerk-->>Browser: Session token

    Browser->>Platform: POST /auth/device/approve with redirectUri and redirectSig
    Platform->>Platform: Re-validate HMAC then approveDeviceCode
    Platform-->>Browser: Success page with meta refresh to matrixos://auth

    Browser->>App: matrixos:// URL scheme handoff

    App->>Platform: Poll GET /api/auth/device/token
    Platform-->>App: accessToken
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/platform/src/auth-routes.ts:126-127
The arguments to `timingSafeTokenEquals` are swapped relative to the function's parameter semantics. The function signature is `(actual: string | undefined, expected: string)`, where `actual` carries the potentially-absent caller value and `expected` is the computed reference. Here the locally-named `expected` (computed HMAC, always defined) is passed as `actual`, and the user-supplied `signatureValue` is passed as `expected`. The result is correct today because HMAC-SHA256 always returns a non-empty string, so the `!actual` early-exit guard is effectively dead for this call. Swapping the order aligns the call with the function's documented intent and makes it clear which side the undefined-guard protects.

```suggestion
  const expected = signNativeRedirectUri(secret, userCode, redirectUri);
  return timingSafeTokenEquals(signatureValue, expected) ? redirectUri : null;
```

Reviews (3): Last reviewed commit: "fix(platform): bind native auth callback..." | Re-trigger Greptile

Comment thread packages/platform/src/auth-routes.ts Outdated
const form = await c.req.parseBody();
formCsrf = typeof form.csrf === 'string' ? form.csrf : undefined;
userCode = typeof form.userCode === 'string' ? form.userCode : undefined;
nativeRedirectUri = normalizeNativeRedirectUri(form.redirectUri);
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.

P2 clientId gate only enforced at code issuance

The PR description states the native callback "is only accepted for clientId=matrix-os-macos", but the enforcement is only in the POST /api/auth/device/code route (line 713). The GET /auth/device approval page and the POST /auth/device/approve handler both accept any matrixos://auth redirect URI without checking that the originating clientId was matrix-os-macos. A CLI client or any other clientId that manually appends ?redirect_uri=matrixos://auth to the verification URL would get the native redirect embedded in the approval page and trigger a matrixos:// callback on success.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/platform/src/auth-routes.ts
Line: 837

Comment:
**`clientId` gate only enforced at code issuance**

The PR description states the native callback "is only accepted for `clientId=matrix-os-macos`", but the enforcement is only in the `POST /api/auth/device/code` route (line 713). The `GET /auth/device` approval page and the `POST /auth/device/approve` handler both accept any `matrixos://auth` redirect URI without checking that the originating `clientId` was `matrix-os-macos`. A CLI client or any other `clientId` that manually appends `?redirect_uri=matrixos://auth` to the verification URL would get the native redirect embedded in the approval page and trigger a `matrixos://` callback on success.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[codex] Fixed in eef92cb. Native redirect callbacks are now signed when the macOS device code is issued, and both the approval page and approval POST ignore unsigned/manual redirect_uri values.

Comment on lines +28 to 29
expect(validateSessionName("matrix-sess_abc123")).toBe("matrix-sess_abc123");
expect(validateLayoutName("dev-workspace-1")).toBe("dev-workspace-1");
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.

P2 New session-name length boundary (64) is untested in the positive direction

The regex change expands the max allowed session name from 31 to 64 characters, but the existing "oversized" test only covers 65-char strings (which exceed both old and new limits). There is no acceptance test for a 64-char session name, so a future tightening of the regex might regress silently.

Suggested change
expect(validateSessionName("matrix-sess_abc123")).toBe("matrix-sess_abc123");
expect(validateLayoutName("dev-workspace-1")).toBe("dev-workspace-1");
expect(validateSessionName("matrix-sess_abc123")).toBe("matrix-sess_abc123");
expect(validateSessionName("a".repeat(64))).toBe("a".repeat(64));
expect(validateLayoutName("dev-workspace-1")).toBe("dev-workspace-1");
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/gateway/shell-names.test.ts
Line: 28-29

Comment:
**New session-name length boundary (64) is untested in the positive direction**

The regex change expands the max allowed session name from 31 to 64 characters, but the existing "oversized" test only covers 65-char strings (which exceed both old and new limits). There is no acceptance test for a 64-char session name, so a future tightening of the regex might regress silently.

```suggestion
    expect(validateSessionName("matrix-sess_abc123")).toBe("matrix-sess_abc123");
    expect(validateSessionName("a".repeat(64))).toBe("a".repeat(64));
    expect(validateLayoutName("dev-workspace-1")).toBe("dev-workspace-1");
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[codex] Fixed in eef92cb by adding an explicit positive 64-character session-name boundary assertion.

@HamedMP HamedMP merged commit 28bdff8 into main Jun 5, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant