import { describe, expect, it } from "@effect/vitest";import { Effect, Layer } from "effect"; import { AccountProvider } from "@executor-js/api/server";import { AccountError, AccountForbidden } from "@executor-js/api"; import { ApiKeyService, OrgApiKeyNotFound } from "../auth/api-keys";import { UserStoreService } from "../auth/context";import { ORG_SELECTOR_HEADER } from "../auth/organization";import { WorkOSClient, type WorkOSClientService } from "../auth/workos";import { AutumnService } from "../extensions/billing/service";import { AccountCaller, workosAccountProvider } from "./workos-account-service"; // ---------------------------------------------------------------------------// `DELETE /account/org-api-keys/:apiKeyId` at the PROVIDER boundary.//// An org key reads every user and connection in the tenant through `/admin/*`,// so it is the most privileged credential the product issues. Two properties// have to hold together, and only this seam sees both://// 1. Revoke is gated on org ADMIN membership — the same gate as the mint. A// plain member calling it gets 403 and no delete goes out.// 2. Revoke resolves ownership against the ORG's keys. `revokeApiKey` (the// personal path) checks the caller's OWN key list, so it can never revoke// an org key; conversely this path must never destroy a member's personal// key. The scope-blind `deleteApiKey` would do exactly that if handed a// user key id, so a wrong id has to fail cleanly instead.//// Ownership filtering itself is pinned per-shape in// `auth/org-api-key-mint.node.test.ts`; here it is asserted end-to-end through// the gate, on the provider the HTTP handler actually calls.// --------------------------------------------------------------------------- const ORG = "org_123";const ADMIN = "user_admin";const MEMBER = "user_member";const ORG_KEY = "key_org_1";const USER_KEY = "key_user_1";const createdAt = new Date("2026-01-01T00:00:00.000Z");const orgHeaders = { [ORG_SELECTOR_HEADER]: ORG }; const session = (accountId: string) => ({ accountId, email: `${accountId}@example.test`, name: null, avatarUrl: null, organizationId: ORG, sealedSession: "sealed", refreshedSession: null,}); /** Membership roles: only ADMIN carries the `admin` role slug. */const stubWorkOS = Layer.succeed( WorkOSClient, new Proxy({} as WorkOSClientService, { get: (_target, prop) => { if (prop === "listUserMemberships") { return (userId: string) => Effect.succeed({ data: [{ userId, organizationId: ORG, status: "active" }], }); } if (prop === "getUserOrgMembership") { return (organizationId: string, userId: string) => Effect.succeed( organizationId === ORG ? { id: `om_${userId}`, userId, organizationId, role: { slug: userId === ADMIN ? "admin" : "member" }, } : null, ); } return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`); }, }),); const stubUsers = Layer.succeed(UserStoreService)({ use: (_op, fn) => Effect.promise(() => fn({ ensureAccount: async (id: string) => ({ id, createdAt }), getAccount: async (id: string) => ({ id, createdAt }), upsertOrganization: async (org: { id: string; name: string }) => ({ ...org, slug: org.id, createdAt, }), getOrganization: async (id: string) => ({ id, name: `Org ${id}`, slug: id, createdAt, }), getOrganizationBySlug: async (slug: string) => ({ id: slug, name: `Org ${slug}`, slug, createdAt, }), deleteOrganizationCascade: async () => {}, }), ),}); const stubAutumn = Layer.succeed(AutumnService)({ use: () => Effect.die("revoke does not touch billing"), checkExecutionBalance: () => Effect.die("revoke does not touch billing"), trackExecution: () => Effect.void,}); /** * The provider under test, over an `ApiKeyService` whose org listing holds a * single org-owned key. Revoked ids land in `revoked` so "no delete happened" * is assertable, not merely inferred from the error. */const providerWith = (accountId: string) => { const revoked: string[] = []; const stubApiKeys = Layer.succeed(ApiKeyService)({ validate: () => Effect.die("revoke test does not validate keys"), listUserKeys: () => Effect.succeed([]), createUserKey: () => Effect.die("revoke test does not create user keys"), revokeUserKey: () => Effect.die("the ORG path must not call the personal revoke"), listOrgKeys: () => Effect.die("revoke test does not list org keys"), createOrgKey: () => Effect.die("revoke test does not create org keys"), revokeOrgKey: ({ keyId }) => keyId === ORG_KEY ? Effect.sync(() => { revoked.push(keyId); }) : Effect.fail(new OrgApiKeyNotFound({ keyId })), }); const provider = AccountProvider.asEffect().pipe( Effect.provide( workosAccountProvider.pipe( Layer.provide( Layer.mergeAll( stubWorkOS, stubUsers, stubApiKeys, stubAutumn, Layer.succeed(AccountCaller)({ session: session(accountId) }), ), ), ), ), ); return { provider, revoked };}; describe("revokeOrgApiKey · provider boundary", () => { it.effect("an org admin revokes an org-owned key", () => Effect.gen(function* () { const { provider, revoked } = providerWith(ADMIN); const account = yield* provider; const result = yield* account.revokeOrgApiKey(orgHeaders, ORG_KEY); expect(result).toEqual({ success: true }); expect(revoked, "the revoke reached the key service").toEqual([ORG_KEY]); }), ); it.effect("a plain member is forbidden, and nothing is revoked", () => Effect.gen(function* () { const { provider, revoked } = providerWith(MEMBER); const account = yield* provider; const error = yield* Effect.flip(account.revokeOrgApiKey(orgHeaders, ORG_KEY)); expect(error, "same admin gate as the mint").toBeInstanceOf(AccountForbidden); expect(revoked, "the gate runs BEFORE the key service is touched").toEqual([]); }), ); it.effect("a user-scoped key id fails cleanly through this path", () => Effect.gen(function* () { // Not found — not a 500, and emphatically not a silent success that would // report a member's live personal credential as destroyed. const { provider, revoked } = providerWith(ADMIN); const account = yield* provider; const error = yield* Effect.flip(account.revokeOrgApiKey(orgHeaders, USER_KEY)); expect(error).toBeInstanceOf(AccountError); expect(revoked).toEqual([]); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import { AccountProvider } from "@executor-js/api/server";
import { AccountError, AccountForbidden } from "@executor-js/api";
import { ApiKeyService, OrgApiKeyNotFound } from "../auth/api-keys";
import { UserStoreService } from "../auth/context";
import { ORG_SELECTOR_HEADER } from "../auth/organization";
import { WorkOSClient, type WorkOSClientService } from "../auth/workos";
import { AutumnService } from "../extensions/billing/service";
import { AccountCaller, workosAccountProvider } from "./workos-account-service";
// ---------------------------------------------------------------------------
// `DELETE /account/org-api-keys/:apiKeyId` at the PROVIDER boundary.
//
// An org key reads every user and connection in the tenant through `/admin/*`,
// so it is the most privileged credential the product issues. Two properties
// have to hold together, and only this seam sees both:
//
// 1. Revoke is gated on org ADMIN membership — the same gate as the mint. A
// plain member calling it gets 403 and no delete goes out.
// 2. Revoke resolves ownership against the ORG's keys. `revokeApiKey` (the
// personal path) checks the caller's OWN key list, so it can never revoke
// an org key; conversely this path must never destroy a member's personal
// key. The scope-blind `deleteApiKey` would do exactly that if handed a
// user key id, so a wrong id has to fail cleanly instead.
//
// Ownership filtering itself is pinned per-shape in
// `auth/org-api-key-mint.node.test.ts`; here it is asserted end-to-end through
// the gate, on the provider the HTTP handler actually calls.
// ---------------------------------------------------------------------------
const ORG = "org_123";
const ADMIN = "user_admin";
const MEMBER = "user_member";
const ORG_KEY = "key_org_1";
const USER_KEY = "key_user_1";
const createdAt = new Date("2026-01-01T00:00:00.000Z");
const orgHeaders = { [ORG_SELECTOR_HEADER]: ORG };
const session = (accountId: string) => ({
accountId,
email: `${accountId}@example.test`,
name: null,
avatarUrl: null,
organizationId: ORG,
sealedSession: "sealed",
refreshedSession: null,
});
/** Membership roles: only ADMIN carries the `admin` role slug. */
const stubWorkOS = Layer.succeed(
WorkOSClient,
new Proxy({} as WorkOSClientService, {
get: (_target, prop) => {
if (prop === "listUserMemberships") {
return (userId: string) =>
Effect.succeed({
data: [{ userId, organizationId: ORG, status: "active" }],
});
}
if (prop === "getUserOrgMembership") {
return (organizationId: string, userId: string) =>
Effect.succeed(
organizationId === ORG
? {
id: `om_${userId}`,
userId,
organizationId,
role: { slug: userId === ADMIN ? "admin" : "member" },
}
: null,
);
}
return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`);
},
}),
);
const stubUsers = Layer.succeed(UserStoreService)({
use: (_op, fn) =>
Effect.promise(() =>
fn({
ensureAccount: async (id: string) => ({ id, createdAt }),
getAccount: async (id: string) => ({ id, createdAt }),
upsertOrganization: async (org: { id: string; name: string }) => ({
...org,
slug: org.id,
createdAt,
}),
getOrganization: async (id: string) => ({
id,
name: `Org ${id}`,
slug: id,
createdAt,
}),
getOrganizationBySlug: async (slug: string) => ({
id: slug,
name: `Org ${slug}`,
slug,
createdAt,
}),
deleteOrganizationCascade: async () => {},
}),
),
});
const stubAutumn = Layer.succeed(AutumnService)({
use: () => Effect.die("revoke does not touch billing"),
checkExecutionBalance: () => Effect.die("revoke does not touch billing"),
trackExecution: () => Effect.void,
});
/**
* The provider under test, over an `ApiKeyService` whose org listing holds a
* single org-owned key. Revoked ids land in `revoked` so "no delete happened"
* is assertable, not merely inferred from the error.
*/
const providerWith = (accountId: string) => {
const revoked: string[] = [];
const stubApiKeys = Layer.succeed(ApiKeyService)({
validate: () => Effect.die("revoke test does not validate keys"),
listUserKeys: () => Effect.succeed([]),
createUserKey: () => Effect.die("revoke test does not create user keys"),
revokeUserKey: () => Effect.die("the ORG path must not call the personal revoke"),
listOrgKeys: () => Effect.die("revoke test does not list org keys"),
createOrgKey: () => Effect.die("revoke test does not create org keys"),
revokeOrgKey: ({ keyId }) =>
keyId === ORG_KEY
? Effect.sync(() => {
revoked.push(keyId);
})
: Effect.fail(new OrgApiKeyNotFound({ keyId })),
});
const provider = AccountProvider.asEffect().pipe(
Effect.provide(
workosAccountProvider.pipe(
Layer.provide(
Layer.mergeAll(
stubWorkOS,
stubUsers,
stubApiKeys,
stubAutumn,
Layer.succeed(AccountCaller)({ session: session(accountId) }),
),
),
),
),
);
return { provider, revoked };
};
describe("revokeOrgApiKey · provider boundary", () => {
it.effect("an org admin revokes an org-owned key", () =>
Effect.gen(function* () {
const { provider, revoked } = providerWith(ADMIN);
const account = yield* provider;
const result = yield* account.revokeOrgApiKey(orgHeaders, ORG_KEY);
expect(result).toEqual({ success: true });
expect(revoked, "the revoke reached the key service").toEqual([ORG_KEY]);
}),
);
it.effect("a plain member is forbidden, and nothing is revoked", () =>
Effect.gen(function* () {
const { provider, revoked } = providerWith(MEMBER);
const account = yield* provider;
const error = yield* Effect.flip(account.revokeOrgApiKey(orgHeaders, ORG_KEY));
expect(error, "same admin gate as the mint").toBeInstanceOf(AccountForbidden);
expect(revoked, "the gate runs BEFORE the key service is touched").toEqual([]);
}),
);
it.effect("a user-scoped key id fails cleanly through this path", () =>
Effect.gen(function* () {
// Not found — not a 500, and emphatically not a silent success that would
// report a member's live personal credential as destroyed.
const { provider, revoked } = providerWith(ADMIN);
const account = yield* provider;
const error = yield* Effect.flip(account.revokeOrgApiKey(orgHeaders, USER_KEY));
expect(error).toBeInstanceOf(AccountError);
expect(revoked).toEqual([]);
}),
);
});