import { TRUSTED_CLIENT_ADDRESS_HEADER } from "@ekklesia/core/identity"; import { describe, expect, it, vi } from "vitest"; import { CLIENT_ADDRESS_UNRESOLVED_LOG_MESSAGE, createClientAddressResolver, wrapFetchWithClientAddress, } from "../src/client-address.js"; function fakeEnv(remoteAddress: string | undefined) { return { incoming: { socket: { remoteAddress } }, outgoing: {}, } as unknown as Parameters[0] extends ( request: Request, env: infer Env, ) => unknown ? Env : never; } describe("wrapFetchWithClientAddress", () => { it("resolves the trusted address from the real socket peer and sets the internal header before calling the wrapped callback", async () => { const resolver = createClientAddressResolver({ mode: "direct" }); const wrappedFetch = vi.fn().mockResolvedValue(new Response("ok")); const wrapped = wrapFetchWithClientAddress(wrappedFetch, resolver); const request = new Request("https://example.test/api/auth/sign-in/email"); await wrapped(request, fakeEnv("203.0.113.9")); expect(wrappedFetch).toHaveBeenCalledTimes(1); const [forwardedRequest] = wrappedFetch.mock.calls[0] as [Request]; expect(forwardedRequest.headers.get(TRUSTED_CLIENT_ADDRESS_HEADER)).toBe("203.0.113.9"); }); it("overwrites — never appends to — a caller-supplied copy of the internal header", async () => { const resolver = createClientAddressResolver({ mode: "direct" }); const wrappedFetch = vi.fn().mockResolvedValue(new Response("ok")); const wrapped = wrapFetchWithClientAddress(wrappedFetch, resolver); const request = new Request("https://example.test/api/auth/sign-in/email", { headers: { [TRUSTED_CLIENT_ADDRESS_HEADER]: "203.0.113.250" }, }); await wrapped(request, fakeEnv("192.0.2.1")); const [forwardedRequest] = wrappedFetch.mock.calls[0] as [Request]; expect(forwardedRequest.headers.get(TRUSTED_CLIENT_ADDRESS_HEADER)).toBe("192.0.2.1"); // A single value, never a comma-joined "caller-value, resolved-value" — set(), not append(). expect(forwardedRequest.headers.get(TRUSTED_CLIENT_ADDRESS_HEADER)).not.toContain(","); }); it("returns a sanitized 503 and never invokes the wrapped callback when resolution fails", async () => { const resolver = createClientAddressResolver({ mode: "direct" }); const wrappedFetch = vi.fn().mockResolvedValue(new Response("should not be reached")); const wrapped = wrapFetchWithClientAddress(wrappedFetch, resolver); const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); try { const response = await wrapped( new Request("https://example.test/api/auth/sign-in/email"), fakeEnv(undefined), ); expect(response.status).toBe(503); expect(await response.json()).toEqual({ code: "CLIENT_ADDRESS_UNRESOLVED" }); expect(response.headers.get("cache-control")).toBe("no-store"); expect(wrappedFetch).not.toHaveBeenCalled(); expect(errorSpy).toHaveBeenCalledWith(CLIENT_ADDRESS_UNRESOLVED_LOG_MESSAGE); } finally { errorSpy.mockRestore(); } }); it("never logs the peer address, headers, or request path on a resolution failure", async () => { const resolver = createClientAddressResolver({ mode: "direct" }); const wrapped = wrapFetchWithClientAddress(vi.fn(), resolver); const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); try { await wrapped( new Request("https://example.test/api/auth/sign-in/email", { headers: { cookie: "super-secret-session", "x-forwarded-for": "203.0.113.1" }, }), fakeEnv("not-a-valid-ip"), ); const emitted = errorSpy.mock.calls.flat().map(String).join("\n"); expect(emitted).not.toContain("super-secret-session"); expect(emitted).not.toContain("203.0.113.1"); expect(emitted).not.toContain("sign-in"); expect(emitted).not.toContain("not-a-valid-ip"); } finally { errorSpy.mockRestore(); } }); it("preserves the request method, body, and other headers unrelated to the internal address header", async () => { const resolver = createClientAddressResolver({ mode: "direct" }); const wrappedFetch = vi.fn().mockResolvedValue(new Response("ok")); const wrapped = wrapFetchWithClientAddress(wrappedFetch, resolver); const request = new Request("https://example.test/api/auth/sign-up/email", { body: JSON.stringify({ email: "a@example.test" }), headers: { "content-type": "application/json", origin: "https://dashboard.example.test" }, method: "POST", }); await wrapped(request, fakeEnv("192.0.2.1")); const [forwardedRequest] = wrappedFetch.mock.calls[0] as [Request]; expect(forwardedRequest.method).toBe("POST"); expect(forwardedRequest.headers.get("origin")).toBe("https://dashboard.example.test"); expect(await forwardedRequest.text()).toBe(JSON.stringify({ email: "a@example.test" })); }); });