// Cloud: an Executor API key presented as a bearer is validated against // WorkOS once per TTL window, not once per request. Every API-key- // authenticated request used to buy a live `POST /api_keys/validations` // round trip; with the per-isolate success cache, the first request pays it // and back-to-back follow-ups ride the cached owner. // // Pinned black-box at the real upstream: the WorkOS emulator the cloud stack // boots against keeps a request ledger, and a validation request carries the // presented key value in its body — so "how many times was THIS key // validated" is readable from the upstream's own records. The guarantees: // // 1. Back-to-back requests on one key land exactly ONE validation on // WorkOS. Without the cache this is one validation per request. // 2. A DIFFERENT key is validated on its own first request — the cache is // keyed by credential, so key B neither rides key A's cached owner // (which would be a cross-credential auth hole) nor disturbs key A's // entry. // // Two scenarios because the two bearer surfaces wire the cache differently: // // - /api/* resolves `ApiKeyService` from the app's boot layer, built once // per isolate. // - /mcp is served outside the app envelope by the agent handler, which // builds `cloudMcpAuth` once per isolate on a memoized ManagedRuntime // (apps/cloud/src/mcp/agent-handler.ts). It used to provide that layer // per request — a fresh `ApiKeyService`, a fresh cache map, a WorkOS // round trip on every MCP request — so this scenario pins the shared // wiring, not just the cache. // // Attribution: the emulator redacts secret-named fields (the response's // `api_key` object), but the request body's `value` survives, and every key // minted here is unique to this run — so filtering the shared suite-wide // ledger by the key's own value is collision-free. import { expect } from "@effect/vitest"; import { Effect } from "effect"; import { AccountHttpApi } from "@executor-js/api"; import { connectEmulator, type EmulatorClient, type LedgerEntry } from "@executor-js/emulate"; import { scenario } from "../src/scenario"; import { Api, Target } from "../src/services"; import { WORKOS_EMULATOR_PORT } from "../targets/cloud"; const JSON_AND_SSE = "application/json, text/event-stream"; const PROTOCOL_VERSION = "2025-03-26"; /** The WorkOS route `apiKeys.validateApiKey` posts to — the round trip the * cache exists to avoid. */ const VALIDATIONS_PATH = "/api_keys/validations"; const INITIALIZE_REQUEST = { jsonrpc: "2.0" as const, id: 1, method: "initialize", params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: "executor-e2e-apikey-cache", version: "0.0.1" }, }, }; const INITIALIZED_NOTIFICATION = { jsonrpc: "2.0" as const, method: "notifications/initialized", }; const toolsList = (id: number) => ({ jsonrpc: "2.0" as const, id, method: "tools/list", params: {}, }); const mcpPost = ( url: string, init: { readonly bearer: string; readonly sessionId?: string; readonly body: unknown; }, ): Promise => fetch(url, { method: "POST", headers: { accept: JSON_AND_SSE, "content-type": "application/json", authorization: `Bearer ${init.bearer}`, ...(init.sessionId ? { "mcp-session-id": init.sessionId } : {}), }, body: JSON.stringify(init.body), }); /** initialize → session id → notifications/initialized, all on the API key. */ const openSession = async (mcpUrl: string, bearer: string): Promise => { const initialize = await mcpPost(mcpUrl, { bearer, body: INITIALIZE_REQUEST }); const sessionId = initialize.headers.get("mcp-session-id"); await initialize.text(); if (initialize.status !== 200 || !sessionId) { throw new Error(`openSession: initialize failed (${initialize.status})`); } const initialized = await mcpPost(mcpUrl, { bearer, sessionId, body: INITIALIZED_NOTIFICATION, }); await initialized.text(); if (initialized.status !== 202) { throw new Error(`openSession: notifications/initialized failed (${initialized.status})`); } return sessionId; }; /** One tools/list on an open session; the response must be a real tool list, * so a validation count of one can never come from requests that bounced at * the door. */ const listTools = async ( mcpUrl: string, init: { readonly bearer: string; readonly sessionId: string; readonly id: number }, ): Promise => { const response = await mcpPost(mcpUrl, { bearer: init.bearer, sessionId: init.sessionId, body: toolsList(init.id), }); const text = await response.text(); if (response.status !== 200 || !text.includes('"tools"')) { throw new Error(`listTools(id=${init.id}): failed (${response.status}): ${text.slice(0, 200)}`); } }; /** One API-key-authenticated read of the protected API; the response must be * a real integration listing (same reason as `listTools`). */ const listIntegrations = async (baseUrl: string, bearer: string): Promise => { const response = await fetch(new URL("/api/integrations", baseUrl), { headers: { authorization: `Bearer ${bearer}` }, }); const text = await response.text(); if (response.status !== 200 || !text.includes('"slug"')) { throw new Error(`listIntegrations: failed (${response.status}): ${text.slice(0, 200)}`); } }; /** The ledger entries in which WorkOS was asked to validate THIS key value. */ const validationsOf = ( entries: ReadonlyArray, keyValue: string, ): ReadonlyArray => entries.filter( (entry) => entry.path === VALIDATIONS_PATH && typeof entry.request.body === "object" && entry.request.body !== null && (entry.request.body as { readonly value?: unknown }).value === keyValue, ); /** The shared fixtures both scenarios start from: two freshly minted keys * (revoked on exit) and the WorkOS emulator the target validates against. */ const mintTwoKeys = Effect.gen(function* () { const target = yield* Target; const { client: apiClient } = yield* Api; const identity = yield* target.newIdentity(); const client = yield* apiClient(AccountHttpApi, identity); const workos: EmulatorClient = yield* Effect.promise(() => connectEmulator({ baseUrl: `http://127.0.0.1:${WORKOS_EMULATOR_PORT}` }), ); const mintKey = (name: string) => Effect.gen(function* () { const created = yield* client.account.createApiKey({ payload: { name } }); yield* Effect.addFinalizer(() => client.account.revokeApiKey({ params: { apiKeyId: created.id } }).pipe(Effect.ignore), ); return created; }); const keyA = yield* mintKey("e2e validation-cache key A"); const keyB = yield* mintKey("e2e validation-cache key B"); const readValidations = (keyValue: string) => Effect.promise(async () => validationsOf(await workos.ledger.list(500), keyValue)); return { target, keyA, keyB, readValidations } as const; }); scenario( "API keys · back-to-back API requests validate the key with WorkOS once, and each key validates independently", {}, Effect.scoped( Effect.gen(function* () { const { target, keyA, keyB, readValidations } = yield* mintTwoKeys; // ── Key A: two authenticated reads back to back ─────────────────────── // Without the cache each request is its own WorkOS round trip; with it, // only the first request's miss reaches WorkOS. yield* Effect.promise(() => listIntegrations(target.baseUrl, keyA.value)); yield* Effect.promise(() => listIntegrations(target.baseUrl, keyA.value)); const afterA = yield* readValidations(keyA.value); expect( afterA.map((entry) => entry.summary), "back-to-back API requests cost exactly one WorkOS validation", ).toHaveLength(1); // ── Key B: a different credential misses the cache on its own ───────── // Key B's first request MUST reach WorkOS: a zero here would mean the // cache handed key A's owner to a different credential. yield* Effect.promise(() => listIntegrations(target.baseUrl, keyB.value)); const afterB = yield* readValidations(keyB.value); expect( afterB.map((entry) => entry.summary), "a different key is validated on its own first request", ).toHaveLength(1); // And key B's request did not spend or duplicate key A's cache entry. const finalA = yield* readValidations(keyA.value); expect( finalA.map((entry) => entry.summary), "key A's single validation is undisturbed by key B's request", ).toHaveLength(1); }), ), ); scenario( "MCP · back-to-back API-key requests validate the key with WorkOS once, and each key validates independently", {}, Effect.scoped( Effect.gen(function* () { const { target, keyA, keyB, readValidations } = yield* mintTwoKeys; // ── Key A: one session, two tool-surface calls back to back ────────── // Four API-key-authenticated requests in total (initialize, initialized, // tools/list ×2). Without the cache each one is its own WorkOS round // trip; with it, only the first request's miss reaches WorkOS. const sessionA = yield* Effect.promise(() => openSession(target.mcpUrl, keyA.value)); yield* Effect.promise(() => listTools(target.mcpUrl, { bearer: keyA.value, sessionId: sessionA, id: 2 }), ); yield* Effect.promise(() => listTools(target.mcpUrl, { bearer: keyA.value, sessionId: sessionA, id: 3 }), ); const afterA = yield* readValidations(keyA.value); expect( afterA.map((entry) => entry.summary), "the whole API-key session cost exactly one WorkOS validation", ).toHaveLength(1); // ── Key B: a different credential misses the cache on its own ──────── // Key B's first request MUST reach WorkOS: a zero here would mean the // cache handed key A's owner to a different credential. const sessionB = yield* Effect.promise(() => openSession(target.mcpUrl, keyB.value)); yield* Effect.promise(() => listTools(target.mcpUrl, { bearer: keyB.value, sessionId: sessionB, id: 2 }), ); const afterB = yield* readValidations(keyB.value); expect( afterB.map((entry) => entry.summary), "a different key is validated on its own first request", ).toHaveLength(1); // And key B's session did not spend or duplicate key A's cache entry. const finalA = yield* readValidations(keyA.value); expect( finalA.map((entry) => entry.summary), "key A's single validation is undisturbed by key B's session", ).toHaveLength(1); }), ), );