Files
Ekklesia/core/tests/identity-runtime.test.ts
ekklesiaandClaude Opus 5 ea28f8aa2e
CI / verify (pull_request) Successful in 42s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 1m42s
feat: verify email addresses and recover forgotten passwords
Ekklesia could authenticate a person and, since Pass 1, send a
transactional email — but it had no way to establish that an address
belonged to whoever claimed it, and no way back in for someone who
forgot their password.

A verified address is now a precondition for holding a session. Better
Auth enforces that after the password check and before any session
exists, so an unverified sign-in returns 403 with no cookie. That is why
no "unverified principal" was needed: a correct password on an
unverified address carries exactly the authority of no password, and
/api/v1/principal is unchanged.

Better Auth keeps every token. Ekklesia adds no token table, no hashing,
and no expiry clock. It supplies a recipient, a subject, and two bodies,
and the mail provider supplies everything about SMTP.

Verification and reset links are rebuilt from a validated Dashboard
origin, taking only the token — never the URL Better Auth prebuilds from
a caller-supplied callbackURL — so no request can steer where a
recipient lands. Following a verification link mints no session, because
mail infrastructure dereferences links. Reset revokes that person's
other sessions and leaves managed devices alone.

Recovery answers identically for an address with an account, without
one, already verified, or undeliverable — including when SMTP is broken,
which is why the delivery adapter never throws.

The initial platform owner is the one exception: created already
verified by an operator command with no mail prerequisite, so a fresh
server can be signed in to before a relay exists. Migration 0007 extends
that to owners of installations that already exist. Public signup stays
disabled; verification is not a licence to self-register.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 07:21:17 -05:00

