diff --git a/packages/pds/src/api/com/atproto/identity/updateHandle.ts b/packages/pds/src/api/com/atproto/identity/updateHandle.ts index deb22d0b649..0119ae79810 100644 --- a/packages/pds/src/api/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/updateHandle.ts @@ -47,6 +47,14 @@ export default function (server: Server, ctx: AppContext) { } } + // Pessimistic check to handle spam: also enforced by updateHandle() and the db. + const available = await ctx.services + .account(ctx.db) + .isHandleAvailable(handle) + if (!available) { + throw new InvalidRequestError(`Handle already taken: ${handle}`) + } + const seqHandleTok = await ctx.db.transaction(async (dbTxn) => { let tok: HandleSequenceToken try { diff --git a/packages/pds/src/services/account/index.ts b/packages/pds/src/services/account/index.ts index 05a512d2f3a..c1f267fd6b0 100644 --- a/packages/pds/src/services/account/index.ts +++ b/packages/pds/src/services/account/index.ts @@ -160,6 +160,7 @@ export class AccountService { .set({ handle }) .where('did', '=', did) .whereNotExists( + // @NOTE see also condition in isHandleAvailable() this.db.db .selectFrom('did_handle') .where('handle', '=', handle) @@ -178,6 +179,16 @@ export class AccountService { await sequencer.sequenceEvt(this.db, seqEvt) } + async isHandleAvailable(handle: string) { + // @NOTE see also condition in updateHandle() + const found = await this.db.db + .selectFrom('did_handle') + .where('handle', '=', handle) + .select('handle') + .executeTakeFirst() + return !found + } + async updateEmail(did: string, email: string) { await this.db.db .updateTable('user_account')