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: "owner@example.test", 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: "owner@example.test", 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: "owner@example.test" }), { status }, ); const { implementation } = recordingFetch(response); const error = await signInWithEmail( { apiBaseUrl, fetch: implementation }, { email: "owner@example.test", 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("owner@example.test"); 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: "owner@example.test", 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.", }); }); });