Public: anyone can read this; the crew can change it.
When signup is closed, no new account is made by any door, except the forge's first #2258
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/2256-when-signup-is-closed-no-new-account-is → main 3 commits fork 4 threads · 4 unresolved
1 file
changed +118
−0
test/signup-closed.test.ts added +118
| @@ -0,0 +1,118 @@ | ||
| 1 | /** | |
| 2 | * With `signup` closed (or `invite`, which acts as closed), no new account is | |
| 3 | * made by any door; the forge's first always is; a switch is live. | |
| 4 | */ | |
| 5 | ||
| 6 | import { afterAll, beforeAll, describe, expect, 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, type App } from "../src/app.ts"; | |
| 11 | import { setSignup, type SignupMode } from "../src/settings/signup.ts"; | |
| 12 | import { signUp, signUpWithPasskey } from "../src/actions/users.ts"; | |
| 13 | import { getUserByName } from "../src/core/users.ts"; | |
| 14 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 15 | ||
| 16 | const TOKEN = "edge-secret-0123456789"; | |
| 17 | const edge = (login: string) => ({ "x-plat-user": login, "x-plat-edge-token": TOKEN }); | |
| 18 | ||
| 19 | describe("closed signup", () => { | |
| 20 | let dataDir: string; | |
| 21 | let app: App; | |
| 22 | let servers: Servers; | |
| 23 | let base: string; | |
| 24 | ||
| 25 | beforeAll(async () => { | |
| 26 | dataDir = await mkdtemp(join(tmpdir(), "forge-signup-closed-")); | |
| 27 | app = createApp({ dataDir, port: 0, url: "https://forge.example", edgeToken: TOKEN }); | |
| 28 | servers = await serve(app); | |
| 29 | base = servers.url; | |
| 30 | }); | |
| 31 | afterAll(async () => { | |
| 32 | await servers.stop(); | |
| 33 | app.close(); | |
| 34 | await rm(dataDir, { recursive: true, force: true }); | |
| 35 | }); | |
| 36 | ||
| 37 | const mode = (m: SignupMode) => setSignup(app.db, m, "boot"); | |
| 38 | const post = (username: string) => | |
| 39 | fetch(`${base}/api/v1/users`, { | |
| 40 | method: "POST", | |
| 41 | headers: { "content-type": "application/json", origin: "https://forge.example" }, | |
| 42 | body: JSON.stringify({ username, email: `${username}@example.com`, password: "a-good-passphrase" }), | |
| 43 | }); | |
| 44 | const me = (login: string) => fetch(`${base}/api/v1/user`, { headers: edge(login) }); | |
| 45 | ||
| 46 | test("the first account is made even when closed", async () => { | |
| 47 | mode("closed"); | |
| 48 | const first = await me("founder"); | |
| 49 | expect(first.status).toBe(200); | |
| 50 | expect(((await first.json()) as { isAdmin: boolean }).isAdmin).toBe(true); | |
| 51 | }); | |
| 52 | ||
| 53 | for (const closed of ["closed", "invite"] as const) { | |
| 54 | test(`${closed}: page, API and edge make no account`, async () => { | |
| 55 | mode(closed); | |
| 56 | const page = await fetch(`${base}/signup`); | |
| 57 | const html = await page.text(); | |
| 58 | expect(html).toContain("Signup is closed on this forge."); | |
| 59 | expect(html).not.toContain('name="password"'); | |
| 60 | ||
| 61 | const res = await post(`via-api-${closed}`); | |
| 62 | expect(res.status).toBe(403); | |
| 63 | const body = (await res.json()) as { error: string; message: string }; | |
| 64 | expect(body.error).toBe("signup_closed"); | |
| 65 | expect(body.message).toContain("closed"); | |
| 66 | expect(getUserByName(app.db, `via-api-${closed}`)).toBeUndefined(); | |
| 67 | ||
| 68 | expect((await me(`via-edge-${closed}`)).status).toBe(401); | |
| 69 | expect(getUserByName(app.db, `via-edge-${closed}`)).toBeUndefined(); | |
| 70 | }); | |
| 71 | } | |
| 72 | ||
| 73 | test("existing accounts still sign in through the edge when closed", async () => { | |
| 74 | mode("closed"); | |
| 75 | expect((await me("founder")).status).toBe(200); | |
| 76 | }); | |
| 77 | ||
| 78 | test("open makes accounts; switching takes effect on the next request", async () => { | |
| 79 | mode("open"); | |
| 80 | expect(await (await fetch(`${base}/signup`)).text()).toContain('name="password"'); | |
| 81 | expect((await post("newcomer")).status).toBe(201); | |
| 82 | expect((await me("edgy")).status).toBe(200); | |
| 83 | mode("closed"); | |
| 84 | expect((await post("late")).status).toBe(403); | |
| 85 | expect((await me("later")).status).toBe(401); | |
| 86 | mode("open"); | |
| 87 | expect((await post("late")).status).toBe(201); | |
| 88 | }); | |
| 89 | ||
| 90 | test("closed: the passkey and form doors and the terminal refuse too", async () => { | |
| 91 | mode("closed"); | |
| 92 | const json = (path: string, body: unknown) => | |
| 93 | fetch(`${base}${path}`, { | |
| 94 | method: "POST", | |
| 95 | headers: { "content-type": "application/json", origin: "https://forge.example" }, | |
| 96 | body: JSON.stringify(body), | |
| 97 | }); | |
| 98 | const options = await json("/signup/passkey/options", { username: "pk", email: "[email protected]" }); | |
| 99 | expect(options.status).toBe(403); | |
| 100 | expect(JSON.stringify(await options.json())).toContain("Signup is closed on this forge."); | |
| 101 | ||
| 102 | const form = await fetch(`${base}/signup`, { | |
| 103 | method: "POST", | |
| 104 | headers: { "content-type": "application/x-www-form-urlencoded", origin: "https://forge.example" }, | |
| 105 | body: new URLSearchParams({ username: "formed", email: "[email protected]", password: "a-good-passphrase" }), | |
| 106 | }); | |
| 107 | expect(form.status).toBe(403); | |
| 108 | expect(await form.text()).toContain("Signup is closed on this forge."); | |
| 109 | expect(getUserByName(app.db, "formed")).toBeUndefined(); | |
| 110 | ||
| 111 | const input = { username: "direct", email: "[email protected]" }; | |
| 112 | const key = await signUpWithPasskey(app, { ...input, passkey: {} as never, passkeyName: "k" }); | |
| 113 | expect(key).toEqual({ ok: false, error: "signup_closed" }); | |
| 114 | const terminal = await signUp(app, { ...input, password: "a-good-passphrase" }, { door: "terminal" }); | |
| 115 | expect(terminal).toEqual({ ok: false, error: "signup_closed" }); | |
| 116 | expect(getUserByName(app.db, "direct")).toBeUndefined(); | |
| 117 | }); | |
| 118 | }); |
new above
offline