import { expect } from "vitest"; import { denFetch } from "@openwork/behaviors"; import type { DenSession } from "@openwork/behaviors"; import { needs, server, SkipError, test, unmetNeeds, } from "@openwork/testkit"; import type { TestNeeds } from "@openwork/testkit"; const ORGANIZATION_NAME = "Org API Key Auth"; const API_KEY_NAME = "Enterprise Provisioner"; const PROVIDER_NAME = "API-Key Provisioned Gateway"; const PROVIDER_KEY = "openwork-api-key-auth"; const PROVIDER_ENV = "ORG_API_KEY_AUTH_PROVIDER_KEY"; const MODEL_ID = "openwork-api-key-auth-model"; const MEMBER_LLM_KEY = "sk-litellm-member-key-provisioned-over-api-key"; const REQUEST_TIMEOUT_MS = 30_000; const requirements: TestNeeds = { optIn: ["OPENWORK_EVAL_E2E_TESTS"], commands: ["docker"] }; const missingRequirements = unmetNeeds(requirements, process.env); const title = missingRequirements.length > 0 ? `Organization API key auth proof skipped — needs: ${missingRequirements.join(", ")}` : "an organization API key authenticates enterprise LLM-key provisioning against the Den API"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function auth(session: DenSession): Record { return { authorization: `Bearer ${session.token}` }; } function orgHeaders(session: DenSession, orgId: string): Record { return { ...auth(session), "x-openwork-org-id": orgId }; } /** * The enterprise wire shape under test: the request carries ONLY the * organization API key. No bearer token, no session cookie, no org header — * the key's own metadata scopes it to the organization (see * ee/apps/den-api/src/middleware/organization-context.ts). */ function apiKeyOnlyHeaders(key: string): Record { return { "x-api-key": key }; } async function organizationId(session: DenSession): Promise { const result = await denFetch(session, "/v1/me/orgs", { headers: auth(session), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const organizations = isRecord(result.body) && Array.isArray(result.body.orgs) ? result.body.orgs.filter(isRecord) : []; const organization = organizations.find((entry) => entry.name === ORGANIZATION_NAME); const id = organization && typeof organization.id === "string" ? organization.id : ""; if (!result.response.ok || !id) { throw new Error(`Finding the test organization failed: HTTP ${result.response.status} ${result.text.slice(0, 500)}`); } return id; } async function memberIdByEmail(admin: DenSession, orgId: string, email: string): Promise { const result = await denFetch(admin, "/v1/org", { headers: orgHeaders(admin, orgId), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const members = isRecord(result.body) && Array.isArray(result.body.members) ? result.body.members.filter(isRecord) : []; const member = members.find((entry) => isRecord(entry.user) && entry.user.email === email); const id = member && typeof member.id === "string" ? member.id : ""; if (!result.response.ok || !id) { throw new Error(`Finding member ${email} failed: HTTP ${result.response.status} ${result.text.slice(0, 500)}`); } return id; } async function createOrganizationApiKey( admin: DenSession, orgId: string, ): Promise<{ id: string; key: string }> { const result = await denFetch(admin, "/v1/api-keys", { method: "POST", headers: orgHeaders(admin, orgId), body: JSON.stringify({ name: API_KEY_NAME }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const created = isRecord(result.body) && isRecord(result.body.apiKey) ? result.body.apiKey : null; const id = created && typeof created.id === "string" ? created.id : ""; const key = isRecord(result.body) && typeof result.body.key === "string" ? result.body.key : ""; if (result.response.status !== 201 || !id || !key) { throw new Error(`Creating the organization API key failed: HTTP ${result.response.status} ${result.text.slice(0, 500)}`); } return { id, key }; } async function createPerMemberProvider(admin: DenSession, orgId: string): Promise { const result = await denFetch(admin, "/v1/llm-providers", { method: "POST", headers: orgHeaders(admin, orgId), body: JSON.stringify({ name: PROVIDER_NAME, source: "custom", customConfig: { id: PROVIDER_KEY, name: PROVIDER_NAME, npm: "@ai-sdk/openai-compatible", env: [PROVIDER_ENV], api: "https://litellm.api-key-auth.eval.invalid/v1", models: [{ id: MODEL_ID, name: "API Key Auth Witness Model", limit: { context: 32_000, input: 32_000, output: 32_000 }, }], }, credentialMode: "per_member", allMembers: true, memberIds: [], teamIds: [], }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const provider = isRecord(result.body) && isRecord(result.body.llmProvider) ? result.body.llmProvider : null; const id = provider && typeof provider.id === "string" ? provider.id : ""; if (result.response.status !== 201 || !id) { throw new Error(`Creating the per-member provider failed: HTTP ${result.response.status} ${result.text.slice(0, 500)}`); } return id; } test.skipIf(missingRequirements.length > 0)(title, { timeout: 30 * 60_000 }, async ({ evidence, place }) => { needs(requirements); if (process.env.OPENWORK_EVAL_DEN_API_URL?.trim()) { throw new SkipError("The organization API key proof requires a cold managed Den."); } await using den = await server({ place, org: { name: ORGANIZATION_NAME, admin: { name: "API Key Admin" }, members: { member: { name: "Enterprise Member" } }, }, }); const member = den.members.member; if (!member) throw new Error("The cold managed Den did not provision the member."); const orgId = await organizationId(den.admin); const memberId = await memberIdByEmail(den.admin, orgId, member.email); const providerId = await createPerMemberProvider(den.admin, orgId); // An admin mints the organization API key over their signed-in session. const minted = await createOrganizationApiKey(den.admin, orgId); expect(minted.key.startsWith("den_")).toBe(true); evidence.recordAssertionEvidence( "An organization admin can mint an organization API key", `POST /v1/api-keys returned HTTP 201 with key id ${minted.id} and a den_-prefixed secret.`, minted.key.startsWith("den_"), ); // Regression witness — PR #3679 dropped x-api-key resolution from the Den // session middleware, so every API-key-only request resolves to no user. // The exact enterprise wire shape: only `x-api-key`, nothing else. const listWithKey = await denFetch(den.admin, "/v1/api-keys", { headers: apiKeyOnlyHeaders(minted.key), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const listedKeys = isRecord(listWithKey.body) && Array.isArray(listWithKey.body.apiKeys) ? listWithKey.body.apiKeys.filter(isRecord) : []; expect(listWithKey.response.status).toBe(200); expect(listedKeys.some((entry) => entry.id === minted.id)).toBe(true); evidence.recordAssertionEvidence( "An API-key-only request authenticates and is scoped to the key's organization", `GET /v1/api-keys with only x-api-key returned HTTP ${listWithKey.response.status} and listed the minted key without any bearer token, cookie, or org header.`, listWithKey.response.status === 200 && listedKeys.some((entry) => entry.id === minted.id), ); // The incident scenario: an enterprise provisioner uses the org API key to // generate an LLM key for one of its members. const provisioned = await denFetch( den.admin, `/v1/llm-providers/${encodeURIComponent(providerId)}/member-credentials/${encodeURIComponent(memberId)}`, { method: "PUT", headers: apiKeyOnlyHeaders(minted.key), body: JSON.stringify({ apiKey: MEMBER_LLM_KEY, externalPrincipalId: "litellm-user-enterprise-member", externalCredentialId: "litellm-key-enterprise-member", }), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }, ); const provisionedState = isRecord(provisioned.body) && typeof provisioned.body.state === "string" ? provisioned.body.state : ""; expect(provisioned.response.status).toBe(200); expect(provisionedState).toBe("active"); evidence.recordAssertionEvidence( "An API-key-authenticated provisioner can generate a member LLM credential", `PUT /v1/llm-providers/:id/member-credentials/:memberId with only x-api-key returned HTTP ${provisioned.response.status} and state ${provisionedState || "(none)"}.`, provisioned.response.status === 200 && provisionedState === "active", ); // The provisioned LLM key actually reaches the member it was generated for. const connect = await denFetch(member, `/v1/llm-providers/${encodeURIComponent(providerId)}/connect`, { headers: orgHeaders(member, orgId), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const connectProvider = isRecord(connect.body) && isRecord(connect.body.llmProvider) ? connect.body.llmProvider : null; const memberCredential = connectProvider && isRecord(connectProvider.memberCredential) ? connectProvider.memberCredential : null; expect(connect.response.status).toBe(200); expect(memberCredential?.state).toBe("active"); expect(connectProvider?.apiKey).toBe(MEMBER_LLM_KEY); evidence.recordAssertionEvidence( "The member receives exactly the LLM key the API-key provisioner generated", `The member's connect call returned HTTP ${connect.response.status} with memberCredential.state=${String(memberCredential?.state)} and the provisioned secret.`, connect.response.status === 200 && memberCredential?.state === "active" && connectProvider?.apiKey === MEMBER_LLM_KEY, ); // Negative half: a forged key must not authenticate. const forged = await denFetch(den.admin, "/v1/api-keys", { headers: apiKeyOnlyHeaders("den_forged_key_that_was_never_minted"), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); expect(forged.response.status).toBe(401); // Negative half: no credential at all must not authenticate. const anonymous = await denFetch(den.admin, "/v1/api-keys", { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); expect(anonymous.response.status).toBe(401); evidence.recordAssertionEvidence( "Forged and missing API keys are rejected", `A never-minted x-api-key returned HTTP ${forged.response.status} and an unauthenticated request returned HTTP ${anonymous.response.status}.`, forged.response.status === 401 && anonymous.response.status === 401, ); // Negative half: revocation ends access for the previously working key. const revoked = await denFetch(den.admin, `/v1/api-keys/${encodeURIComponent(minted.id)}`, { method: "DELETE", headers: orgHeaders(den.admin, orgId), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); expect(revoked.response.status).toBe(204); const afterRevocation = await denFetch(den.admin, "/v1/api-keys", { headers: apiKeyOnlyHeaders(minted.key), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); expect(afterRevocation.response.status).toBe(401); evidence.recordAssertionEvidence( "Deleting the organization API key revokes its access", `DELETE /v1/api-keys/:id returned HTTP ${revoked.response.status}; the same key then received HTTP ${afterRevocation.response.status}.`, revoked.response.status === 204 && afterRevocation.response.status === 401, ); });