feat: add browser auth shell
CI / verify (pull_request) Successful in 38s
CI / infrastructure-smoke (pull_request) Successful in 12s
CI / migration-smoke (pull_request) Successful in 1m0s

This commit is contained in:
ekklesia
2026-08-23 22:01:26 -05:00
parent 91aa9c2930
commit a8c7c50b4d
26 changed files with 2702 additions and 194 deletions
+42 -10
View File
@@ -128,16 +128,20 @@ describe("host protocol — descriptor validation", () => {
}
});
it("reports an unauthenticated caller distinctly from any other failure", async () => {
for (const status of [401, 403]) {
const { implementation } = stubFetch({
"/api/v1/dashboard/navigation": () =>
jsonResponse({ code: "AUTHORIZATION_DENIED" }, status),
});
await expect(
loadDashboardNavigation({ apiBaseUrl, fetch: implementation }),
).rejects.toMatchObject({ code: "HOST_UNAUTHENTICATED" });
}
it("distinguishes an authentication challenge from an authorization denial", async () => {
const unauthenticated = stubFetch({
"/api/v1/dashboard/navigation": () => jsonResponse({ code: "AUTHORIZATION_DENIED" }, 401),
});
await expect(
loadDashboardNavigation({ apiBaseUrl, fetch: unauthenticated.implementation }),
).rejects.toMatchObject({ code: "HOST_UNAUTHENTICATED" });
const forbidden = stubFetch({
"/api/v1/dashboard/navigation": () => jsonResponse({ code: "AUTHORIZATION_DENIED" }, 403),
});
await expect(
loadDashboardNavigation({ apiBaseUrl, fetch: forbidden.implementation }),
).rejects.toMatchObject({ code: "HOST_ACCESS_DENIED" });
const { implementation } = stubFetch({
"/api/v1/dashboard/navigation": () => jsonResponse({ code: "INTERNAL_ERROR" }, 500),
@@ -346,6 +350,34 @@ describe("module UI host context", () => {
});
});
it("reports only 401/403 to the host while returning the module's original response", async () => {
const statuses = [401, 403, 404, 200];
const onAuthorizationResponse = vi.fn();
const implementation = vi.fn(async () => {
const status = statuses.shift();
if (status === undefined) {
throw new Error("unexpected request");
}
return new Response("module response", { status });
}) as unknown as typeof fetch;
const context = createModuleUiContext({
config: { apiBaseUrl, fetch: implementation },
descriptor: exampleDescriptor,
onAuthorizationResponse,
principal: { kind: "authenticated", userId: "user-1" },
});
const responses = await Promise.all([
context.api.request("one"),
context.api.request("two"),
context.api.request("three"),
context.api.request("four"),
]);
expect(responses.map((response) => response.status)).toEqual([401, 403, 404, 200]);
expect(onAuthorizationResponse.mock.calls).toEqual([[401], [403]]);
});
it("refuses to address anything outside the module's own API", async () => {
const { calls, implementation } = recordingFetch();
const api = contextFor(implementation).api;