import { describe, expect, it } from "vitest"; import { ApiConfigError, apiTrustedOrigins, createApiRuntimeConfig } from "../src/config.js"; const validSecret = "correct-horse-battery-staple-0123456789"; function validInput() { return { databaseUrl: "postgresql://ekklesia:ekklesia@127.0.0.1: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" }, // Production defaults to certificate-verified TLS with no configuration at all. databaseTransport: { mode: "verify-full" }, databaseUrl: validInput().databaseUrl, host: "127.0.0.1", identity: { baseUrl: "https://identity.example.test", // Defaults to the identity base URL: the Dashboard and the API share one origin unless a // deployment separates them (ADR 0038). dashboardOrigin: "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 development enablement through to identity config", () => { expect(createApiRuntimeConfig(validInput()).identity.publicSignupEnabled).toBe(false); expect( createApiRuntimeConfig( { ...validInput(), identity: { baseUrl: "http://localhost:3000", publicSignupEnabled: true, secret: validSecret, trustedOrigins: ["http://localhost:3000"], }, }, { environment: "development" }, ).identity.publicSignupEnabled, ).toBe(true); }); it("refuses a production runtime configuration that enables public signup, before any resource is constructed", () => { let captured: unknown; try { createApiRuntimeConfig( { ...validInput(), identity: { ...validInput().identity, publicSignupEnabled: true }, }, { environment: "production" }, ); } catch (error) { captured = error; } expect(captured).toMatchObject({ code: "PUBLIC_SIGNUP_UNSUPPORTED_IN_PRODUCTION" }); }); 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"], }); }); describe("PostgreSQL transport policy", () => { it("defaults to certificate-verified TLS in production and plaintext in development", () => { expect(createApiRuntimeConfig(validInput()).databaseTransport).toEqual({ mode: "verify-full", }); expect( createApiRuntimeConfig( { ...validInput(), identity: { baseUrl: "http://localhost:3000", secret: validSecret, trustedOrigins: ["http://localhost:3000"], }, }, { environment: "development" }, ).databaseTransport, ).toEqual({ mode: "disable" }); }); it("refuses a production configuration that disables TLS", () => { let captured: unknown; try { createApiRuntimeConfig( { ...validInput(), databaseTransport: { mode: "disable" } }, { environment: "production" }, ); } catch (error) { captured = error; } expect(captured).toMatchObject({ code: "DATABASE_TLS_REQUIRED_IN_PRODUCTION" }); }); it("rejects a malformed or loosely spelled TLS mode in every environment, without coercion", () => { for (const mode of [ "VERIFY-FULL", " verify-full ", "require", "verify-ca", "prefer", "no-verify", "true", true, 1, "", ]) { for (const environment of ["production", "development"] as const) { let captured: unknown; try { createApiRuntimeConfig( { ...validInput(), databaseTransport: { mode }, ...(environment === "development" ? { identity: { baseUrl: "http://localhost:3000", secret: validSecret, trustedOrigins: ["http://localhost:3000"], }, } : {}), }, { environment }, ); } catch (error) { captured = error; } expect(captured, `expected mode ${JSON.stringify(mode)} to be rejected`).toMatchObject({ code: "DATABASE_TLS_MODE_INVALID", }); } } }); it("rejects a database URL carrying its own TLS parameters, which the pinned driver would let override the policy", () => { for (const parameter of [ "sslmode=disable", "sslmode=require", "sslmode=verify-full", "ssl=true", "sslrootcert=/tmp/ca.crt", "sslcert=/tmp/c.crt", "sslkey=/tmp/c.key", "uselibpqcompat=true", ]) { let captured: unknown; try { createApiRuntimeConfig( { ...validInput(), databaseUrl: `postgresql://ekklesia:ekklesia@db.example.test:5432/ekklesia?${parameter}`, }, { environment: "production" }, ); } catch (error) { captured = error; } expect(captured, `expected ${parameter} to be rejected`).toMatchObject({ code: "DATABASE_TLS_CONNECTION_STRING_CONFLICT", }); } }); it("never leaks the database URL, credentials, host, or CA material through a transport error", () => { let captured: unknown; try { createApiRuntimeConfig( { ...validInput(), databaseTransport: { certificateAuthority: "-----BEGIN CERTIFICATE-----SECRET" }, databaseUrl: "postgresql://admin:hunter2@db.internal.example:5432/ekklesia?sslmode=disable", }, { environment: "production" }, ); } catch (error) { captured = error; } const publicText = `${String(captured)}\n${ captured instanceof Error ? (captured.stack ?? "") : "" }\n${JSON.stringify(captured)}`; expect(publicText).not.toContain("hunter2"); expect(publicText).not.toContain("db.internal.example"); expect(publicText).not.toContain("SECRET"); expect(publicText).not.toContain("sslmode"); }); }); it("never leaks the database URL, secret, or trusted origins through a thrown error", () => { const hostileUrl = "postgresql://admin:s3cr3t@db.internal.example: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"); }); }); describe("the API's trusted browser origins (ADR 0045)", () => { it("adds the API's own origin to the configured ones, as Better Auth does", () => { const origins = apiTrustedOrigins({ baseUrl: "https://api.example.test", trustedOrigins: ["https://dash.example.test"], }); expect(origins).toEqual(["https://dash.example.test", "https://api.example.test"]); expect(Object.isFrozen(origins)).toBe(true); }); it("does not duplicate an API origin already listed (the same-origin topology)", () => { expect( apiTrustedOrigins({ baseUrl: "https://ekklesia.example.test", trustedOrigins: ["https://ekklesia.example.test"], }), ).toEqual(["https://ekklesia.example.test"]); }); });