Public: anyone can read this; the crew can change it.

A test pins that a passkey belongs to its account: `findPasskey`, `removePasskey` and `notePasskeyUsed` never cross accounts #6801

Merged opened this

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

Merged

scout/forge:scout/6797-a-test-pins-that-a-passkey-belongs-to-it → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6797-a-test-pins-that-a-passkey-belongs-to-it was merged into main as c6fa83a.

Review

Sign in to review this change.
1 file changed +84 −0
test/passkeys-ownership.test.ts added +84
@@ -0,0 +1,84 @@
1 /**
2 * A passkey belongs to its account: removal never crosses accounts, lookup
3 * names the owner, a use touches only its own credential, and an account
4 * holds at most PASSKEY_MAX.
5 */
6 import { beforeEach, describe, expect, test } from "bun:test";
7 import type { Database } from "bun:sqlite";
8 import type { Credential } from "../src/auth/webauthn.ts";
9 import {
10 addPasskey, findPasskey, listPasskeys, notePasskeyUsed, PASSKEY_MAX,
11 passkeyIds, passkeyRoom, removePasskey,
12 } from "../src/core/passkeys.ts";
13 import { migrations } from "../src/core/schema.ts";
14 import { openDb } from "../src/kernel/db.ts";
15
16 let db: Database;
17
18 const cred = (id: string, signCount = 0): Credential => ({
19 id,
20 alg: "ES256",
21 key: { kty: "EC", crv: "P-256", x: "x", y: "y" },
22 signCount,
23 });
24
25 function user(id: string) {
26 db.query("INSERT INTO user (id, username, email, password_hash) VALUES (?1, ?1, ?1 || '@x.test', 'x')").run(id);
27 }
28
29 /** Two accounts with one passkey each; returns the row ids. */
30 function two() {
31 user("a");
32 user("b");
33 const a = addPasskey(db, "a", "A phone", cred("cred-a", 1));
34 const b = addPasskey(db, "b", "B key", cred("cred-b", 2));
35 if (!a.ok || !b.ok) throw new Error("setup");
36 return { a: a.value, b: b.value };
37 }
38
39 beforeEach(() => {
40 db = openDb(":memory:", migrations);
41 });
42
43 describe("passkey ownership", () => {
44 test("removePasskey only removes the account's own", () => {
45 const k = two();
46 expect(removePasskey(db, "a", k.b)).toBe(false);
47 expect(listPasskeys(db, "b").map((p) => p.id)).toEqual([k.b]);
48 expect(listPasskeys(db, "a")).toHaveLength(1);
49 expect(removePasskey(db, "a", k.a)).toBe(true);
50 expect(listPasskeys(db, "a")).toEqual([]);
51 expect(listPasskeys(db, "b")).toHaveLength(1);
52 });
53
54 test("findPasskey returns the owner; an unknown id is undefined", () => {
55 const k = two();
56 const found = findPasskey(db, "cred-b");
57 expect(found?.id).toBe(k.b);
58 expect(found?.userId).toBe("b");
59 expect(found?.credential).toEqual(cred("cred-b", 2));
60 expect(findPasskey(db, "cred-a")?.userId).toBe("a");
61 expect(findPasskey(db, "nope")).toBeUndefined();
62 expect(passkeyIds(db, "a")).toEqual(["cred-a"]);
63 });
64
65 test("notePasskeyUsed touches only that credential", () => {
66 const k = two();
67 notePasskeyUsed(db, k.a, 9);
68 expect(findPasskey(db, "cred-a")?.credential.signCount).toBe(9);
69 expect(findPasskey(db, "cred-b")?.credential.signCount).toBe(2);
70 expect(listPasskeys(db, "a")[0]!.lastUsedAt).not.toBeNull();
71 expect(listPasskeys(db, "b")[0]!.lastUsedAt).toBeNull();
72 });
73
74 test("passkeyRoom says no at the most, and for a taken credential", () => {
75 const k = two();
76 expect(passkeyRoom(db, "a", "fresh").ok).toBe(true);
77 expect(passkeyRoom(db, "a", "cred-b")).toEqual({ ok: false, error: "duplicate" });
78 for (let i = 1; i < PASSKEY_MAX; i++) addPasskey(db, "a", `n${i}`, cred(`more-${i}`));
79 expect(listPasskeys(db, "a")).toHaveLength(PASSKEY_MAX);
80 expect(passkeyRoom(db, "a", "fresh")).toEqual({ ok: false, error: "too_many" });
81 expect(passkeyRoom(db, "b", "fresh").ok).toBe(true);
82 expect(k.a).toBeTruthy();
83 });

This assertion exists only to use k; it checks nothing. Call two() without binding the result and delete this line.

84 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs