301 lines · 10.8 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect, Layer } from "effect";import type * as Tracer from "effect/Tracer"; import { MemberDirectory, type DirectoryMember } from "@executor-js/api/server"; import { ApiKeyService } from "./api-keys";import { UserStoreService } from "./context";import { AUTHORIZE_ORGANIZATION_SPAN } from "./organization";import { resolveSessionPrincipal } from "./workos-auth-provider";import { WorkOSClient, type WorkOSClientService } from "./workos";import { WorkOsMirror, type WorkOsMirrorShape } from "./workos-mirror"; // The org a console request resolves to is the URL's org (sent in the// `x-executor-organization` selector header) — NEVER the session's stored org.// The sealed cookie's org is a browser-global pinned to whichever org WorkOS// last touched, so a fallback to it silently scopes a multi-org user's request// to the wrong org; a header-less request fails closed instead. Membership is// re-checked against the local mirror either way. This is what makes two// browser tabs on different orgs independent. const createdAt = new Date("2026-01-01T00:00:00.000Z"); // The mirror's account row as `ensureAccount` mints it: id only, profile// columns unfilled until a WorkOS user payload arrives.const bareAccount = (id: string) => ({  id,  email: null,  firstName: null,  lastName: null,  avatarUrl: null,  workosUpdatedAt: null,  lastSignInAt: null,  createdAt,}); // user_session belongs to BOTH orgs; the URL selects which one a request hits.// Their membership in PENDING_ORG is only pending — an invite, not access.const MEMBER = "user_session";const SESSION_ORG = "org_session";const URL_ORG = "org_url";const PENDING_ORG = "org_pending";const URL_SLUG = "acme";const PENDING_SLUG = "pending-acme"; const mirrored = (  organizationId: string,  overrides: Partial<DirectoryMember> = {},): DirectoryMember => ({  accountId: MEMBER,  membershipId: `om_${MEMBER}_${organizationId}`,  organizationId,  email: null,  name: null,  avatarUrl: null,  role: "member",  status: "active",  lastActiveAt: null,  ...overrides,}); // The mirror as the directory reads it: MEMBER is active in both real orgs,// an admin of URL_ORG, and merely invited to PENDING_ORG.const memberships = new Map<string, DirectoryMember>([  [SESSION_ORG, mirrored(SESSION_ORG)],  [URL_ORG, mirrored(URL_ORG, { role: "admin" })],  [PENDING_ORG, mirrored(PENDING_ORG, { status: "pending" })],]); const stubDirectory = Layer.succeed(MemberDirectory)({  membership: (accountId, organizationId) =>    Effect.succeed(accountId === MEMBER ? (memberships.get(organizationId) ?? null) : null),  membershipById: () => Effect.die("session resolution does not look up by membership id"),  membershipsOf: () => Effect.die("session resolution reads one membership, not the list"),  members: () => Effect.die("session resolution does not list members"),  membersById: () => Effect.die("session resolution does not batch members"),  findByEmail: () => Effect.die("session resolution does not resolve emails"),}); const stubApiKeys = Layer.succeed(ApiKeyService)({  // No Authorization header in these tests → the api-key path returns null and  // resolution falls through to the session path.  validate: () => Effect.succeed(null),  listUserKeys: () => Effect.succeed([]),  createUserKey: () => Effect.die("not used"),  revokeUserKey: () => Effect.void,  listOrgKeys: () => Effect.die("auth resolution test does not list org API keys"),  createOrgKey: () => Effect.die("auth resolution test does not create org API keys"),  revokeOrgKey: () => Effect.die("auth resolution test does not revoke org API keys"),}); const stubWorkOS = Layer.succeed(  WorkOSClient,  new Proxy({} as WorkOSClientService, {    get: (_t, prop) => {      if (prop === "authenticateRequest") {        return () =>          Effect.succeed({            userId: MEMBER,            email: "u@e2e.test",            organizationId: SESSION_ORG,          });      }      // Membership is read from the mirror, never from WorkOS: any WorkOS      // call past session authentication fails the test.      return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`);    },  }),); const stubUsers = Layer.succeed(UserStoreService)({  use: (_op, fn) =>    Effect.promise(() =>      fn({        ensureAccount: async (id: string) => bareAccount(id),        getAccount: async (id: string) => bareAccount(id),        // Slug is minted at insert now — the stub returns a slugged row.        upsertOrganization: async (org: { id: string; name: string }) => ({          ...org,          slug: org.id,          backfilledAt: createdAt,          deletedAt: null,          workosUpdatedAt: null,          createdAt,        }),        getOrganization: async (id: string) => ({          id,          name: `Org ${id}`,          slug: id,          backfilledAt: createdAt,          deletedAt: null,          workosUpdatedAt: null,          createdAt,        }),        // The URL slug maps to URL_ORG (the member's other org), the pending        // slug to the org they are only invited to; any other slug maps to an        // org the caller is NOT a member of, so membership rejects it.        getOrganizationBySlug: async (slug: string) => ({          id: slug === URL_SLUG ? URL_ORG : slug === PENDING_SLUG ? PENDING_ORG : "org_outsider",          name: `Org ${slug}`,          slug,          backfilledAt: createdAt,          deletedAt: null,          workosUpdatedAt: null,          createdAt,        }),        markOrganizationDeleted: async () => null,        deleteOrganizationCascade: async () => {},      }),    ),}); // Authorization scans an organization the backfill never covered before it// reads the mirror (`auth/organization.ts`); every org row above is marked// backfilled, so the scan is never reached and the mirror is never written.const stubMirror = Layer.succeed(  WorkOsMirror,  new Proxy({} as WorkOsMirrorShape, {    get: (_target, prop) => () => Effect.die(`unexpected WorkOsMirror.${String(prop)} call`),  }),); const run = (headers: Record<string, string>) =>  resolveSessionPrincipal(new Request("https://executor.test/api/tools", { headers })).pipe(    Effect.provide(Layer.mergeAll(stubApiKeys, stubWorkOS, stubUsers, stubDirectory, stubMirror)),  ); /** * A tracer that keeps every span's attributes, so the authorization span * itself — {@link AUTHORIZE_ORGANIZATION_SPAN} — is assertable. */const makeRecordingTracer = () => {  const spans: {    readonly name: string;    readonly attributes: Map<string, unknown>;  }[] = [];  const tracer: Tracer.Tracer = {    span: (options) => {      const attributes = new Map<string, unknown>();      spans.push({ name: options.name, attributes });      let status: Tracer.SpanStatus = {        _tag: "Started",        startTime: options.startTime,      };      return {        _tag: "Span",        name: options.name,        spanId: `span-${spans.length}`,        traceId: "trace-1",        parent: options.parent,        annotations: options.annotations,        get status() {          return status;        },        attributes,        links: options.links,        sampled: options.sampled,        kind: options.kind,        end: (endTime, exit) => {          status = {            _tag: "Ended",            startTime: options.startTime,            endTime,            exit,          };        },        attribute: (key, value) => {          attributes.set(key, value);        },        event: () => undefined,        addLinks: () => undefined,      };    },  };  const attributesOf = (name: string) => spans.find((span) => span.name === name)?.attributes;  return { tracer, attributesOf };}; describe("resolveSessionPrincipal · URL org selector", () => {  it.effect("fails closed when no selector header is sent", () =>    Effect.gen(function* () {      // No fallback to the session org: the cookie's org is browser-global      // and can name a different org than the tab's URL for a multi-org user.      const error = yield* Effect.flip(run({ cookie: "wos-session=x" }));      expect(error, "rejects instead of scoping to the session org").toMatchObject({        _tag: "NoOrganization",        code: "no_organization",      });    }),  );   it.effect("scopes to the URL org (by slug) over the session org", () =>    Effect.gen(function* () {      const principal = yield* run({        cookie: "wos-session=x",        "x-executor-organization": URL_SLUG,      });      expect(principal.organizationId, "the slug header wins over the session org").toBe(URL_ORG);      expect(principal.orgRole, "the mirrored role binds the executor's write authority").toBe(        "admin",      );    }),  );   it.effect("rejects a selector for an org where the membership is only pending", () =>    Effect.gen(function* () {      // An invite is mirrored as a pending membership; it grants no access      // until accepted.      const error = yield* Effect.flip(        run({          cookie: "wos-session=x",          "x-executor-organization": PENDING_SLUG,        }),      );      expect(error).toMatchObject({ _tag: "NoOrganization" });    }),  );   it.effect("accepts a WorkOS org id as the selector too", () =>    Effect.gen(function* () {      const principal = yield* run({        cookie: "wos-session=x",        "x-executor-organization": URL_ORG,      });      expect(principal.organizationId).toBe(URL_ORG);    }),  );   it.effect("rejects a selector for an org the caller is not a member of", () =>    Effect.gen(function* () {      // The slug resolves to a real org id, but membership is re-checked — a      // slug is a selector, not a trust boundary, so a non-member is rejected.      const error = yield* Effect.flip(        run({          cookie: "wos-session=x",          "x-executor-organization": "outsider-slug",        }),      );      expect(error).toMatchObject({ _tag: "NoOrganization" });    }),  );}); // Membership is read from the local mirror unconditionally — there is no// readiness gate and no per-request WorkOS fallback (`auth/organization.ts`).// The span every authorization runs under is still pinned here; a stalled// reconciler is now an operational alert (`workos-events-runner.ts`), not a// request-path branch, so it has no span attribute left to assert on.describe("resolveSessionPrincipal · authorization span", () => {  it.effect("runs the authorization under its span", () =>    Effect.gen(function* () {      const recorder = makeRecordingTracer();      const principal = yield* run({        cookie: "wos-session=x",        "x-executor-organization": URL_SLUG,      }).pipe(Effect.withTracer(recorder.tracer));      expect(principal.organizationId).toBe(URL_ORG);      expect(recorder.attributesOf(AUTHORIZE_ORGANIZATION_SPAN)).toBeDefined();    }),  );});