// End-to-end coverage for connection (credential) isolation *through the real// HTTP API* (v2).//// Complements tenant-isolation.node.test.ts (plain cross-org isolation) by// exercising the owner model the cloud app actually ships: the per-request// executor binds `{ tenant: organizationId, subject: accountId }`, and every// connection is filed under `owner: "org"` (tenant-shared) or `owner: "user"`// (this subject's own). Every request goes through `HttpApiClient` → `fetch` →// the real `ProtectedCloudApi` → the real Drizzle/FumaDB path.//// Invariants the product is staking on://// 1. Users in different orgs can't see each other's org connections.// 2. Users in the same org can't see each other's user-owned connections// (per-user OAuth tokens etc. don't leak to co-workers).// 3. Org-owned connections ARE visible to every user in that org — an admin// writing a shared API key serves the whole tenant.// 4. The same user id in different orgs is a different tenant binding — a// user connection written in org A is invisible in org B. import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect";import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi";import { Schema } from "effect"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug, connectionIdentifier,} from "@executor-js/sdk";import { makeOpenApiHttpApiTestAddSpecPayload } from "@executor-js/plugin-openapi/testing"; import { asUser } from "./testing/api-harness"; const uniq = () => crypto.randomUUID().slice(0, 8);const nextOrgId = () => `org_iso_${uniq()}`;const nextUserId = () => `user_iso_${uniq()}`; const TEMPLATE_API_KEY = AuthTemplateSlug.make("apiKey");const canonicalConnectionName = (name: ConnectionName): ConnectionName => connectionIdentifier(String(name)); const PingApi = HttpApi.make("isolationApiTest") .add( HttpApiGroup.make("default", { topLevel: true }).add( HttpApiEndpoint.get("ping", "/ping", { success: Schema.Unknown }), ), ) .annotateMerge(OpenApi.annotations({ title: "Isolation API Test", version: "1.0.0" })); // Registers a minimal openapi integration under `org` (acting as `userId`) so// connections have an integration to bind to. Returns the slug.const registerIntegration = (userId: string, org: string) => Effect.gen(function* () { const slug = IntegrationSlug.make(`ns_${crypto.randomUUID().replace(/-/g, "_")}`); yield* asUser(userId, org, (client) => client.openapi.addSpec({ payload: makeOpenApiHttpApiTestAddSpecPayload(PingApi, { slug, baseUrl: "http://example.com", }), }), ); return slug; }); describe("cloud connection isolation (HTTP, owner model)", () => { it.effect("users in different orgs cannot read each other's org connections", () => Effect.gen(function* () { const orgA = nextOrgId(); const orgB = nextOrgId(); const alice = nextUserId(); const charlie = nextUserId(); const name = ConnectionName.make(`conn_${uniq()}`); const storedName = canonicalConnectionName(name); const integrationA = yield* registerIntegration(alice, orgA); yield* asUser(alice, orgA, (client) => client.connections.create({ payload: { owner: "org", name, integration: integrationA, template: TEMPLATE_API_KEY, value: "alice-org-secret", }, }), ); // Charlie in a different org sees no connections under his (empty) catalog. const charlieList = yield* asUser(charlie, orgB, (client) => client.connections.list({ query: {} }), ); expect(charlieList.map((c) => c.name)).not.toContain(storedName); }), ); it.effect("users in same org cannot read each other's user-owned connections", () => Effect.gen(function* () { const organizationId = nextOrgId(); const aliceId = nextUserId(); const bobId = nextUserId(); const name = ConnectionName.make(`conn_${uniq()}`); const storedName = canonicalConnectionName(name); const integration = yield* registerIntegration(aliceId, organizationId); // Alice writes her personal (`owner: "user"`) connection. yield* asUser(aliceId, organizationId, (client) => client.connections.create({ payload: { owner: "user", name, integration, template: TEMPLATE_API_KEY, value: "alice-token-value", }, }), ); // Bob is in the same org — his subject differs. He must not see Alice's // user connection in a user-owner list. const bobUserList = yield* asUser(bobId, organizationId, (client) => client.connections.list({ query: { integration, owner: "user" } }), ); expect(bobUserList.map((c) => c.name)).not.toContain(storedName); // And Alice still sees her own connection. const aliceUserList = yield* asUser(aliceId, organizationId, (client) => client.connections.list({ query: { integration, owner: "user" } }), ); expect(aliceUserList.map((c) => c.name)).toContain(storedName); }), ); it.effect("org-owned connections are visible to every user in that org", () => Effect.gen(function* () { const organizationId = nextOrgId(); const adminId = nextUserId(); const memberId = nextUserId(); const name = ConnectionName.make(`conn_${uniq()}`); const storedName = canonicalConnectionName(name); const integration = yield* registerIntegration(adminId, organizationId); yield* asUser(adminId, organizationId, (client) => client.connections.create({ payload: { owner: "org", name, integration, template: TEMPLATE_API_KEY, value: "shared-org-key", }, }), ); const adminList = yield* asUser(adminId, organizationId, (client) => client.connections.list({ query: { integration, owner: "org" } }), ); const memberList = yield* asUser(memberId, organizationId, (client) => client.connections.list({ query: { integration, owner: "org" } }), ); expect(adminList.map((c) => c.name)).toContain(storedName); expect(memberList.map((c) => c.name)).toContain(storedName); }), ); it.effect("same userId in different orgs is a distinct tenant binding", () => Effect.gen(function* () { const userId = nextUserId(); const orgA = nextOrgId(); const orgB = nextOrgId(); const name = ConnectionName.make(`conn_${uniq()}`); const storedName = canonicalConnectionName(name); const integrationA = yield* registerIntegration(userId, orgA); yield* asUser(userId, orgA, (client) => client.connections.create({ payload: { owner: "user", name, integration: integrationA, template: TEMPLATE_API_KEY, value: "value-in-a", }, }), ); // Same user id, different org → different tenant. Org A's connection (and // its integration) must not be visible in org B. const listInB = yield* asUser(userId, orgB, (client) => client.connections.list({ query: {} }), ); expect(listInB.map((c) => c.name)).not.toContain(storedName); // Sanity: still visible under org A's user-owner list. const listInA = yield* asUser(userId, orgA, (client) => client.connections.list({ query: { integration: integrationA, owner: "user" } }), ); expect(listInA.map((c) => c.name)).toContain(storedName); }), );});
// End-to-end coverage for connection (credential) isolation *through the real
// HTTP API* (v2).
//
// Complements tenant-isolation.node.test.ts (plain cross-org isolation) by
// exercising the owner model the cloud app actually ships: the per-request
// executor binds `{ tenant: organizationId, subject: accountId }`, and every
// connection is filed under `owner: "org"` (tenant-shared) or `owner: "user"`
// (this subject's own). Every request goes through `HttpApiClient` → `fetch` →
// the real `ProtectedCloudApi` → the real Drizzle/FumaDB path.
//
// Invariants the product is staking on:
//
// 1. Users in different orgs can't see each other's org connections.
// 2. Users in the same org can't see each other's user-owned connections
// (per-user OAuth tokens etc. don't leak to co-workers).
// 3. Org-owned connections ARE visible to every user in that org — an admin
// writing a shared API key serves the whole tenant.
// 4. The same user id in different orgs is a different tenant binding — a
// user connection written in org A is invisible in org B.
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi";
import { Schema } from "effect";
import {
AuthTemplateSlug,
ConnectionName,
IntegrationSlug,
connectionIdentifier,
} from "@executor-js/sdk";
import { makeOpenApiHttpApiTestAddSpecPayload } from "@executor-js/plugin-openapi/testing";
import { asUser } from "./testing/api-harness";
const uniq = () => crypto.randomUUID().slice(0, 8);
const nextOrgId = () => `org_iso_${uniq()}`;
const nextUserId = () => `user_iso_${uniq()}`;
const TEMPLATE_API_KEY = AuthTemplateSlug.make("apiKey");
const canonicalConnectionName = (name: ConnectionName): ConnectionName =>
connectionIdentifier(String(name));
const PingApi = HttpApi.make("isolationApiTest")
.add(
HttpApiGroup.make("default", { topLevel: true }).add(
HttpApiEndpoint.get("ping", "/ping", { success: Schema.Unknown }),
),
)
.annotateMerge(OpenApi.annotations({ title: "Isolation API Test", version: "1.0.0" }));
// Registers a minimal openapi integration under `org` (acting as `userId`) so
// connections have an integration to bind to. Returns the slug.
const registerIntegration = (userId: string, org: string) =>
Effect.gen(function* () {
const slug = IntegrationSlug.make(`ns_${crypto.randomUUID().replace(/-/g, "_")}`);
yield* asUser(userId, org, (client) =>
client.openapi.addSpec({
payload: makeOpenApiHttpApiTestAddSpecPayload(PingApi, {
slug,
baseUrl: "http://example.com",
}),
}),
);
return slug;
});
describe("cloud connection isolation (HTTP, owner model)", () => {
it.effect("users in different orgs cannot read each other's org connections", () =>
Effect.gen(function* () {
const orgA = nextOrgId();
const orgB = nextOrgId();
const alice = nextUserId();
const charlie = nextUserId();
const name = ConnectionName.make(`conn_${uniq()}`);
const storedName = canonicalConnectionName(name);
const integrationA = yield* registerIntegration(alice, orgA);
yield* asUser(alice, orgA, (client) =>
client.connections.create({
payload: {
owner: "org",
name,
integration: integrationA,
template: TEMPLATE_API_KEY,
value: "alice-org-secret",
},
}),
);
// Charlie in a different org sees no connections under his (empty) catalog.
const charlieList = yield* asUser(charlie, orgB, (client) =>
client.connections.list({ query: {} }),
);
expect(charlieList.map((c) => c.name)).not.toContain(storedName);
}),
);
it.effect("users in same org cannot read each other's user-owned connections", () =>
Effect.gen(function* () {
const organizationId = nextOrgId();
const aliceId = nextUserId();
const bobId = nextUserId();
const name = ConnectionName.make(`conn_${uniq()}`);
const storedName = canonicalConnectionName(name);
const integration = yield* registerIntegration(aliceId, organizationId);
// Alice writes her personal (`owner: "user"`) connection.
yield* asUser(aliceId, organizationId, (client) =>
client.connections.create({
payload: {
owner: "user",
name,
integration,
template: TEMPLATE_API_KEY,
value: "alice-token-value",
},
}),
);
// Bob is in the same org — his subject differs. He must not see Alice's
// user connection in a user-owner list.
const bobUserList = yield* asUser(bobId, organizationId, (client) =>
client.connections.list({ query: { integration, owner: "user" } }),
);
expect(bobUserList.map((c) => c.name)).not.toContain(storedName);
// And Alice still sees her own connection.
const aliceUserList = yield* asUser(aliceId, organizationId, (client) =>
client.connections.list({ query: { integration, owner: "user" } }),
);
expect(aliceUserList.map((c) => c.name)).toContain(storedName);
}),
);
it.effect("org-owned connections are visible to every user in that org", () =>
Effect.gen(function* () {
const organizationId = nextOrgId();
const adminId = nextUserId();
const memberId = nextUserId();
const name = ConnectionName.make(`conn_${uniq()}`);
const storedName = canonicalConnectionName(name);
const integration = yield* registerIntegration(adminId, organizationId);
yield* asUser(adminId, organizationId, (client) =>
client.connections.create({
payload: {
owner: "org",
name,
integration,
template: TEMPLATE_API_KEY,
value: "shared-org-key",
},
}),
);
const adminList = yield* asUser(adminId, organizationId, (client) =>
client.connections.list({ query: { integration, owner: "org" } }),
);
const memberList = yield* asUser(memberId, organizationId, (client) =>
client.connections.list({ query: { integration, owner: "org" } }),
);
expect(adminList.map((c) => c.name)).toContain(storedName);
expect(memberList.map((c) => c.name)).toContain(storedName);
}),
);
it.effect("same userId in different orgs is a distinct tenant binding", () =>
Effect.gen(function* () {
const userId = nextUserId();
const orgA = nextOrgId();
const orgB = nextOrgId();
const name = ConnectionName.make(`conn_${uniq()}`);
const storedName = canonicalConnectionName(name);
const integrationA = yield* registerIntegration(userId, orgA);
yield* asUser(userId, orgA, (client) =>
client.connections.create({
payload: {
owner: "user",
name,
integration: integrationA,
template: TEMPLATE_API_KEY,
value: "value-in-a",
},
}),
);
// Same user id, different org → different tenant. Org A's connection (and
// its integration) must not be visible in org B.
const listInB = yield* asUser(userId, orgB, (client) =>
client.connections.list({ query: {} }),
);
expect(listInB.map((c) => c.name)).not.toContain(storedName);
// Sanity: still visible under org A's user-owner list.
const listInA = yield* asUser(userId, orgA, (client) =>
client.connections.list({ query: { integration: integrationA, owner: "user" } }),
);
expect(listInA.map((c) => c.name)).toContain(storedName);
}),
);
});