354 lines
17 KiB
TypeScript
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);
|
|
});
|