Files
Ekklesia/apps/api/tests/config.test.ts
T
ekklesiaandClaude Sonnet 5 cafd513019 fix: secure authentication request identity
Replace Better Auth's default trust of a caller-controlled X-Forwarded-For
with a platform-owned client-address resolution boundary: direct mode (the
safe default) trusts only the real socket peer, and an explicit trusted-proxy
mode requires a header name, a concrete trusted-proxy list, and only honors a
forwarded chain from an actual trusted peer. This closes a rate-limit bypass
where rotating a spoofed header let a caller choose a fresh bucket on every
request. Public sign-up is also disabled by default and requires an explicit
typed opt-in, using Better Auth's own disableSignUp gate so no email-existence
leak or second signup path is introduced.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 12:20:51 -05:00

182 lines
5.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { ApiConfigError, createApiRuntimeConfig } from "../src/config.js";
const validSecret = "correct-horse-battery-staple-0123456789";
function validInput() {
return {
databaseUrl: "postgresql://ekklesia:[email protected]:5432/ekklesia",
host: "127.0.0.1",
identity: {
baseUrl: "https://identity.example.test",
secret: validSecret,
trustedOrigins: ["https://identity.example.test"],
},
port: 3000,
};
}
describe("API runtime configuration", () => {
it("accepts valid API and identity configuration", () => {
const config = createApiRuntimeConfig(validInput(), { environment: "production" });
expect(config).toEqual({
clientAddress: { mode: "direct" },
databaseUrl: validInput().databaseUrl,
host: "127.0.0.1",
identity: {
baseUrl: "https://identity.example.test",
publicSignupEnabled: false,
secret: validSecret,
trustedOrigins: ["https://identity.example.test"],
},
port: 3000,
});
expect(Object.isFrozen(config)).toBe(true);
});
it("defaults host to loopback and port to 3000 when omitted", () => {
const { host: _host, port: _port, ...rest } = validInput();
const config = createApiRuntimeConfig(rest, { environment: "production" });
expect(config.host).toBe("127.0.0.1");
expect(config.port).toBe(3000);
});
it("permits an explicit non-loopback bind address", () => {
const config = createApiRuntimeConfig(
{ ...validInput(), host: "0.0.0.0" },
{ environment: "production" },
);
expect(config.host).toBe("0.0.0.0");
});
it("rejects a missing database URL", () => {
let captured: unknown;
try {
createApiRuntimeConfig({ ...validInput(), databaseUrl: undefined });
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(ApiConfigError);
expect(captured).toMatchObject({ code: "DATABASE_URL_MISSING" });
});
it("rejects an invalid database URL", () => {
for (const databaseUrl of ["", "not-a-url", "mysql://example.test/db", "http://example.test"]) {
let captured: unknown;
try {
createApiRuntimeConfig({ ...validInput(), databaseUrl });
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(ApiConfigError);
}
});
it("rejects an empty listen host", () => {
let captured: unknown;
try {
createApiRuntimeConfig({ ...validInput(), host: "" });
} catch (error) {
captured = error;
}
expect(captured).toMatchObject({ code: "HOST_INVALID" });
});
it("rejects an invalid listen port", () => {
for (const port of [-1, 1.5, 70_000, Number.NaN, "not-a-port"]) {
let captured: unknown;
try {
createApiRuntimeConfig({ ...validInput(), port });
} catch (error) {
captured = error;
}
expect(captured).toMatchObject({ code: "PORT_INVALID" });
}
});
it("accepts port 0 as an explicit request for an OS-assigned ephemeral port", () => {
const config = createApiRuntimeConfig({ ...validInput(), port: 0 });
expect(config.port).toBe(0);
});
it("delegates identity validation to Core's createIdentityConfig, rejecting a short secret", () => {
let captured: unknown;
try {
createApiRuntimeConfig({
...validInput(),
identity: { ...validInput().identity, secret: "too-short" },
});
} catch (error) {
captured = error;
}
expect(captured).toMatchObject({ code: "SECRET_TOO_SHORT" });
});
it("explicitly allows local development origins only when environment is development", () => {
const config = createApiRuntimeConfig(
{
databaseUrl: validInput().databaseUrl,
host: "127.0.0.1",
identity: {
baseUrl: "http://localhost:3000",
secret: validSecret,
trustedOrigins: ["http://localhost:3000"],
},
port: 3000,
},
{ environment: "development" },
);
expect(config.identity.baseUrl).toBe("http://localhost:3000");
});
it("defaults public signup to disabled and threads an explicit enablement through to identity config", () => {
expect(createApiRuntimeConfig(validInput()).identity.publicSignupEnabled).toBe(false);
expect(
createApiRuntimeConfig({
...validInput(),
identity: { ...validInput().identity, publicSignupEnabled: true },
}).identity.publicSignupEnabled,
).toBe(true);
});
it("defaults client-address configuration to direct mode and threads explicit trusted-proxy configuration through", () => {
expect(createApiRuntimeConfig(validInput()).clientAddress).toEqual({ mode: "direct" });
const config = createApiRuntimeConfig({
...validInput(),
clientAddress: {
header: "x-forwarded-for",
mode: "trusted-proxy",
trustedProxies: ["10.0.0.1"],
},
});
expect(config.clientAddress).toEqual({
header: "x-forwarded-for",
mode: "trusted-proxy",
trustedProxies: ["10.0.0.1"],
});
});
it("never leaks the database URL, secret, or trusted origins through a thrown error", () => {
const hostileUrl = "postgresql://admin:[email protected]:5432/ekklesia";
let captured: unknown;
try {
createApiRuntimeConfig({ ...validInput(), databaseUrl: hostileUrl, port: -1 });
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(ApiConfigError);
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}\n${JSON.stringify(captured)}`;
expect(publicText).not.toContain(hostileUrl);
expect(publicText).not.toContain("s3cr3t");
expect(publicText).not.toContain("db.internal.example");
});
});