Repository navigation
Inherit collab identity from Cloudflare Access - #148
Merged
molefrog merged 10 commits intoSep 28, 2026
Merged
Conversation
A deployment behind Cloudflare Access (Zero Trust) can set `cloudflareAccess` in config.json, or MOI_CLOUDFLARE_ACCESS_TEAM_DOMAIN and MOI_CLOUDFLARE_ACCESS_AUD, so every viewer inherits their Access identity instead of creating a dev profile. The server verifies the Access application token (Cf-Access-Jwt-Assertion, or the CF_Authorization cookie when a proxy drops the header): RS256 signature against the team's published keys, issuer, audience, and expiry. The profile takes id from `sub` and email from `email`; tokens carry no display name, so the name is the email's local part, and the color is a stable pick from the persona palette hashed from the id. GET /api/identity serves the verified profile and loads before the app mounts. The browser uses it over a saved dev profile, and an outer host still wins. Presence sockets verify the token on upgrade (401 without one) and pin every join and identity update to the verified profile, so one viewer cannot appear as another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47075a1467
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…oudflare-identity-inherit-7w5lxk
The base now allows users without names, with built-in labels falling back to the email. Access tokens carry no display name, so the inherited profile keeps only id, email, and the id-derived color instead of deriving a name from the email. The /dev/collab notice labels the profile with userDisplayName, which also fixes the typecheck against the optional name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
Fold the verifier rejections into one table, drop cases that repeat other coverage (concurrent fetches, clock drift, profile helpers), and merge the config, endpoint, socket pinning, and identity precedence checks into fewer tests. Coverage stays on signature, issuer, audience, expiry, key refresh throttling, header and cookie tokens, config parsing, socket pinning, and source precedence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
A presence socket verified by Cloudflare Access now joins only with the verified id. A different id, such as an outer host's own identity, is refused with identity_mismatch instead of being rewritten to the Access user, so peers never see a different person than the browser shows. Cached signing keys now expire after ten minutes, so a key Cloudflare stops publishing stops verifying tokens even when no new key id arrives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
Replace the hand-rolled JWT parsing, WebCrypto verification, and key cache with jose's jwtVerify and createRemoteJWKSet, the library Cloudflare's own verification examples use. Tokens must be RS256, name the team domain as issuer and one of the configured AUD tags, and carry an expiry, with 60 seconds of clock tolerance. jose caches the team's keys for ten minutes and refetches for an unknown key id after a 30 second cooldown. Failed key fetches are logged; bad tokens are not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
…oudflare-identity-inherit-7w5lxk
The not-yet-valid case set nbf 61 seconds past a timestamp taken at module load, one second outside the verifier's 60 second tolerance, so it failed whenever key generation in beforeAll took over a second, as on a slow CI runner. Expired and not-yet-valid tokens now sit ten minutes outside the tolerance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
Resolve app-config conflicts: keep the new experimental startup flags alongside the Cloudflare Access deployment config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK
tonyfresher
added a commit
that referenced
this pull request
Oct 2, 2026
* Add collab protocol and transactional shared storage * Add workspace collab workers and explicit CLI setup * Add collab client state and external identity bridge * Add collaborative applet primitives and dev identity setup * Integrate collab with workspace views and personal navigation * Remember everyone who joins a workspace in a people directory A person's id now resolves to a name and face after they leave. The collab worker keeps a `people` table (schema v2, migrated from v1), upserts it on join and identity change, sends it in `welcome.people`, and announces new or changed profiles with a `people` message. The client store merges the directory with live participants. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add id-based people components on a swappable collab backend Applets refer to a person by id (`id`, or `ids` for several) and the components resolve name, face, and presence themselves: `Person` (with `avatarOnly` and `showStatus`), `Facepile`, `Cursor`, `PresenceFrame`, `PresenceGutter`, and the `usePerson` hook. `PresenceFrame`, and with it `PresenceField` and `Selection`, hugs the single element it wraps and copies its corner radius. `SyncStatus` is gone; applets render `isSaving` and `error` from the shared-state hooks. Hooks and components now talk to a `CollabBackend` contract from context instead of the socket client. The workspace provides the live backend; `createFakeBackend` is an in-memory room behind the same store. `/dev/collab-kit` runs on it: every component with made-up people, then the connected components and the exported hooks against scripted participants. The registry Avatar gains an `xs` size and expresses its default size as a variant so applet-scoped CSS cannot override it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Keep the collab capability type until its callers move off it The people directory commit picked up an unrelated removal of `CollabCapability` from the working tree, while the code that stops using it is not committed yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Move collab availability into startup config * Combine collab playground and optional dev identity setup * Document the experimental collab architecture and dev workflow * Remove the collab controls import cycle * Limit collaboration to presence and add host user directories Remove persistent shared-state synchronization, expose connected peers and separate presence reads/publications, and resolve profiles through authoritative workspace directories supplied by Batiok. Built-in indicators publish only while active; disabled applets keep inert APIs. Validated with 1744 passing tests (4 platform skips), production build, lint, changed-file formatting, and typecheck excluding pre-existing invalid untracked sketch.tsx. Equivalent checks ran manually because the pre-commit hook formats the whole tree and would touch unrelated files. * Add keyed list presence and expose workspace members Show only connected people in the workspace header. Export the full workspace directory hook with status filtering, clarify applet/page scoping, and support one presence wrapper around keyed list or form children. Verified 1754 tests, production build, two live browser sessions, filtered project typecheck, lint, and changed-file formatting. Ran checks manually instead of the whole-tree autoformat hook to preserve the existing invalid untracked sketch.tsx unchanged. * Enable collaboration only through CLI flags Forward --experimental-collab directly to child servers and initialize process configuration from parsed flags. Remove the environment toggle and service handling; keep init documentation installation separate from runtime opt-in. Verified 1755 tests, lint, changed-file formatting, and project typecheck with the pre-existing invalid sketch.tsx excluded. Ran checks manually to preserve that untracked file from the whole-tree formatting hook. * Handle missing collab bridges and isolate presence lifecycle Return empty hooks and render nothing with one warning when an applet collab bridge is unavailable. Keep personal chat and view-builder creation from updating shared selection. Stop workspace presence, including pending connections, when the workspace is removed. Addresses PR #147 review findings. Verified 1774 tests, production client build, lint, formatting, and project typecheck excluding the pre-existing invalid sketch.tsx. Ran checks manually to avoid the whole-tree hook modifying that unrelated untracked file. * Consolidate collaboration engine and atomic host state * Allow collaboration users without names * Scope presence targets with explicit component and group IDs * Include emails in collaboration hook examples * Keep collaboration guide with workspace references * Simplify collaborative applet authoring guide * Keep applet messages in personal chats and verify review fixes * Document collaboration component props and defaults * Show Activity scopes in a dedicated collaboration demo * Simplify experimental startup flags * Remove redundant workspace layout provider key * Simplify workspace collaboration boundary * Inherit collab identity from Cloudflare Access (#148) * Inherit collab identity from Cloudflare Access A deployment behind Cloudflare Access (Zero Trust) can set `cloudflareAccess` in config.json, or MOI_CLOUDFLARE_ACCESS_TEAM_DOMAIN and MOI_CLOUDFLARE_ACCESS_AUD, so every viewer inherits their Access identity instead of creating a dev profile. The server verifies the Access application token (Cf-Access-Jwt-Assertion, or the CF_Authorization cookie when a proxy drops the header): RS256 signature against the team's published keys, issuer, audience, and expiry. The profile takes id from `sub` and email from `email`; tokens carry no display name, so the name is the email's local part, and the color is a stable pick from the persona palette hashed from the id. GET /api/identity serves the verified profile and loads before the app mounts. The browser uses it over a saved dev profile, and an outer host still wins. Presence sockets verify the token on upgrade (401 without one) and pin every join and identity update to the verified profile, so one viewer cannot appear as another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK * Leave the name out of Cloudflare Access profiles The base now allows users without names, with built-in labels falling back to the email. Access tokens carry no display name, so the inherited profile keeps only id, email, and the id-derived color instead of deriving a name from the email. The /dev/collab notice labels the profile with userDisplayName, which also fixes the typecheck against the optional name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK * Condense Cloudflare Access tests Fold the verifier rejections into one table, drop cases that repeat other coverage (concurrent fetches, clock drift, profile helpers), and merge the config, endpoint, socket pinning, and identity precedence checks into fewer tests. Coverage stays on signature, issuer, audience, expiry, key refresh throttling, header and cookie tokens, config parsing, socket pinning, and source precedence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK * Refuse mismatched Access identities and expire cached keys A presence socket verified by Cloudflare Access now joins only with the verified id. A different id, such as an outer host's own identity, is refused with identity_mismatch instead of being rewritten to the Access user, so peers never see a different person than the browser shows. Cached signing keys now expire after ten minutes, so a key Cloudflare stops publishing stops verifying tokens even when no new key id arrives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK * Verify Cloudflare Access tokens with jose Replace the hand-rolled JWT parsing, WebCrypto verification, and key cache with jose's jwtVerify and createRemoteJWKSet, the library Cloudflare's own verification examples use. Tokens must be RS256, name the team domain as issuer and one of the configured AUD tags, and carry an expiry, with 60 seconds of clock tolerance. jose caches the team's keys for ten minutes and refetches for an unknown key id after a 30 second cooldown. Failed key fetches are logged; bad tokens are not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK * Widen the Access token time margins in tests The not-yet-valid case set nbf 61 seconds past a timestamp taken at module load, one second outside the verifier's 60 second tolerance, so it failed whenever key generation in beforeAll took over a second, as on a slow CI runner. Expired and not-yet-valid tokens now sit ten minutes outside the tolerance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK --------- Co-authored-by: Claude <noreply@anthropic.com> * Clarify collaboration names and tab state * Clarify applet collaboration bridge types * Remove obsolete collaboration error boundary * Simplify avatar size guidance * Allow host user profiles without colors * Name proxy verified user consistently * Remove obsolete collab layout cleanup * Trim Batiok collab integration guide * Use named user colors for collaboration * Remove unused collaboration location title * Resolve collab reference in server chat context * Shorten browser tab session helper names * Start unshipped collab protocol at version 1 * Unify applet URL resolution for pages and files * Omit blank user profile names * Trim applet and collaboration documentation * Return undefined for unresolved collaboration users * Load collaboration runtime from typed source * Clarify applet runtime structure and worker messages * Reuse public applet declarations as type contracts * Use undefined for absent collaboration users * Name cursor areas with id * Use explicit status for collaboration connections * Name collaboration applet scopes with appletId * Simplify collaboration type names and share JsonValue * Clarify applet presence naming and reuse applet IDs * Use path-shaped applet IDs and rename AppletContainer * Document the collaboration host bridge generically * Trim collaboration docs and clarify applet guidance * Round generated collaboration avatars * Simplify collaboration structure and recover rejected joins * Rename collaboration toolbar and fix scoped color token checks * Simplify collaboration directory state and public hooks * Initialize local collaboration users during startup * Simplify collaboration user rendering and component types * Check collaboration implementations against the public contract * Compose user avatars and clarify collaboration docs and tests * Clarify collaboration component names and playground examples * Group dev UI component pages and simplify collab page naming * Refine user component sizes and rename detail to description * Simplify avatar badges and mask their background gap * Reuse shared avatar groups and simplify count sizing * Use transparent masks for avatar group overlaps * Simplify collaboration user components and remove Activity * Simplify collaboration badges and refine presence visuals * Make collaboration playground interactive with two participants * Unify collaboration presence components and simplify focus tracking * Simplify presence frame geometry and add badge alignment * Fix collaboration cursor entry and scroll positioning * Add vertical alignment to presence avatars * Clarify when to use collaboration presence components * Simplify workspace collaboration users menu * Polish workspace users menu * Use fruit names for anonymous collaboration users * Refine collaboration avatars and users menu ordering * Restore avatar group overlap direction * Add optional muted You label to collaboration users --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Anton Frehser <hey@tonyfresher.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #147. A moi deployment behind Cloudflare Access (Zero Trust) can now be configured globally to inherit each viewer's identity, so presence shows who is who without dev profiles. The id and email come from Access's verified token. Tokens carry no display name, so the profile has no
nameand labels show the email (the nameless users from #147). The color is a stable pick from the persona palette, hashed from the id.The app loads
/api/identitybefore mounting. The inherited profile replaces a saved dev profile, and/dev/collabshows it instead of the form. Setup and details: Cloudflare Access identity.Review focus
server/collab/cloudflare-access.ts):jose(jwtVerify+createRemoteJWKSet, as in Cloudflare's own examples) against<team>/cdn-cgi/access/certs: RS256 only,iss,aud, requiredexp, 60 s clock tolerance. jose caches keys for ten minutes and refetches for an unknownkidafter a 30 s cooldown; failed key fetches are logged and verify nothing. Service tokens (emptysub) resolve to no identity. TheCF_Authorizationcookie is used only when the header is absent.joseis a new direct dependency (already in the tree through the MCP SDK).manager.ts,web.ts): with Access configured, the presence upgrade returns 401 without a valid token.join/identitymessages must carry the verified id and get the verified profile; a different id, such as an outer host's own identity, is refused withidentity_mismatchrather than rewritten.identity.ts): an outer host beats Access, which beats a dev profile, whatever the load order. An unverified request signs the tab out. Access supplies no membership, so directories keep the live fallback.Verification
bun teston the latestcodex/collab: 1,931 passed, 4 skipped, 0 failed. Lint, format, registry, typecheck, and a frozen-lockfile install are clean. Manual check: ran the server with Access configured, a mocked certs endpoint, and signed tokens for two users. Header and cookie tokens resolved to the profile; a forged signature or a missing token resolved toidentity: null, withCache-Control: private, no-store. On the presence socket, a join with the verified id but a spoofed name came back as the verified profile, another id was refused withidentity_mismatch, and a tokenless upgrade was refused. Two Playwright sessions holding the Access cookie saw each other in the workspace header (alex@acme.dev (you),sam.lee@acme.dev · Overview).🤖 Generated with Claude Code
https://claude.ai/code/session_01Ld6kvjMXg9eSvYsUJwo9NK