From 2486ef30c17e5e464bbd11dd56e79bb36eef8b2d Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:49:44 -0700 Subject: [PATCH] Verify cloud coexistence identity authorization --- e2e/cloud/coexistence-identity.test.ts | 76 ++++++++++++++++++++++++++ e2e/setup/cloud.globalsetup.ts | 1 + 2 files changed, 77 insertions(+) create mode 100644 e2e/cloud/coexistence-identity.test.ts diff --git a/e2e/cloud/coexistence-identity.test.ts b/e2e/cloud/coexistence-identity.test.ts new file mode 100644 index 0000000000..e4c65a1e4c --- /dev/null +++ b/e2e/cloud/coexistence-identity.test.ts @@ -0,0 +1,76 @@ +import { expect } from "@effect/vitest"; +import { Effect, Schema } from "effect"; +import { scenario } from "../src/scenario"; +import { Mcp, Target } from "../src/services"; + +const Identity = Schema.Struct({ + userId: Schema.String, + organizationId: Schema.String, + organizationSlug: Schema.NullOr(Schema.String), + role: Schema.Literals(["admin", "member", "owner"]), +}); +const bridgeKey = "synthetic-coexistence-e2e-key-32-characters"; + +scenario( + "cloud coexistence identity verifies sessions and denies foreign organizations", + {}, + Effect.gen(function* () { + const target = yield* Target; + const first = yield* target.newIdentity(); + const second = yield* target.newIdentity(); + const read = (query: string, headers: Record = {}) => + Effect.promise(() => fetch(`${target.baseUrl}/__coexistence/identity?${query}`, { headers })); + expect((yield* read("kind=browser", first.headers)).status).toBe(404); + expect( + (yield* read("kind=browser", { ...first.headers, "x-executor-coexistence-key": "wrong" })) + .status, + ).toBe(404); + expect((yield* read("kind=browser", { "x-executor-coexistence-key": bridgeKey })).status).toBe( + 401, + ); + const firstReply = yield* read("kind=browser", { + ...first.headers, + "x-executor-coexistence-key": bridgeKey, + }); + expect(firstReply.status).toBe(200); + const firstIdentity = yield* Effect.promise(() => firstReply.json()).pipe( + Effect.flatMap(Schema.decodeUnknownEffect(Identity)), + ); + expect(firstIdentity).toMatchObject({ role: "admin" }); + expect(Object.keys(firstIdentity).sort()).toEqual([ + "organizationId", + "organizationSlug", + "role", + "userId", + ]); + const mcp = yield* Mcp; + const bearer = yield* mcp.mintBearer(first.credentials?.email ?? first.label); + const authorized = yield* read( + `kind=mcp&organization=${encodeURIComponent(firstIdentity.organizationId)}`, + { authorization: `Bearer ${bearer}`, "x-executor-coexistence-key": bridgeKey }, + ); + expect(authorized.status).toBe(200); + const mcpIdentity = yield* Effect.promise(() => authorized.json()).pipe( + Effect.flatMap(Schema.decodeUnknownEffect(Identity)), + ); + expect(mcpIdentity).toEqual(firstIdentity); + const secondReply = yield* read("kind=browser", { + ...second.headers, + "x-executor-coexistence-key": bridgeKey, + }); + expect(secondReply.status).toBe(200); + const secondIdentity = yield* Effect.promise(() => secondReply.json()).pipe( + Effect.flatMap(Schema.decodeUnknownEffect(Identity)), + ); + expect( + (yield* read( + `kind=browser&organization=${encodeURIComponent(secondIdentity.organizationId)}`, + { ...first.headers, "x-executor-coexistence-key": bridgeKey }, + )).status, + ).toBe(403); + expect( + (yield* read("kind=mcp", { ...first.headers, "x-executor-coexistence-key": bridgeKey })) + .status, + ).toBe(401); + }), +); diff --git a/e2e/setup/cloud.globalsetup.ts b/e2e/setup/cloud.globalsetup.ts index 2867e5f90b..6f859c9d52 100644 --- a/e2e/setup/cloud.globalsetup.ts +++ b/e2e/setup/cloud.globalsetup.ts @@ -31,6 +31,7 @@ const optionalCloudEnv = (): Record => { // env vars remain overridable for local runs against a different setup. const env: Record = { SENTRY_OTEL_VERIFY: "true", + EXECUTOR_COEXISTENCE_KEY: "synthetic-coexistence-e2e-key-32-characters", SENTRY_OTEL_LOG_PAYLOAD: "true", // Boot the BROWSER crash reporter too, so what the frontend actually // reports is observable to a scenario. Production always has this set;