diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index d6602413..69b75915 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -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)) {