Files
Ekklesia/apps/api/tests/api-http.integration.ts
T
ekklesia fc6c8045c4
CI / verify (pull_request) Successful in 37s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 1m7s
feat: add native module realtime transport
2026-08-23 18:47:18 -05:00

3549 lines
142 KiB
TypeScript

import { randomBytes } from "node:crypto";
import { request as httpRequest } from "node:http";
import net from "node:net";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { parseEkklesiaUserId, parsePermissionId, SYSTEM_ACTOR } from "@ekklesia/contracts";
import {
bootstrapPlatformOwner,
createAuthorizationDrizzleDatabase,
createAuthorizationStore,
} from "@ekklesia/core/authorization";
import { diagnoseEkklesia } from "@ekklesia/core/diagnostics";
import {
applyModuleMigrations,
createPostgresMigrationDatabase,
discoverAllMigrations,
} from "@ekklesia/core/migrations";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { Client, type QueryResultRow } from "pg";
import { afterAll, describe, expect, it, vi } from "vitest";
import type { ClientAddressConfigInput } from "../src/client-address.js";
import { createApiRuntimeConfig } from "../src/config.js";
import {
MODULE_UI_CACHE_CONTROL,
MODULE_UI_CONTENT_TYPE,
moduleUiArtifactUrl,
} from "../src/module-ui-routes.js";
import { startApi, type NodeServerTimeouts, type RunningApi } from "../src/server.js";
const integrationDatabaseUrlEnvironment = "EKKLESIA_MIGRATION_TEST_DATABASE_URL";
const coreMigrationsDirectory = path.join(
path.dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"..",
"core",
"drizzle",
);
const trackedDatabases = new Set<string>();
let databaseCounter = 0;
class IntegrationHarnessError extends Error {
constructor(message: string) {
super(message);
this.name = "IntegrationHarnessError";
this.stack = `${this.name}: ${this.message}`;
}
}
function harnessError(message: string): IntegrationHarnessError {
return new IntegrationHarnessError(message);
}
async function withTransportDeadline<Value>(
operation: Promise<Value>,
onTimeout: () => void,
): Promise<Value> {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
operation,
new Promise<never>((_, reject) => {
timer = setTimeout(() => {
onTimeout();
reject(harnessError("An integration transport operation timed out."));
}, 5_000);
}),
]);
} finally {
clearTimeout(timer);
}
}
function baseDatabaseUrl(): string {
const value = process.env[integrationDatabaseUrlEnvironment];
if (typeof value !== "string" || value.trim() === "") {
throw harnessError("The API integration database is not configured.");
}
try {
const parsed = new URL(value);
if (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:") {
throw new TypeError("Unsupported protocol.");
}
} catch {
throw harnessError("The API integration database configuration is invalid.");
}
return value;
}
function quoteDatabaseName(databaseName: string): string {
if (!/^[a-z][a-z0-9_]{0,62}$/u.test(databaseName)) {
throw harnessError("The generated integration database name is invalid.");
}
return `"${databaseName}"`;
}
function makeDatabaseName(label: string): string {
const safeLabel = label
.toLowerCase()
.replaceAll(/[^a-z0-9]+/gu, "_")
.replaceAll(/^_+|_+$/gu, "")
.slice(0, 14);
databaseCounter += 1;
return `ekk_api_${process.pid}_${databaseCounter}_${safeLabel || "scenario"}_${randomBytes(4).toString("hex")}`;
}
function databaseUrl(databaseName: string): string {
const parsed = new URL(baseDatabaseUrl());
parsed.pathname = `/${databaseName}`;
parsed.searchParams.delete("application_name");
return parsed.toString();
}
async function openAdministrativeClient(connectionString: string): Promise<Client> {
const client = new Client({ connectionString });
client.on("error", () => undefined);
try {
await client.connect();
return client;
} catch {
try {
await client.end();
} catch {
// The public harness failure remains static when connection cleanup also fails.
}
throw harnessError("The API integration database connection failed.");
}
}
async function safeQuery<Row extends QueryResultRow>(
client: Client,
text: string,
values: unknown[] = [],
): Promise<readonly Row[]> {
try {
const result = await client.query<Row>(text, values);
return result.rows;
} catch {
throw harnessError("An integration database administration operation failed.");
}
}
async function safeEnd(client: Client): Promise<void> {
try {
await client.end();
} catch {
throw harnessError("An integration database connection could not be closed.");
}
}
async function createDatabase(databaseName: string): Promise<void> {
const admin = await openAdministrativeClient(baseDatabaseUrl());
try {
await safeQuery(admin, `CREATE DATABASE ${quoteDatabaseName(databaseName)} TEMPLATE template0`);
} finally {
await safeEnd(admin);
}
}
async function dropDatabase(databaseName: string): Promise<void> {
const admin = await openAdministrativeClient(baseDatabaseUrl());
try {
await safeQuery(
admin,
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()",
[databaseName],
);
await safeQuery(
admin,
`DROP DATABASE IF EXISTS ${quoteDatabaseName(databaseName)} WITH (FORCE)`,
);
trackedDatabases.delete(databaseName);
} finally {
await safeEnd(admin);
}
}
async function activeConnectionCount(databaseName: string): Promise<number> {
const admin = await openAdministrativeClient(baseDatabaseUrl());
try {
const rows = await safeQuery<{ count: string }>(
admin,
"SELECT count(*)::text AS count FROM pg_stat_activity WHERE datname = $1",
[databaseName],
);
return Number(rows[0]?.count ?? "0");
} finally {
await safeEnd(admin);
}
}
async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> {
const name = makeDatabaseName(label);
await createDatabase(name);
trackedDatabases.add(name);
return { name, url: databaseUrl(name) };
}
async function applyCoreMigrations(url: string): Promise<void> {
const emptyModulesDirectory = await mkdtemp(path.join(tmpdir(), "ekklesia-api-no-modules-"));
try {
const aggregate = await discoverAllMigrations({
coreMigrationsDirectory,
moduleDiscovery: {
moduleSdkVersion: "0.1.0",
modulesDirectory: emptyModulesDirectory,
platformVersion: "0.1.0",
},
});
// Phase 4A's and Phase 4C pass 1's committed migrations must not have drifted while building
// this transport slice.
expect(aggregate.migrations.map((entry) => entry.identity)).toEqual([
"core:0000_identity_core_tables",
"core:0001_authorization_core_tables",
"core:0002_authorization_integrity",
"core:0003_dashboard_navigation_tables",
"core:0004_module_lifecycle_state",
"core:0005_module_configuration",
]);
expect(aggregate.migrations[0]?.checksum).toBe(
"f8f2086ef06f7a6943e8ffc4350bb010b37aed04c0a72030d2b153c986b1325f",
);
expect(aggregate.migrations[1]?.checksum).toBe(
"830e1fbc26d05af08df8d8710c3d10e1f3016d32c41b8363cb661c49afcbe81e",
);
// Pass 3's new Core migration, pinned the same way: 0000/0001 above must stay byte-identical.
expect(aggregate.migrations[2]?.checksum).toBe(
"63429473118065dfe3877a1baec61b8f61663b88127d9cb17707c859f27f6036",
);
await applyModuleMigrations({ aggregate, database: createPostgresMigrationDatabase(url) });
} finally {
await rm(emptyModulesDirectory, { recursive: true, force: true });
}
}
const trackedModulesDirectories: string[] = [];
const eventSymbols: symbol[] = [];
async function createFixtureModulesDirectory(): Promise<string> {
const directory = await mkdtemp(path.join(tmpdir(), "ekklesia-api-installed-modules-"));
trackedModulesDirectories.push(directory);
return directory;
}
/** A dynamically imported module fixture cannot close over this test file's local variables, so
* lifecycle markers cross that boundary through a `globalThis` symbol instead (same technique as
* `core/tests/module-runtime.test.ts`). */
function createEventRecorder(): { events: string[]; expression: string } {
const key = `ekklesia.api.integration.test.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
const symbol = Symbol.for(key);
const events: string[] = [];
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = events;
eventSymbols.push(symbol);
return { events, expression: `globalThis[Symbol.for(${JSON.stringify(key)})]` };
}
/** Writes a minimal, structurally valid module fixture straight to disk — real discovery,
* composition, and (where a scenario actually starts the API) real module runtime execution all
* run against it, nothing hand-built. Register/start/stop push markers onto `recorder.events`
* instead of throwing, so both "Doctor/composition never executed this" (an empty recorder) and
* "startApi actually imported, registered, and started this" (a populated recorder) can be proven
* against the exact same fixture. When declared with the `calendar.events.read` permission, the
* module also registers a restricted `GET /events/:eventId` route and a restricted, state-changing
* `POST /events` route against that same permission, so the full HTTP mounting/enforcement chain
* can be exercised live. */
/** A handler gate shared with a dynamically imported fixture module through `globalThis`, the same
* technique `createEventRecorder` uses. The handler resolves `entered` when it is genuinely inside
* user code and then awaits `released`, so a test can position shutdown precisely instead of
* inferring handler entry from a connection count. `release` is idempotent and safe in `finally`. */
function createHandlerGate(): {
readonly entered: Promise<void>;
readonly expression: string;
release: () => void;
} {
const key = `ekklesia.api.integration.gate.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
const symbol = Symbol.for(key);
let signalEntered: () => void = () => undefined;
let signalReleased: () => void = () => undefined;
const entered = new Promise<void>((resolve) => {
signalEntered = resolve;
});
const released = new Promise<void>((resolve) => {
signalReleased = resolve;
});
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = {
enter: () => signalEntered(),
released,
};
eventSymbols.push(symbol);
return {
entered,
expression: `globalThis[Symbol.for(${JSON.stringify(key)})]`,
release: () => signalReleased(),
};
}
/** A fixture module with one public route whose handler blocks on the supplied gate, plus a stop
* hook that records when it ran — everything the graceful-drain ordering assertion needs. */
async function createGatedRouteModule(
modulesDirectory: string,
id: string,
recorder: { expression: string },
gate: { expression: string },
): Promise<void> {
const permissionId = `${id}.gated.read`;
const moduleDirectory = path.join(modulesDirectory, id);
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
await Promise.all([
writeFile(
path.join(moduleDirectory, "package.json"),
JSON.stringify({
ekklesia: { layer: "module" },
name: `@test/module-${id}`,
type: "module",
version: "0.1.0",
}),
),
writeFile(
path.join(moduleDirectory, "ekklesia.module.json"),
JSON.stringify({
compatibility: { moduleSdk: "^0.1.0", platform: "^0.1.0" },
entrypoints: { server: "./dist/server/index.js" },
id,
manifestVersion: 5,
name: `${id} module`,
permissions: [{ baseline: "public", description: "Gated drain probe.", id: permissionId }],
version: "0.1.0",
}),
),
writeFile(
path.join(moduleDirectory, "dist/server/index.js"),
`export default {
id: ${JSON.stringify(id)},
version: "0.1.0",
register({ http, realtime }) {
realtime.channel({ channel: "state", permission: ${JSON.stringify(permissionId)} });
http.route({
method: "GET",
path: "/gated",
permission: ${JSON.stringify(permissionId)},
maxBodyBytes: 0,
async handler(_request, context) {
${recorder.expression}.push(${JSON.stringify(`${id}:handler-entered`)});
${gate.expression}.enter();
await ${gate.expression}.released;
context.realtime.publish("state", { status: "finished" });
${recorder.expression}.push(${JSON.stringify(`${id}:handler-published`)});
${recorder.expression}.push(${JSON.stringify(`${id}:handler-finished`)});
return Response.json({ gated: true });
},
});
},
start({ realtime }) { realtime.publish("state", { status: "waiting" }); },
stop() { ${recorder.expression}.push(${JSON.stringify(`${id}:stop`)}); },
};`,
),
]);
}
async function createFixtureModule(
modulesDirectory: string,
id: string,
recorder: { expression: string },
manifestOverrides: Record<string, unknown> = {},
): Promise<void> {
const moduleDirectory = path.join(modulesDirectory, id);
const manifest = {
manifestVersion: 5,
id,
name: `${id} module`,
version: "0.1.0",
compatibility: { platform: "^0.1.0", moduleSdk: "^0.1.0" },
permissions: [],
entrypoints: { server: "./dist/server/index.js" },
...manifestOverrides,
};
const declaresCalendarRead = (manifest.permissions as ReadonlyArray<{ id: string }>).some(
(declaration) => declaration.id === "calendar.events.read",
);
const realtimePermission = (manifest.permissions as ReadonlyArray<{ id: string }>).find(
(declaration) => declaration.id === `${id}.state.view`,
)?.id;
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
await Promise.all([
writeFile(
path.join(moduleDirectory, "package.json"),
JSON.stringify({
ekklesia: { layer: "module" },
name: `@test/module-${id}`,
type: "module",
version: "0.1.0",
}),
),
writeFile(path.join(moduleDirectory, "ekklesia.module.json"), JSON.stringify(manifest)),
writeFile(
path.join(moduleDirectory, "dist/server/index.js"),
`export default {
id: ${JSON.stringify(id)},
version: "0.1.0",
register({ http, realtime }) {
${recorder.expression}.push(${JSON.stringify(`${id}:register`)});
${
realtimePermission
? `realtime.channel({ channel: "state", permission: ${JSON.stringify(realtimePermission)} });`
: ""
}
${
declaresCalendarRead
? `http.route({
method: "GET",
path: "/events/:eventId",
permission: "calendar.events.read",
maxBodyBytes: 0,
async handler(request, context) {
return Response.json({ eventId: context.params.eventId, principal: context.principal });
},
});
http.route({
method: "POST",
path: "/events",
permission: "calendar.events.read",
maxBodyBytes: 1024,
async handler(request, context) {
return Response.json({ created: true, principal: context.principal }, { status: 201 });
},
});`
: ""
}
},
start({ realtime }) {
${
realtimePermission
? `realtime.publish("state", { module: ${JSON.stringify(id)}, status: "running" });`
: ""
}
${recorder.expression}.push(${JSON.stringify(`${id}:start`)});
},
stop() {
${recorder.expression}.push(${JSON.stringify(`${id}:stop`)});
},
};`,
),
]);
}
/** A module that ships a real, loadable UI artifact plus the neighbouring files a naive static
* mount would have exposed: a source map, a sibling bundle chunk, and a package-root secret. */
async function createUiModule(
modulesDirectory: string,
id: string,
version: string,
artifact: string,
): Promise<void> {
const moduleDirectory = path.join(modulesDirectory, id);
await mkdir(path.join(moduleDirectory, "dist/ui"), { recursive: true });
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
await Promise.all([
writeFile(
path.join(moduleDirectory, "package.json"),
JSON.stringify({
ekklesia: { layer: "module" },
name: `@test/module-${id}`,
type: "module",
version,
}),
),
writeFile(
path.join(moduleDirectory, "ekklesia.module.json"),
JSON.stringify({
compatibility: { moduleSdk: "^0.1.0", platform: "^0.1.0" },
entrypoints: { server: "./dist/server/index.js", ui: "./dist/ui/index.js" },
id,
manifestVersion: 5,
name: `${id} module`,
version,
}),
),
writeFile(
path.join(moduleDirectory, "dist/server/index.js"),
`export default { id: ${JSON.stringify(id)}, version: ${JSON.stringify(version)} };`,
),
writeFile(path.join(moduleDirectory, "dist/ui/index.js"), artifact),
writeFile(
path.join(moduleDirectory, "dist/ui/index.js.map"),
'{"version":3,"sourcesContent":["map-secret"]}',
),
writeFile(path.join(moduleDirectory, "dist/ui/chunk.js"), "export const chunkSecret = 1;\n"),
writeFile(path.join(moduleDirectory, ".env"), "EKKLESIA_IDENTITY_SECRET=env-secret\n"),
]);
}
afterAll(async () => {
for (const databaseName of [...trackedDatabases]) {
await dropDatabase(databaseName).catch(() => undefined);
}
for (const directory of trackedModulesDirectories.splice(0)) {
await rm(directory, { recursive: true, force: true }).catch(() => undefined);
}
const globalValues = globalThis as unknown as Record<PropertyKey, unknown>;
for (const symbol of eventSymbols.splice(0)) {
delete globalValues[symbol];
}
});
const identityBaseUrl = "https://identity.example.test";
const dashboardOrigin = "https://dashboard.example.test";
const untrustedOrigin = "https://evil.example.test";
function apiOrigin(running: RunningApi): string {
const address = running.server.address();
if (!address || typeof address !== "object") {
throw harnessError("The API integration server has no bound address.");
}
return `http://127.0.0.1:${address.port}`;
}
interface StartConfiguredApiOptions {
readonly clientAddress?: ClientAddressConfigInput;
/** Test-only narrowing of the platform's 30-second graceful HTTP drain deadline. */
readonly drainTimeoutMsOverride?: number;
readonly modulesDirectoryOverride?: string;
readonly nodeServerTimeoutOverride?: Partial<NodeServerTimeouts>;
/** Defaults to `true`: almost every existing scenario in this suite signs accounts up over HTTP
* and needs it enabled. Pass `false` explicitly for the dedicated signup-gating scenarios. */
readonly publicSignupEnabled?: boolean;
/** Defaults to a generous override so this suite's own request volume never trips Better Auth's
* strict built-in sign-in/sign-up rule. The dedicated rate-limit/client-address scenarios pass
* `undefined` explicitly to exercise the real, unmodified production rule instead. */
readonly rateLimitOverride?: { readonly max: number; readonly window: number } | undefined;
}
async function startConfiguredApi(
url: string,
options: StartConfiguredApiOptions = {},
): Promise<RunningApi> {
const publicSignupEnabled = options.publicSignupEnabled ?? true;
// This harness explicitly selects development configuration, for two independent reasons that
// both apply to every scenario in this suite:
//
// 1. It connects to the disposable *plaintext* loopback PostgreSQL. Production configuration
// requires certificate-verified TLS with no exception, so a plaintext test database can
// only be used by a deployment that has explicitly declared itself non-production.
// 2. Scenarios that create accounts over HTTP need public signup, which production refuses to
// enable at all until durable abuse controls exist.
//
// This changes only Ekklesia's own configuration-validation strictness — `environment` never
// reaches Better Auth — and the harness keeps the identical https, non-loopback base URL and
// trusted origins regardless, so the production-equivalent origin/CSRF behavior ADR 0012
// established is exercised exactly the same either way.
const environment = "development" as const;
const config = createApiRuntimeConfig(
{
...(options.clientAddress === undefined ? {} : { clientAddress: options.clientAddress }),
databaseUrl: url,
host: "127.0.0.1",
identity: {
baseUrl: identityBaseUrl,
publicSignupEnabled,
secret: "integration-test-secret-0123456789abcdefgh",
trustedOrigins: [identityBaseUrl, dashboardOrigin],
},
port: 0,
},
{ environment },
);
return startApi(config, {
...("rateLimitOverride" in options
? { ...(options.rateLimitOverride ? { rateLimitOverride: options.rateLimitOverride } : {}) }
: { rateLimitOverride: { max: 1000, window: 1 } }),
...(options.modulesDirectoryOverride === undefined
? {}
: { modulesDirectoryOverride: options.modulesDirectoryOverride }),
...(options.nodeServerTimeoutOverride === undefined
? {}
: { nodeServerTimeoutOverride: options.nodeServerTimeoutOverride }),
...(options.drainTimeoutMsOverride === undefined
? {}
: { drainTimeoutMsOverride: options.drainTimeoutMsOverride }),
});
}
async function startMigratedApi(
url: string,
modulesDirectoryOverride?: string,
): Promise<RunningApi> {
// Narrow, explicit test-only overrides: production composition (server.ts's `main`) never sets
// either. Better Auth's built-in sign-in/sign-up rate limit (3 requests / 10s) would otherwise
// reject this suite's many sequential requests; `modulesDirectoryOverride` lets a scenario point
// permission composition at a real, disposable module fixture instead of this repository's own
// (empty) `modules/` directory.
return startConfiguredApi(url, {
...(modulesDirectoryOverride === undefined ? {} : { modulesDirectoryOverride }),
});
}
function setCookieValue(response: Response): string | undefined {
return response.headers.get("set-cookie") ?? undefined;
}
function firstCookiePair(setCookieHeader: string): string {
return setCookieHeader.split(",")[0]?.split(";")[0]?.trim() ?? "";
}
interface SignUpResult {
readonly response: Response;
readonly body: { token: string | null; user: { id: string; email: string } };
}
async function signUp(
origin: string,
email: string,
password: string,
headers: Record<string, string> = {},
): Promise<SignUpResult> {
const response = await fetch(`${origin}/api/auth/sign-up/email`, {
body: JSON.stringify({ email, name: "Integration Test User", password }),
// A trusted Origin by default, matching signIn below: Node's own fetch() (and every browser)
// automatically sends `Sec-Fetch-Mode: cors`, which Better Auth's first-login CSRF protection
// treats as "this is a Fetch-API call" and requires a trusted Origin/Referer for regardless of
// whether a cookie is present — see the dedicated no-Origin scenario below, which uses a raw
// `http.request` instead of `fetch()` to exercise a genuinely Fetch-Metadata-free caller.
headers: { "content-type": "application/json", origin: dashboardOrigin, ...headers },
method: "POST",
});
return { body: (await response.clone().json()) as SignUpResult["body"], response };
}
interface SignInResult {
readonly cookiePair: string | undefined;
readonly response: Response;
}
async function signIn(
origin: string,
email: string,
password: string,
headers: Record<string, string> = {},
): Promise<SignInResult> {
const response = await fetch(`${origin}/api/auth/sign-in/email`, {
body: JSON.stringify({ email, password }),
headers: {
"content-type": "application/json",
origin: dashboardOrigin,
...headers,
},
method: "POST",
});
const setCookie = setCookieValue(response);
return { cookiePair: setCookie ? firstCookiePair(setCookie) : undefined, response };
}
/**
* Signs up an account over real HTTP, grants it platform-owner authority through Core's own real
* bootstrap operation (exactly what `pnpm authorization:bootstrap-owner` performs), and signs it
* in — so a scenario needing an administrator exercises the genuine evaluator path rather than a
* stubbed decision.
*/
async function signUpPlatformOwner(
origin: string,
url: string,
): Promise<{ readonly cookie: string; readonly userId: string }> {
const email = `owner-${randomBytes(6).toString("hex")}@example.test`;
const password = "correct horse battery staple";
const created = await signUp(origin, email, password);
const { close, db } = createAuthorizationDrizzleDatabase(url);
try {
await bootstrapPlatformOwner(db, { userId: parseEkklesiaUserId(created.body.user.id) });
} finally {
await close();
}
const session = await signIn(origin, email, password);
if (!session.cookiePair) {
throw harnessError("The platform-owner session cookie was not issued.");
}
return { cookie: session.cookiePair, userId: created.body.user.id };
}
async function fetchPrincipal(
origin: string,
headers: Record<string, string> = {},
): Promise<{ body: unknown; response: Response }> {
const response = await fetch(`${origin}/api/v1/principal`, { headers });
return { body: await response.clone().json(), response };
}
/**
* A raw `node:http` POST, deliberately not `fetch()`. Node's own `fetch()` (undici) — like every
* browser — automatically sends `Sec-Fetch-Mode: cors` on every request, which Better Auth's
* first-login CSRF protection (`formCsrfMiddleware`) treats as "this came from a Fetch-API
* client" and requires a trusted Origin/Referer for regardless of cookie presence. A genuine
* non-browser, non-Fetch server-to-server integration (curl, most backend HTTP client libraries,
* or this raw socket) sends none of the Fetch Metadata headers, so it is unaffected by that
* specific protection and needs no Origin header at all.
*/
function rawPostRequest(
origin: string,
requestPath: string,
body: unknown,
): Promise<{ body: string; status: number }> {
return new Promise((resolve, reject) => {
const url = new URL(requestPath, origin);
const payload = JSON.stringify(body);
const req = httpRequest(
url,
{
headers: {
"content-length": Buffer.byteLength(payload),
"content-type": "application/json",
},
method: "POST",
},
(res) => {
const chunks: Buffer[] = [];
res.on("data", (chunk) => chunks.push(chunk as Buffer));
res.on("end", () => {
resolve({ body: Buffer.concat(chunks).toString("utf8"), status: res.statusCode ?? 0 });
});
},
);
req.on("error", reject);
req.end(payload);
});
}
interface RawSignInAttemptOptions {
/** Originates the outbound connection from this local address — real, distinct loopback
* addresses (127.0.0.2, 127.0.0.3, ...) are all independently routable on the loopback interface,
* so this proves genuinely different real socket peers, not merely different header claims. */
readonly localAddress?: string;
/** A raw, unvalidated `X-Forwarded-For` header value to send — deliberately not parsed or
* validated here, since these tests are proving how the *server* handles it. */
readonly forwardedFor?: string;
}
interface RawResponse {
readonly body: string;
readonly headers: Record<string, string | string[] | undefined>;
readonly status: number;
}
/**
* A raw `node:http` sign-in attempt against a deliberately wrong password. Better Auth's rate
* limiter keys and counts every `/sign-in*` request before ever validating credentials, so this
* never needs a real account and always resolves to either `401` (accepted by the limiter, wrong
* credentials) or `429` (rejected by the limiter) — the only two outcomes these tests distinguish.
* Raw `node:http`, not `fetch()`, for the same Fetch-Metadata reason `rawPostRequest` documents.
*/
function rawSignInAttempt(
origin: string,
options: RawSignInAttemptOptions = {},
): Promise<RawResponse> {
return new Promise((resolve, reject) => {
const url = new URL("/api/auth/sign-in/email", origin);
const payload = JSON.stringify({
email: "[email protected]",
password: "definitely-the-wrong-password",
});
const req = httpRequest(
url,
{
headers: {
"content-length": Buffer.byteLength(payload),
"content-type": "application/json",
...(options.forwardedFor === undefined
? {}
: { "x-forwarded-for": options.forwardedFor }),
},
...(options.localAddress === undefined ? {} : { localAddress: options.localAddress }),
method: "POST",
},
(res) => {
const chunks: Buffer[] = [];
res.on("data", (chunk) => chunks.push(chunk as Buffer));
res.on("end", () => {
resolve({
body: Buffer.concat(chunks).toString("utf8"),
headers: res.headers,
status: res.statusCode ?? 0,
});
});
},
);
req.on("error", reject);
req.end(payload);
});
}
describe.sequential("live API — signup, sign-in, principal, sign-out", () => {
let database: { name: string; url: string };
let running: RunningApi;
let origin: string;
const emailOne = "[email protected]";
const emailTwo = "[email protected]";
const password = "correct horse battery staple";
it("applies migrations and starts the API against a freshly migrated database", async () => {
database = await createDisposableDatabase("signup_flow");
await applyCoreMigrations(database.url);
running = await startMigratedApi(database.url);
origin = apiOrigin(running);
expect(running.server.listening).toBe(true);
});
it("returns the anonymous principal shape before any sign-in", async () => {
const { body, response } = await fetchPrincipal(origin);
expect(response.status).toBe(200);
expect(body).toEqual({ kind: "anonymous" });
});
it("publicly creates the first ordinary identity via sign-up, with no automatic session (autoSignIn: false)", async () => {
const { body, response } = await signUp(origin, emailOne, password);
expect(response.status).toBe(200);
expect(body.token).toBeNull();
expect(body.user.email).toBe(emailOne);
expect(setCookieValue(response)).toBeUndefined();
});
it("does not sign in automatically after sign-up", async () => {
const { body } = await fetchPrincipal(origin);
expect(body).toEqual({ kind: "anonymous" });
});
it("rejects wrong credentials without leaking raw internal information", async () => {
const { response } = await signIn(origin, emailOne, "wrong password entirely");
const text = await response.text();
expect(response.status).toBe(401);
expect(text).not.toContain(database.url);
expect(text).not.toContain("postgres");
expect(text.toLowerCase()).not.toContain("stack");
});
let sessionCookie: string;
it("signs in explicitly and returns a valid, host-only, HttpOnly, SameSite=Lax, secure session cookie", async () => {
const { cookiePair, response } = await signIn(origin, emailOne, password);
const setCookie = setCookieValue(response);
expect(response.status).toBe(200);
expect(setCookie).toBeDefined();
expect(cookiePair).toBeDefined();
sessionCookie = cookiePair as string;
const cookieText = setCookie as string;
expect(cookieText).toContain("HttpOnly");
expect(cookieText).toContain("SameSite=Lax");
expect(cookieText).toContain("Secure");
expect(cookieText).not.toMatch(/domain=/iu);
});
it("resolves the authenticated principal containing only kind and the opaque user id", async () => {
const { body, response } = await fetchPrincipal(origin, { cookie: sessionCookie });
expect(response.status).toBe(200);
expect(body).toMatchObject({ kind: "authenticated" });
const record = body as Record<string, unknown>;
expect(Object.keys(record).sort()).toEqual(["kind", "userId"]);
expect(typeof record.userId).toBe("string");
});
it("resolves a malformed cookie as anonymous rather than failing", async () => {
const { body, response } = await fetchPrincipal(origin, {
cookie: "better-auth.session_token=not-a-real-token",
});
expect(response.status).toBe(200);
expect(body).toEqual({ kind: "anonymous" });
});
it("resolves a nonexistent cookie name as anonymous", async () => {
const { body, response } = await fetchPrincipal(origin, { cookie: "unrelated=value" });
expect(response.status).toBe(200);
expect(body).toEqual({ kind: "anonymous" });
});
it("does not create a second identity on duplicate sign-up, and does not leak unstable internal details", async () => {
const { body, response } = await signUp(origin, emailOne, "a totally different password");
expect(response.status).toBe(200);
expect(body.token).toBeNull();
expect(typeof body.user.id).toBe("string");
});
it("keeps exactly one user row for the duplicated email after the duplicate sign-up attempt", async () => {
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ count: string }>(
admin,
'select count(*)::text as count from "ekklesia_core_user" where email = $1',
[emailOne],
);
expect(rows[0]?.count).toBe("1");
} finally {
await safeEnd(admin);
}
});
it("signs out and invalidates the session", async () => {
const response = await fetch(`${origin}/api/auth/sign-out`, {
body: "{}",
headers: {
"content-type": "application/json",
cookie: sessionCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(response.status).toBe(200);
expect(setCookieValue(response)).toBeDefined();
});
it("resolves the old cookie as anonymous after sign-out", async () => {
const { body, response } = await fetchPrincipal(origin, { cookie: sessionCookie });
expect(response.status).toBe(200);
expect(body).toEqual({ kind: "anonymous" });
});
let secondUserId: string;
it("registers and signs in a second, distinct public account", async () => {
const signUpResult = await signUp(origin, emailTwo, password);
expect(signUpResult.response.status).toBe(200);
const signInResult = await signIn(origin, emailTwo, password);
expect(signInResult.response.status).toBe(200);
expect(signInResult.cookiePair).toBeDefined();
const { body } = await fetchPrincipal(origin, {
cookie: signInResult.cookiePair as string,
});
const record = body as { kind: string; userId: string };
expect(record.kind).toBe("authenticated");
secondUserId = record.userId;
expect(secondUserId).toBeTruthy();
});
it("gives the first and second account identical principal shapes with no elevation", async () => {
const first = await signIn(origin, emailOne, password);
const { body: firstBody } = await fetchPrincipal(origin, {
cookie: first.cookiePair as string,
});
const second = await signIn(origin, emailTwo, password);
const { body: secondBody } = await fetchPrincipal(origin, {
cookie: second.cookiePair as string,
});
expect(Object.keys(firstBody as object).sort()).toEqual(
Object.keys(secondBody as object).sort(),
);
expect((firstBody as { kind: string }).kind).toBe((secondBody as { kind: string }).kind);
});
it("gives neither account owner, role, permission, membership, or volunteer data at the database level", async () => {
const admin = await openAdministrativeClient(database.url);
try {
const columns = await safeQuery<{ column_name: string }>(
admin,
"select column_name from information_schema.columns where table_schema = 'public' and table_name = 'ekklesia_core_user'",
);
const columnNames = columns.map((row) => row.column_name);
for (const forbidden of [
"role",
"is_admin",
"is_owner",
"is_staff",
"is_volunteer",
"is_member",
"permissions",
"grants",
]) {
expect(columnNames).not.toContain(forbidden);
}
} finally {
await safeEnd(admin);
}
});
it("never stores a plaintext password", async () => {
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ password: string | null }>(
admin,
`select a.password from "ekklesia_core_account" a
inner join "ekklesia_core_user" u on u.id = a.user_id
where u.email = $1`,
[emailOne],
);
const stored = rows[0]?.password;
expect(stored).toBeTruthy();
expect(stored).not.toBe(password);
expect(stored?.length ?? 0).toBeGreaterThan(password.length);
} finally {
await safeEnd(admin);
}
});
it("supports a genuine server-to-server sign-up request that carries no Origin header and no Fetch Metadata headers at all", async () => {
const { status } = await rawPostRequest(origin, "/api/auth/sign-up/email", {
email: "[email protected]",
name: "Integration Test User",
password,
});
expect(status).toBe(200);
});
it("allows a trusted browser origin for the principal route with credentials", async () => {
const response = await fetch(`${origin}/api/v1/principal`, {
headers: { cookie: sessionCookie, origin: dashboardOrigin },
});
expect(response.status).toBe(200);
expect(response.headers.get("access-control-allow-origin")).toBe(dashboardOrigin);
expect(response.headers.get("access-control-allow-credentials")).toBe("true");
});
it("never returns Access-Control-Allow-Origin for an untrusted browser origin", async () => {
const principalResponse = await fetch(`${origin}/api/v1/principal`, {
headers: { origin: untrustedOrigin },
});
expect(principalResponse.headers.get("access-control-allow-origin")).toBeNull();
});
// Production-equivalent Better Auth origin/CSRF protection (ADR 0012). Explicitly setting
// `advanced: { disableCSRFCheck: false, disableOriginCheck: false }` (`core/src/identity/runtime.ts`)
// removes Better Auth's own `isTest()`-driven default that otherwise silently skips origin/CSRF
// validation under a test runner, so the same behavior verified below is also what production
// enforces.
it("accepts a no-cookie first authentication attempt from a trusted browser origin", async () => {
const response = await fetch(`${origin}/api/auth/sign-in/email`, {
body: JSON.stringify({ email: emailOne, password }),
headers: { "content-type": "application/json", origin: dashboardOrigin },
method: "POST",
});
expect(response.status).toBe(200);
expect(response.headers.get("access-control-allow-origin")).toBe(dashboardOrigin);
});
it("rejects a no-cookie first authentication attempt from an untrusted browser origin", async () => {
const response = await fetch(`${origin}/api/auth/sign-in/email`, {
body: JSON.stringify({ email: emailOne, password }),
headers: { "content-type": "application/json", origin: untrustedOrigin },
method: "POST",
});
const text = await response.text();
expect(response.status).toBe(403);
expect(response.headers.get("access-control-allow-origin")).toBeNull();
expect(text).not.toContain(database.url);
expect(text.toLowerCase()).not.toContain("stack");
});
it("rejects a cookie-bearing state-changing request from an untrusted browser origin, and does not invalidate the session", async () => {
const { cookiePair } = await signIn(origin, emailOne, password);
expect(cookiePair).toBeDefined();
const response = await fetch(`${origin}/api/auth/sign-out`, {
body: "{}",
headers: {
"content-type": "application/json",
cookie: cookiePair as string,
origin: untrustedOrigin,
},
method: "POST",
});
const text = await response.text();
expect(response.status).toBe(403);
expect(response.headers.get("access-control-allow-origin")).toBeNull();
expect(text).not.toContain(database.url);
expect(text.toLowerCase()).not.toContain("stack");
// The forged/rejected sign-out must never have reached sign-out logic: the session presented
// alongside it must still be valid.
const { body } = await fetchPrincipal(origin, { cookie: cookiePair as string });
expect(body).toMatchObject({ kind: "authenticated" });
});
it("rejects a 7-character password as too short", async () => {
const { response } = await signUp(origin, "[email protected]", "1234567");
expect(response.status).toBe(400);
});
it("accepts an 8-character password", async () => {
const { response, body } = await signUp(origin, "[email protected]", "12345678");
expect(response.status).toBe(200);
expect(body.user.email).toBe("[email protected]");
});
it("accepts a 128-character password", async () => {
const { response } = await signUp(origin, "[email protected]", "a".repeat(128));
expect(response.status).toBe(200);
});
it("rejects a 129-character password as too long", async () => {
const { response } = await signUp(origin, "[email protected]", "a".repeat(129));
expect(response.status).toBe(400);
});
it("leaves Better Auth's handler unavailable outside /api/auth/*", async () => {
const response = await fetch(`${origin}/sign-in/email`, { method: "POST" });
expect(response.status).toBe(404);
});
it("shuts down cleanly, closing the HTTP listener and the PostgreSQL pool", async () => {
await running.shutdown();
await expect(fetch(origin, { signal: AbortSignal.timeout(1_000) })).rejects.toBeTruthy();
const remainingConnections = await activeConnectionCount(database.name);
expect(remainingConnections).toBe(0);
await dropDatabase(database.name);
});
});
describe.sequential("live API — startup and migration guarantees", () => {
it("refuses to start against an unmigrated database, and creates no tables or migration history", async () => {
const database = await createDisposableDatabase("unmigrated");
try {
const config = createApiRuntimeConfig(
{
databaseUrl: database.url,
host: "127.0.0.1",
identity: {
baseUrl: identityBaseUrl,
publicSignupEnabled: true,
secret: "integration-test-secret-0123456789abcdefgh",
trustedOrigins: [identityBaseUrl],
},
port: 0,
},
// Development validation, for the same reason `startConfiguredApi` documents: this scenario
// uses the disposable plaintext loopback database, which production configuration refuses.
{ environment: "development" },
);
// Startup reads the stored module enablement configuration and then the stored module
// configuration, both before importing any module entrypoint (ADR 0028, ADR 0029). Against an
// unmigrated database neither read can succeed, and the platform fails closed rather than
// starting every discovered module: it cannot know what an administrator decided, and starting
// a module they disabled — or one whose required configuration is absent — is worse than not
// starting. The two reads are sequential rather than raced precisely so this error code is
// deterministic; the failure is Core's own sanitized store error, with no connection string,
// host, SQL, driver message, or stack.
let captured: unknown;
try {
await startApi(config, { rateLimitOverride: { max: 1000, window: 1 } });
} catch (error) {
captured = error;
}
expect(captured).toMatchObject({
code: "MODULE_LIFECYCLE_READ_FAILED",
name: "ModuleLifecycleStoreError",
});
const rendered = `${String(captured)} ${JSON.stringify(captured)}`;
expect(rendered).not.toContain(database.name);
expect(rendered).not.toContain("127.0.0.1");
expect(rendered).not.toContain("select");
const admin = await openAdministrativeClient(database.url);
try {
// The invariant this scenario has always existed to protect, unchanged and now proven on a
// path that never even reached Better Auth: a failed start repairs nothing, creates
// nothing, and applies no migration.
const rows = await safeQuery<{ count: string }>(
admin,
"select count(*)::text as count from information_schema.tables where table_schema = 'public'",
);
expect(rows[0]?.count).toBe("0");
} finally {
await safeEnd(admin);
}
} finally {
await dropDatabase(database.name);
}
});
});
describe.sequential("live API — principal infrastructure failure is never downgraded to anonymous", () => {
it("returns a sanitized failure, not a false anonymous 200, when the database becomes unreachable mid-session", async () => {
const database = await createDisposableDatabase("infra_failure");
await applyCoreMigrations(database.url);
const running = await startMigratedApi(database.url);
const origin = apiOrigin(running);
await signUp(origin, "[email protected]", "correct horse battery staple");
const { cookiePair } = await signIn(
origin,
"[email protected]",
"correct horse battery staple",
);
expect(cookiePair).toBeDefined();
// Force the database itself unreachable out from under the running API pool.
await dropDatabase(database.name);
const { body, response } = await fetchPrincipal(origin, { cookie: cookiePair as string });
expect(response.status).toBe(503);
expect(body).toEqual({ code: "PRINCIPAL_RESOLUTION_FAILED" });
await running.shutdown().catch(() => undefined);
});
});
describe.sequential("live API — session-store readiness against real PostgreSQL", () => {
/**
* Every scenario below drives a real, migrated PostgreSQL database, breaks exactly one property
* of the *session store specifically*, and asserts the public contract. The administrative
* connection each helper opens (`openAdministrativeClient`) is **test-only provisioning
* infrastructure** — it renames a relation, drops a column, or revokes a privilege in a
* disposable database, and is closed in `finally`. It is never part of the API runtime, which
* continues to own exactly one pool through `startApi`.
*/
async function withBrokenSessionStore(
label: string,
breakStore: (admin: Client) => Promise<void>,
assertions: (context: {
readonly cookie: string;
readonly origin: string;
readonly logs: string[];
}) => Promise<void>,
): Promise<void> {
const database = await createDisposableDatabase(label);
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const origin = apiOrigin(running);
const email = `${label}@example.test`;
const password = "correct horse battery staple";
await signUp(origin, email, password);
const { cookiePair } = await signIn(origin, email, password);
expect(cookiePair).toBeDefined();
// The session resolves normally before anything is broken.
const before = await fetchPrincipal(origin, { cookie: cookiePair as string });
expect(before.response.status).toBe(200);
expect(before.body).toMatchObject({ kind: "authenticated" });
const admin = await openAdministrativeClient(database.url);
try {
await breakStore(admin);
} finally {
await safeEnd(admin);
}
await assertions({
cookie: cookiePair as string,
logs: errorSpy.mock.calls.flat().map((value) => String(value)),
origin,
});
} finally {
errorSpy.mockRestore();
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
}
/** The exact public contract a broken session store must produce — never anonymous, never 403,
* and never carrying a relation name, column name, privilege detail, SQL, host, or stack. */
async function expectSanitizedUnavailable(origin: string, cookie: string): Promise<Response> {
const response = await fetch(`${origin}/api/v1/principal`, { headers: { cookie } });
expect(response.status).toBe(503);
expect(response.headers.get("cache-control")).toBe("no-store");
const text = await response.clone().text();
expect(JSON.parse(text)).toEqual({ code: "PRINCIPAL_RESOLUTION_FAILED" });
for (const forbidden of [
"ekklesia_core_session",
"permission denied",
"does not exist",
"relation",
"column",
"select",
"postgres",
"5432",
]) {
expect(text.toLowerCase()).not.toContain(forbidden.toLowerCase());
}
return response;
}
it("returns a sanitized 503 — not anonymous — when the session table is missing, even though a generic SELECT 1 still succeeds", async () => {
await withBrokenSessionStore(
"session_missing_table",
async (admin) => {
// Prove the distinction this pass exists for: the connection is perfectly healthy for a
// trivial statement, so the previous `select 1` probe would have reported "reachable" and
// the caller would have been reported as genuinely anonymous.
const trivial = await safeQuery<{ one: number }>(admin, "SELECT 1 AS one");
expect(trivial[0]?.one).toBe(1);
await safeQuery(
admin,
'ALTER TABLE "ekklesia_core_session" RENAME TO "ekklesia_core_session_moved"',
);
},
async ({ cookie, origin }) => {
await expectSanitizedUnavailable(origin, cookie);
},
);
});
it("returns a sanitized 503 when an expected session column is missing", async () => {
await withBrokenSessionStore(
"session_missing_column",
async (admin) => {
await safeQuery(admin, 'ALTER TABLE "ekklesia_core_session" DROP COLUMN "token" CASCADE');
},
async ({ cookie, origin }) => {
await expectSanitizedUnavailable(origin, cookie);
},
);
});
it("returns a sanitized 503 when SELECT privilege on the session table is genuinely revoked", async () => {
// A real privilege denial needs the API to connect as a role that is *not* the table owner —
// an owner's implicit rights cannot be revoked. This provisions a dedicated restricted role
// (test-only infrastructure), grants it everything except SELECT on the session table, and
// runs the API entirely as that role.
const database = await createDisposableDatabase("session_denied");
await applyCoreMigrations(database.url);
const restrictedRole = `ekk_restricted_${randomBytes(4).toString("hex")}`;
const restrictedPassword = randomBytes(12).toString("hex");
const admin = await openAdministrativeClient(database.url);
try {
await safeQuery(
admin,
`CREATE ROLE "${restrictedRole}" LOGIN PASSWORD '${restrictedPassword}'`,
);
await safeQuery(admin, `GRANT USAGE ON SCHEMA public TO "${restrictedRole}"`);
await safeQuery(
admin,
`GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO "${restrictedRole}"`,
);
// The one thing this role may not do: read the session relation.
await safeQuery(admin, `REVOKE ALL ON "ekklesia_core_session" FROM "${restrictedRole}"`);
} finally {
await safeEnd(admin);
}
const restrictedUrl = (() => {
const parsed = new URL(database.url);
parsed.username = restrictedRole;
parsed.password = restrictedPassword;
return parsed.toString();
})();
const running = await startConfiguredApi(restrictedUrl);
try {
const origin = apiOrigin(running);
// The restricted role can still run a trivial statement and can still read non-session
// tables, so only the session store specifically is broken.
const response = await fetch(`${origin}/api/v1/principal`, {
headers: { cookie: "__Secure-better-auth.session_token=any-token-shape" },
});
const text = await response.clone().text();
expect(response.status).toBe(503);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(JSON.parse(text)).toEqual({ code: "PRINCIPAL_RESOLUTION_FAILED" });
expect(text).not.toContain("ekklesia_core_session");
expect(text.toLowerCase()).not.toContain("permission denied");
expect(text).not.toContain(restrictedRole);
} finally {
await running.shutdown().catch(() => undefined);
// Drop the disposable database first (it holds the role's dependencies), then the role.
await dropDatabase(database.name).catch(() => undefined);
const cleanup = await openAdministrativeClient(baseDatabaseUrl());
try {
await safeQuery(cleanup, `DROP ROLE IF EXISTS "${restrictedRole}"`);
} finally {
await safeEnd(cleanup);
}
}
});
it("keeps a genuinely unknown, expired, or revoked session anonymous while the store is healthy", async () => {
const database = await createDisposableDatabase("session_healthy");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url);
try {
const origin = apiOrigin(running);
const email = "[email protected]";
const password = "correct horse battery staple";
await signUp(origin, email, password);
const { cookiePair } = await signIn(origin, email, password);
expect(cookiePair).toBeDefined();
const cookieName = (cookiePair as string).split("=")[0] as string;
// A completely unknown token against a healthy store is genuinely anonymous.
const unknown = await fetchPrincipal(origin, { cookie: `${cookieName}=not-a-real-token` });
expect(unknown.response.status).toBe(200);
expect(unknown.body).toEqual({ kind: "anonymous" });
// A revoked (signed-out) session is likewise genuinely anonymous, not a 503.
const signOut = await fetch(`${origin}/api/auth/sign-out`, {
body: "{}",
headers: {
"content-type": "application/json",
cookie: cookiePair as string,
origin: dashboardOrigin,
},
method: "POST",
});
expect(signOut.status).toBe(200);
const revoked = await fetchPrincipal(origin, { cookie: cookiePair as string });
expect(revoked.response.status).toBe(200);
expect(revoked.body).toEqual({ kind: "anonymous" });
// An expired session (expiry forced into the past) is anonymous, not a 503.
const admin = await openAdministrativeClient(database.url);
try {
await safeQuery(
admin,
`UPDATE "ekklesia_core_session" SET expires_at = now() - interval '1 day'`,
);
} finally {
await safeEnd(admin);
}
const secondSignIn = await signIn(origin, email, password);
const expiredCookie = secondSignIn.cookiePair as string;
const adminAgain = await openAdministrativeClient(database.url);
try {
await safeQuery(
adminAgain,
`UPDATE "ekklesia_core_session" SET expires_at = now() - interval '1 day'`,
);
} finally {
await safeEnd(adminAgain);
}
const expired = await fetchPrincipal(origin, { cookie: expiredCookie });
expect(expired.response.status).toBe(200);
expect(expired.body).toEqual({ kind: "anonymous" });
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("performs no identity database work for an unrelated cookie, proven against a database whose session table is missing", async () => {
const database = await createDisposableDatabase("session_unrelated_cookie");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url);
try {
const origin = apiOrigin(running);
// Break the session store outright. Any request that actually touched it would now 503.
const admin = await openAdministrativeClient(database.url);
try {
await safeQuery(
admin,
'ALTER TABLE "ekklesia_core_session" RENAME TO "ekklesia_core_session_moved"',
);
} finally {
await safeEnd(admin);
}
// Unrelated cookies must still resolve as anonymous with a 200: they never triggered
// session-store work in the first place. This is the original reproduction, inverted.
for (const cookie of ["x=y", "a=1; b=2; c=3", "better_auth_session_token=v"]) {
const { body, response } = await fetchPrincipal(origin, { cookie });
expect(response.status).toBe(200);
expect(body).toEqual({ kind: "anonymous" });
}
// No cookie at all behaves identically.
const none = await fetchPrincipal(origin);
expect(none.response.status).toBe(200);
expect(none.body).toEqual({ kind: "anonymous" });
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
});
describe.sequential("live API — Better Auth logging never leaks operational detail", () => {
it("emits no raw exception, SQL, connection detail, origin header, or stack trace when a real identity database failure occurs", async () => {
const database = await createDisposableDatabase("logging_leak");
await applyCoreMigrations(database.url);
const running = await startMigratedApi(database.url);
const origin = apiOrigin(running);
await signUp(origin, "[email protected]", "correct horse battery staple");
// Force the database itself unreachable out from under the running API pool, then drive a
// request through Better Auth's own database-backed handler (not Ekklesia's own principal
// route) so Better Auth's internal logging — configured in `core/src/identity/runtime.ts` —
// is the thing actually being exercised here, not Core's own error handling. Without
// `onAPIError: { throw: true }`, the pinned release's own `better-call` transport dependency
// would catch this internally and print it raw via an unconditional
// `console.error("# SERVER_ERROR: ", error)` that no `logger` configuration can reach; with
// it, the failure instead propagates out of `auth.handler()` and becomes a sanitized
// `IdentityRuntimeError` at `handleAuthRequest`'s own boundary, and nothing is logged at all.
await dropDatabase(database.name);
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 {
const response = await fetch(`${origin}/api/auth/sign-in/email`, {
body: JSON.stringify({
email: "[email protected]",
password: "correct horse battery staple",
}),
headers: { "content-type": "application/json", origin: dashboardOrigin },
method: "POST",
});
const body = await response.clone().json();
expect(response.status).not.toBe(200);
expect(body).toEqual({ code: "AUTH_REQUEST_FAILED" });
const emitted = [...errorSpy.mock.calls, ...warnSpy.mock.calls, ...logSpy.mock.calls]
.flat()
.map((value) =>
value instanceof Error ? `${value.message}\n${value.stack ?? ""}` : String(value),
)
.join("\n");
// The primary guarantee: onAPIError.throw routes this exact failure out to Core's own
// sanitized boundary before Better Auth (or its own transport dependency) ever gets a
// chance to log anything about it, so nothing is emitted at all.
expect(emitted).toBe("");
// Defense in depth, kept even though the assertion above is currently sufficient: if a
// future change causes something to be logged after all, it must still never contain any
// of this.
expect(emitted).not.toContain(database.url);
expect(emitted).not.toContain(database.name);
expect(emitted.toLowerCase()).not.toContain("password");
expect(emitted.toLowerCase()).not.toContain("stack");
expect(emitted).not.toMatch(/select|insert|update|delete|drizzle/iu);
expect(emitted).not.toContain(dashboardOrigin);
expect(emitted).not.toContain(identityBaseUrl);
} finally {
errorSpy.mockRestore();
warnSpy.mockRestore();
logSpy.mockRestore();
await running.shutdown().catch(() => undefined);
}
});
});
describe.sequential("live API — platform owner and delegated grant management", () => {
let database: { name: string; url: string };
let running: RunningApi;
let origin: string;
let ownerUserId: string;
let ownerCookie: string;
let managerUserId: string;
let managerCookie: string;
let createdGrantId: string;
const ownerEmail = "[email protected]";
const managerEmail = "[email protected]";
const password = "correct horse battery staple";
// This scenario intentionally proves out the explicit-grant lifecycle (create, list, use,
// revoke) against Core's own grant-management permission rather than a module's — a module
// permission would exercise the exact same route and evaluator code, adding coverage breadth
// without coverage depth. The "live API — installed module permission end-to-end" scenario
// below is where a real installed module's declared permission is proven end-to-end instead.
const testPermission = "core.authorization.grants.manage";
// Well-formed (passes `PermissionId` shape validation) but never registered — used to prove the
// live route rejects it even for a caller who otherwise has full grant-management authority.
const unregisteredPermission = "calendar.events.read";
it("starts a freshly migrated API and signs up the eventual owner and eventual delegated manager", async () => {
database = await createDisposableDatabase("authz_http");
await applyCoreMigrations(database.url);
running = await startMigratedApi(database.url);
origin = apiOrigin(running);
const ownerSignUp = await signUp(origin, ownerEmail, password);
ownerUserId = ownerSignUp.body.user.id;
const managerSignUp = await signUp(origin, managerEmail, password);
managerUserId = managerSignUp.body.user.id;
const ownerSignIn = await signIn(origin, ownerEmail, password);
expect(ownerSignIn.cookiePair).toBeDefined();
ownerCookie = ownerSignIn.cookiePair as string;
});
it("denies grants access to an anonymous caller", async () => {
const response = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`);
expect(response.status).toBe(403);
expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" });
});
it("denies grants access to an authenticated caller with no owner authority and no grant", async () => {
const managerSignInBefore = await signIn(origin, managerEmail, password);
const response = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`, {
headers: { cookie: managerSignInBefore.cookiePair as string },
});
expect(response.status).toBe(403);
});
it("bootstraps the owner via Core's real bootstrap operation, then confirms owner authority over the HTTP grants API", async () => {
// Exactly what `pnpm authorization:bootstrap-owner` would do, driven directly against the
// same disposable database the running API already uses — no second process needed to prove
// the HTTP layer honors real platform-owner state.
const { close, db } = createAuthorizationDrizzleDatabase(database.url);
try {
await bootstrapPlatformOwner(db, { userId: parseEkklesiaUserId(ownerUserId) });
} finally {
await close();
}
const response = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`, {
headers: { cookie: ownerCookie },
});
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ grants: [], nextCursor: null });
});
it("lets the owner delegate grants.manage to another account through the explicit-grant HTTP API", async () => {
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({
permission: "core.authorization.grants.manage",
subjectUserId: managerUserId,
}),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
const body = (await response.json()) as { grant: { sourceKind: string; userId: string } };
expect(response.status).toBe(201);
expect(body.grant.sourceKind).toBe("explicit");
expect(body.grant.userId).toBe(managerUserId);
});
it("lets the delegated manager — who is not a platform owner — manage another user's grants", async () => {
const managerSignIn = await signIn(origin, managerEmail, password);
expect(managerSignIn.cookiePair).toBeDefined();
managerCookie = managerSignIn.cookiePair as string;
const response = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`, {
headers: { cookie: managerCookie },
});
expect(response.status).toBe(200);
});
it("rejects a well-formed but unregistered permission even from a caller with full grant-management authority, and writes no grant or audit row", async () => {
const admin = await openAdministrativeClient(database.url);
try {
const grantCountBefore = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_grant" where permission_id = $1`,
[unregisteredPermission],
);
const auditCountBefore = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_authorization_audit" where permission_id = $1`,
[unregisteredPermission],
);
expect(grantCountBefore[0]?.count).toBe("0");
expect(auditCountBefore[0]?.count).toBe("0");
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({
permission: unregisteredPermission,
subjectUserId: managerUserId,
}),
headers: {
"content-type": "application/json",
cookie: managerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(response.status).toBe(403);
expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" });
const grantCountAfter = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_grant" where permission_id = $1`,
[unregisteredPermission],
);
const auditCountAfter = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_authorization_audit" where permission_id = $1`,
[unregisteredPermission],
);
expect(grantCountAfter[0]?.count).toBe("0");
expect(auditCountAfter[0]?.count).toBe("0");
} finally {
await safeEnd(admin);
}
});
it("creates an explicit grant via HTTP, deriving sourceKind and actor server-side, and records an atomic audit entry", async () => {
const response = await fetch(`${origin}/api/v1/grants`, {
// A subject with no equivalent grant yet — the manager already holds an equivalent
// `grants.manage` grant from the delegation scenario above, which the new duplicate
// invariant would (correctly) refuse. Unscoped: the generic HTTP surface creates only
// unscoped explicit grants, since a scope it accepted could never authorize anything.
body: JSON.stringify({
permission: testPermission,
subjectUserId: ownerUserId,
}),
headers: {
"content-type": "application/json",
cookie: managerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
const body = (await response.json()) as {
grant: { id: string; sourceKind: string; userId: string };
};
expect(response.status).toBe(201);
expect(body.grant.sourceKind).toBe("explicit");
expect(body.grant.userId).toBe(ownerUserId);
createdGrantId = body.grant.id;
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ action: string; actor_id: string | null }>(
admin,
`select action, actor_id from "ekklesia_core_authorization_audit" where grant_id = $1`,
[createdGrantId],
);
expect(rows).toEqual([{ action: "grant.create", actor_id: managerUserId }]);
} finally {
await safeEnd(admin);
}
});
it("lists the explicit grant via HTTP, bounded and with an explicit next-page cursor field", async () => {
const response = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`, {
headers: { cookie: ownerCookie },
});
const body = (await response.json()) as {
grants: Array<{ id: string; scope: string | null }>;
nextCursor: string | null;
};
expect(response.status).toBe(200);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(body.grants.some((grant) => grant.id === createdGrantId)).toBe(true);
// Created through the generic HTTP surface, so it is unscoped.
expect(body.grants.find((grant) => grant.id === createdGrantId)?.scope).toBeNull();
expect(body.nextCursor).toBeNull();
});
it("rejects a create-grant request carrying disallowed fields such as actor or sourceKind, even alongside otherwise-valid fields", async () => {
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({
actor: { type: "system" },
permission: testPermission,
sourceKind: "relationship",
subjectUserId: managerUserId,
}),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(response.status).toBe(400);
});
it("rejects a mutation request with no trusted Origin header, and never returns a permissive CORS header", async () => {
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({ permission: testPermission, subjectUserId: managerUserId }),
headers: { "content-type": "application/json", cookie: ownerCookie },
method: "POST",
});
expect(response.status).toBe(403);
expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" });
expect(response.headers.get("access-control-allow-origin")).toBeNull();
});
it("rejects a mutation request from an untrusted Origin the same way", async () => {
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({ permission: testPermission, subjectUserId: managerUserId }),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: untrustedOrigin,
},
method: "POST",
});
expect(response.status).toBe(403);
expect(response.headers.get("access-control-allow-origin")).toBeNull();
});
it("revokes the explicit grant via HTTP and records the revocation audit entry", async () => {
const response = await fetch(`${origin}/api/v1/grants/${createdGrantId}/revoke`, {
headers: { cookie: ownerCookie, origin: dashboardOrigin },
method: "POST",
});
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ success: true });
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ action: string }>(
admin,
`select action from "ekklesia_core_authorization_audit" where grant_id = $1 order by created_at`,
[createdGrantId],
);
expect(rows.map((row) => row.action)).toEqual(["grant.create", "grant.revoke"]);
} finally {
await safeEnd(admin);
}
});
it("never exposes a relationship-derived grant through the generic list, create, or revoke HTTP API", async () => {
// Seeded directly through the store, simulating a module's own business operation — never
// through HTTP, which has no way to express sourceKind at all.
const { close, db } = createAuthorizationDrizzleDatabase(database.url);
let relationshipGrantId: string;
try {
const store = createAuthorizationStore(db);
const relationshipGrant = await store.createGrant({
actor: SYSTEM_ACTOR,
permission: parsePermissionId("roster.volunteer.read"),
sourceKind: "relationship",
sourceRef: "roster:volunteer:99",
userId: parseEkklesiaUserId(managerUserId),
});
relationshipGrantId = relationshipGrant.id;
} finally {
await close();
}
const listResponse = await fetch(`${origin}/api/v1/grants?userId=${managerUserId}`, {
headers: { cookie: ownerCookie },
});
const listBody = (await listResponse.json()) as { grants: Array<{ id: string }> };
expect(listBody.grants.some((grant) => grant.id === relationshipGrantId)).toBe(false);
const revokeResponse = await fetch(`${origin}/api/v1/grants/${relationshipGrantId}/revoke`, {
headers: { cookie: ownerCookie, origin: dashboardOrigin },
method: "POST",
});
expect(revokeResponse.status).toBe(404);
expect(await revokeResponse.json()).toEqual({ code: "AUTHORIZATION_GRANT_NOT_FOUND" });
});
it("shuts down cleanly, closing the one shared pool", async () => {
await running.shutdown();
const remainingConnections = await activeConnectionCount(database.name);
expect(remainingConnections).toBe(0);
await dropDatabase(database.name);
});
});
describe.sequential("live API — authorization infrastructure failure is never downgraded to a false denial", () => {
it("returns a sanitized 503, not a 403, when the database becomes unreachable mid-session", async () => {
const database = await createDisposableDatabase("authz_infra_failure");
await applyCoreMigrations(database.url);
const running = await startMigratedApi(database.url);
const origin = apiOrigin(running);
await signUp(origin, "[email protected]", "correct horse battery staple");
const { cookiePair } = await signIn(
origin,
"[email protected]",
"correct horse battery staple",
);
expect(cookiePair).toBeDefined();
// Force the database itself unreachable out from under the running API pool — the same
// technique the identity infra-failure test above uses, applied to a grants route this time.
await dropDatabase(database.name);
const response = await fetch(`${origin}/api/v1/grants?userId=does-not-matter`, {
headers: { cookie: cookiePair as string },
});
expect(response.status).toBe(503);
const body = (await response.json()) as { code: string };
expect(["PRINCIPAL_RESOLUTION_FAILED", "AUTHORIZATION_EVALUATION_FAILED"]).toContain(body.code);
await running.shutdown().catch(() => undefined);
});
});
/**
* Pass 3's explicit-grant invariants, exercised end-to-end through a real authorized
* grants-management session against real PostgreSQL — the layer where a validation decision that
* "should" happen before the database either does or does not.
*/
describe.sequential("live API — explicit-grant invariants over real HTTP", () => {
let database: { name: string; url: string };
let running: RunningApi;
let origin: string;
let ownerUserId: string;
let subjectUserId: string;
let ownerCookie: string;
const password = "correct horse battery staple";
const managePermission = "core.authorization.grants.manage";
async function post(path: string, body?: unknown): Promise<Response> {
return await fetch(`${origin}${path}`, {
...(body === undefined ? {} : { body: JSON.stringify(body) }),
headers: {
...(body === undefined ? {} : { "content-type": "application/json" }),
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
}
async function list(query: string): Promise<{ body: unknown; response: Response }> {
const response = await fetch(`${origin}/api/v1/grants?${query}`, {
headers: { cookie: ownerCookie },
});
return { body: await response.json(), response };
}
it("starts a freshly migrated API with a bootstrapped owner and a separate grant subject", async () => {
database = await createDisposableDatabase("grant_invariants");
await applyCoreMigrations(database.url);
running = await startMigratedApi(database.url);
origin = apiOrigin(running);
ownerUserId = (await signUp(origin, "[email protected]", password)).body.user.id;
subjectUserId = (await signUp(origin, "[email protected]", password)).body.user
.id;
const { close, db } = createAuthorizationDrizzleDatabase(database.url);
try {
await bootstrapPlatformOwner(db, { userId: parseEkklesiaUserId(ownerUserId) });
} finally {
await close();
}
const ownerSignIn = await signIn(origin, "[email protected]", password);
expect(ownerSignIn.cookiePair).toBeDefined();
ownerCookie = ownerSignIn.cookiePair as string;
});
it("rejects a malformed non-UUID grant id with a sanitized 400, never a database failure", async () => {
for (const malformed of [
"not-a-uuid",
"0B36B1B6-9A29-4C4E-9E2D-4C0E4C5E6F70",
"0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f7",
]) {
const response = await post(`/api/v1/grants/${malformed}/revoke`);
const raw = await response.text();
// Before Pass 3 this reached PostgreSQL's own UUID comparison and returned 503.
expect(response.status, malformed).toBe(400);
expect(JSON.parse(raw)).toEqual({ code: "INVALID_REQUEST" });
expect(response.headers.get("cache-control")).toBe("no-store");
expect(raw).not.toContain(malformed);
}
});
it("returns the sanitized 404 for a canonical but unknown grant id", async () => {
const response = await post("/api/v1/grants/00000000-0000-4000-8000-000000000000/revoke");
expect(response.status).toBe(404);
expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_NOT_FOUND" });
});
it("rejects a scoped create body with a sanitized 400 and writes nothing", async () => {
const response = await post("/api/v1/grants", {
permission: managePermission,
scope: "calendar-event:1",
subjectUserId,
});
expect(response.status).toBe(400);
expect(await response.json()).toEqual({ code: "INVALID_REQUEST" });
const admin = await openAdministrativeClient(database.url);
try {
expect(
await safeQuery(admin, `select id from "ekklesia_core_grant" where user_id = $1`, [
subjectUserId,
]),
).toHaveLength(0);
} finally {
await safeEnd(admin);
}
});
it("rejects normalized-impossible, non-canonical, and past expirations with a sanitized 400", async () => {
for (const expiresAt of [
"2026-02-31T00:00:00.000Z",
"March 5, 2027",
"2027-01-01",
"2027-01-01T00:00:00Z",
"2027-01-01T00:00:00.000+02:00",
"2020-01-01T00:00:00.000Z",
]) {
const response = await post("/api/v1/grants", {
expiresAt,
permission: managePermission,
subjectUserId,
});
expect(response.status, expiresAt).toBe(400);
expect(await response.json()).toEqual({ code: "INVALID_REQUEST" });
}
});
it("creates one grant, then answers an equivalent creation with a sanitized 409 and no second row", async () => {
const created = await post("/api/v1/grants", {
permission: managePermission,
subjectUserId,
});
expect(created.status).toBe(201);
const duplicate = await post("/api/v1/grants", {
permission: managePermission,
subjectUserId,
});
const raw = await duplicate.text();
expect(duplicate.status).toBe(409);
expect(JSON.parse(raw)).toEqual({ code: "AUTHORIZATION_GRANT_ALREADY_EXISTS" });
expect(duplicate.headers.get("cache-control")).toBe("no-store");
// No existing grant id, actor, scope, source reference, SQL, or driver text leaks.
expect(raw).not.toContain(ownerUserId);
expect(raw).not.toContain(subjectUserId);
expect(raw).not.toContain("ekklesia_core_grant");
// A differing expiration is not a different grant either.
const withExpiry = await post("/api/v1/grants", {
expiresAt: "2099-01-01T00:00:00.000Z",
permission: managePermission,
subjectUserId,
});
expect(withExpiry.status).toBe(409);
const admin = await openAdministrativeClient(database.url);
try {
expect(
await safeQuery(
admin,
`select id from "ekklesia_core_grant" where user_id = $1 and revoked_at is null`,
[subjectUserId],
),
).toHaveLength(1);
} finally {
await safeEnd(admin);
}
});
it("paginates deterministically across at least three pages and ends with a null cursor", async () => {
// Seeded through the test-only administrative client so the page count is deterministic and
// independent of how many grants the scenarios above happened to create.
const admin = await openAdministrativeClient(database.url);
try {
await safeQuery(
admin,
`insert into "ekklesia_core_grant"
(user_id, permission_id, scope, source_kind, source_ref, created_by_actor_type, created_by_actor_id, created_at)
select $1, 'calendar.events.page' || generation, null, 'explicit', null, 'user', $2,
timestamptz '2026-01-01 00:00:00Z'
from generate_series(1, 6) as generation`,
[subjectUserId, ownerUserId],
);
await safeQuery(
admin,
`insert into "ekklesia_core_grant"
(user_id, permission_id, scope, source_kind, source_ref, created_by_actor_type, created_by_actor_id)
values ($1, 'roster.volunteer.read', null, 'relationship', 'roster:1', 'user', $2)`,
[subjectUserId, ownerUserId],
);
} finally {
await safeEnd(admin);
}
const seen: string[] = [];
let cursor: string | null = null;
let pages = 0;
for (;;) {
const query = `userId=${subjectUserId}&limit=3${
cursor === null ? "" : `&cursor=${encodeURIComponent(cursor)}`
}`;
const { body, response } = await list(query);
const page = body as {
grants: Array<{ id: string; sourceKind: string }>;
nextCursor: string | null;
};
expect(response.status).toBe(200);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(page.grants.length).toBeLessThanOrEqual(3);
// Relationship grants never appear on any page.
expect(page.grants.every((grant) => grant.sourceKind === "explicit")).toBe(true);
seen.push(...page.grants.map((grant) => grant.id));
pages += 1;
if (page.nextCursor === null) {
break;
}
cursor = page.nextCursor;
expect(pages).toBeLessThan(10);
}
expect(pages).toBeGreaterThanOrEqual(3);
expect(seen).toHaveLength(7);
expect(new Set(seen).size).toBe(seen.length);
});
it("rejects an out-of-range limit and a foreign or malformed cursor with a sanitized 400", async () => {
for (const query of [
`userId=${subjectUserId}&limit=0`,
`userId=${subjectUserId}&limit=101`,
`userId=${subjectUserId}&limit=01`,
`userId=${subjectUserId}&limit=1&limit=2`,
`userId=${subjectUserId}&unknown=1`,
`userId=${subjectUserId}&cursor=!!!`,
// A cursor minted for a different subject must not be replayable against this one.
`userId=${subjectUserId}&cursor=${encodeURIComponent(
Buffer.from(
JSON.stringify({
g: "00000000-0000-4000-8000-000000000000",
t: "2026-01-01T00:00:00.000Z",
u: ownerUserId,
v: 1,
}),
"utf8",
).toString("base64url"),
)}`,
]) {
const { body, response } = await list(query);
expect(response.status, query).toBe(400);
expect(body).toEqual({ code: "INVALID_REQUEST" });
}
});
it("revokes every unrevoked equivalent through one HTTP revocation, leaving nothing authorizing", async () => {
const permission = "calendar.events.legacydupe";
const admin = await openAdministrativeClient(database.url);
let ids: string[] = [];
try {
// Legacy duplicates the pre-Pass-3 store could produce, planted directly because the
// production store now refuses to create them.
const rows = await safeQuery<{ id: string }>(
admin,
`insert into "ekklesia_core_grant"
(user_id, permission_id, scope, source_kind, source_ref, created_by_actor_type, created_by_actor_id)
values ($1, $2, null, 'explicit', null, 'user', $3),
($1, $2, null, 'explicit', null, 'user', $3),
($1, $2, null, 'explicit', null, 'user', $3)
returning id`,
[subjectUserId, permission, ownerUserId],
);
ids = rows.map((row) => row.id);
} finally {
await safeEnd(admin);
}
const response = await post(`/api/v1/grants/${ids[0]}/revoke`);
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ success: true });
const inspector = await openAdministrativeClient(database.url);
try {
const remaining = await safeQuery(
inspector,
`select id from "ekklesia_core_grant"
where user_id = $1 and permission_id = $2 and revoked_at is null`,
[subjectUserId, permission],
);
expect(remaining).toHaveLength(0);
const audit = await safeQuery<{ grant_id: string }>(
inspector,
`select grant_id from "ekklesia_core_authorization_audit"
where action = 'grant.revoke' and grant_id = any($1::uuid[])`,
[ids],
);
expect([...new Set(audit.map((row) => row.grant_id))].sort()).toEqual([...ids].sort());
} finally {
await safeEnd(inspector);
}
});
it("shuts down cleanly", async () => {
await running.shutdown();
await dropDatabase(database.name);
});
});
describe.sequential("live API — installed module permission end-to-end", () => {
let database: { name: string; url: string };
let modulesDirectory: string;
let running: RunningApi;
let origin: string;
let ownerUserId: string;
let ownerCookie: string;
let createdGrantId: string;
const ownerEmail = "[email protected]";
const password = "correct horse battery staple";
const modulePermission = "calendar.events.read";
const absentPermission = "scheduler.jobs.read";
const recorder = createEventRecorder();
it("recognizes a real installed module and passes permission composition in Doctor, without executing its server entrypoint", async () => {
modulesDirectory = await createFixtureModulesDirectory();
await createFixtureModule(modulesDirectory, "calendar", recorder, {
permissions: [
{
baseline: "restricted",
description: "Read calendar events.",
id: modulePermission,
},
],
});
const report = await diagnoseEkklesia({
moduleDiscovery: {
moduleSdkVersion: "0.1.0",
modulesDirectory,
platformVersion: "0.1.0",
},
runtimeRange: ">=0.0.0",
runtimeVersion: process.versions.node,
});
// Only the module-discovery and permission-composition checks are asserted here — this
// fixture's temporary directory has no `core/drizzle` sibling for migrations discovery to
// find, which is unrelated to what this scenario proves and would otherwise make the check
// fail for a reason that has nothing to do with permission composition.
const moduleCheck = report.checks.find((entry) => entry.id === "modules.discovery");
const permissionsCheck = report.checks.find((entry) => entry.id === "modules.permissions");
expect(moduleCheck).toMatchObject({ status: "pass" });
expect(moduleCheck?.details).toEqual(["calendar 0.1.0"]);
expect(permissionsCheck).toMatchObject({
// Core's own five (`core.authorization.grants.manage`, `core.modules.ui.read`,
// `core.dashboard.navigation.manage`, `core.modules.lifecycle.manage`,
// `core.modules.configuration.manage`) plus the fixture module's one.
details: ["6 permissions registered"],
status: "pass",
});
// Doctor and permission composition read only the manifest — the fixture's register/start
// markers must remain entirely unset.
expect(recorder.events).toEqual([]);
});
it("starts a freshly migrated API that discovers the fixture module, imports and starts its runtime exactly once, and mounts its route", async () => {
database = await createDisposableDatabase("authz_module");
await applyCoreMigrations(database.url);
running = await startMigratedApi(database.url, modulesDirectory);
origin = apiOrigin(running);
// Unlike Doctor above, a successful production startup does deliberately import, register,
// and start the module's runtime — exactly once each.
expect(recorder.events).toEqual(["calendar:register", "calendar:start"]);
const ownerSignUp = await signUp(origin, ownerEmail, password);
ownerUserId = ownerSignUp.body.user.id;
const ownerSignIn = await signIn(origin, ownerEmail, password);
expect(ownerSignIn.cookiePair).toBeDefined();
ownerCookie = ownerSignIn.cookiePair as string;
// Existing identity HTTP behavior — sign-up, sign-in, principal resolution — is unaffected by
// module permission composition running before it.
const { body: principal } = await fetchPrincipal(origin, { cookie: ownerCookie });
expect(principal).toEqual({ kind: "authenticated", userId: ownerUserId });
});
it("lets an authorized platform owner create an explicit grant for the installed module's permission, with an atomic audit entry using its declared id", async () => {
const { close, db } = createAuthorizationDrizzleDatabase(database.url);
try {
await bootstrapPlatformOwner(db, { userId: parseEkklesiaUserId(ownerUserId) });
} finally {
await close();
}
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({ permission: modulePermission, subjectUserId: ownerUserId }),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
const body = (await response.json()) as {
grant: { id: string; permission: string; sourceKind: string };
};
expect(response.status).toBe(201);
expect(body.grant.permission).toBe(modulePermission);
expect(body.grant.sourceKind).toBe("explicit");
createdGrantId = body.grant.id;
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ action: string; permission_id: string }>(
admin,
`select action, permission_id from "ekklesia_core_authorization_audit" where grant_id = $1`,
[createdGrantId],
);
expect(rows).toEqual([{ action: "grant.create", permission_id: modulePermission }]);
} finally {
await safeEnd(admin);
}
});
it("denies the module's own mounted route to an anonymous caller, then serves it to the newly granted owner with the decoded path parameter and authenticated principal", async () => {
const anonymousResponse = await fetch(`${origin}/api/v1/modules/calendar/events/evt-42`);
expect(anonymousResponse.status).toBe(403);
expect(await anonymousResponse.json()).toEqual({ code: "AUTHORIZATION_DENIED" });
const grantedResponse = await fetch(`${origin}/api/v1/modules/calendar/events/evt-42`, {
headers: { cookie: ownerCookie },
});
expect(grantedResponse.status).toBe(200);
const body = (await grantedResponse.json()) as {
eventId: string;
principal: { kind: string; userId: string };
};
expect(body.eventId).toBe("evt-42");
expect(body.principal).toEqual({ kind: "authenticated", userId: ownerUserId });
});
it("requires a trusted Origin for the module's own state-changing route, exactly like the platform's own mutation routes", async () => {
const noOriginResponse = await fetch(`${origin}/api/v1/modules/calendar/events`, {
body: JSON.stringify({}),
headers: { "content-type": "application/json", cookie: ownerCookie },
method: "POST",
});
expect(noOriginResponse.status).toBe(403);
expect(await noOriginResponse.json()).toEqual({ code: "UNTRUSTED_ORIGIN" });
const untrustedResponse = await fetch(`${origin}/api/v1/modules/calendar/events`, {
body: JSON.stringify({}),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: untrustedOrigin,
},
method: "POST",
});
expect(untrustedResponse.status).toBe(403);
const trustedResponse = await fetch(`${origin}/api/v1/modules/calendar/events`, {
body: JSON.stringify({}),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(trustedResponse.status).toBe(201);
expect(await trustedResponse.json()).toMatchObject({ created: true });
});
it("rejects a well-formed permission absent from the composed registry, writing no grant or audit row", async () => {
const admin = await openAdministrativeClient(database.url);
try {
const before = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_grant" where permission_id = $1`,
[absentPermission],
);
expect(before[0]?.count).toBe("0");
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({ permission: absentPermission, subjectUserId: ownerUserId }),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(response.status).toBe(403);
expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" });
const after = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_grant" where permission_id = $1`,
[absentPermission],
);
const audit = await safeQuery<{ count: string }>(
admin,
`select count(*)::text as count from "ekklesia_core_authorization_audit" where permission_id = $1`,
[absentPermission],
);
expect(after[0]?.count).toBe("0");
expect(audit[0]?.count).toBe("0");
} finally {
await safeEnd(admin);
}
});
it("shuts down cleanly, then restarting with the module absent rejects new grants for its former permission, returns NOT_FOUND for its former route, while the historical grant remains listable", async () => {
await running.shutdown();
const remainingConnections = await activeConnectionCount(database.name);
expect(remainingConnections).toBe(0);
// Exactly one stop, matching the exactly-one register/start already asserted above.
expect(recorder.events.filter((event) => event === "calendar:stop")).toHaveLength(1);
const emptyModulesDirectory = await createFixtureModulesDirectory();
running = await startMigratedApi(database.url, emptyModulesDirectory);
origin = apiOrigin(running);
// The module's own route is unmounted along with the module itself.
const removedRouteResponse = await fetch(`${origin}/api/v1/modules/calendar/events/evt-42`, {
headers: { cookie: ownerCookie },
});
expect(removedRouteResponse.status).toBe(404);
expect(await removedRouteResponse.json()).toEqual({ code: "NOT_FOUND" });
// The owner's existing session survives the restart (sessions live in the database, not the
// process), so the same cookie proves ordinary identity behavior is unaffected here too.
const listResponse = await fetch(`${origin}/api/v1/grants?userId=${ownerUserId}`, {
headers: { cookie: ownerCookie },
});
const listBody = (await listResponse.json()) as {
grants: Array<{ id: string; permission: string }>;
};
expect(listResponse.status).toBe(200);
expect(listBody.grants.some((grant) => grant.id === createdGrantId)).toBe(true);
const response = await fetch(`${origin}/api/v1/grants`, {
body: JSON.stringify({ permission: modulePermission, subjectUserId: ownerUserId }),
headers: {
"content-type": "application/json",
cookie: ownerCookie,
origin: dashboardOrigin,
},
method: "POST",
});
expect(response.status).toBe(403);
expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" });
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ id: string }>(
admin,
`select id from "ekklesia_core_grant" where id = $1 and revoked_at is null`,
[createdGrantId],
);
expect(rows).toHaveLength(1);
} finally {
await safeEnd(admin);
}
await running.shutdown();
await dropDatabase(database.name);
});
});
/**
* The publication route over a real socket, against a real started API: real Node HTTP, real
* headers, real bytes. The unit suite (`module-ui-routes.test.ts`) covers the security matrix in
* depth; this proves the same route behaves identically once it is behind the actual server rather
* than `app.request`.
*/
describe.sequential("live API — published module UI artifact", () => {
const uiArtifact = `export default {
id: "signage",
version: "2.0.1",
mount(container, context) {
container.textContent = context.moduleId;
return () => {
container.textContent = "";
};
},
};
`;
it("serves exactly the declared artifact, and nothing else in the module package", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
await createUiModule(modulesDirectory, "signage", "2.0.1", uiArtifact);
const database = await createDisposableDatabase("module_ui");
await applyCoreMigrations(database.url);
const running = await startMigratedApi(database.url, modulesDirectory);
try {
const origin = apiOrigin(running);
const url = `${origin}${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}`;
const response = await fetch(url);
const body = await response.text();
expect(response.status).toBe(200);
expect(body).toBe(uiArtifact);
expect(response.headers.get("content-type")).toBe(MODULE_UI_CONTENT_TYPE);
expect(response.headers.get("cache-control")).toBe(MODULE_UI_CACHE_CONTROL);
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
const etag = response.headers.get("etag");
expect(etag).toMatch(/^"[A-Za-z0-9_-]+"$/u);
// Revalidation over the wire, with the validator the server itself just issued.
const revalidated = await fetch(url, { headers: { "if-none-match": etag as string } });
expect(revalidated.status).toBe(304);
expect(await revalidated.text()).toBe("");
// The exact-version rule, and the neighbouring files, over the same real transport.
const unreachable = [
moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.0" }),
moduleUiArtifactUrl({ moduleId: "announcements", version: "2.0.1" }),
`${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}/index.js.map`,
`${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}/chunk.js`,
`${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}/.env`,
`${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}.map`,
];
for (const candidate of unreachable) {
const refused = await fetch(`${origin}${candidate}`);
const refusedBody = await refused.text();
expect(refused.status, candidate).toBe(404);
expect(refusedBody, candidate).not.toContain("secret");
expect(refusedBody, candidate).not.toContain("mount");
}
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("answers a trusted browser origin with CORS, and an untrusted one without", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
await createUiModule(modulesDirectory, "signage", "2.0.1", uiArtifact);
const database = await createDisposableDatabase("module_ui_cors");
await applyCoreMigrations(database.url);
const running = await startMigratedApi(database.url, modulesDirectory);
try {
const origin = apiOrigin(running);
const url = `${origin}${moduleUiArtifactUrl({ moduleId: "signage", version: "2.0.1" })}`;
// A cross-origin browser `import()` of this URL needs exactly this header, which is why the
// route lives inside the application's one CORS-governed `/api/v1/*` space.
const trusted = await fetch(url, { headers: { origin: dashboardOrigin } });
expect(trusted.status).toBe(200);
expect(trusted.headers.get("access-control-allow-origin")).toBe(dashboardOrigin);
expect(trusted.headers.get("vary")?.toLowerCase()).toContain("origin");
const untrusted = await fetch(url, { headers: { origin: untrustedOrigin } });
// The bytes are not confidential (ADR 0025), but the platform still never reflects an origin
// it does not trust, so a page on that origin cannot read the response.
expect(untrusted.headers.get("access-control-allow-origin")).toBeNull();
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
});
describe.sequential("live API — public signup is disabled by default", () => {
it("rejects sign-up with a stable sanitized 400 when publicSignupEnabled is not set, creating no user row", async () => {
const database = await createDisposableDatabase("signup_disabled");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url, { publicSignupEnabled: false });
try {
const origin = apiOrigin(running);
const response = await fetch(`${origin}/api/auth/sign-up/email`, {
body: JSON.stringify({
email: "[email protected]",
name: "Blocked",
password: "correct horse battery staple",
}),
headers: { "content-type": "application/json", origin: dashboardOrigin },
method: "POST",
});
const text = await response.text();
expect(response.status).toBe(400);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(text.toLowerCase()).not.toContain("stack");
expect(text).not.toContain(database.url);
const admin = await openAdministrativeClient(database.url);
try {
const rows = await safeQuery<{ count: string }>(
admin,
'select count(*)::text as count from "ekklesia_core_user"',
);
expect(rows[0]?.count).toBe("0");
} finally {
await safeEnd(admin);
}
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("gives an identical rejection whether or not the requested email is already registered", async () => {
const database = await createDisposableDatabase("signup_disabled_enum");
await applyCoreMigrations(database.url);
const existingEmail = "[email protected]";
// Briefly enable signup on this same database to seed one real, pre-existing account, matching
// how a production database might have been seeded before signup was later disabled.
const seedRunning = await startConfiguredApi(database.url, { publicSignupEnabled: true });
try {
const seedSignUp = await signUp(
apiOrigin(seedRunning),
existingEmail,
"correct horse battery",
);
expect(seedSignUp.response.status).toBe(200);
} finally {
await seedRunning.shutdown().catch(() => undefined);
}
const running = await startConfiguredApi(database.url, { publicSignupEnabled: false });
try {
const origin = apiOrigin(running);
const signUpAttempt = (email: string) =>
fetch(`${origin}/api/auth/sign-up/email`, {
body: JSON.stringify({ email, name: "X", password: "correct horse battery staple" }),
headers: { "content-type": "application/json", origin: dashboardOrigin },
method: "POST",
});
const existingResponse = await signUpAttempt(existingEmail);
const newResponse = await signUpAttempt("[email protected]");
expect(existingResponse.status).toBe(400);
expect(newResponse.status).toBe(400);
expect(existingResponse.status).toBe(newResponse.status);
expect(await existingResponse.text()).toBe(await newResponse.text());
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("keeps sign-in and existing-session behavior fully functional for a pre-existing account while signup stays disabled", async () => {
const database = await createDisposableDatabase("signup_disabled_signin");
await applyCoreMigrations(database.url);
const email = "[email protected]";
const password = "correct horse battery staple";
const seedRunning = await startConfiguredApi(database.url, { publicSignupEnabled: true });
try {
const seedSignUp = await signUp(apiOrigin(seedRunning), email, password);
expect(seedSignUp.response.status).toBe(200);
} finally {
await seedRunning.shutdown().catch(() => undefined);
}
const running = await startConfiguredApi(database.url, { publicSignupEnabled: false });
try {
const origin = apiOrigin(running);
const signInResult = await signIn(origin, email, password);
expect(signInResult.response.status).toBe(200);
expect(signInResult.cookiePair).toBeDefined();
const { body } = await fetchPrincipal(origin, { cookie: signInResult.cookiePair as string });
expect(body).toMatchObject({ kind: "authenticated" });
const blockedSignUp = await fetch(`${origin}/api/auth/sign-up/email`, {
body: JSON.stringify({ email: "[email protected]", name: "X", password }),
headers: { "content-type": "application/json", origin: dashboardOrigin },
method: "POST",
});
expect(blockedSignUp.status).toBe(400);
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
});
describe.sequential("live API — client-address resolution and auth rate-limit identity", () => {
let database: { name: string; url: string };
let directModeApi: RunningApi;
let directOrigin: string;
it("starts a direct-mode API (the safe default) against a freshly migrated database, with Better Auth's real unmodified rate-limit rule", async () => {
database = await createDisposableDatabase("client_address");
await applyCoreMigrations(database.url);
directModeApi = await startConfiguredApi(database.url, { rateLimitOverride: undefined });
directOrigin = apiOrigin(directModeApi);
expect(directModeApi.server.listening).toBe(true);
});
it("reproduces the original vulnerability's exact shape and proves it is closed: rotating spoofed X-Forwarded-For values never bypass the limiter in direct mode", async () => {
// Before this pass, Better Auth trusted a caller-controlled X-Forwarded-For directly: each
// distinct spoofed value looked like an unrelated client, so a caller could send an unlimited
// number of sign-in attempts merely by rotating the header on every request. Direct mode now
// resolves identity from the real socket peer only — the header is never consulted.
const peer = "127.0.0.21";
const first = await rawSignInAttempt(directOrigin, {
forwardedFor: "1.1.1.1",
localAddress: peer,
});
const second = await rawSignInAttempt(directOrigin, {
forwardedFor: "2.2.2.2",
localAddress: peer,
});
const third = await rawSignInAttempt(directOrigin, {
forwardedFor: "3.3.3.3",
localAddress: peer,
});
expect([first.status, second.status, third.status]).toEqual([401, 401, 401]);
// The fabricated value on this fourth request is new *again* — under the pre-fix behavior this
// would still look like a brand-new, never-before-seen client and would succeed. It must not.
const fourth = await rawSignInAttempt(directOrigin, {
forwardedFor: "4.4.4.4",
localAddress: peer,
});
expect(fourth.status).toBe(429);
// The limiter's own existing response semantics are preserved unmodified.
expect(fourth.headers["x-retry-after"]).toBeDefined();
expect(JSON.parse(fourth.body)).toMatchObject({ message: expect.any(String) });
// A request from the identical real peer with *no* forwarded header at all is equally blocked
// — proving the header was never what determined the bucket, in either direction.
const fifth = await rawSignInAttempt(directOrigin, { localAddress: peer });
expect(fifth.status).toBe(429);
});
it("gives distinct real peer addresses independent rate-limit buckets", async () => {
const peerA = "127.0.0.22";
const peerB = "127.0.0.23";
expect((await rawSignInAttempt(directOrigin, { localAddress: peerA })).status).toBe(401);
expect((await rawSignInAttempt(directOrigin, { localAddress: peerA })).status).toBe(401);
expect((await rawSignInAttempt(directOrigin, { localAddress: peerA })).status).toBe(401);
expect((await rawSignInAttempt(directOrigin, { localAddress: peerA })).status).toBe(429);
// An unrelated real peer's budget is completely untouched by peerA's exhaustion.
expect((await rawSignInAttempt(directOrigin, { localAddress: peerB })).status).toBe(401);
});
it("shuts down the direct-mode API", async () => {
await directModeApi.shutdown();
});
let proxyModeApi: RunningApi;
let proxyOrigin: string;
const trustedProxyPeer = "127.0.0.24";
it("starts a second API in explicit trusted-proxy mode against the same migrated database", async () => {
proxyModeApi = await startConfiguredApi(database.url, {
clientAddress: {
header: "x-forwarded-for",
mode: "trusted-proxy",
trustedProxies: [trustedProxyPeer],
},
rateLimitOverride: undefined,
});
proxyOrigin = apiOrigin(proxyModeApi);
});
it("honors a forwarded address only when it comes from the explicitly trusted proxy peer", async () => {
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.50",
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.50",
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.50",
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.50",
localAddress: trustedProxyPeer,
})
).status,
).toBe(429);
// A *different* forwarded client from the same trusted proxy gets its own independent bucket.
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.51",
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
});
it("ignores a forwarded address claimed by an untrusted peer, using that peer's own real address instead", async () => {
// 203.0.113.50 is already exhausted above when legitimately forwarded by the trusted proxy —
// an untrusted peer merely claiming to *be* that address must not inherit its exhausted bucket,
// and must not be able to choose an arbitrary bucket of its own by forging the header either.
const untrustedPeer = "127.0.0.25";
const response = await rawSignInAttempt(proxyOrigin, {
forwardedFor: "203.0.113.50",
localAddress: untrustedPeer,
});
expect(response.status).toBe(401);
});
it("walks a chained forwarded header right-to-left, skipping the trusted-proxy hop to find the genuine client", async () => {
const chained = `203.0.113.60, ${trustedProxyPeer}`;
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: chained,
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: chained,
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: chained,
localAddress: trustedProxyPeer,
})
).status,
).toBe(401);
expect(
(
await rawSignInAttempt(proxyOrigin, {
forwardedFor: chained,
localAddress: trustedProxyPeer,
})
).status,
).toBe(429);
});
it("fails safely with a sanitized 503 on a malformed forwarded chain from a trusted proxy, never falling back to a shared bucket", async () => {
const response = await rawSignInAttempt(proxyOrigin, {
forwardedFor: "not-an-ip",
localAddress: trustedProxyPeer,
});
expect(response.status).toBe(503);
expect(JSON.parse(response.body)).toEqual({ code: "CLIENT_ADDRESS_UNRESOLVED" });
expect(response.headers["cache-control"]).toBe("no-store");
});
it("shuts down the trusted-proxy-mode API and drops the shared database", async () => {
await proxyModeApi.shutdown();
await dropDatabase(database.name);
});
});
describe.sequential("live API — Node header/request timeouts and client errors", () => {
/**
* Drives one raw, deliberately-malformed or deliberately-slow byte sequence through a real
* listening API and returns everything observable at the transport boundary, plus everything the
* *real* production logging seam (`console.error`, which `apps/api/src/server.ts` writes its
* sanitized operational line through) actually emitted while it happened.
*
* This is the only shape of proof that counts for the timeout-observability claim: no event is
* manually emitted, no logger is called directly, and nothing asserts merely that a listener was
* registered — a real socket is opened against a real server, real bytes are written, and the
* real Node transport is left to do whatever it genuinely does.
*/
async function driveRawConnection(
label: string,
write: (socket: net.Socket) => void,
timeouts: Partial<NodeServerTimeouts> = {
connectionsCheckingIntervalMs: 100,
headersTimeoutMs: 200,
keepAliveTimeoutMs: 200,
requestTimeoutMs: 300,
},
): Promise<{ destroyed: boolean; logs: string[]; received: string }> {
const database = await createDisposableDatabase(label);
await applyCoreMigrations(database.url);
const config = createApiRuntimeConfig(
{
databaseUrl: database.url,
host: "127.0.0.1",
identity: {
baseUrl: identityBaseUrl,
publicSignupEnabled: false,
secret: "integration-test-secret-0123456789abcdefgh",
trustedOrigins: [identityBaseUrl, dashboardOrigin],
},
port: 0,
},
// Development configuration, for the same reason `startConfiguredApi` documents: this suite
// connects to the disposable plaintext loopback PostgreSQL, which production configuration
// never permits. Nothing about the transport-level timeout behavior under test depends on it.
{ environment: "development" },
);
// Deliberately tiny, test-only timeouts — production composition (server.ts's `main`) never
// sets this override — so a real timeout is observed deterministically in well under a second
// rather than by waiting out the production values.
const running = await startApi(config, { nodeServerTimeoutOverride: timeouts });
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const address = running.server.address();
if (!address || typeof address !== "object") {
throw harnessError("The API integration server has no bound address.");
}
const socket = net.connect({ host: "127.0.0.1", port: address.port });
await new Promise<void>((resolve, reject) => {
socket.once("connect", () => resolve());
socket.once("error", reject);
});
const receivedChunks: Buffer[] = [];
// A `net.Socket` stays paused (and never surfaces the peer's eventual FIN as `close`) until
// something actively consumes its readable side — attaching this listener is what makes the
// socket start flowing, not merely a way to capture the response for the assertions below.
socket.on("data", (chunk) => receivedChunks.push(chunk as Buffer));
socket.on("error", () => undefined);
const closed = new Promise<void>((resolve) => socket.once("close", resolve));
write(socket);
await closed;
const logs = errorSpy.mock.calls.flat().map((value) => String(value));
return {
destroyed: socket.destroyed,
logs,
received: Buffer.concat(receivedChunks).toString("utf8"),
};
} finally {
errorSpy.mockRestore();
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
}
/** Every status line in a response stream — more than one would mean the platform corrupted the
* connection by writing a second response after Node (or a handler) already wrote one. */
function statusLines(received: string): readonly string[] {
return received.match(/^HTTP\/1\.1 \d{3}[^\r\n]*/gmu) ?? [];
}
/** The exact operational line `apps/api/src/server.ts` promises for a transport-level client
* failure — no request path, query, body, cookie, authorization value, client-address header,
* forwarding chain, raw peer address, stack, or parser payload may appear anywhere in the logs. */
function expectSanitizedTransportLog(logs: readonly string[]): void {
expect(logs).toHaveLength(1);
const emitted = logs.join("\n");
for (const forbidden of [
"/api/v1/principal",
"/api/auth",
"127.0.0.1",
"Cookie",
"cookie",
"Authorization",
"authorization",
"x-forwarded-for",
"x-ekklesia-client-address",
"partial",
"Parse Error",
"HPE_",
"ERR_HTTP_REQUEST_TIMEOUT",
"at ",
]) {
expect(emitted).not.toContain(forbidden);
}
}
it("answers a never-completed header sequence with exactly one 408, closes the socket, invokes no handler, and emits exactly one sanitized operational log", async () => {
const { destroyed, logs, received } = await driveRawConnection(
"node_timeouts",
// An incomplete request: a request line and one header, deliberately never terminated by the
// blank line that would mark the end of the headers, and no further data ever sent.
(socket) => socket.write("GET /api/v1/principal HTTP/1.1\r\nHost: 127.0.0.1\r\n"),
);
expect(received).toBe("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
expect(statusLines(received)).toHaveLength(1);
// A handler would have produced a JSON principal body; Node answered before routing.
expect(received).not.toContain("anonymous");
expect(destroyed).toBe(true);
expectSanitizedTransportLog(logs);
});
it("answers a never-completed body with exactly one 408, closes the socket, invokes no auth logic, and emits exactly one sanitized operational log", async () => {
const { destroyed, logs, received } = await driveRawConnection(
"node_timeouts_body",
// Complete, well-formed headers declaring a body that never actually arrives.
(socket) =>
socket.write(
"POST /api/auth/sign-in/email HTTP/1.1\r\n" +
"Host: 127.0.0.1\r\n" +
"Content-Type: application/json\r\n" +
"Content-Length: 1000\r\n" +
"\r\n" +
'{"partial":',
),
{
connectionsCheckingIntervalMs: 100,
headersTimeoutMs: 300,
keepAliveTimeoutMs: 200,
requestTimeoutMs: 400,
},
);
expect(received).toBe("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
expect(statusLines(received)).toHaveLength(1);
expect(received).not.toContain("Set-Cookie");
expect(destroyed).toBe(true);
expectSanitizedTransportLog(logs);
});
it("preserves Node's own 400 for a malformed request line, without logging the malformed bytes", async () => {
const { destroyed, logs, received } = await driveRawConnection("node_bad_request", (socket) =>
socket.write("!!!!NOT-HTTP-AT-ALL!!!! /secret-path\r\n\r\n"),
);
expect(received).toBe("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
expect(statusLines(received)).toHaveLength(1);
expect(destroyed).toBe(true);
expectSanitizedTransportLog(logs);
// The attacker-controlled bytes must never reach the log.
expect(logs.join("\n")).not.toContain("NOT-HTTP-AT-ALL");
expect(logs.join("\n")).not.toContain("secret-path");
});
it("preserves Node's own 431 for an oversized header block, without logging the header contents", async () => {
const { destroyed, logs, received } = await driveRawConnection(
"node_header_overflow",
(socket) =>
socket.write(
`GET /api/v1/principal HTTP/1.1\r\nHost: 127.0.0.1\r\nX-Overflow: ${"z".repeat(20_000)}\r\n\r\n`,
),
);
expect(received).toBe(
"HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n",
);
expect(statusLines(received)).toHaveLength(1);
expect(destroyed).toBe(true);
expectSanitizedTransportLog(logs);
expect(logs.join("\n")).not.toContain("zzz");
});
});
/**
* Pass 4: shutdown is finite even when HTTP work does not settle.
*
* These drive a real bound listener and a real socket rather than a mock, because the behavior
* under test is what Node's own `server.close()` does and does not do. Every socket, server, and
* disposable database is closed in `finally`, and each scenario carries its own outer deadline so
* a regression fails rather than hangs.
*/
describe.sequential("live API — module lifecycle governs what actually runs", () => {
/** Writes one explicit enablement decision through the real table, before the API starts —
* exactly what `pnpm modules:disable` or a prior `PUT` would have left behind. Nothing in this
* suite reaches into the store through a double. */
async function storeEnablement(url: string, moduleId: string, enabled: boolean): Promise<void> {
const client = await openAdministrativeClient(url);
try {
await safeQuery(
client,
`insert into ekklesia_core_module_state (module_id, enabled) values ($1, $2)
on conflict (module_id) do update set enabled = excluded.enabled`,
[moduleId, enabled],
);
} finally {
await safeEnd(client);
}
}
it("never imports, registers, starts, or mounts a disabled module, and still runs its neighbours", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
await createFixtureModule(modulesDirectory, "calendar", recorder, {
permissions: [
{ baseline: "public", description: "Read calendar events.", id: "calendar.events.read" },
{ baseline: "public", description: "View calendar state.", id: "calendar.state.view" },
],
});
await createFixtureModule(modulesDirectory, "announcements", recorder, {
permissions: [
{
baseline: "public",
description: "Read announcements.",
id: "announcements.events.read",
},
{
baseline: "public",
description: "View announcement state.",
id: "announcements.state.view",
},
],
});
const database = await createDisposableDatabase("lifecycle_off");
await applyCoreMigrations(database.url);
await storeEnablement(database.url, "announcements", false);
const running = await startConfiguredApi(database.url, {
modulesDirectoryOverride: modulesDirectory,
});
const realtimeAbort = new AbortController();
let calendarReader: ReadableStreamDefaultReader<Uint8Array> | undefined;
try {
const origin = apiOrigin(running);
// The enabled module really did run: its markers are present, and its route answers.
expect(recorder.events).toContain("calendar:register");
expect(recorder.events).toContain("calendar:start");
const calendarRoute = await fetch(`${origin}/api/v1/modules/calendar/events/abc`);
expect(calendarRoute.status).toBe(200);
await withTransportDeadline(
(async () => {
const calendarRealtime = await fetch(`${origin}/api/v1/module-realtime/calendar/state`, {
signal: realtimeAbort.signal,
});
expect(calendarRealtime.status).toBe(200);
calendarReader = calendarRealtime.body?.getReader();
const calendarSnapshot = await calendarReader?.read();
expect(new TextDecoder().decode(calendarSnapshot?.value)).toContain("event: snapshot");
})(),
() => realtimeAbort.abort(),
);
// The disabled module ran nothing at all. Not "registered but skipped", not "started and
// gated per request" — its compiled entrypoint was never imported, so no hook could run.
expect(recorder.events).not.toContain("announcements:register");
expect(recorder.events).not.toContain("announcements:start");
expect(recorder.events.filter((event) => event.startsWith("announcements:"))).toEqual([]);
// No route was ever mounted, so the platform's ordinary 404 answers — there is no per-request
// "is this module enabled?" check anywhere, because there is nothing mounted to check.
const disabledRoute = await fetch(`${origin}/api/v1/modules/announcements/events/abc`);
expect(disabledRoute.status).toBe(404);
expect(await disabledRoute.json()).toEqual({ code: "NOT_FOUND" });
const disabledRealtime = await fetch(`${origin}/api/v1/module-realtime/announcements/state`);
expect(disabledRealtime.status).toBe(404);
expect(await disabledRealtime.json()).toEqual({ code: "NOT_FOUND" });
// Shutdown never invokes a stop hook for a module that never started.
const streamClosed = calendarReader?.read();
const shutdown = running.shutdown();
await expect(
withTransportDeadline(
streamClosed ?? Promise.reject(harnessError("The realtime stream was unavailable.")),
() => realtimeAbort.abort(),
),
).resolves.toEqual({ done: true, value: undefined });
await shutdown;
expect(recorder.events).toContain("calendar:stop");
expect(recorder.events).not.toContain("announcements:stop");
} finally {
realtimeAbort.abort();
await calendarReader?.cancel().catch(() => undefined);
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("blocks a dependent of a disabled required dependency, and explains why over HTTP", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
await createFixtureModule(modulesDirectory, "storage", recorder);
await createFixtureModule(modulesDirectory, "announcements", recorder, {
dependencies: { modules: { required: { storage: "^0.1.0" } } },
permissions: [
{
baseline: "public",
description: "View announcement state.",
id: "announcements.state.view",
},
],
});
const database = await createDisposableDatabase("lifecycle_block");
await applyCoreMigrations(database.url);
await storeEnablement(database.url, "storage", false);
const running = await startConfiguredApi(database.url, {
modulesDirectoryOverride: modulesDirectory,
});
try {
const origin = apiOrigin(running);
// Neither ran: the disabled dependency by decision, the dependent because starting it would
// violate its own required-dependency contract.
expect(recorder.events).toEqual([]);
const owner = await signUpPlatformOwner(origin, database.url);
const lifecycle = await fetch(`${origin}/api/v1/module-lifecycle`, {
headers: { cookie: owner.cookie, origin: dashboardOrigin },
});
expect(lifecycle.status).toBe(200);
const body = (await lifecycle.json()) as {
readonly modules: readonly {
readonly id: string;
readonly configured: { readonly enabled: boolean; readonly explicit: boolean };
readonly runtime: {
readonly state: string;
readonly reason?: string;
readonly dependency?: { readonly kind: string; readonly moduleId: string };
};
readonly restartRequired: boolean;
}[];
};
expect(body.modules).toEqual([
{
configured: { enabled: false, explicit: true },
id: "storage",
restartRequired: false,
runtime: { state: "disabled" },
version: "0.1.0",
},
{
configured: { enabled: true, explicit: false },
id: "announcements",
restartRequired: false,
runtime: {
dependency: { kind: "module", moduleId: "storage" },
reason: "required_dependency_disabled",
state: "blocked",
},
version: "0.1.0",
},
]);
// The operator's question — "why is this module unavailable?" — answered without a log line,
// and without a filesystem path anywhere in the payload.
const raw = JSON.stringify(body);
expect(raw).not.toContain(modulesDirectory);
expect(raw).not.toContain("dist/");
const blockedRealtime = await fetch(`${origin}/api/v1/module-realtime/announcements/state`);
expect(blockedRealtime.status).toBe(404);
expect(await blockedRealtime.json()).toEqual({ code: "NOT_FOUND" });
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("applies a change made over HTTP only at the next start, and says so in the meantime", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
await createFixtureModule(modulesDirectory, "calendar", recorder, {
permissions: [
{ baseline: "public", description: "Read calendar events.", id: "calendar.events.read" },
],
});
const database = await createDisposableDatabase("lifecycle_restart");
await applyCoreMigrations(database.url);
const first = await startConfiguredApi(database.url, {
modulesDirectoryOverride: modulesDirectory,
});
try {
const origin = apiOrigin(first);
const owner = await signUpPlatformOwner(origin, database.url);
const mutation = await fetch(`${origin}/api/v1/module-lifecycle/calendar/enabled`, {
body: JSON.stringify({ enabled: false }),
headers: {
"content-type": "application/json",
cookie: owner.cookie,
origin: dashboardOrigin,
},
method: "PUT",
});
expect(mutation.status).toBe(200);
expect(await mutation.json()).toEqual({
configured: { enabled: false, explicit: true },
id: "calendar",
restartRequired: true,
// Honest: this process really is still serving that module.
runtime: { state: "running" },
version: "0.1.0",
});
// And it demonstrably still is — nothing was hot-unloaded to make the change feel immediate.
expect((await fetch(`${origin}/api/v1/modules/calendar/events/abc`)).status).toBe(200);
} finally {
await first.shutdown().catch(() => undefined);
}
recorder.events.length = 0;
const second = await startConfiguredApi(database.url, {
modulesDirectoryOverride: modulesDirectory,
});
try {
const origin = apiOrigin(second);
// The next start applies the stored decision: nothing imported, nothing mounted.
expect(recorder.events).toEqual([]);
expect((await fetch(`${origin}/api/v1/modules/calendar/events/abc`)).status).toBe(404);
} finally {
await second.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("keeps a disabled module's UI out of the inventory and out of Dashboard navigation", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
await createUiModule(
modulesDirectory,
"shown",
"1.0.0",
'export default { id: "shown", version: "1.0.0", mount() {} };\n',
);
await createUiModule(
modulesDirectory,
"hidden",
"1.0.0",
'export default { id: "hidden", version: "1.0.0", mount() {} };\n',
);
const database = await createDisposableDatabase("lifecycle_ui");
await applyCoreMigrations(database.url);
await storeEnablement(database.url, "hidden", false);
const running = await startConfiguredApi(database.url, {
modulesDirectoryOverride: modulesDirectory,
});
try {
const origin = apiOrigin(running);
const owner = await signUpPlatformOwner(origin, database.url);
const headers = { cookie: owner.cookie, origin: dashboardOrigin };
const inventory = (await (await fetch(`${origin}/api/v1/module-ui`, { headers })).json()) as {
readonly modules: readonly { readonly id: string }[];
};
const navigation = (await (
await fetch(`${origin}/api/v1/dashboard/navigation`, { headers })
).json()) as {
readonly ungrouped: readonly { readonly module: { readonly id: string } }[];
};
expect(inventory.modules.map((entry) => entry.id)).toEqual(["shown"]);
expect(navigation.ungrouped.map((entry) => entry.module.id)).toEqual(["shown"]);
// The artifact itself is unreachable too, even to a caller that knows both halves of its
// identity — publication is derived from the eligible set, not from the file's existence.
const artifact = await fetch(`${origin}/api/v1/module-ui/hidden/1.0.0`);
expect(artifact.status).toBe(404);
expect((await fetch(`${origin}/api/v1/module-ui/shown/1.0.0`)).status).toBe(200);
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("still discovers a disabled module's migrations: deployed schema ownership is not runtime state", async () => {
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
await createFixtureModule(modulesDirectory, "calendar", recorder);
const database = await createDisposableDatabase("lifecycle_migrations");
await applyCoreMigrations(database.url);
await storeEnablement(database.url, "calendar", false);
// Migration discovery is offline and reads the deployed inventory, never the enablement table.
// A disabled module's tables must still be migrated, or enabling it later would need a schema
// change in the middle of a restart.
const aggregate = await discoverAllMigrations({
coreMigrationsDirectory,
moduleDiscovery: {
moduleSdkVersion: "0.1.0",
modulesDirectory,
platformVersion: "0.1.0",
},
});
expect(aggregate.discoveredModules.map((module) => module.manifest.id)).toEqual(["calendar"]);
});
});
describe.sequential("live API — bounded HTTP drain and shutdown", () => {
async function withDeadline<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
operation,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(
() => reject(harnessError("shutdown did not settle in time")),
timeoutMs,
);
}),
]);
} finally {
clearTimeout(timer);
}
}
it("serves a real request and then drains cleanly, well inside the deadline", async () => {
const database = await createDisposableDatabase("drain_ok");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url, { drainTimeoutMsOverride: 5_000 });
try {
const origin = apiOrigin(running);
const response = await fetch(`${origin}/api/v1/principal`);
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ kind: "anonymous" });
// Nothing is stalled, so the graceful path completes and shutdown reports success — the
// drain deadline is never reached and no connection is force-closed.
await expect(withDeadline(running.shutdown(), 20_000)).resolves.toBeUndefined();
// The listener really is closed afterwards.
await expect(fetch(`${origin}/api/v1/principal`)).rejects.toBeTruthy();
} finally {
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("lets a handler that has already entered finish, and closes its transport before module stop", async () => {
// A real handler gate, not an inference from the connection count: the fixture module's route
// handler announces that it has *entered*, then blocks until this test releases it. Shutdown
// therefore begins at a point where the handler is provably inside user code, and the ordering
// claim below is an assertion about recorded events rather than about timing.
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
const gate = createHandlerGate();
await createGatedRouteModule(modulesDirectory, "drainorder", recorder, gate);
const database = await createDisposableDatabase("drain_order");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url, {
drainTimeoutMsOverride: 20_000,
modulesDirectoryOverride: modulesDirectory,
});
const address = running.server.address();
if (!address || typeof address !== "object") {
throw harnessError("The API integration server has no bound address.");
}
const socket = net.connect({ host: "127.0.0.1", port: address.port });
let shutdown: Promise<void> | undefined;
try {
const responseBytes: Buffer[] = [];
const responseClosed = new Promise<void>((resolve) => {
socket.once("close", () => resolve());
});
socket.on("data", (chunk: Buffer) => {
responseBytes.push(chunk);
});
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.write(
`GET /api/v1/modules/drainorder/gated HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n`,
() => resolve(),
);
});
// Bounded wait on the handler's own signal — no sleep, no proxy for "entered".
await withDeadline(gate.entered, 20_000);
expect(recorder.events).toContain("drainorder:handler-entered");
// Shutdown starts while the handler is still inside user code.
shutdown = running.shutdown();
// Release it, then require the graceful path to complete.
gate.release();
await expect(withDeadline(shutdown, 30_000)).resolves.toBeUndefined();
// The response really was served, in full, rather than force-closed mid-flight.
await withDeadline(responseClosed, 10_000);
const raw = Buffer.concat(responseBytes).toString("utf8");
expect(raw).toContain("HTTP/1.1 200");
expect(raw).toContain('"gated":true');
// Explicit ordering: the already-running command can publish its committed state while HTTP
// drains, then the handler finishes and only afterward does its module stop.
const published = recorder.events.indexOf("drainorder:handler-published");
const finished = recorder.events.indexOf("drainorder:handler-finished");
const stopped = recorder.events.indexOf("drainorder:stop");
expect(published).toBeGreaterThanOrEqual(0);
expect(finished).toBeGreaterThanOrEqual(0);
expect(stopped).toBeGreaterThanOrEqual(0);
expect(published).toBeLessThan(finished);
expect(finished).toBeLessThan(stopped);
} finally {
gate.release();
if (shutdown) {
await shutdown.catch(() => undefined);
}
socket.destroy();
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("force-closes a stalled connection at the drain deadline and still reaches pool closure", async () => {
const database = await createDisposableDatabase("drain_forced");
await applyCoreMigrations(database.url);
// A short, test-only drain deadline; production keeps HTTP_DRAIN_TIMEOUT_MS unchanged.
const running = await startConfiguredApi(database.url, { drainTimeoutMsOverride: 250 });
const address = running.server.address();
if (!address || typeof address !== "object") {
throw harnessError("The API integration server has no bound address.");
}
const socket = net.connect({ host: "127.0.0.1", port: address.port });
try {
// A real, deliberately incomplete request: headers are never finished, so Node keeps the
// connection open and `server.close()` alone would wait indefinitely.
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.on("data", () => undefined);
socket.write("GET /api/v1/principal HTTP/1.1\r\nHost: 127.0.0.1\r\n", () => resolve());
});
const closed = new Promise<void>((resolve) => {
socket.once("close", () => resolve());
});
let captured: unknown;
try {
await withDeadline(running.shutdown(), 20_000);
} catch (error) {
captured = error;
}
// A forced drain is a genuine shutdown failure, never reported as success.
expect(captured).toMatchObject({ drainTimedOut: true, name: "ApiShutdownError" });
// ...and it still carries nothing about the request, socket, or database.
const publicText = `${String(captured)}\n${(captured as Error).stack ?? ""}`;
expect(publicText).not.toContain("127.0.0.1");
expect(publicText).not.toContain(database.name);
// The connection was actually closed by the platform, not left dangling.
await withDeadline(closed, 10_000);
} finally {
socket.destroy();
await running.shutdown().catch(() => undefined);
await dropDatabase(database.name);
}
});
it("shares one memoized shutdown across concurrent callers", async () => {
const database = await createDisposableDatabase("drain_once");
await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url, { drainTimeoutMsOverride: 5_000 });
try {
const [first, second, third] = await withDeadline(
Promise.all([running.shutdown(), running.shutdown(), running.shutdown()]),
20_000,
);
expect([first, second, third]).toEqual([undefined, undefined, undefined]);
// A later caller observes the same completed result rather than restarting cleanup.
await expect(running.shutdown()).resolves.toBeUndefined();
} finally {
await dropDatabase(database.name);
}
});
});