Files
openwork/evals/specs/initial-admin-bootstrap.e2e.test.ts

354 lines
17 KiB
TypeScript

import { expect } from "vitest";
import { clickButton, denFetch, evalIn, fill, signIn, visibleText, waitFor } from "@openwork/behaviors";
import { localMysqlIsRunning, localRedisIsRunning, queryDenDatabase, server, test } from "@openwork/testkit";
import { navigate } from "@openwork/cdp";
import { chrome } from "@openwork/hosts";
const localPlacement = process.env.OPENWORK_EVAL_DAYTONA !== "1" && !process.env.OPENWORK_EVAL_DEN_API_URL?.trim();
const mysqlOpen = await localMysqlIsRunning();
const redisOpen = await localRedisIsRunning();
const title = !localPlacement
? "Initial administrator bootstrap skipped — needs local placement without OPENWORK_EVAL_DEN_API_URL"
: !mysqlOpen
? "Initial administrator bootstrap skipped — needs MySQL on 127.0.0.1:3306"
: !redisOpen
? "Initial administrator bootstrap skipped — needs Redis on 127.0.0.1:6379"
: "a private zero-user Den deployment can be claimed once by a configured administrator with an operator code";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stringField(value: unknown, key: string): string {
if (!isRecord(value)) return "";
const field = value[key];
return typeof field === "string" ? field : "";
}
function numberField(value: unknown, key: string): number {
if (!isRecord(value)) return 0;
const field = value[key];
return typeof field === "number" ? field : 0;
}
async function readBootstrapFacts(databaseUrl: string, adminEmail: string) {
const users = await queryDenDatabase(databaseUrl, "SELECT id, email FROM `user` ORDER BY created_at, id");
const orgs = await queryDenDatabase(databaseUrl, "SELECT id, slug FROM organization ORDER BY created_at, id");
const members = await queryDenDatabase(
databaseUrl,
"SELECT m.role, m.user_id, o.slug FROM member m INNER JOIN organization o ON o.id = m.organization_id WHERE lower((SELECT email FROM `user` WHERE id = m.user_id)) = ?",
[adminEmail.toLowerCase()],
);
const admins = await queryDenDatabase(databaseUrl, "SELECT email FROM admin_allowlist WHERE email = ?", [adminEmail.toLowerCase()]);
return {
users: users.length,
orgs: orgs.length,
members: members.length,
adminAllowlistRows: admins.length,
firstMemberRole: isRecord(members[0]) ? stringField(members[0], "role") : "",
firstOrgSlug: isRecord(orgs[0]) ? stringField(orgs[0], "slug") : "",
};
}
test.skipIf(!localPlacement || !mysqlOpen || !redisOpen)(title, { timeout: 600_000 }, async ({ evidence, place }) => {
const runId = `${Date.now().toString(36)}${process.pid.toString(36)}`;
const adminEmail = `Initial.Admin.${runId}@example.com`;
const normalizedAdminEmail = adminEmail.toLowerCase();
const otherAdminEmail = `second.${runId}@example.com`;
const setupCode = `eval-initial-admin-code-${runId}`;
const adminPassword = "OpenWorkEval123!";
await using den = await server({
place,
provision: false,
env: {
DEN_ORG_MODE: "single_org",
DEN_SINGLE_ORG_NAME: "Private OpenWork",
DEN_SINGLE_ORG_SLUG: "private-openwork",
DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP: "false",
DEN_SINGLE_ORG_OWNER_EMAILS: `${adminEmail},${otherAdminEmail}`,
DEN_BOOTSTRAP_ADMIN_EMAILS: adminEmail,
DEN_INITIAL_ADMIN_BOOTSTRAP_CODE: setupCode,
},
});
const databaseUrl = den.database?.url;
if (!databaseUrl) throw new Error("initial admin bootstrap spec requires the local isolated database handle");
const availability = await denFetch(den.ref, "/v1/auth/bootstrap/status");
expect(availability.response.ok, `bootstrap status: HTTP ${availability.response.status} ${availability.text.slice(0, 300)}`).toBe(true);
expect(stringField(availability.body, "status")).toBe("available");
expect(availability.text).not.toContain(normalizedAdminEmail);
evidence.recordAssertionEvidence(
"A private zero-user deployment reports bootstrap availability without exposing configured administrator emails",
`GET /v1/auth/bootstrap/status returned ${stringField(availability.body, "status")} and did not include the configured email.`,
availability.response.ok && stringField(availability.body, "status") === "available" && !availability.text.includes(normalizedAdminEmail),
);
const unconfigured = await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: `outsider.${runId}@example.com`, code: setupCode }),
});
expect(unconfigured.response.status).toBe(403);
expect(unconfigured.text).not.toContain(normalizedAdminEmail);
expect(unconfigured.text).not.toContain("owner");
expect(unconfigured.text).not.toContain("allowlist");
const invalidCode = await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: adminEmail, code: "not-the-code" }),
});
expect(invalidCode.response.status).toBe(403);
expect(invalidCode.text).not.toContain(normalizedAdminEmail);
expect(invalidCode.text).toBe(unconfigured.text);
evidence.recordAssertionEvidence(
"Failed bootstrap verification gives a generic rejection for both unconfigured emails and invalid codes",
`Unconfigured email and invalid code both returned the same HTTP 403 without privileged email, owner, or allowlist details.`,
unconfigured.response.status === 403
&& invalidCode.response.status === 403
&& invalidCode.text === unconfigured.text
&& !unconfigured.text.includes(normalizedAdminEmail)
&& !invalidCode.text.includes(normalizedAdminEmail)
&& !unconfigured.text.includes("allowlist")
&& !invalidCode.text.includes("allowlist"),
);
const rateLimitedEmail = `rate.${runId}@example.com`;
const rateLimitAttempts = [];
for (let index = 0; index < 6; index += 1) {
rateLimitAttempts.push(await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
headers: { "x-forwarded-for": `192.0.2.${index + 1}` },
body: JSON.stringify({ email: rateLimitedEmail, code: `wrong-${index}` }),
}));
}
const rateLimitStatuses = rateLimitAttempts.map((attempt) => attempt.response.status);
expect(rateLimitStatuses.slice(0, 5)).toEqual([403, 403, 403, 403, 403]);
expect(rateLimitStatuses[5]).toBe(429);
evidence.recordAssertionEvidence(
"Bootstrap verification rate limiting cannot be bypassed by changing forwarded IP headers",
`Six attempts for the same email with six different X-Forwarded-For values returned statuses ${rateLimitStatuses.join(", ")}.`,
rateLimitStatuses.slice(0, 5).every((status) => status === 403) && rateLimitStatuses[5] === 429,
);
const valid = await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: adminEmail, code: setupCode }),
});
const grant = stringField(valid.body, "grant");
expect(valid.response.ok, `valid setup verification: HTTP ${valid.response.status} ${valid.text.slice(0, 300)}`).toBe(true);
expect(grant).toMatch(/^ow_bootstrap_/);
const weakSignup = await denFetch(den.ref, "/api/auth/sign-up/email", {
method: "POST",
body: JSON.stringify({ email: adminEmail, name: "Initial Admin", password: "short", bootstrapGrant: grant }),
});
expect(weakSignup.response.status).toBe(400);
expect(weakSignup.text).toMatch(/password/i);
const factsAfterWeakPassword = await readBootstrapFacts(databaseUrl, adminEmail);
expect(factsAfterWeakPassword.users).toBe(0);
evidence.recordAssertionEvidence(
"Bootstrap account creation still exercises password policy and leaves setup available on failure",
`Weak password signup returned HTTP ${weakSignup.response.status}; users=${factsAfterWeakPassword.users}.`,
weakSignup.response.status === 400 && factsAfterWeakPassword.users === 0,
);
await using browser = await chrome({ name: "initial-admin-bootstrap", startUrl: den.ref.webUrl, headless: true });
await browser.client.send("Emulation.setDeviceMetricsOverride", {
width: 1280,
height: 900,
deviceScaleFactor: 1,
mobile: false,
});
await navigate(browser.client, `${den.ref.webUrl}/setup`);
await waitFor(browser, `/set up your administrator account/i.test(document.body?.innerText ?? "")`, {
timeoutMs: 90_000,
label: "setup page title",
});
await waitFor(browser, `/one-time setup code/i.test(document.body?.innerText ?? "") && Boolean(document.querySelector('input[name="setupCode"]'))`, {
timeoutMs: 30_000,
label: "setup verification form",
});
let setupText = await visibleText(browser);
expect(setupText).toMatch(/Set up your administrator account/i);
expect(setupText).toMatch(/one-time setup code/i);
expect(setupText).not.toContain(normalizedAdminEmail);
expect(await evalIn(browser, `document.querySelector('input[name="setupCode"]')?.getAttribute("type")`)).toBe("password");
evidence.recordAssertionEvidence(
"The setup page explains first-admin setup without displaying configured privileged emails",
`The setup page text includes the administrator title and one-time setup code prompt, and omits the configured email.`,
/set up your administrator account/i.test(setupText)
&& /one-time setup code/i.test(setupText)
&& !setupText.includes(normalizedAdminEmail),
);
await fill(browser, 'input[name="email"]', adminEmail.toUpperCase());
await fill(browser, 'input[name="setupCode"]', setupCode);
await clickButton(browser, "Continue");
await waitFor(browser, `/create your administrator account/i.test(document.body?.innerText ?? "")`, {
timeoutMs: 60_000,
label: "account creation step",
});
setupText = await visibleText(browser);
expect(setupText).toMatch(/Create your administrator account/i);
expect(setupText).not.toContain(setupCode);
await fill(browser, 'input[name="name"]', "Initial Admin");
await fill(browser, 'input[name="password"]', "short");
await clickButton(browser, "Create administrator");
await waitFor(browser, `/password/i.test(document.body?.innerText ?? "")`, {
timeoutMs: 30_000,
label: "password policy feedback",
});
await fill(browser, 'input[name="password"]', adminPassword);
await clickButton(browser, "Create administrator");
await waitFor(browser, `location.pathname === "/install" || location.pathname.startsWith("/dashboard") || /setup is complete/i.test(document.body?.innerText ?? "")`, {
timeoutMs: 90_000,
label: "signed in after bootstrap",
});
const factsAfterSetup = await readBootstrapFacts(databaseUrl, adminEmail);
expect(factsAfterSetup.users).toBe(1);
expect(factsAfterSetup.orgs).toBe(1);
expect(factsAfterSetup.members).toBe(1);
expect(factsAfterSetup.firstMemberRole).toBe("owner");
expect(factsAfterSetup.firstOrgSlug).toBe("private-openwork");
expect(factsAfterSetup.adminAllowlistRows).toBe(1);
evidence.recordAssertionEvidence(
"Successful setup creates exactly one user, singleton organization, owner membership, and platform-admin authorization",
`users=${factsAfterSetup.users}; orgs=${factsAfterSetup.orgs}; members=${factsAfterSetup.members}; role=${factsAfterSetup.firstMemberRole}; slug=${factsAfterSetup.firstOrgSlug}; platformAdminRows=${factsAfterSetup.adminAllowlistRows}.`,
factsAfterSetup.users === 1
&& factsAfterSetup.orgs === 1
&& factsAfterSetup.members === 1
&& factsAfterSetup.firstMemberRole === "owner"
&& factsAfterSetup.firstOrgSlug === "private-openwork"
&& factsAfterSetup.adminAllowlistRows === 1,
);
const replayGrant = await denFetch(den.ref, "/api/auth/sign-up/email", {
method: "POST",
body: JSON.stringify({ email: otherAdminEmail, name: "Second Admin", password: adminPassword, bootstrapGrant: grant }),
});
expect(replayGrant.response.status).toBe(403);
const replayCode = await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: adminEmail, code: setupCode }),
});
expect(replayCode.response.status).toBe(409);
const replayUnconfiguredEmail = await denFetch(den.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: `outsider-after.${runId}@example.com`, code: setupCode }),
});
expect(replayUnconfiguredEmail.response.status).toBe(409);
evidence.recordAssertionEvidence(
"The setup code and bootstrap grant cannot be replayed or used for email enumeration after setup completes",
`grant replay returned HTTP ${replayGrant.response.status}; configured-email code replay returned HTTP ${replayCode.response.status}; unconfigured-email code replay returned HTTP ${replayUnconfiguredEmail.response.status}.`,
replayGrant.response.status === 403 && replayCode.response.status === 409 && replayUnconfiguredEmail.response.status === 409,
);
await navigate(browser.client, `${den.ref.webUrl}/setup`);
await waitFor(browser, `/setup is complete/i.test(document.body?.innerText ?? "")`, {
timeoutMs: 60_000,
label: "setup complete page",
});
const completedText = await visibleText(browser);
expect(completedText).toMatch(/Setup is complete/i);
expect(completedText).toMatch(/Sign in/i);
const publicSignup = await denFetch(den.ref, "/api/auth/sign-up/email", {
method: "POST",
body: JSON.stringify({ email: `unknown.${runId}@example.com`, name: "Unknown User", password: adminPassword }),
});
expect(publicSignup.response.status).toBe(403);
await denFetch(den.ref, "/api/auth/sign-out", { method: "POST" });
const normalSignin = await signIn(den.ref, { email: normalizedAdminEmail, password: adminPassword });
expect(normalSignin.token).toBeTruthy();
evidence.recordAssertionEvidence(
"After bootstrap, public signup remains disabled while the administrator uses normal password sign-in",
`Unknown signup returned HTTP ${publicSignup.response.status}; normal admin signin produced a session token length ${normalSignin.token.length}.`,
publicSignup.response.status === 403 && normalSignin.token.length > 0,
);
await using existingUsersDen = await server({
place,
web: false,
org: { name: `Existing Users ${runId}`, admin: { email: `existing.${runId}@example.com` }, members: {} },
env: {
DEN_ORG_MODE: "multi_org",
DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP: "true",
DEN_SINGLE_ORG_OWNER_EMAILS: `existing.${runId}@example.com`,
DEN_BOOTSTRAP_ADMIN_EMAILS: adminEmail,
DEN_INITIAL_ADMIN_BOOTSTRAP_CODE: setupCode,
},
});
const unavailable = await denFetch(existingUsersDen.ref, "/v1/auth/bootstrap/status");
expect(stringField(unavailable.body, "status")).toBe("complete");
evidence.recordAssertionEvidence(
"An existing installation with users does not expose setup even if bootstrap configuration remains present",
`GET /v1/auth/bootstrap/status on a provisioned Den returned ${stringField(unavailable.body, "status")}.`,
stringField(unavailable.body, "status") === "complete",
);
const concurrentCode = `eval-concurrent-code-${runId}`;
await using raceDen = await server({
place,
provision: false,
web: false,
env: {
DEN_ORG_MODE: "single_org",
DEN_SINGLE_ORG_NAME: "Race OpenWork",
DEN_SINGLE_ORG_SLUG: "race-openwork",
DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP: "false",
DEN_SINGLE_ORG_OWNER_EMAILS: `${adminEmail},${otherAdminEmail}`,
DEN_BOOTSTRAP_ADMIN_EMAILS: `${adminEmail},${otherAdminEmail}`,
DEN_INITIAL_ADMIN_BOOTSTRAP_CODE: concurrentCode,
},
});
if (!raceDen.database?.url) throw new Error("concurrency Den needs database handle");
const [raceGrantOne, raceGrantTwo] = await Promise.all([
denFetch(raceDen.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: adminEmail, code: concurrentCode }),
}),
denFetch(raceDen.ref, "/v1/auth/bootstrap/verify", {
method: "POST",
body: JSON.stringify({ email: adminEmail.toUpperCase(), code: concurrentCode }),
}),
]);
const raceResponses = await Promise.all([
denFetch(raceDen.ref, "/api/auth/sign-up/email", {
method: "POST",
body: JSON.stringify({ email: adminEmail, name: "Race One", password: adminPassword, bootstrapGrant: stringField(raceGrantOne.body, "grant") }),
}),
denFetch(raceDen.ref, "/api/auth/sign-up/email", {
method: "POST",
body: JSON.stringify({ email: adminEmail, name: "Race Two", password: adminPassword, bootstrapGrant: stringField(raceGrantTwo.body, "grant") }),
}),
]);
const raceSuccesses = raceResponses.filter((result) => result.response.ok).length;
const raceFailures = raceResponses.filter((result) => !result.response.ok).length;
const raceFacts = await readBootstrapFacts(raceDen.database.url, adminEmail);
expect(raceSuccesses).toBe(1);
expect(raceFailures).toBe(1);
expect(raceFacts.users).toBe(1);
expect(raceFacts.orgs).toBe(1);
evidence.recordAssertionEvidence(
"Concurrent same-email setup attempts create exactly one administrator",
`successes=${raceSuccesses}; expectedFailures=${raceFailures}; users=${raceFacts.users}; orgs=${raceFacts.orgs}.`,
raceSuccesses === 1 && raceFailures === 1 && raceFacts.users === 1 && raceFacts.orgs === 1,
);
const log = await den.apiLog();
expect(log).not.toContain(setupCode);
expect(log).not.toContain(concurrentCode);
evidence.recordAssertionEvidence(
"Den API logs do not contain submitted setup codes",
`The captured API log did not contain either submitted setup code.`,
!log.includes(setupCode) && !log.includes(concurrentCode),
);
expect(numberField({ count: factsAfterSetup.users }, "count")).toBe(1);
});