Context
src/routes/auth.ts:351-363: on login, the route does "find-or-create" by pubkey for the user. If the find misses, it creates a new device row.
Problem / Observation
The devices table has no UNIQUE(user_id, pubkey) constraint (migration 001_init.sql:21-28). The find-then-insert is not transactional, so two concurrent logins from the same device (rare but possible: client opens two tabs, both retry on a flaky network) can create two device rows for the same pubkey. The user then sees a duplicate in /devices, and each new login may pick a different one to attach to a session.
There's no SQLite UNIQUE INDEX to make this a hard error either — the schema permits the duplicate.
Proposed approach
- Migration:
CREATE UNIQUE INDEX uniq_device_user_pubkey ON devices(user_id, pubkey).
- Wrap
find-or-create in auth.ts in a db.transaction that uses INSERT … ON CONFLICT DO NOTHING and reads back.
Acceptance criteria
Context
src/routes/auth.ts:351-363: on login, the route does "find-or-create" by pubkey for the user. If the find misses, it creates a new device row.
Problem / Observation
The
devicestable has noUNIQUE(user_id, pubkey)constraint (migration001_init.sql:21-28). The find-then-insert is not transactional, so two concurrent logins from the same device (rare but possible: client opens two tabs, both retry on a flaky network) can create two device rows for the same pubkey. The user then sees a duplicate in/devices, and each new login may pick a different one to attach to a session.There's no SQLite UNIQUE INDEX to make this a hard error either — the schema permits the duplicate.
Proposed approach
CREATE UNIQUE INDEX uniq_device_user_pubkey ON devices(user_id, pubkey).find-or-createinauth.tsin adb.transactionthat usesINSERT … ON CONFLICT DO NOTHINGand reads back.Acceptance criteria