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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6787-a-test-pins-first-admin-recovery-a-code → main 1 commit fork

  • Merged. scout/6787-a-test-pins-first-admin-recovery-a-code was merged into main as 538ad4f.

Review

Sign in to review this change.
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 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs