Skip to content
File

Blob: tests/worker/bootstrap-admin.test.ts

typescript100 lines
1import { env } from "cloudflare:workers";
2import { eq, sql } from "drizzle-orm";
3import { beforeEach, describe, expect, it } from "vitest";
4 
5import { makeAuth } from "@/worker/auth";
6import { makeDb } from "@/worker/db";
7import { accounts, sessions, users } from "@/worker/db/schema";
8 
9import { ISSUER } from "./helpers";
10 
11const fetchRoleByEmail = async (email: string): Promise<string | null> => {
12 const db = makeDb(env);
13 const row = await db.select({ role: users.role }).from(users).where(eq(users.email, email)).get();
14 return row?.role ?? null;
15};
16 
17const wipeUsers = async (): Promise<void> => {
18 const db = makeDb(env);
19 // Order matters: accounts/sessions FK -> users. Cascade should handle it,
20 // but be explicit for clarity in this fixture.
21 await db.delete(sessions).where(sql`1 = 1`);
22 await db.delete(accounts).where(sql`1 = 1`);
23 await db.delete(users).where(sql`1 = 1`);
24};
25 
26// The bootstrap-admin promotion hook is gated on userCount === 0 so a
27// stale BOOTSTRAP_ADMIN_EMAIL is safe to leave configured. Each test
28// clears the users table first to make assertions on the "first signup"
29// branch unambiguous.
30describe("bootstrap admin promotion", () => {
31 beforeEach(wipeUsers);
32 
33 it("promotes the email matching BOOTSTRAP_ADMIN_EMAIL on the very first signup", async () => {
34 const adminEmail = "bootstrap-admin@example.com";
35 const auth = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: adminEmail } as Env, { baseURL: ISSUER, issuer: ISSUER });
36 await auth.api.signUpEmail({
37 body: { email: adminEmail, password: "correct-horse-battery-staple", name: "Bootstrap" },
38 asResponse: false,
39 });
40 expect(await fetchRoleByEmail(adminEmail)).toBe("admin");
41 }, 15_000);
42 
43 it("does NOT promote a matching email when users already exist (stale env safety)", async () => {
44 const adminEmail = "stale-admin@example.com";
45 // Seed an unrelated user so userCount > 0 before the BOOTSTRAP signup.
46 const auth = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: "" } as Env, { baseURL: ISSUER, issuer: ISSUER });
47 await auth.api.signUpEmail({
48 body: { email: "first-user@example.com", password: "correct-horse-battery-staple", name: "First" },
49 asResponse: false,
50 });
51 expect(await fetchRoleByEmail("first-user@example.com")).toBe("user");
52 
53 // Now sign up the BOOTSTRAP_ADMIN_EMAIL — must be `user`, not `admin`.
54 const authWithStaleEnv = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: adminEmail } as Env, {
55 baseURL: ISSUER,
56 issuer: ISSUER,
57 });
58 await authWithStaleEnv.api.signUpEmail({
59 body: { email: adminEmail, password: "correct-horse-battery-staple", name: "Stale" },
60 asResponse: false,
61 });
62 expect(await fetchRoleByEmail(adminEmail)).toBe("user");
63 }, 30_000);
64 
65 it("does not promote a non-matching email even when BOOTSTRAP_ADMIN_EMAIL is set", async () => {
66 const adminEmail = "intended-admin@example.com";
67 const otherEmail = "not-the-admin@example.com";
68 const auth = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: adminEmail } as Env, { baseURL: ISSUER, issuer: ISSUER });
69 await auth.api.signUpEmail({
70 body: { email: otherEmail, password: "correct-horse-battery-staple", name: "Other" },
71 asResponse: false,
72 });
73 expect(await fetchRoleByEmail(otherEmail)).toBe("user");
74 }, 15_000);
75 
76 it("matches BOOTSTRAP_ADMIN_EMAIL case-insensitively on first signup", async () => {
77 const adminEmail = "Mixed-Case-Admin@Example.com";
78 const auth = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: "mixed-case-admin@example.com" } as Env, {
79 baseURL: ISSUER,
80 issuer: ISSUER,
81 });
82 await auth.api.signUpEmail({
83 body: { email: adminEmail, password: "correct-horse-battery-staple", name: "Mixed" },
84 asResponse: false,
85 });
86 // Better Auth normalises emails to lowercase before insert.
87 expect(await fetchRoleByEmail(adminEmail.toLowerCase())).toBe("admin");
88 }, 15_000);
89 
90 it("leaves new users as role=user when BOOTSTRAP_ADMIN_EMAIL is empty", async () => {
91 const email = "no-bootstrap@example.com";
92 const auth = makeAuth({ ...env, BOOTSTRAP_ADMIN_EMAIL: "" } as Env, { baseURL: ISSUER, issuer: ISSUER });
93 await auth.api.signUpEmail({
94 body: { email, password: "correct-horse-battery-staple", name: "Plain" },
95 asResponse: false,
96 });
97 expect(await fetchRoleByEmail(email)).toBe("user");
98 }, 15_000);
99});