fix(register): reject duplicate usernames before passkey prompt

Friend hit a 500 on /api/register/complete because the Postgres unique
constraint fired after the WebAuthn ceremony — they'd already burned a
passkey creation by the time the server refused. Pre-check the username in
/api/register/start so the join page shows "Username is already taken"
before the browser prompts. Also catch the 23505 duplicate-key error in
/api/register/complete as a race-condition safety net.
This commit is contained in:
Jeff Emmett 2026-04-17 10:14:54 -04:00
parent 5bb46afe6d
commit 93a63cd42b
1 changed files with 15 additions and 1 deletions

View File

@ -569,6 +569,13 @@ app.post('/api/register/start', async (c) => {
return c.json({ error: 'Username required' }, 400);
}
// Reject duplicates up front so the user isn't prompted to create a passkey
// that the /complete step would then reject with a unique-constraint crash.
const existing = await getUserByUsername(username);
if (existing) {
return c.json({ error: `Username "${username}" is already taken — pick a different one.` }, 409);
}
// Generate challenge
const challenge = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64url');
@ -658,7 +665,14 @@ app.post('/api/register/complete', async (c) => {
const did = (clientDid && typeof clientDid === 'string' && clientDid.startsWith('did:key:z'))
? clientDid
: `did:key:${userId.slice(0, 32)}`;
await createUser(userId, username, username, did);
try {
await createUser(userId, username, username, did);
} catch (err: any) {
if (err?.code === '23505' || /unique constraint/i.test(err?.message || '')) {
return c.json({ error: `Username "${username}" is already taken — pick a different one.` }, 409);
}
throw err;
}
// Set recovery email if provided during registration
if (email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {