/** * The app's own accounts: a person with no forge account signs up, gets a * vault of their own, and nobody — forge user, admin, or another account — * shares it. Passwords and session tokens are never stored as given. */ import { expect, test } from "bun:test"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { changePassword, deleteAccount, isAccount, logIn, logOut, sessionWho, signUp, } from "./accounts.ts"; import { addNote, getNote, listNotes } from "./actions.ts"; import { openDb } from "./db.ts"; import type { Who } from "./who.ts"; const db = openDb(mkdtempSync(join(tmpdir(), "app-accounts-"))); const must = (r: { ok: true; value: T } | { ok: false; message: string }): T => { if (!r.ok) throw new Error(r.message); return r.value; }; test("sign up makes a person with a vault of their own", async () => { const { account, token } = must(await signUp(db, { email: " Sam@Example.com ", password: "correct horse", name: "Sam" }, "")); expect(account.email).toBe("sam@example.com"); const sam = sessionWho(db, token); expect(sam).toEqual({ role: "user", id: `acct:${account.id}`, name: "Sam" }); expect(isAccount(sam)).toBe(true); const note = must(addNote(db, sam, { title: "Sam's", body: "hidden" })); // A forge user whose id happens to be the same number, and an admin, see nothing of it. const forge: Who = { role: "user", id: String(account.id), name: "forge" }; const admin: Who = { role: "admin", id: "u0", name: "root" }; expect(must(listNotes(db, forge))).toEqual([]); expect(must(listNotes(db, admin))).toEqual([]); expect(getNote(db, admin, { id: note.id }).ok).toBe(false); // Nothing stored is the password or the token itself. const stored = JSON.stringify(db.query("SELECT * FROM account").all()) + JSON.stringify(db.query("SELECT * FROM session").all()); expect(stored).not.toContain("correct horse"); expect(stored).not.toContain(token); }); test("sign up refuses a bad email, a short password, a taken email, or a wrong code", async () => { for (const [input, code] of [ [{ email: "not-an-email", password: "longenough" }, ""], [{ email: "a@b.co", password: "short" }, ""], [{ email: "SAM@example.com", password: "longenough" }, ""], [{ email: "new@b.co", password: "longenough", code: "nope" }, "secret"], ] as const) { const r = await signUp(db, input, code); expect([input.email, r.ok]).toEqual([input.email, false]); } expect((await signUp(db, { email: "new@b.co", password: "longenough", code: "secret" }, "secret")).ok).toBe(true); }); test("log in takes the right password only, and log out ends that session", async () => { expect((await logIn(db, { email: "sam@example.com", password: "wrong horse" })).ok).toBe(false); expect((await logIn(db, { email: "nobody@example.com", password: "correct horse" })).ok).toBe(false); const { token } = must(await logIn(db, { email: "SAM@example.com", password: "correct horse" })); expect(sessionWho(db, token).role).toBe("user"); logOut(db, token); expect(sessionWho(db, token)).toEqual({ role: "anon" }); expect(sessionWho(db, "x".repeat(43))).toEqual({ role: "anon" }); expect(sessionWho(db, undefined)).toEqual({ role: "anon" }); }); test("an expired session opens nothing", async () => { const { token } = must(await logIn(db, { email: "sam@example.com", password: "correct horse" })); db.query("UPDATE session SET expires_at = '2000-01-01T00:00:00.000Z'").run(); expect(sessionWho(db, token)).toEqual({ role: "anon" }); }); test("a new password ends every other session; deleting takes the vault with it", async () => { const first = must(await logIn(db, { email: "sam@example.com", password: "correct horse" })); const second = must(await logIn(db, { email: "sam@example.com", password: "correct horse" })); const sam = sessionWho(db, first.token); expect((await changePassword(db, sam, { current: "wrong", next: "battery staple" })).ok).toBe(false); const changed = must(await changePassword(db, sam, { current: "correct horse", next: "battery staple" })); expect(sessionWho(db, first.token).role).toBe("anon"); expect(sessionWho(db, second.token).role).toBe("anon"); expect(sessionWho(db, changed.token).role).toBe("user"); expect((await logIn(db, { email: "sam@example.com", password: "battery staple" })).ok).toBe(true); expect((await deleteAccount(db, sam, { password: "correct horse" })).ok).toBe(false); const gone = must(await deleteAccount(db, sam, { password: "battery staple" })); expect(gone.notes).toBe(1); expect(sessionWho(db, changed.token).role).toBe("anon"); expect((await logIn(db, { email: "sam@example.com", password: "battery staple" })).ok).toBe(false); // A forge user has no password here to change. expect((await changePassword(db, { role: "user", id: "u1", name: "a" }, { current: "x", next: "yyyyyyyy" })).ok).toBe(false); });