Public: anyone can read this; the crew can change it.
A test pins first-admin recovery: a code is spent once, never when expired, and `unburnFirstAdmin` gives back only a spent code #6806
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6787-a-test-pins-first-admin-recovery-a-code → main 1 commit fork
1 file
changed +91
−0
test/first-admin-recovery-unit.test.ts added +91
| @@ -0,0 +1,91 @@ | ||
| 1 | /** | |
| 2 | * The recovery half of `core/first_admin.ts` called directly, with `now` passed | |
| 3 | * in: a code is spent once, never when expired, `unburnFirstAdmin` gives back | |
| 4 | * only a spent code, and `strandedAdmins` / `adminCanSignIn` follow credentials. | |
| 5 | */ | |
| 6 | import { afterAll, expect, setDefaultTimeout, test } from "bun:test"; | |
| 7 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 8 | import { tmpdir } from "node:os"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { createApp } from "../src/app.ts"; | |
| 11 | import { | |
| 12 | FIRST_ADMIN_TTL_MS, | |
| 13 | adminCanSignIn, | |
| 14 | burnFirstAdminRecovery, | |
| 15 | mintFirstAdminRecovery, | |
| 16 | strandedAdmins, | |
| 17 | unburnFirstAdmin, | |
| 18 | } from "../src/core/first_admin.ts"; | |
| 19 | import { createToken } from "../src/core/tokens.ts"; | |
| 20 | import { createUser } from "../src/core/users.ts"; | |
| 21 | import { quietLogs } from "./helpers/globals.ts"; | |
| 22 | ||
| 23 | quietLogs(); | |
| 24 | setDefaultTimeout(30_000); | |
| 25 | ||
| 26 | const dirs: string[] = []; | |
| 27 | afterAll(async () => { | |
| 28 | for (const d of dirs) await rm(d, { recursive: true, force: true }); | |
| 29 | }); | |
| 30 | ||
| 31 | const NOW = Date.now(); | |
| 32 | ||
| 33 | /** One admin with no credential at all. */ | |
| 34 | async function stranded() { | |
| 35 | const dir = await mkdtemp(join(tmpdir(), "forge-first-admin-unit-")); | |
| 36 | dirs.push(dir); | |
| 37 | const app = createApp({ dataDir: dir, port: 0, passkeyOnlySignup: true }); | |
| 38 | const made = await createUser(app.db, { origin: "seed", trusted: true, username: "opsbot", email: "[email protected]", isAdmin: true }); | |
| 39 | if (!made.ok) throw new Error("user"); | |
| 40 | return { db: app.db, id: made.value.id }; | |
| 41 | } | |
| 42 | ||
| 43 | test("a minted code burns once and returns the admin's id", async () => { | |
| 44 | const { db, id } = await stranded(); | |
| 45 | const { code } = mintFirstAdminRecovery(db, id, NOW)!; | |
| 46 | expect(burnFirstAdminRecovery(db, code, NOW + 1000)).toBe(id); | |
| 47 | expect(burnFirstAdminRecovery(db, code, NOW + 2000)).toBeUndefined(); | |
| 48 | }); | |
| 49 | ||
| 50 | test("an expired, unknown or empty code is worth nothing", async () => { | |
| 51 | const { db, id } = await stranded(); | |
| 52 | const { code } = mintFirstAdminRecovery(db, id, NOW)!; | |
| 53 | expect(burnFirstAdminRecovery(db, code, NOW + FIRST_ADMIN_TTL_MS)).toBeUndefined(); | |
| 54 | expect(burnFirstAdminRecovery(db, "A".repeat(43), NOW)).toBeUndefined(); | |
| 55 | expect(burnFirstAdminRecovery(db, "", NOW)).toBeUndefined(); | |
| 56 | // Still unspent: just inside the deadline it works. | |
| 57 | expect(burnFirstAdminRecovery(db, code, NOW + FIRST_ADMIN_TTL_MS - 1)).toBe(id); | |
| 58 | }); | |
| 59 | ||
| 60 | test("unburnFirstAdmin makes a spent code usable once more, and ignores the rest", async () => { | |
| 61 | const { db, id } = await stranded(); | |
| 62 | const { code } = mintFirstAdminRecovery(db, id, NOW)!; | |
| 63 | expect(burnFirstAdminRecovery(db, code, NOW)).toBe(id); | |
| 64 | unburnFirstAdmin(db, code); | |
| 65 | expect(burnFirstAdminRecovery(db, code, NOW)).toBe(id); | |
| 66 | expect(burnFirstAdminRecovery(db, code, NOW)).toBeUndefined(); | |
| 67 | // An unknown code gives nothing back; the spent one stays spent. | |
| 68 | unburnFirstAdmin(db, "B".repeat(43)); | |
| 69 | unburnFirstAdmin(db, ""); | |
| 70 | expect(burnFirstAdminRecovery(db, code, NOW)).toBeUndefined(); | |
| 71 | }); | |
| 72 | ||
| 73 | test("a code that was never spent is untouched by unburn", async () => { | |
| 74 | const { db, id } = await stranded(); | |
| 75 | const { code } = mintFirstAdminRecovery(db, id, NOW)!; | |
| 76 | unburnFirstAdmin(db, code); | |
| 77 | expect(burnFirstAdminRecovery(db, code, NOW)).toBe(id); | |
| 78 | expect(burnFirstAdminRecovery(db, code, NOW)).toBeUndefined(); | |
| 79 | }); | |
| 80 | ||
| 81 | test("strandedAdmins lists an admin with no way in, and adminCanSignIn turns true with one", async () => { | |
| 82 | const { db, id } = await stranded(); | |
| 83 | expect(strandedAdmins(db).map((a) => a.id)).toEqual([id]); | |
| 84 | expect(adminCanSignIn(db, NOW)).toBe(false); | |
| 85 | createToken(db, id, "t", "admin", { expiresAt: new Date(NOW - 1000).toISOString() }); | |
| 86 | expect(adminCanSignIn(db, NOW)).toBe(false); | |
| 87 | createToken(db, id, "t2", "admin", { expiresAt: new Date(NOW + 60_000).toISOString() }); | |
| 88 | expect(adminCanSignIn(db, NOW)).toBe(true); | |
| 89 | // Once someone can sign in, no recovery code is minted. | |
| 90 | expect(mintFirstAdminRecovery(db, id, NOW)).toBeUndefined(); | |
| 91 | }); |
new above
offline