Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions apps/mesh/src/core/context-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export interface AuthContext {
role?: string; // User's role (for built-in role bypass)
permissions?: Permission; // Permissions from API key or custom role (MCP OAuth)
userId?: string; // User ID for server-side API key operations
serverSide?: boolean; // MCP OAuth / API key — BA session APIs can't resolve these tokens
}

/**
Expand All @@ -220,7 +221,12 @@ export interface AuthContext {
* 2. Browser sessions → delegate to Better Auth's hasPermission API
*/
export function createBoundAuthClient(ctx: AuthContext): BoundAuthClient {
const { auth, headers, role, permissions, userId } = ctx;
const { auth, headers, role, permissions, userId, serverSide } = ctx;

// For MCP OAuth / API key auth, the Bearer token in headers confuses BA's
// session middleware (it tries cookie or API key resolution, both fail).
// Use empty headers for server-side calls — BA still works via query/body params.
const serverHeaders = new Headers();

// Get hasPermission from Better Auth's organization plugin (for browser sessions)
const hasPermissionApi = (auth.api as { hasPermission?: HasPermissionAPI })
Expand Down Expand Up @@ -303,15 +309,16 @@ export function createBoundAuthClient(ctx: AuthContext): BoundAuthClient {

get: async (organizationId) => {
return auth.api.getFullOrganization({
headers,
headers: serverSide ? serverHeaders : headers,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

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

P2: Clearing headers here breaks organization.get() for server-side auth because existing callers rely on the active organization from session and do not pass an organizationId.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/core/context-factory.ts, line 312:

<comment>Clearing headers here breaks `organization.get()` for server-side auth because existing callers rely on the active organization from session and do not pass an `organizationId`.</comment>

<file context>
@@ -303,15 +309,16 @@ export function createBoundAuthClient(ctx: AuthContext): BoundAuthClient {
       get: async (organizationId) => {
         return auth.api.getFullOrganization({
-          headers,
+          headers: serverSide ? serverHeaders : headers,
           query: organizationId ? { organizationId } : undefined,
         });
</file context>
Fix with Cubic

query: organizationId ? { organizationId } : undefined,
});
},

list: async (userId?: string) => {
list: async (targetUserId?: string) => {
const uid = serverSide ? (targetUserId ?? userId) : targetUserId;
return auth.api.listOrganizations({
headers,
query: userId ? { userId } : undefined,
headers: serverSide ? serverHeaders : headers,
query: uid ? { userId: uid } : undefined,
});
},

Expand All @@ -331,7 +338,7 @@ export function createBoundAuthClient(ctx: AuthContext): BoundAuthClient {

listMembers: async (options) => {
return auth.api.listMembers({
headers,
headers: serverSide ? serverHeaders : headers,
query: options
? {
organizationId: options.organizationId,
Expand Down Expand Up @@ -460,6 +467,7 @@ async function authenticateRequest(
permissions?: Permission; // Permissions from API key or custom role (for non-browser sessions)
apiKeyId?: string;
organization?: OrganizationContext;
isMcpOAuthSession?: boolean;
}> {
const authHeader = req.headers.get("Authorization");

Expand Down Expand Up @@ -521,6 +529,7 @@ async function authenticateRequest(
role,
permissions,
organization,
isMcpOAuthSession: true,
};
}
} catch (error) {
Expand Down Expand Up @@ -850,12 +859,17 @@ export async function createMeshContextFactory(
: { user: undefined };

// Create bound auth client (encapsulates HTTP headers and auth context)
// MCP OAuth / API key tokens aren't resolvable by BA's session middleware,
// so flag those as serverSide to use empty headers + explicit userId/query params.
const isServerSide =
!!authResult.isMcpOAuthSession || !!authResult.apiKeyId;
const boundAuth = createBoundAuthClient({
auth: config.auth,
headers: req?.headers ?? new Headers(),
role: authResult.role,
permissions: authResult.permissions,
userId: authResult.user?.id, // For server-side API key operations
userId: authResult.user?.id,
serverSide: isServerSide,
});

// Build auth object for MeshContext
Expand Down
Loading