import { randomBytes } from "node:crypto"; import { request as httpRequest } from "node:http"; 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 { applyModuleMigrations, createPostgresMigrationDatabase, discoverAllMigrations, } from "@ekklesia/core/migrations"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { Client, type QueryResultRow } from "pg"; import { afterAll, describe, expect, it, vi } from "vitest"; import { createApiRuntimeConfig } from "../src/config.js"; import { startApi, 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(); 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); } 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 { 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( client: Client, text: string, values: unknown[] = [], ): Promise { try { const result = await client.query(text, values); return result.rows; } catch { throw harnessError("An integration database administration operation failed."); } } async function safeEnd(client: Client): Promise { try { await client.end(); } catch { throw harnessError("An integration database connection could not be closed."); } } async function createDatabase(databaseName: string): Promise { 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 { 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 { 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 { 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", ]); expect(aggregate.migrations[0]?.checksum).toBe( "f8f2086ef06f7a6943e8ffc4350bb010b37aed04c0a72030d2b153c986b1325f", ); expect(aggregate.migrations[1]?.checksum).toBe( "830e1fbc26d05af08df8d8710c3d10e1f3016d32c41b8363cb661c49afcbe81e", ); await applyModuleMigrations({ aggregate, database: createPostgresMigrationDatabase(url) }); } finally { await rm(emptyModulesDirectory, { recursive: true, force: true }); } } afterAll(async () => { for (const databaseName of [...trackedDatabases]) { await dropDatabase(databaseName).catch(() => undefined); } }); 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}`; } async function startMigratedApi(url: string): Promise { const config = createApiRuntimeConfig( { databaseUrl: url, host: "127.0.0.1", identity: { baseUrl: identityBaseUrl, secret: "integration-test-secret-0123456789abcdefgh", trustedOrigins: [identityBaseUrl, dashboardOrigin], }, port: 0, }, { environment: "production" }, ); // A narrow, explicit test-only override: production composition (server.ts's `main`) never // sets this. Better Auth's built-in sign-in/sign-up rate limit (3 requests / 10s) would // otherwise reject this suite's many sequential requests. return startApi(config, { rateLimitOverride: { max: 1000, window: 1 } }); } 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 = {}, ): Promise { 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 = {}, ): Promise { 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 }; } async function fetchPrincipal( origin: string, headers: Record = {}, ): 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); }); } describe.sequential("live API — signup, sign-in, principal, sign-out", () => { let database: { name: string; url: string }; let running: RunningApi; let origin: string; const emailOne = "alice@example.test"; const emailTwo = "bob@example.test"; 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; 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: "carol@example.test", 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, "pw-short@example.test", "1234567"); expect(response.status).toBe(400); }); it("accepts an 8-character password", async () => { const { response, body } = await signUp(origin, "pw-min@example.test", "12345678"); expect(response.status).toBe(200); expect(body.user.email).toBe("pw-min@example.test"); }); it("accepts a 128-character password", async () => { const { response } = await signUp(origin, "pw-max@example.test", "a".repeat(128)); expect(response.status).toBe(200); }); it("rejects a 129-character password as too long", async () => { const { response } = await signUp(origin, "pw-over@example.test", "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("does not create any tables or migration history when started against an unmigrated database", async () => { const database = await createDisposableDatabase("unmigrated"); try { const config = createApiRuntimeConfig( { databaseUrl: database.url, host: "127.0.0.1", identity: { baseUrl: identityBaseUrl, secret: "integration-test-secret-0123456789abcdefgh", trustedOrigins: [identityBaseUrl], }, port: 0, }, { environment: "production" }, ); const running = await startApi(config, { rateLimitOverride: { max: 1000, window: 1 } }); const origin = apiOrigin(running); try { // A request that touches the (nonexistent) identity tables fails, but must not repair // or create anything. The exact failure body is not asserted here (Better Auth's // internal-error shape for this case is not a stable public contract); only that it is // not a successful sign-up. const response = await fetch(`${origin}/api/auth/sign-up/email`, { body: JSON.stringify({ email: "dana@example.test", name: "Integration Test User", password: "correct horse battery staple", }), headers: { "content-type": "application/json" }, method: "POST", }); expect(response.status).not.toBe(200); } finally { await running.shutdown(); } const admin = await openAdministrativeClient(database.url); try { 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, "erin@example.test", "correct horse battery staple"); const { cookiePair } = await signIn( origin, "erin@example.test", "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 — 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, "frank@example.test", "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: "frank@example.test", 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 = "authz-owner@example.test"; const managerEmail = "authz-manager@example.test"; const password = "correct horse battery staple"; // Module permission registration does not exist yet, so `core.authorization.grants.manage` — // the one permission `CORE_PERMISSION_REGISTRY` actually declares — stands in for "some // registered permission" throughout this scenario's explicit-grant lifecycle. 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: [] }); }); 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`, { body: JSON.stringify({ permission: testPermission, scope: "calendar-event:1", subjectUserId: managerUserId, }), 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(managerUserId); 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", async () => { const response = await fetch(`${origin}/api/v1/grants?userId=${managerUserId}`, { headers: { cookie: ownerCookie }, }); const body = (await response.json()) as { grants: Array<{ id: string }> }; expect(response.status).toBe(200); expect(body.grants.some((grant) => grant.id === createdGrantId)).toBe(true); }); 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, "authz-infra@example.test", "correct horse battery staple"); const { cookiePair } = await signIn( origin, "authz-infra@example.test", "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); }); });