fix(auth): eliminate multi-tab org context bleed#2507
fix(auth): eliminate multi-tab org context bleed#2507viktormarinho wants to merge 1 commit intomainfrom
Conversation
Two browser tabs open on different orgs would clobber each other's session because shell-layout called setActive() on every navigation, writing activeOrganizationId to the shared server-side session. Fix across both request surfaces: Server (MeshContext / MCP routes): - context-factory.ts: when x-org-id header is present on a browser session request, resolve org directly from DB with membership verification instead of reading session.activeOrganizationId. The MCP client already sends x-org-id on every call, so all MCP tool operations are now per-request rather than per-session. Client (Better Auth org-management routes): - org-store.ts: module-level (per-tab) store for the current org ID. Each browser tab has its own JS execution context so this is naturally isolated. - auth-client.ts: fetchOptions.onRequest injects organizationId on every Better Auth organization route call — as a query param for GET requests (listMembers, listRoles, getFullOrganization) and in the request body for POST requests (inviteMember, removeMember, createRole, updateRole, deleteRole). Better Auth accepts an explicit organizationId that overrides the session fallback. - shell-layout.tsx: replaced setActive() with getFullOrganization() using the org slug from the URL. Pure read — zero session mutation. Also populates the org store so subsequent calls are scoped to the correct tab org from the start. - organizations-home.tsx, inbox.tsx: update org store when the user explicitly switches orgs so the store stays current. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsShould a new version be published when this PR is merged? React with an emoji to vote on the release type:
Current version: Deployment
|
There was a problem hiding this comment.
1 issue found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/layouts/shell-layout.tsx">
<violation number="1" location="apps/mesh/src/web/layouts/shell-layout.tsx:200">
P1: Bug: `setCurrentOrgId` inside `queryFn` won't fire on cache hits, causing stale org context when revisiting an org.
With `staleTime: Infinity` + `refetchOnMount: false`, navigating org A → org B → back to org A returns cached data for A without executing `queryFn`. The org store keeps B's ID, so all subsequent auth-client calls target the wrong org.
Move the store sync out of `queryFn` into a `useEffect` that runs whenever the query data changes.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| // Populate the per-tab org store so the auth client injects organizationId | ||
| // on all subsequent Better Auth org-management calls from this tab. | ||
| setCurrentOrgId(data?.id ?? null); |
There was a problem hiding this comment.
P1: Bug: setCurrentOrgId inside queryFn won't fire on cache hits, causing stale org context when revisiting an org.
With staleTime: Infinity + refetchOnMount: false, navigating org A → org B → back to org A returns cached data for A without executing queryFn. The org store keeps B's ID, so all subsequent auth-client calls target the wrong org.
Move the store sync out of queryFn into a useEffect that runs whenever the query data changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/layouts/shell-layout.tsx, line 200:
<comment>Bug: `setCurrentOrgId` inside `queryFn` won't fire on cache hits, causing stale org context when revisiting an org.
With `staleTime: Infinity` + `refetchOnMount: false`, navigating org A → org B → back to org A returns cached data for A without executing `queryFn`. The org store keeps B's ID, so all subsequent auth-client calls target the wrong org.
Move the store sync out of `queryFn` into a `useEffect` that runs whenever the query data changes.</comment>
<file context>
@@ -187,10 +188,17 @@ function ShellLayoutContent() {
+ // Populate the per-tab org store so the auth client injects organizationId
+ // on all subsequent Better Auth org-management calls from this tab.
+ setCurrentOrgId(data?.id ?? null);
+
return {
</file context>
Summary
shell-layout.tsxcalledsetActive()on every org navigation, writingactiveOrganizationIdto the shared server-side session. The last tab to navigate won, causing every other open tab to fetch data from the wrong org.Changes
Server — MeshContext / MCP routes
context-factory.ts: whenx-org-idheader is present on a browser session request, resolve org directly from DB with membership verification instead of readingsession.activeOrganizationId. The MCP client already sendsx-org-idon every call, so all MCP tool operations are now per-request rather than per-session.Client — Better Auth org-management routes
org-store.ts(new): module-level per-tab org ID store. Each browser tab has its own JS execution context so this is naturally isolated.auth-client.ts:fetchOptions.onRequestinjectsorganizationIdon every Better Auth organization route call — as a query param forGETrequests (listMembers,listRoles,getFullOrganization) and in the request body forPOSTrequests (inviteMember,removeMember,createRole,updateRole,deleteRole). Better Auth already accepts an explicitorganizationIdthat overrides the session fallback on all these endpoints.shell-layout.tsx: replacedsetActive()withgetFullOrganization({ query: { organizationSlug } }). Pure read — zero session mutation. Populates the org store so all subsequent calls are scoped correctly.organizations-home.tsx,inbox.tsx: update org store on explicit intentional org switches so the store stays current.Testing
bun run check— all workspaces passbun test apps/mesh/src/core/context-factory.test.ts— 11/11 pass🤖 Generated with Claude Code
Summary by cubic
Fixes multi-tab org context bleed by removing session mutations on nav and scoping org per request/per tab. Each browser tab now stays in its own org without clobbering others.
Written for commit f67f5be. Summary will update on new commits.