592 lines
24 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const betterAuthMock = vi.hoisted(() => vi.fn());
const drizzleAdapterMock = vi.hoisted(() =>
vi.fn((db: unknown, options: unknown) => ({ __adapterOptions: options, db })),
);
vi.mock("better-auth", () => ({ betterAuth: betterAuthMock }));
vi.mock("better-auth/adapters/drizzle", () => ({ drizzleAdapter: drizzleAdapterMock }));
import type { IdentityConfig } from "../src/identity/config.js";
import type { IdentityDrizzleDatabase } from "../src/identity/database.js";
import {
createIdentityRuntime,
IdentityRuntimeError,
TRUSTED_CLIENT_ADDRESS_HEADER,
} from "../src/identity/runtime.js";
import { identityAdapterSchema } from "../src/identity/schema.js";
function validConfig(overrides: Partial<IdentityConfig> = {}): IdentityConfig {
return Object.freeze({
baseUrl: "https://identity.example.test",
dashboardOrigin: "https://identity.example.test",
publicSignupEnabled: false,
secret: "correct-horse-battery-staple-0123456789",
trustedOrigins: Object.freeze(["https://identity.example.test"]),
...overrides,
});
}
interface FakeDbOptions {
/** Makes the session-store readiness read reject, standing in for a missing relation, a dropped
* column, a denied `SELECT` privilege, an unavailable connection, or a query timeout — every one
* of which surfaces to this seam identically, as a rejected query. */
readonly sessionProbeError?: Error;
}
/**
* A fake Identity Drizzle view that records every query actually issued, so a test can prove the
* *absence* of identity database work — not merely that a particular helper was not called. Both
* the legacy `execute` seam and the `select(...).from(...).where(...)` chain the session-store
* readiness check uses are recorded into one ordered list.
*/
function fakeDb(options: FakeDbOptions = {}) {
const queries: string[] = [];
const execute = vi.fn(() => {
queries.push("execute");
return Promise.resolve(undefined);
});
// Drizzle's query builder is awaited directly, so the terminal `where(...)` returns a real
// Promise rather than a hand-rolled thenable object.
const select = vi.fn((columns: Record<string, unknown>) => {
queries.push(`select:${Object.keys(columns).sort().join(",")}`);
const builder = {
from: vi.fn(() => builder),
where: vi.fn(() =>
options.sessionProbeError
? Promise.reject(options.sessionProbeError)
: Promise.resolve([] as unknown[]),
),
};
return builder;
});
const db = { __fakeDb: true, execute, queries, select };
// The runtime only ever uses the narrow surface faked above; the cast keeps the fake honest
// about that instead of stubbing all of `NodePgDatabase`, while `queries` stays observable.
return db as unknown as IdentityDrizzleDatabase & { readonly queries: string[] };
}
describe("Core identity runtime construction boundary", () => {
beforeEach(() => {
betterAuthMock.mockReset();
drizzleAdapterMock.mockClear();
});
it("does not construct Better Auth or the Drizzle adapter merely because the module is imported", () => {
expect(betterAuthMock).not.toHaveBeenCalled();
expect(drizzleAdapterMock).not.toHaveBeenCalled();
});
it("constructs explicitly and returns only the two narrow transport capabilities", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
const runtime = createIdentityRuntime({ config: validConfig(), db: fakeDb() });
expect(Object.keys(runtime).sort()).toEqual(["handleAuthRequest", "resolvePrincipal"]);
expect(typeof runtime.handleAuthRequest).toBe("function");
expect(typeof runtime.resolvePrincipal).toBe("function");
expect(Object.isFrozen(runtime)).toBe(true);
});
it("does not expose the raw database, adapter, or Better Auth instance on the returned value", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
const db = fakeDb();
const runtime = createIdentityRuntime({ config: validConfig(), db });
expect(JSON.stringify(runtime)).toBe("{}");
});
it("passes the explicit ekklesia_core_* schema mapping to the Drizzle adapter", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
const db = fakeDb();
createIdentityRuntime({ config: validConfig(), db });
expect(drizzleAdapterMock).toHaveBeenCalledTimes(1);
const [passedDb, options] = drizzleAdapterMock.mock.calls[0] as [unknown, unknown];
expect(passedDb).toBe(db);
expect(options).toEqual({ provider: "pg", schema: identityAdapterSchema });
});
it("enables exactly email/password, verification, and rate limiting, nothing else", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
expect(betterAuthMock).toHaveBeenCalledTimes(1);
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
// Exhaustive on purpose: a new top-level Better Auth feature has to be added here deliberately,
// never by inheriting a default.
expect(Object.keys(options).sort()).toEqual([
"advanced",
"baseURL",
"database",
"emailAndPassword",
"emailVerification",
"logger",
"onAPIError",
"rateLimit",
"secret",
"trustedOrigins",
]);
expect(options.emailAndPassword).toEqual({
autoSignIn: false,
disableSignUp: true,
enabled: true,
maxPasswordLength: 128,
minPasswordLength: 8,
// A verified address is a precondition for holding a session (ADR 0038).
requireEmailVerification: true,
resetPasswordTokenExpiresIn: 3600,
// Password reset revokes that human's other sessions, so recovering from a compromise is not
// cosmetic.
revokeSessionsOnPasswordReset: true,
});
expect(options.emailVerification).toEqual({
// Following an emailed link never mints a session: mail infrastructure dereferences links.
autoSignInAfterVerification: false,
expiresIn: 3600,
sendOnSignIn: true,
sendOnSignUp: true,
});
// No `customRules`: production inherits Better Auth's own stricter per-path defaults.
expect(options.rateLimit).toEqual({ enabled: true, max: 100, window: 10 });
expect(options.baseURL).toBe(validConfig().baseUrl);
expect(options.secret).toBe(validConfig().secret);
expect(options.trustedOrigins).toEqual([...validConfig().trustedOrigins]);
});
it("wires the injected notifier into both delivery callbacks, and neither without it", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
const sendResetPassword = vi.fn(async () => undefined);
const sendVerificationEmail = vi.fn(async () => undefined);
createIdentityRuntime({
config: validConfig(),
db: fakeDb(),
notifications: { sendResetPassword, sendVerificationEmail },
});
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect((options.emailAndPassword as Record<string, unknown>).sendResetPassword).toBe(
sendResetPassword,
);
expect((options.emailVerification as Record<string, unknown>).sendVerificationEmail).toBe(
sendVerificationEmail,
);
betterAuthMock.mockClear();
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [withoutMail] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
// An installation with no mail configures the identical policy and simply cannot deliver.
// Crucially it does **not** relax `requireEmailVerification`.
expect(withoutMail.emailAndPassword).toMatchObject({ requireEmailVerification: true });
expect(
(withoutMail.emailAndPassword as Record<string, unknown>).sendResetPassword,
).toBeUndefined();
expect(
(withoutMail.emailVerification as Record<string, unknown>).sendVerificationEmail,
).toBeUndefined();
});
it("enables sign-up (disableSignUp: false) only when publicSignupEnabled is explicitly true", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig({ publicSignupEnabled: true }), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.emailAndPassword).toMatchObject({ disableSignUp: false });
});
it("explicitly disables Better Auth's default test-environment origin/CSRF skip", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.advanced).toEqual({
disableCSRFCheck: false,
disableOriginCheck: false,
ipAddress: { ipAddressHeaders: [TRUSTED_CLIENT_ADDRESS_HEADER] },
});
});
it("trusts exactly the one platform-owned client-address header, never Better Auth's own default", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
const advanced = options.advanced as { ipAddress?: { ipAddressHeaders?: string[] } };
expect(advanced.ipAddress?.ipAddressHeaders).toEqual([TRUSTED_CLIENT_ADDRESS_HEADER]);
expect(advanced.ipAddress?.ipAddressHeaders).not.toContain("x-forwarded-for");
});
it("configures onAPIError.throw so an unexpected failure escapes Better Auth's own unsanitized fallback logger instead of being swallowed internally", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.onAPIError).toEqual({ throw: true });
});
it("configures an explicit, error-only Better Auth logger that never forwards Better Auth's own message or arguments", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
const loggerOption = options.logger as {
disabled?: boolean;
level?: string;
log?: (...args: unknown[]) => void;
};
expect(loggerOption.disabled).toBe(false);
expect(loggerOption.level).toBe("error");
expect(typeof loggerOption.log).toBe("function");
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const hostileException = new Error(
"password=hunter2 host=db.internal.example port=5432 statement_timeout at /var/app/src/secret.ts:10:5",
);
loggerOption.log?.(
"error",
"Invalid origin: https://evil.example.test",
hostileException,
"select * from ekklesia_core_session",
);
expect(errorSpy).toHaveBeenCalledTimes(1);
const emitted = errorSpy.mock.calls
.flat()
.map((value) =>
value instanceof Error ? `${value.message}\n${value.stack ?? ""}` : String(value),
)
.join("\n");
expect(emitted).not.toContain("hunter2");
expect(emitted).not.toContain("db.internal.example");
expect(emitted).not.toContain("5432");
expect(emitted).not.toContain("/var/app");
expect(emitted).not.toContain("evil.example.test");
expect(emitted.toLowerCase()).not.toContain("select");
expect(emitted.toLowerCase()).not.toContain("stack");
} finally {
errorSpy.mockRestore();
}
});
it("never publishes a Better Auth log at or below warn level", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
const loggerOption = options.logger as { log?: (...args: unknown[]) => void };
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
try {
loggerOption.log?.("warn", "a warning", "detail");
loggerOption.log?.("info", "some info", "detail");
loggerOption.log?.("debug", "some debug", "detail");
expect(errorSpy).not.toHaveBeenCalled();
expect(warnSpy).not.toHaveBeenCalled();
expect(logSpy).not.toHaveBeenCalled();
} finally {
errorSpy.mockRestore();
warnSpy.mockRestore();
logSpy.mockRestore();
}
});
it("only widens the four strict rate-limit windows when a narrow override is explicitly injected", () => {
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler: vi.fn() });
createIdentityRuntime({
config: validConfig(),
db: fakeDb(),
rateLimitOverride: { max: 1000, window: 1 },
});
const [options] = betterAuthMock.mock.calls[0] as [Record<string, unknown>];
expect(options.rateLimit).toEqual({
customRules: {
// The two paths that trigger an email are included, so a test suite driving many recovery
// requests is not throttled by Better Auth's 3-per-60-seconds default for them.
"/request-password-reset": { max: 1000, window: 1 },
"/send-verification-email": { max: 1000, window: 1 },
"/sign-in/email": { max: 1000, window: 1 },
"/sign-up/email": { max: 1000, window: 1 },
},
enabled: true,
max: 100,
window: 10,
});
});
it("sanitizes a construction failure without leaking the underlying error, secret, or URL", () => {
const hostileSecret = validConfig().secret;
betterAuthMock.mockImplementation(() => {
throw new Error(
`Better Auth failed for secret ${hostileSecret} at https://internal.example.test`,
);
});
let captured: unknown;
try {
createIdentityRuntime({ config: validConfig(), db: fakeDb() });
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(IdentityRuntimeError);
expect(captured).toMatchObject({ code: "IDENTITY_CONSTRUCTION_FAILED" });
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}`;
expect(publicText).not.toContain(hostileSecret);
expect(publicText).not.toContain("internal.example.test");
});
});
describe("Core identity runtime — handleAuthRequest", () => {
beforeEach(() => {
betterAuthMock.mockReset();
drizzleAdapterMock.mockClear();
});
it("delegates directly to Better Auth's Fetch handler and passes the response through unmodified", async () => {
const expectedResponse = new Response("ok", { status: 200 });
const handler = vi.fn().mockResolvedValue(expectedResponse);
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler });
const runtime = createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const request = new Request("https://identity.example.test/api/auth/sign-in/email");
const response = await runtime.handleAuthRequest(request);
expect(handler).toHaveBeenCalledWith(request);
expect(response).toBe(expectedResponse);
});
it("passes through Better Auth's own error responses (e.g. 401, 403, 422) unmodified", async () => {
const errorResponse = new Response(JSON.stringify({ code: "INVALID_EMAIL_OR_PASSWORD" }), {
status: 401,
});
const handler = vi.fn().mockResolvedValue(errorResponse);
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler });
const runtime = createIdentityRuntime({ config: validConfig(), db: fakeDb() });
const response = await runtime.handleAuthRequest(
new Request("https://identity.example.test/x"),
);
expect(response.status).toBe(401);
});
it("sanitizes a genuinely unexpected handler failure", async () => {
const handler = vi.fn().mockRejectedValue(new Error("postgresql://admin:[email protected]/x"));
betterAuthMock.mockReturnValue({ api: { getSession: vi.fn() }, handler });
const runtime = createIdentityRuntime({ config: validConfig(), db: fakeDb() });
let captured: unknown;
try {
await runtime.handleAuthRequest(new Request("https://identity.example.test/x"));
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(IdentityRuntimeError);
expect(captured).toMatchObject({ code: "AUTH_REQUEST_FAILED" });
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}`;
expect(publicText).not.toContain("db.internal");
expect(publicText).not.toContain("admin");
});
});
describe("Core identity runtime — resolvePrincipal", () => {
beforeEach(() => {
betterAuthMock.mockReset();
drizzleAdapterMock.mockClear();
});
/** The configured production (https) session-cookie name, derived by Better Auth's own
* `getCookies` helper from the exact same `baseUrl` this suite's `validConfig` uses. */
const productionSessionCookie = "__Secure-better-auth.session_token";
it("maps a valid session to an authenticated principal containing only the opaque user id, without any follow-up probe", async () => {
const getSession = vi.fn().mockResolvedValue({
session: { id: "sess_1", token: "tok" },
user: { email: "[email protected]", id: "user_123", name: "Person" },
});
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
const db = fakeDb();
const runtime = createIdentityRuntime({ config: validConfig(), db });
const principal = await runtime.resolvePrincipal(
new Headers({ cookie: `${productionSessionCookie}=tok` }),
);
expect(principal).toEqual({ kind: "authenticated", userId: "user_123" });
expect(Object.keys(principal)).toEqual(["kind", "userId"]);
expect(Object.isFrozen(principal)).toBe(true);
// A resolved session needs no readiness confirmation — the store demonstrably worked.
expect(db.queries).toEqual([]);
});
it("performs zero identity database work for no cookie, unrelated cookies, or session-cookie lookalikes", async () => {
const getSession = vi.fn().mockResolvedValue(null);
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
for (const cookie of [
undefined,
"x=y",
"a=1; b=2; c=3",
// Lookalikes: a prefix, a suffix, a differently-cased spelling, a percent-encoded name, and
// the unprefixed development name (wrong for this https configuration).
`${productionSessionCookie}_extra=v`,
`extra_${productionSessionCookie}=v`,
`${productionSessionCookie.toUpperCase()}=v`,
"__Secure-better-auth%2Esession_token=v",
"better-auth.session_token=v",
]) {
const db = fakeDb();
const runtime = createIdentityRuntime({ config: validConfig(), db });
const principal = await runtime.resolvePrincipal(
cookie === undefined ? new Headers() : new Headers({ cookie }),
);
expect(principal).toEqual({ kind: "anonymous" });
expect(db.queries).toEqual([]);
}
});
it("reads the real session relation and its expected columns only when the configured session cookie was presented", async () => {
const getSession = vi.fn().mockResolvedValue(null);
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
const db = fakeDb();
const runtime = createIdentityRuntime({ config: validConfig(), db });
const principal = await runtime.resolvePrincipal(
new Headers({ cookie: `${productionSessionCookie}=stale-or-garbage` }),
);
// A healthy session store plus an unresolvable token is a genuinely anonymous caller.
expect(principal).toEqual({ kind: "anonymous" });
// The readiness check is a real read of the session relation's expected columns — never
// `select 1`, and never a generic pool ping.
expect(db.queries).toEqual(["select:expiresAt,id,token,userId"]);
});
it("detects the session cookie among several cookies and with irregular whitespace", async () => {
const getSession = vi.fn().mockResolvedValue(null);
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
for (const cookie of [
`a=1; ${productionSessionCookie}=tok; b=2`,
` ${productionSessionCookie}=tok `,
`a=1;${productionSessionCookie}=tok`,
// A duplicated session cookie is ambiguous and must fail safe *towards* checking the store.
`${productionSessionCookie}=one; ${productionSessionCookie}=two`,
// A valueless session cookie still means Better Auth saw the name.
`${productionSessionCookie}`,
]) {
const db = fakeDb();
const runtime = createIdentityRuntime({ config: validConfig(), db });
await runtime.resolvePrincipal(new Headers({ cookie }));
expect(db.queries).toEqual(["select:expiresAt,id,token,userId"]);
}
});
it("uses the unprefixed development cookie name when the configured base URL is http", async () => {
const getSession = vi.fn().mockResolvedValue(null);
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
const developmentConfig = validConfig({
baseUrl: "http://localhost:3000",
trustedOrigins: Object.freeze(["http://localhost:3000"]),
});
const unprefixed = fakeDb();
await createIdentityRuntime({ config: developmentConfig, db: unprefixed }).resolvePrincipal(
new Headers({ cookie: "better-auth.session_token=tok" }),
);
expect(unprefixed.queries).toEqual(["select:expiresAt,id,token,userId"]);
// The production `__Secure-` name is *not* what an http deployment reads.
const prefixed = fakeDb();
await createIdentityRuntime({ config: developmentConfig, db: prefixed }).resolvePrincipal(
new Headers({ cookie: `${productionSessionCookie}=tok` }),
);
expect(prefixed.queries).toEqual([]);
});
it("never downgrades a broken session store to anonymous, and leaks no database detail", async () => {
const getSession = vi.fn().mockResolvedValue(null);
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
// Every distinguishable session-store failure reaches this seam as a rejected read: a missing
// relation, a dropped column, a denied privilege, a dead connection, and a timeout alike.
const hostileFailures = [
new Error('relation "ekklesia_core_session" does not exist'),
new Error('column "token" does not exist'),
new Error("permission denied for table ekklesia_core_session at 10.1.2.3:5432"),
new Error("connection terminated unexpectedly"),
new Error("timed out"),
];
for (const sessionProbeError of hostileFailures) {
const runtime = createIdentityRuntime({
config: validConfig(),
db: fakeDb({ sessionProbeError }),
});
let captured: unknown;
try {
await runtime.resolvePrincipal(new Headers({ cookie: `${productionSessionCookie}=x` }));
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(IdentityRuntimeError);
expect(captured).toMatchObject({ code: "PRINCIPAL_RESOLUTION_FAILED" });
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}\n${JSON.stringify(captured)}`;
expect(publicText).not.toContain("ekklesia_core_session");
expect(publicText).not.toContain("permission denied");
expect(publicText).not.toContain("10.1.2.3");
expect(publicText).not.toContain("does not exist");
}
});
it("treats an unexpected throw from getSession itself as a sanitized infrastructure failure", async () => {
const hostile = "password=hunter2 host=db.internal.example port=5432";
const getSession = vi.fn().mockRejectedValue(new Error(hostile));
betterAuthMock.mockReturnValue({ api: { getSession }, handler: vi.fn() });
const runtime = createIdentityRuntime({ config: validConfig(), db: fakeDb() });
let captured: unknown;
try {
await runtime.resolvePrincipal(new Headers({ cookie: "better-auth.session_token=x" }));
} catch (error) {
captured = error;
}
expect(captured).toBeInstanceOf(IdentityRuntimeError);
expect(captured).toMatchObject({ code: "PRINCIPAL_RESOLUTION_FAILED" });
const publicText = `${String(captured)}\n${
captured instanceof Error ? (captured.stack ?? "") : ""
}`;
expect(publicText).not.toContain("hunter2");
expect(publicText).not.toContain("db.internal.example");
});
});