Files
clean-architecture-frontend…/tests/integration/http-execution-v3-auth-profile.test.ts
T
DongHyeonkaandClaude Opus 5 4e87bacdf3 fix: enforce installed HTTP auth profiles
Install the REST auth profile registry once at composition and make it the
single transport authority for V3. Contract composition now rejects an
unregistered authProfileId, so the executor never resolves a profile at
runtime.

The credential collaborator contributes proof headers only: Fetch credentials
come from the resolved profile, transport-owned and forbidden headers are
rejected, headers outside the profile's allowed set are rejected, and a missing
required header fails closed as AUTH_INTEGRATION_FAILURE with zero fetch calls.
The final invariant re-proves credentials mode and the exact header sets.

Demo mode satisfies the strict bearer profile with a fixed non-secret marker
instead of weakening REFERENCE_EXTERNAL_BEARER. Credential owners now receive
the operation lifetime through AuthOperationContext.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 22:56:44 +09:00

290 lines
8.8 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
import {
composeContractContributions,
ContractContributionError,
} from "../../src/contracts/external-contract-runtime.ts";
import {
installRestAuthProfileRegistry,
REST_AUTH_PROFILES,
} from "../../src/contracts/rest-profiles.ts";
import {
TEST_CONTRACT_CONTRIBUTION,
TEST_LIST_HTTP_CONTRACT,
} from "../helpers/external-contract-fixture.ts";
const ROUTE_ID = "TEST_ROUTE";
/**
* The shipped fixture policy declares `TEST_AUTH`; the installed registry owns
* the concrete transport rules, so this suite installs an equivalent bearer
* profile under that identity.
*/
const TEST_PROFILES = installRestAuthProfileRegistry({
TEST_AUTH: {
authProfileId: "TEST_AUTH",
transport: "BEARER_HEADER",
credentials: "omit",
allowedCredentialHeaders: ["authorization"],
requiredCredentialHeaders: ["authorization"],
},
TEST_ANONYMOUS: {
authProfileId: "TEST_ANONYMOUS",
transport: "ANONYMOUS",
credentials: "omit",
allowedCredentialHeaders: [],
requiredCredentialHeaders: [],
},
});
function scopeSnapshot(isCurrent: () => boolean = () => true) {
return Object.freeze({
generation: 1,
fingerprint: "scope-1",
identities: Object.freeze({}) as never,
signal: new AbortController().signal,
isCurrent,
});
}
function operationWithProfile(authProfileId: string, deadlineMs?: number) {
return {
...TEST_LIST_HTTP_CONTRACT,
frontend: {
...TEST_LIST_HTTP_CONTRACT.frontend,
authProfileId,
...(deadlineMs === undefined ? {} : { totalDeadlineMs: deadlineMs }),
},
};
}
const bearerOperation = () => operationWithProfile("TEST_AUTH");
const anonymousOperation = () => operationWithProfile("TEST_ANONYMOUS");
describe("V3 installed auth profile authority", () => {
it("rejects an unknown auth profile during composition", () => {
const contribution = {
...TEST_CONTRACT_CONTRIBUTION,
http: [
{
...TEST_LIST_HTTP_CONTRACT,
frontend: {
...TEST_LIST_HTTP_CONTRACT.frontend,
authProfileId: "NO_SUCH_PROFILE",
},
},
] as unknown as readonly never[],
};
let thrown: unknown;
try {
composeContractContributions([contribution] as never);
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(ContractContributionError);
expect((thrown as ContractContributionError).reason).toContain(
"unknown authProfileId NO_SUCH_PROFILE",
);
expect(() =>
installRestAuthProfileRegistry({
BROKEN: {
authProfileId: "BROKEN",
transport: "BEARER_HEADER",
credentials: "include",
allowedCredentialHeaders: [],
requiredCredentialHeaders: ["authorization"],
},
}),
).toThrow(TypeError);
});
it("rejects credential attempts to replace Accept Content-Type or credentials", async () => {
for (const hostileHeaders of [
{ accept: "text/plain" },
{ "content-type": "text/plain" },
{ cookie: "session=1" },
]) {
const fetcher = vi.fn();
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: () =>
({
kind: "READY" as const,
headers: {
authorization: "Bearer token",
...hostileHeaders,
},
}) as never,
fetcher: fetcher as unknown as typeof fetch,
});
const outcome = await executor.execute(
bearerOperation(),
{ limit: 5 },
{ routeId: ROUTE_ID, scope: scopeSnapshot() },
);
expect(outcome).toMatchObject({
kind: "AUTH_INTEGRATION_FAILURE",
effect: "NOT_APPLICABLE",
});
expect(fetcher).not.toHaveBeenCalled();
}
});
it("requires authorization for a bearer profile before fetch", async () => {
const fetcher = vi.fn();
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: () => ({
kind: "READY" as const,
headers: {},
}),
fetcher: fetcher as unknown as typeof fetch,
});
const outcome = await executor.execute(
bearerOperation(),
{ limit: 5 },
{ routeId: ROUTE_ID, scope: scopeSnapshot() },
);
expect(outcome).toMatchObject({
kind: "AUTH_INTEGRATION_FAILURE",
reason: "MISSING_REQUIRED_CREDENTIAL_HEADER",
});
expect(fetcher).not.toHaveBeenCalled();
});
it("forbids credential headers for an anonymous profile", async () => {
const withCredential = vi.fn();
const rejected = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: () => ({
kind: "READY" as const,
headers: { authorization: "Bearer token" },
}),
fetcher: withCredential as unknown as typeof fetch,
});
const rejectedOutcome = await rejected.execute(
anonymousOperation(),
{ limit: 5 },
{ routeId: ROUTE_ID, scope: scopeSnapshot() },
);
expect(rejectedOutcome).toMatchObject({
kind: "AUTH_INTEGRATION_FAILURE",
reason: "CREDENTIAL_HEADER_NOT_ALLOWED",
});
expect(withCredential).not.toHaveBeenCalled();
const observed: Array<Readonly<Record<string, string>>> = [];
const accepted = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: () => ({ kind: "READY" as const, headers: {} }),
fetcher: (async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers);
observed.push(
Object.freeze(Object.fromEntries(headers.entries())),
);
expect(init?.credentials).toBe("omit");
return Response.json([]);
}) as unknown as typeof fetch,
});
const acceptedOutcome = await accepted.execute(
anonymousOperation(),
{ limit: 5 },
{ routeId: ROUTE_ID, scope: scopeSnapshot() },
);
expect(acceptedOutcome.kind).toBe("SUCCESS");
expect(observed).toHaveLength(1);
expect(observed[0]?.authorization).toBeUndefined();
expect(observed[0]?.accept).toBe("application/json");
});
it("bounds a non-cooperative credential owner by the operation lifetime", async () => {
const fetcher = vi.fn();
let observedSignal: AbortSignal | undefined;
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: (_operation, context) => {
observedSignal = context.signal;
// Non-cooperative: it never settles on its own.
return new Promise<never>(() => {});
},
fetcher: fetcher as unknown as typeof fetch,
});
const outcome = await executor.execute(
operationWithProfile("TEST_AUTH", 10),
{ limit: 5 },
{ routeId: ROUTE_ID, scope: scopeSnapshot() },
);
expect(outcome).toMatchObject({
kind: "TRANSPORT_FAILURE",
failure: { kind: "TIMEOUT" },
});
expect(fetcher).not.toHaveBeenCalled();
expect(observedSignal?.aborted).toBe(true);
});
it("ignores a late credential completion after caller abort or scope fence", async () => {
const fetcher = vi.fn();
let release: ((patch: unknown) => void) | undefined;
const caller = new AbortController();
const executor = createContractHttpExecutor({
baseUrl: "https://api.example/",
maxRetryAttempts: 0,
authProfiles: TEST_PROFILES,
attachCredentials: () =>
new Promise((resolve) => {
release = resolve as (patch: unknown) => void;
}) as never,
fetcher: fetcher as unknown as typeof fetch,
});
const execution = executor.execute(
bearerOperation(),
{ limit: 5 },
{
routeId: ROUTE_ID,
scope: scopeSnapshot(),
signal: caller.signal,
},
);
await Promise.resolve();
caller.abort();
const outcome = await execution;
expect(outcome.kind).toBe("CANCELLED");
release?.({
kind: "READY",
headers: { authorization: "Bearer late" },
});
await Promise.resolve();
expect(fetcher).not.toHaveBeenCalled();
});
it("keeps the shipped bearer profile strict", () => {
expect(REST_AUTH_PROFILES.REFERENCE_EXTERNAL_BEARER).toMatchObject({
transport: "BEARER_HEADER",
credentials: "omit",
requiredCredentialHeaders: ["authorization"],
});
expect(REST_AUTH_PROFILES.ANONYMOUS.requiredCredentialHeaders).toEqual([]);
});
});