fix(core): serialise first-user registration, equalise login timing
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
e1faa7433b
commit
fe3c9e4881
@@ -106,4 +106,50 @@ forEachDialect("auth", (getDb) => {
|
||||
await revokeAppToken(db, u.id, id);
|
||||
expect(await verifyAppToken(db, token, "read")).toBeNull();
|
||||
});
|
||||
|
||||
test("concurrent first registrations yield exactly one admin", async () => {
|
||||
const db = getDb();
|
||||
const results = await Promise.allSettled([
|
||||
registerUser(db, { username: "alice", password: "x" }),
|
||||
registerUser(db, { username: "bob", password: "x" }),
|
||||
]);
|
||||
const ok = results.filter((r) => r.status === "fulfilled");
|
||||
const bad = results.filter((r) => r.status === "rejected");
|
||||
expect(ok.length).toBe(1);
|
||||
expect((ok[0] as PromiseFulfilledResult<{ role: string }>).value.role).toBe("admin");
|
||||
expect(((bad[0] as PromiseRejectedResult).reason as AuthError).code).toBe("invite_required");
|
||||
const admins = await db.k.selectFrom("users").select("id").where("role", "=", "admin").execute();
|
||||
expect(admins.length).toBe(1);
|
||||
});
|
||||
|
||||
test("no invite means invite_required even for a taken username", async () => {
|
||||
const db = getDb();
|
||||
await registerUser(db, { username: "alice", password: "x" });
|
||||
expect(await code(registerUser(db, { username: "alice", password: "x" }))).toBe("invite_required");
|
||||
});
|
||||
|
||||
test("only hashes are stored for invites and app tokens", async () => {
|
||||
const db = getDb();
|
||||
const u = await registerUser(db, { username: "alice", password: "x" });
|
||||
const inv = await createInvite(db, u.id);
|
||||
const { token } = await createAppToken(db, u.id, "cli", ["read"]);
|
||||
const invRow = await db.k.selectFrom("invites").selectAll().executeTakeFirstOrThrow();
|
||||
const tokRow = await db.k.selectFrom("app_tokens").selectAll().executeTakeFirstOrThrow();
|
||||
expect(invRow.code_hash).not.toBe(inv);
|
||||
expect(tokRow.token_hash).not.toBe(token);
|
||||
expect(tokRow.token_hash).not.toContain(token);
|
||||
});
|
||||
|
||||
test("revoking with another user's id leaves the token valid", async () => {
|
||||
const db = getDb();
|
||||
const a = await registerUser(db, { username: "alice", password: "x" });
|
||||
const b = await registerUser(db, {
|
||||
username: "bob",
|
||||
password: "x",
|
||||
inviteCode: await createInvite(db, a.id),
|
||||
});
|
||||
const { id, token } = await createAppToken(db, a.id, "cli", ["read"]);
|
||||
await revokeAppToken(db, b.id, id);
|
||||
expect(await verifyAppToken(db, token, "read")).toEqual(a);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user