Files
Ekklesia/surfaces/shell/tests/auth-protocol.test.ts
T
ekklesia a8c7c50b4d
CI / verify (pull_request) Successful in 38s
CI / infrastructure-smoke (pull_request) Successful in 12s
CI / migration-smoke (pull_request) Successful in 1m0s
feat: add browser auth shell
2026-08-23 22:01:26 -05:00

114 lines
4.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { HostAuthError, signInWithEmail, signOutHost } from "../src/auth-protocol.js";
const apiBaseUrl = "https://ekklesia.example.test";
function recordingFetch(response: Response) {
const calls: Array<{ readonly init: RequestInit | undefined; readonly url: string }> = [];
const implementation = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
calls.push({ init, url: String(input) });
return response.clone();
}) as unknown as typeof fetch;
return { calls, implementation };
}
describe("browser authentication transport", () => {
it("posts email/password to the existing endpoint with browser-managed credentials", async () => {
const { calls, implementation } = recordingFetch(Response.json({ user: {} }));
const password = "correct horse battery staple";
await signInWithEmail(
{ apiBaseUrl, fetch: implementation },
{ email: "[email protected]", password },
);
expect(calls).toHaveLength(1);
expect(calls[0]?.url).toBe(`${apiBaseUrl}/api/auth/sign-in/email`);
expect(calls[0]?.url).not.toContain(password);
expect(calls[0]?.init).toMatchObject({
cache: "no-store",
credentials: "include",
method: "POST",
redirect: "error",
});
expect(JSON.parse(String(calls[0]?.init?.body))).toEqual({
email: "[email protected]",
password,
});
expect(calls[0]?.init?.headers).toEqual({
accept: "application/json",
"content-type": "application/json",
});
expect(JSON.stringify(calls[0]?.init?.headers).toLowerCase()).not.toContain("origin");
});
it("maps invalid credentials to one account-neutral, response-neutral failure", async () => {
for (const status of [400, 401]) {
const response = new Response(
JSON.stringify({ detail: "user exists at /private/database", email: "[email protected]" }),
{ status },
);
const { implementation } = recordingFetch(response);
const error = await signInWithEmail(
{ apiBaseUrl, fetch: implementation },
{ email: "[email protected]", password: "wrong-secret" },
).catch((caught: unknown) => caught);
expect(error).toBeInstanceOf(HostAuthError);
expect(error).toMatchObject({
code: "AUTH_CREDENTIALS_INVALID",
message: "Email or password is incorrect.",
});
expect(JSON.stringify(error)).not.toContain("[email protected]");
expect(JSON.stringify(error)).not.toContain("wrong-secret");
expect((error as Error).message).not.toContain("private");
}
});
it("sanitizes transport and server failures without retaining credentials or a cause", async () => {
const password = "do-not-log-this";
const implementation = vi.fn(async () => {
throw new Error(`network failed password=${password} /private/socket`);
}) as unknown as typeof fetch;
const error = await signInWithEmail(
{ apiBaseUrl, fetch: implementation },
{ email: "[email protected]", password },
).catch((caught: unknown) => caught);
expect(error).toMatchObject({
code: "AUTH_REQUEST_FAILED",
message: "Unable to sign in right now.",
});
expect("cause" in (error as object)).toBe(false);
expect(JSON.stringify(error)).not.toContain(password);
expect((error as Error).message).not.toContain("private");
});
it("posts an empty body to the existing sign-out endpoint", async () => {
const { calls, implementation } = recordingFetch(Response.json({ success: true }));
await signOutHost({ apiBaseUrl, fetch: implementation });
expect(calls).toHaveLength(1);
expect(calls[0]?.url).toBe(`${apiBaseUrl}/api/auth/sign-out`);
expect(calls[0]?.init).toMatchObject({
body: "{}",
credentials: "include",
method: "POST",
});
});
it("uses a sign-out-specific safe error", async () => {
const { implementation } = recordingFetch(
new Response("internal session token", { status: 500 }),
);
await expect(signOutHost({ apiBaseUrl, fetch: implementation })).rejects.toMatchObject({
code: "AUTH_SIGN_OUT_FAILED",
message: "Unable to sign out right now.",
});
});
});