From 45528e13cc3f1ff0b888171253cf7fd9e6c2aa33 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:49:43 -0700 Subject: [PATCH] Add private cloud coexistence identity endpoint --- apps/cloud/src/coexistence/identity.ts | 80 ++++++++++++++++++++++++++ apps/cloud/src/env-augment.d.ts | 2 + apps/cloud/src/server.ts | 4 ++ notes/cloud-coexistence.md | 27 +++++++++ 4 files changed, 113 insertions(+) create mode 100644 apps/cloud/src/coexistence/identity.ts create mode 100644 notes/cloud-coexistence.md diff --git a/apps/cloud/src/coexistence/identity.ts b/apps/cloud/src/coexistence/identity.ts new file mode 100644 index 0000000000..1e2d1ff7d2 --- /dev/null +++ b/apps/cloud/src/coexistence/identity.ts @@ -0,0 +1,80 @@ +/** Private rollout boundary; existing auth providers still own credential and membership checks. */ +import { timingSafeEqual } from "node:crypto"; +import { Effect, Predicate } from "effect"; +import { McpAuthProvider } from "@executor-js/host-mcp"; +import { cloudMcpAuth } from "../mcp/auth-provider"; +import { prepareMcpOrgScope } from "../mcp/mount"; +import { CoreSharedServices, WorkOSClient } from "../auth/workos"; +import { authorizeOrganizationSelector } from "../auth/organization"; +import { RequestScopedServicesLive } from "../api/layers"; + +const reply = (body: unknown, status = 200): Response => + Response.json(body, { + status, + headers: { "cache-control": "no-store" }, + }); +const keyMatches = (expected: string, actual: string): boolean => { + const encoder = new TextEncoder(); + const left = encoder.encode(expected); + const right = encoder.encode(actual); + return left.length === right.length && timingSafeEqual(left, right); +}; + +/** Disabled unless explicitly configured. It never returns a token, email, or credential. */ +export const coexistenceIdentity = ( + request: Request, + secret: string | undefined, +): Promise => { + const supplied = request.headers.get("x-executor-coexistence-key"); + if (!secret || secret.length < 32 || !supplied || !keyMatches(secret, supplied)) + return Promise.resolve(reply({ error: "Not found" }, 404)); + if (request.method !== "GET") return Promise.resolve(reply({ error: "Method not allowed" }, 405)); + const url = new URL(request.url); + const kind = url.searchParams.get("kind"); + const selector = url.searchParams.get("organization"); + const mcp = Effect.gen(function* () { + const auth = yield* McpAuthProvider; + const target = new URL( + selector === null ? "/mcp" : `/${encodeURIComponent(selector)}/mcp`, + request.url, + ); + const outcome = yield* auth.authenticate( + prepareMcpOrgScope(new Request(target, { headers: request.headers })), + ); + if (Predicate.isTagged(outcome, "Authenticated")) { + const principal = outcome.principal; + return reply({ + userId: principal.accountId, + organizationId: principal.organizationId, + organizationSlug: principal.organizationSlug ?? null, + role: principal.orgRole, + }); + } + if (Predicate.isTagged(outcome, "Unauthorized")) return reply({ error: "Unauthorized" }, 401); + if (Predicate.isTagged(outcome, "Forbidden")) return reply({ error: "Forbidden" }, 403); + return reply({ error: "Authentication unavailable" }, 503); + }).pipe(Effect.provide(cloudMcpAuth)); + const browser = Effect.gen(function* () { + const workos = yield* WorkOSClient; + const session = yield* workos.authenticateRequest(request); + if (session === null) return reply({ error: "Unauthorized" }, 401); + const organization = selector ?? session.organizationId; + if (!organization) return reply({ error: "Organization required" }, 403); + const membership = yield* authorizeOrganizationSelector(session.userId, organization); + if (membership === null) return reply({ error: "Forbidden" }, 403); + return reply({ + userId: session.userId, + organizationId: membership.id, + organizationSlug: membership.slug ?? null, + role: membership.memberRole, + }); + }).pipe(Effect.provide(RequestScopedServicesLive), Effect.provide(CoreSharedServices)); + if (kind !== "mcp" && kind !== "browser") + return Promise.resolve(reply({ error: "Invalid identity kind" }, 400)); + return Effect.runPromise( + (kind === "mcp" ? mcp : browser).pipe( + Effect.scoped, + Effect.catchCause(() => Effect.succeed(reply({ error: "Authentication unavailable" }, 503))), + ), + ); +}; diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index 017570cf1a..e110498e9b 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -125,6 +125,8 @@ declare global { * the mirror current. */ WORKOS_WEBHOOK_SECRET?: string; + /** Temporary private routing bridge. Unset disables the endpoint. */ + EXECUTOR_COEXISTENCE_KEY?: string; // MCP EXECUTOR_MCP_DEBUG?: string; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 2dde0e8403..5ae7018c81 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -10,6 +10,7 @@ import { import * as Sentry from "@sentry/cloudflare"; import handler from "@tanstack/react-start/server-entry"; +import { coexistenceIdentity } from "./coexistence/identity"; import { isAppOwnedPath, servedByAppPlane } from "./app-paths"; import { marketingProxyRequest } from "./edge/marketing"; import { passthroughResponse } from "./edge/passthrough"; @@ -306,6 +307,9 @@ const prewarmAppPlane = (ctx: ExecutionContext): void => { const cloudflareHandler = { fetch: async (request, env, ctx) => { + if (new URL(request.url).pathname === "/__coexistence/identity") { + return coexistenceIdentity(request, env.EXECUTOR_COEXISTENCE_KEY); + } isolateRequestSeq += 1; // Public pages must not enter TanStack Start: its first-request dynamic diff --git a/notes/cloud-coexistence.md b/notes/cloud-coexistence.md new file mode 100644 index 0000000000..340aa4f377 --- /dev/null +++ b/notes/cloud-coexistence.md @@ -0,0 +1,27 @@ +# Cloud coexistence identity endpoint + +`GET /__coexistence/identity?kind=browser|mcp&organization=` is a +private bridge for the separate cloud coexistence router. It is disabled unless +`EXECUTOR_COEXISTENCE_KEY` is configured with at least 32 characters. The caller +must supply that key as `x-executor-coexistence-key`. + +Browser requests validate the existing sealed WorkOS session and current org +membership. An omitted org uses the session's selected org. MCP requests use the +existing MCP credential validator and explicit organization selector; cookies do +not authenticate MCP requests. Existing WorkOS JWT and API-key behavior remains +owned by that validator. + +The response contains only `userId`, `organizationId`, `organizationSlug` and +`role`. It never returns emails, cookies or credentials. Missing/wrong bridge +keys receive 404, invalid credentials 401, inaccessible orgs 403, and dependency +failures 503. All replies have `Cache-Control: no-store`. + +This endpoint does not freeze or migrate data, change domains, or authorize a +cutover. No production deployment accompanies it. Configure the key only on the +intended backends and gateway; never in a browser bundle. The gateway must strip +private bridge headers from public traffic. + +Verification: from `e2e`, run +`../node_modules/.bin/vitest run --project cloud cloud/coexistence-identity.test.ts`. +The scenario uses the real v1 Cloud runtime and WorkOS emulator, testing session +validation, cross-org denial, private-header protection and cookie-only MCP denial.