import { execFile } from "node:child_process"; import { randomBytes } from "node:crypto"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { createAuthorizationDrizzleDatabase, createInitialPlatformOwner, InitialPlatformOwnerError, } from "@ekklesia/core/authorization"; import { createBootstrapIdentityCreator, createIdentityDrizzleDatabase, } from "@ekklesia/core/identity"; import { applyModuleMigrations, createPostgresMigrationDatabase, discoverAllMigrations, } from "@ekklesia/core/migrations"; import { Client, type QueryResultRow } from "pg"; import { afterAll, describe, expect, it } from "vitest"; import { createApiRuntimeConfig } from "../src/config.js"; import { type RunningApi, startApi } from "../src/server.js"; /** * The initial platform-owner bootstrap, against a real PostgreSQL and the real HTTP transport * (ADR 0035). * * The point of this suite is that it does **not** stop at asserting rows. A fresh installation is * bootstrapped and then the resulting account is signed in through the exact browser-facing login * transport Phase 7B Pass 1 established, its principal is read back, and it is used to reach a * platform-owner-protected resource. Anything less would prove the command wrote a user, not that * an operator can actually get into their dashboard — which is the only thing this pass is for. * * Every scenario runs with `publicSignupEnabled: false`, the production posture, so nothing here * can accidentally depend on the signup route the real deployment does not have. */ const integrationDatabaseUrlEnvironment = "EKKLESIA_MIGRATION_TEST_DATABASE_URL"; const repositoryRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", ".."); const coreMigrationsDirectory = path.join(repositoryRoot, "core", "drizzle"); const bootstrapCliPath = path.join( repositoryRoot, "core", "dist", "cli", "create-initial-owner.js", ); const trackedDatabases = new Set(); let databaseCounter = 0; const identityBaseUrl = "https://identity.example.test"; const dashboardOrigin = "https://dashboard.example.test"; const ownerPassword = "correct horse battery staple"; class IntegrationHarnessError extends Error { constructor(message: string) { super(message); this.name = "IntegrationHarnessError"; this.stack = `${this.name}: ${this.message}`; } } function baseDatabaseUrl(): string { const url = process.env[integrationDatabaseUrlEnvironment]; if (typeof url !== "string" || url.trim() === "") { throw new IntegrationHarnessError("The integration database URL is not configured."); } return url; } function databaseUrl(name: string): string { const url = new URL(baseDatabaseUrl()); url.pathname = `/${name}`; return url.toString(); } function makeDatabaseName(label: string): string { databaseCounter += 1; return `ekk_owner_${label}_${databaseCounter}_${randomBytes(4).toString("hex")}`.toLowerCase(); } async function openAdministrativeClient(): Promise { const client = new Client({ connectionString: baseDatabaseUrl() }); await client.connect(); return client; } async function safeQuery( client: Client, text: string, values: readonly unknown[] = [], ): Promise { try { const result = await client.query(text, [...values]); return result.rows; } catch { // Never rethrows the driver error: it can carry SQL, a connection string, and a host. throw new IntegrationHarnessError("An integration database statement failed."); } } async function createDisposableDatabase(label: string): Promise { const name = makeDatabaseName(label); const admin = await openAdministrativeClient(); try { await safeQuery(admin, `create database "${name}"`); } finally { await admin.end().catch(() => undefined); } trackedDatabases.add(name); return databaseUrl(name); } async function applyCoreMigrations(url: string): Promise { const emptyModules = await mkdtemp(path.join(tmpdir(), "ekklesia-owner-no-modules-")); try { const aggregate = await discoverAllMigrations({ coreMigrationsDirectory, moduleDiscovery: { moduleSdkVersion: "0.1.0", modulesDirectory: emptyModules, platformVersion: "0.1.0", }, }); await applyModuleMigrations({ aggregate, database: createPostgresMigrationDatabase(url) }); } finally { await rm(emptyModules, { force: true, recursive: true }); } } /** A fresh, migrated installation with no identity and no owner — exactly what an operator has * after `docker compose up` and `pnpm migrations:apply`. */ async function freshInstallation(label: string): Promise { const url = await createDisposableDatabase(label); await applyCoreMigrations(url); return url; } interface BootstrapHandles { readonly run: ( email: string, password?: string, name?: string, ) => ReturnType; readonly close: () => Promise; } /** Drives the same operation the CLI drives, with the same production composition. */ function bootstrapAgainst(url: string): BootstrapHandles { const authorization = createAuthorizationDrizzleDatabase(url); const identity = createIdentityDrizzleDatabase(url, {}); return { close: async () => { await Promise.all([ authorization.close().catch(() => undefined), identity.close().catch(() => undefined), ]); }, run: (email, password = ownerPassword, name = "Platform Owner") => createInitialPlatformOwner( { authorizationDb: authorization.db, createIdentity: createBootstrapIdentityCreator(identity.db), identityDb: identity.db, }, { email, name, password }, ), }; } /** * The production posture: public signup disabled, exactly as a real deployment must run — and, just * as deliberately, **no outbound email configured at all**. A fresh installation has no relay yet, * which is the whole reason the initial owner is created already verified (ADR 0038). */ function ownerApiConfig(url: string): ReturnType { return createApiRuntimeConfig( { databaseUrl: url, host: "127.0.0.1", identity: { baseUrl: identityBaseUrl, publicSignupEnabled: false, secret: "integration-test-secret-0123456789abcdefgh", trustedOrigins: [identityBaseUrl, dashboardOrigin], }, port: 0, }, { environment: "development" }, ); } async function startApiWithoutSignup(url: string): Promise { return startApi(ownerApiConfig(url), { rateLimitOverride: { max: 1000, window: 1 } }); } function apiOrigin(running: RunningApi): string { const address = running.server.address(); if (!address || typeof address !== "object") { throw new IntegrationHarnessError("The API integration server has no bound address."); } return `http://127.0.0.1:${address.port}`; } function firstCookiePair(setCookieHeader: string): string { return setCookieHeader.split(",")[0]?.split(";")[0]?.trim() ?? ""; } async function signIn( origin: string, email: string, password: string, ): Promise<{ cookiePair?: string; response: Response }> { const response = await fetch(`${origin}/api/auth/sign-in/email`, { body: JSON.stringify({ email, password }), headers: { "content-type": "application/json", origin: dashboardOrigin }, method: "POST", }); const setCookie = response.headers.get("set-cookie"); return setCookie === null ? { response } : { cookiePair: firstCookiePair(setCookie), response }; } const execFileAsync = promisify(execFile); interface CliOutcome { readonly code: number; readonly stdout: string; readonly stderr: string; } /** Runs the real compiled CLI as a real child process, with the password on stdin and never in * `argv`. */ async function runBootstrapCli( databaseUrl: string | undefined, args: readonly string[], password: string | undefined, ): Promise { const environment: Record = { ...(process.env as Record), NODE_ENV: "development", }; if (databaseUrl === undefined) { delete environment.DATABASE_URL; } else { environment.DATABASE_URL = databaseUrl; } const child = execFileAsync(process.execPath, [bootstrapCliPath, ...args], { env: environment, }); child.child.stdin?.end(password ?? ""); try { const { stdout, stderr } = await child; return { code: 0, stderr, stdout }; } catch (error) { const failure = error as { code?: number; stdout?: string; stderr?: string }; return { code: typeof failure.code === "number" ? failure.code : 1, stderr: failure.stderr ?? "", stdout: failure.stdout ?? "", }; } } async function countRows(url: string, table: string): Promise { const client = new Client({ connectionString: url }); await client.connect(); try { const rows = await safeQuery<{ count: string }>( client, `select count(*)::text from "${table}"`, ); return Number(rows[0]?.count ?? "0"); } finally { await client.end().catch(() => undefined); } } /** Better Auth's own `emailVerified` column — the same value its `/verify-email` endpoint writes. */ async function emailVerifiedFlag(url: string, email: string): Promise { const client = new Client({ connectionString: url }); await client.connect(); try { const rows = await safeQuery<{ email_verified: boolean }>( client, 'select "email_verified" from "ekklesia_core_user" where lower("email") = $1', [email.trim().toLowerCase()], ); return rows[0]?.email_verified; } finally { await client.end().catch(() => undefined); } } afterAll(async () => { const admin = await openAdministrativeClient().catch(() => undefined); if (!admin) { return; } try { for (const name of trackedDatabases) { await safeQuery(admin, `drop database if exists "${name}" with (force)`).catch( () => undefined, ); } } finally { await admin.end().catch(() => undefined); } }); describe.sequential("initial platform-owner bootstrap", () => { it("takes a fresh installation all the way to an authenticated owner in the browser", async () => { const url = await freshInstallation("browserproof"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); let created: Awaited>; try { created = await bootstrap.run(email); } finally { await bootstrap.close(); } expect(created.identity).toBe("created"); expect(created.email).toBe(email.toLowerCase()); const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); // 1. The exact browser-facing login transport, on a deployment where signup does not exist. const session = await signIn(origin, email, ownerPassword); expect(session.response.status).toBe(200); expect(session.cookiePair).toBeDefined(); const cookie = session.cookiePair as string; // 2. The principal the browser bootstrap reads. const principalResponse = await fetch(`${origin}/api/v1/principal`, { headers: { cookie } }); expect(principalResponse.status).toBe(200); const principal = (await principalResponse.json()) as { kind: string; userId?: string; }; expect(principal.kind).toBe("authenticated"); expect(principal.userId).toBe(created.userId); // 3. A platform-owner-protected resource actually opens for them. This is the assertion that // separates "a row was written" from "the operator can run their platform". const grants = await fetch(`${origin}/api/v1/grants?userId=${created.userId}`, { headers: { cookie }, }); expect(grants.status).toBe(200); // 4. And the same resource is still closed to everyone else. const anonymous = await fetch(`${origin}/api/v1/grants?userId=${created.userId}`); expect(anonymous.status).toBe(403); } finally { await api.shutdown(); } }); it("leaves public signup disabled — the owner exists without a signup route existing", async () => { const url = await freshInstallation("nosignup"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); try { await bootstrap.run(email); } finally { await bootstrap.close(); } const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); const attempted = await fetch(`${origin}/api/auth/sign-up/email`, { body: JSON.stringify({ email: `intruder-${randomBytes(4).toString("hex")}@example.test`, name: "Intruder", password: ownerPassword, }), headers: { "content-type": "application/json", origin: dashboardOrigin }, method: "POST", }); // The bootstrap command created an account; it did not open a door for anyone else to. expect(attempted.status).toBe(400); expect(await attempted.text()).toContain("EMAIL_PASSWORD_SIGN_UP_DISABLED"); expect(await countRows(url, "ekklesia_core_user")).toBe(1); // And the owner can still sign in, so disabling signup did not disable the account. expect((await signIn(origin, email, ownerPassword)).response.status).toBe(200); } finally { await api.shutdown(); } }); describe("the guard", () => { it("refuses a second bootstrap, changes no password, and creates no second owner", async () => { const url = await freshInstallation("guard"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); try { await bootstrap.run(email); // Same address, different password: must not become a password reset. await expect(bootstrap.run(email, "an entirely different password")).rejects.toMatchObject({ code: "INITIAL_OWNER_ALREADY_COMPLETED", }); // A different address must not become a second owner. await expect( bootstrap.run(`second-${randomBytes(4).toString("hex")}@example.test`), ).rejects.toMatchObject({ code: "INITIAL_OWNER_ALREADY_COMPLETED" }); } finally { await bootstrap.close(); } expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(1); // The refused second attempt with a new address created no stray identity: the sentinel is // checked before anything is created, not after. expect(await countRows(url, "ekklesia_core_user")).toBe(1); const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); expect((await signIn(origin, email, ownerPassword)).response.status).toBe(200); expect( (await signIn(origin, email, "an entirely different password")).response.status, ).not.toBe(200); } finally { await api.shutdown(); } }); }); describe("validation reuses the identity system's own policy", () => { it("rejects an invalid email, and a password outside the 8–128 bound", async () => { const url = await freshInstallation("validation"); const bootstrap = bootstrapAgainst(url); try { await expect(bootstrap.run("not-an-email-address")).rejects.toMatchObject({ code: "INITIAL_OWNER_EMAIL_INVALID", }); await expect( bootstrap.run(`short-${randomBytes(4).toString("hex")}@example.test`, "1234567"), ).rejects.toMatchObject({ code: "INITIAL_OWNER_PASSWORD_TOO_SHORT" }); await expect( bootstrap.run(`long-${randomBytes(4).toString("hex")}@example.test`, "x".repeat(129)), ).rejects.toMatchObject({ code: "INITIAL_OWNER_PASSWORD_TOO_LONG" }); // Exactly the boundary the browser accepts, so the two can never diverge. await expect( bootstrap.run(`edge-${randomBytes(4).toString("hex")}@example.test`, "x".repeat(128)), ).resolves.toMatchObject({ identity: "created" }); } finally { await bootstrap.close(); } // Every rejection happened before anything was written, and the one success is the only row. expect(await countRows(url, "ekklesia_core_user")).toBe(1); }); it("never puts the password into an error", async () => { const url = await freshInstallation("nosecrets"); const secret = "sup3rSecretBootstrapPassword!"; const bootstrap = bootstrapAgainst(url); try { let caught: unknown; try { await bootstrap.run("not-an-email-address", secret); } catch (error) { caught = error; } expect(caught).toBeInstanceOf(InitialPlatformOwnerError); const rendered = `${String(caught)} ${JSON.stringify(caught)} ${(caught as Error).stack ?? ""}`; expect(rendered).not.toContain(secret); expect(rendered).not.toContain("postgres"); expect(rendered).not.toContain("select"); } finally { await bootstrap.close(); } }); }); describe("an identity that already exists", () => { it("adopts a usable account and leaves its password untouched", async () => { const url = await freshInstallation("adopt"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; // Exactly the partial failure this guards: the identity committed, the grant never ran. const identity = createIdentityDrizzleDatabase(url, {}); try { await createBootstrapIdentityCreator(identity.db)({ email, name: "Half Finished", password: ownerPassword, }); } finally { await identity.close(); } expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(0); const bootstrap = bootstrapAgainst(url); try { const result = await bootstrap.run(email, "a different password than before"); expect(result.identity).toBe("adopted"); } finally { await bootstrap.close(); } expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(1); expect(await countRows(url, "ekklesia_core_user")).toBe(1); const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); // Adoption granted ownership. It did not take the account over: the original password still // works and the one supplied to the rerun does not. expect((await signIn(origin, email, ownerPassword)).response.status).toBe(200); expect( (await signIn(origin, email, "a different password than before")).response.status, ).not.toBe(200); } finally { await api.shutdown(); } }); it("refuses a credential-less row rather than consuming bootstrap on an unusable owner", async () => { const url = await freshInstallation("unusable"); const client = new Client({ connectionString: url }); await client.connect(); try { await safeQuery( client, `insert into "ekklesia_core_user" (id, name, email, email_verified, created_at, updated_at) values ($1, $2, $3, false, now(), now())`, ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "Shell Row", "ghost@example.test"], ); } finally { await client.end().catch(() => undefined); } const bootstrap = bootstrapAgainst(url); try { await expect(bootstrap.run("ghost@example.test")).rejects.toMatchObject({ code: "INITIAL_OWNER_IDENTITY_UNUSABLE", }); } finally { await bootstrap.close(); } // The decisive part: bootstrap is still available. Granting ownership to a row nobody can // authenticate as would have consumed the single use and left the installation unusable with // no second chance. expect(await countRows(url, "ekklesia_core_platform_owner_bootstrap")).toBe(0); expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(0); }); }); describe("partial failure", () => { it("reports a failed grant honestly and stays recoverable by rerunning", async () => { const url = await freshInstallation("partial"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const authorization = createAuthorizationDrizzleDatabase(url); const identity = createIdentityDrizzleDatabase(url, {}); try { await expect( createInitialPlatformOwner( { authorizationDb: authorization.db, createIdentity: createBootstrapIdentityCreator(identity.db), grantOwnership: () => { throw new Error("the grant failed after the identity committed"); }, identityDb: identity.db, }, { email, name: "Owner", password: ownerPassword }, ), ).rejects.toMatchObject({ code: "INITIAL_OWNER_OWNERSHIP_GRANT_FAILED" }); } finally { await Promise.all([authorization.close(), identity.close()]); } // The identity committed; the grant did not; the single-use sentinel was never consumed. expect(await countRows(url, "ekklesia_core_user")).toBe(1); expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(0); expect(await countRows(url, "ekklesia_core_platform_owner_bootstrap")).toBe(0); // Rerunning the identical command finishes the job, which is the whole recovery story. const retry = bootstrapAgainst(url); try { await expect(retry.run(email)).resolves.toMatchObject({ identity: "adopted" }); } finally { await retry.close(); } expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(1); }); }); describe("concurrency", () => { it("gives exactly one owner when several bootstraps race", async () => { const url = await freshInstallation("race"); const attempts = 5; const handles = Array.from({ length: attempts }, () => bootstrapAgainst(url)); try { const outcomes = await Promise.allSettled( handles.map((handle, index) => handle.run(`racer-${index}-${randomBytes(3).toString("hex")}@example.test`), ), ); const fulfilled = outcomes.filter((outcome) => outcome.status === "fulfilled"); expect(fulfilled).toHaveLength(1); for (const outcome of outcomes.filter((candidate) => candidate.status === "rejected")) { expect((outcome as PromiseRejectedResult).reason).toMatchObject({ code: "INITIAL_OWNER_ALREADY_COMPLETED", }); } } finally { await Promise.all(handles.map((handle) => handle.close())); } expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(1); expect(await countRows(url, "ekklesia_core_platform_owner_bootstrap")).toBe(1); // The losers created nothing at all. Serializing whole invocations — not just the grant — is // what keeps a race from leaving signable-in accounts that own nothing behind. expect(await countRows(url, "ekklesia_core_user")).toBe(1); }); }); describe("the command itself", () => { it("succeeds, prints something useful, and leaks nothing", async () => { const url = await freshInstallation("cli"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const result = await runBootstrapCli(url, ["--email", email], ownerPassword); expect(result.code).toBe(0); expect(result.stdout).toContain("Initial platform owner created successfully."); expect(result.stdout).toContain(email.toLowerCase()); const everything = `${result.stdout} ${result.stderr}`; expect(everything).not.toContain(ownerPassword); expect(everything).not.toContain("postgresql://"); expect(everything).not.toContain("password"); const api = await startApiWithoutSignup(url); try { // The command's real product: an operator who can now sign in. expect((await signIn(apiOrigin(api), email, ownerPassword)).response.status).toBe(200); } finally { await api.shutdown(); } }); it("refuses a password supplied on the command line", async () => { const url = await freshInstallation("argv"); const result = await runBootstrapCli( url, ["--email", "owner@example.test", "--password", "hunter2hunter2"], undefined, ); expect(result.code).toBe(1); expect(result.stderr).toContain("--password is not supported"); expect(await countRows(url, "ekklesia_core_user")).toBe(0); }); it("fails clearly without a database, and refuses to migrate one itself", async () => { const missing = await runBootstrapCli( undefined, ["--email", "owner@example.test"], "whatever", ); expect(missing.code).toBe(1); expect(missing.stderr).toContain("DATABASE_URL is required."); // A database that exists but was never migrated is a deployment step that has not happened, // and this command says so rather than quietly running migrations for a production operator. const unmigrated = await createDisposableDatabase("unmigrated"); const result = await runBootstrapCli( unmigrated, ["--email", "owner@example.test"], ownerPassword, ); expect(result.code).toBe(1); expect(result.stderr).toContain("INITIAL_OWNER_SCHEMA_UNAVAILABLE"); const client = new Client({ connectionString: unmigrated }); await client.connect(); try { const tables = await safeQuery<{ count: string }>( client, `select count(*)::text from information_schema.tables where table_schema = 'public'`, ); expect(Number(tables[0]?.count ?? "0")).toBe(0); } finally { await client.end().catch(() => undefined); } }); it("refuses a second run at the command level too", async () => { const url = await freshInstallation("clirerun"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; expect((await runBootstrapCli(url, ["--email", email], ownerPassword)).code).toBe(0); const second = await runBootstrapCli(url, ["--email", email], "a different password"); expect(second.code).toBe(1); expect(second.stderr).toContain("INITIAL_OWNER_ALREADY_COMPLETED"); expect(second.stderr).not.toContain("a different password"); expect(await countRows(url, "ekklesia_core_platform_owner")).toBe(1); }); }); }); describe.sequential("initial platform-owner bootstrap and email verification", () => { /** * The trusted-bootstrap verification exception (ADR 0038). * * A verified address became a precondition for holding a session in Phase 7C Pass 2. The initial * owner is created on a freshly provisioned server, by an operator holding the database * credentials, at a moment when SMTP may not exist — so requiring an email round trip here would * produce a working installation nobody can ever sign in to. These scenarios exist to prove that * the exception is real, that it is narrow, and that mail remains genuinely absent throughout. */ it("creates the owner already verified, proven in the database and in the browser", async () => { const url = await freshInstallation("verifiedowner"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); try { await bootstrap.run(email); } finally { await bootstrap.close(); } // Database state: the exact column Better Auth's own verification endpoint writes, so the row // is indistinguishable from one that verified normally. expect(await emailVerifiedFlag(url, email)).toBe(true); // ...and the browser-facing consequence, which is the only thing that actually matters. const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); const session = await signIn(origin, email, ownerPassword); expect(session.response.status).toBe(200); expect(session.cookiePair).toBeDefined(); } finally { await api.shutdown(); } }); it("needs no outbound email configured, at bootstrap or at sign-in", async () => { const url = await freshInstallation("nomailowner"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); try { // No SMTP host, no relay, no network: `createBootstrapIdentityCreator` constructs its own // Better Auth instance with no `emailVerification` configuration at all. await bootstrap.run(email); } finally { await bootstrap.close(); } const api = await startApiWithoutSignup(url); try { // The API this suite starts configures no mail either, so nothing in this scenario could have // sent or received a message. expect(ownerApiConfig(url).mail).toBeUndefined(); expect((await signIn(apiOrigin(api), email, ownerPassword)).response.status).toBe(200); } finally { await api.shutdown(); } }); it("marks an adopted identity verified too, so bootstrap never yields an owner who cannot sign in", async () => { const url = await freshInstallation("adoptverified"); const email = `owner-${randomBytes(4).toString("hex")}@example.test`; // An identity that exists before bootstrap runs — created here through the same trusted creator, // which always writes `emailVerified: false`. const first = bootstrapAgainst(url); try { await first.run(email); } finally { await first.close(); } const client = new Client({ connectionString: url }); await client.connect(); try { await safeQuery(client, 'update "ekklesia_core_user" set "email_verified" = false'); await safeQuery(client, 'delete from "ekklesia_core_platform_owner_bootstrap"'); await safeQuery(client, 'delete from "ekklesia_core_platform_owner"'); } finally { await client.end().catch(() => undefined); } expect(await emailVerifiedFlag(url, email)).toBe(false); const second = bootstrapAgainst(url); let adopted: Awaited>; try { adopted = await second.run(email); } finally { await second.close(); } expect(adopted.identity).toBe("adopted"); expect(await emailVerifiedFlag(url, email)).toBe(true); }); it("verifies only the owner, leaving every other account subject to the normal policy", async () => { const url = await freshInstallation("narrowexception"); const ownerEmail = `owner-${randomBytes(4).toString("hex")}@example.test`; const otherEmail = `member-${randomBytes(4).toString("hex")}@example.test`; const bootstrap = bootstrapAgainst(url); try { await bootstrap.run(ownerEmail); // A second identity created through the identical trusted creator, but never bootstrapped. const identity = createIdentityDrizzleDatabase(url, {}); try { await createBootstrapIdentityCreator(identity.db)({ email: otherEmail, name: "Ordinary Member", password: ownerPassword, }); } finally { await identity.close().catch(() => undefined); } } finally { await bootstrap.close(); } expect(await emailVerifiedFlag(url, ownerEmail)).toBe(true); expect(await emailVerifiedFlag(url, otherEmail)).toBe(false); const api = await startApiWithoutSignup(url); try { const origin = apiOrigin(api); expect((await signIn(origin, ownerEmail, ownerPassword)).response.status).toBe(200); // The exception did not widen: an ordinary account still has to verify, and on this // mail-less installation it simply cannot sign in yet. const refused = await signIn(origin, otherEmail, ownerPassword); expect(refused.response.status).toBe(403); expect(await refused.response.json()).toMatchObject({ code: "EMAIL_NOT_VERIFIED" }); expect(refused.cookiePair).toBeUndefined(); } finally { await api.shutdown(); } }); });