fix: harden identity HTTP transport
CI / verify (pull_request) Successful in 25s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 25s

This commit is contained in:
ekklesia
2026-07-21 23:50:41 -05:00
parent c91f0c8c6d
commit d9b73fd81a
15 changed files with 967 additions and 72 deletions
+59
View File
@@ -65,6 +65,65 @@ describe("Core identity Drizzle database composition", () => {
);
});
it("passes the operation timeout through to the pool configuration as both query_timeout and statement_timeout", () => {
poolConstructor.mockImplementation(function fakePool() {
return { end: async () => undefined, on: vi.fn() };
});
poolConstructor.mockClear();
createIdentityDrizzleDatabase("postgresql://example.test/ekklesia", {
operationTimeoutMs: 7_500,
});
expect(poolConstructor).toHaveBeenCalledWith(
expect.objectContaining({ query_timeout: 7_500, statement_timeout: 7_500 }),
);
});
it("does not set query_timeout, statement_timeout, or connectionTimeoutMillis when no timeout is configured", () => {
poolConstructor.mockImplementation(function fakePool() {
return { end: async () => undefined, on: vi.fn() };
});
poolConstructor.mockClear();
createIdentityDrizzleDatabase("postgresql://example.test/ekklesia");
const [poolConfig] = poolConstructor.mock.calls[0] as [Record<string, unknown>];
expect(poolConfig).not.toHaveProperty("query_timeout");
expect(poolConfig).not.toHaveProperty("statement_timeout");
expect(poolConfig).not.toHaveProperty("connectionTimeoutMillis");
});
it.each([
["operationTimeoutMs", 0],
["operationTimeoutMs", -1],
["operationTimeoutMs", Number.NaN],
["operationTimeoutMs", 1.5],
["operationTimeoutMs", 2_147_483_648],
["connectionTimeoutMs", 0],
["connectionTimeoutMs", -1],
["connectionTimeoutMs", Number.NaN],
] as const)("rejects an invalid %s (%s) without constructing a pool and without leaking the value", (optionName, value) => {
poolConstructor.mockClear();
let captured: unknown;
try {
createIdentityDrizzleDatabase("postgresql://example.test/ekklesia", {
[optionName]: value,
});
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(IdentityDatabaseError);
expect(captured).toMatchObject({ code: "IDENTITY_DATABASE_CONFIGURATION_INVALID" });
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}`;
expect(publicText).not.toContain(String(value));
expect(poolConstructor).not.toHaveBeenCalled();
});
it("registers a pool-level error listener so an idle client failure never becomes an uncaught exception", () => {
const on = vi.fn();
poolConstructor.mockImplementation(function fakePool() {
+95
View File
@@ -75,9 +75,12 @@ describe("Core identity runtime construction boundary", () => {
expect(betterAuthMock).toHaveBeenCalledTimes(1);
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(Object.keys(options).sort()).toEqual([
"advanced",
"baseURL",
"database",
"emailAndPassword",
"logger",
"onAPIError",
"rateLimit",
"secret",
"trustedOrigins",
@@ -86,6 +89,8 @@ describe("Core identity runtime construction boundary", () => {
autoSignIn: false,
disableSignUp: false,
enabled: true,
maxPasswordLength: 128,
minPasswordLength: 8,
});
expect(options.rateLimit).toEqual({ enabled: true, max: 100, window: 10 });
expect(options.baseURL).toBe(validConfig().baseUrl);
@@ -93,6 +98,96 @@ describe("Core identity runtime construction boundary", () => {
expect(options.trustedOrigins).toEqual([...validConfig().trustedOrigins]);
});
it("explicitly disables Better Auth's default test-environment origin/CSRF skip", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.advanced).toEqual({ disableCSRFCheck: false, disableOriginCheck: false });
});
it("configures onAPIError.throw so an unexpected failure escapes Better Auth's own unsanitized fallback logger instead of being swallowed internally", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.onAPIError).toEqual({ throw: true });
});
it("configures an explicit, error-only Better Auth logger that never forwards Better Auth's own message or arguments", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
const loggerOption = options.logger as {
disabled?: boolean;
level?: string;
log?: (...args: unknown[]) => void;
};
expect(loggerOption.disabled).toBe(false);
expect(loggerOption.level).toBe("error");
expect(typeof loggerOption.log).toBe("function");
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const hostileException = new Error(
"password=hunter2 host=db.internal.example port=5432 statement_timeout at /var/app/src/secret.ts:10:5",
);
loggerOption.log?.(
"error",
"Invalid origin: https://evil.example.test",
hostileException,
"select * from ekklesia_core_session",
);
expect(errorSpy).toHaveBeenCalledTimes(1);
const emitted = errorSpy.mock.calls
.flat()
.map((value) =>
value instanceof Error ? `${value.message}\n${value.stack ?? ""}` : String(value),
)
.join("\n");
expect(emitted).not.toContain("hunter2");
expect(emitted).not.toContain("db.internal.example");
expect(emitted).not.toContain("5432");
expect(emitted).not.toContain("/var/app");
expect(emitted).not.toContain("evil.example.test");
expect(emitted.toLowerCase()).not.toContain("select");
expect(emitted.toLowerCase()).not.toContain("stack");
} finally {
errorSpy.mockRestore();
}
});
it("never publishes a Better Auth log at or below warn level", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
const loggerOption = options.logger as { log?: (...args: unknown[]) => void };
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
try {
loggerOption.log?.("warn", "a warning", "detail");
loggerOption.log?.("info", "some info", "detail");
loggerOption.log?.("debug", "some debug", "detail");
expect(errorSpy).not.toHaveBeenCalled();
expect(warnSpy).not.toHaveBeenCalled();
expect(logSpy).not.toHaveBeenCalled();
} finally {
errorSpy.mockRestore();
warnSpy.mockRestore();
logSpy.mockRestore();
}
});
it("only widens the sign-in/sign-up rate-limit window when a narrow override is explicitly injected", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });