diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index 6965eb596a0..af660d862f8 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -124,9 +124,17 @@ export const ensureCodeIsAvailable = async ( inviteCode: string, withLock = false, ): Promise => { + const { ref } = db.db.dynamic const invite = await db.db .selectFrom('invite_code') .selectAll() + .whereNotExists((qb) => + qb + .selectFrom('repo_root') + .selectAll() + .where('takedownId', 'is not', null) + .whereRef('did', '=', ref('invite_code.forUser')), + ) .where('code', '=', inviteCode) .if(withLock && db.dialect === 'pg', (qb) => qb.forUpdate().skipLocked()) .executeTakeFirst() diff --git a/packages/pds/tests/invite-codes.test.ts b/packages/pds/tests/invite-codes.test.ts index c52dbe76175..e9c34fcc800 100644 --- a/packages/pds/tests/invite-codes.test.ts +++ b/packages/pds/tests/invite-codes.test.ts @@ -4,6 +4,7 @@ import { AppContext } from '../src' import * as util from './_util' import { DAY } from '@atproto/common' import { genInvCodes } from '../src/api/com/atproto/server/util' +import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs' describe('account', () => { let serverUrl: string @@ -45,6 +46,49 @@ describe('account', () => { ) }) + it('fails on invite code from takendown account', async () => { + const account = await makeLoggedInAccount(agent) + // assign an invite code to the user + const code = await createInviteCode(agent, 1, account.did) + // takedown the user's account + const { data: takedownAction } = + await agent.api.com.atproto.admin.takeModerationAction( + { + action: TAKEDOWN, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: account.did, + }, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: util.adminAuth() }, + }, + ) + // attempt to create account with the previously generated invite code + const promise = createAccountWithInvite(agent, code) + await expect(promise).rejects.toThrow( + ComAtprotoServerCreateAccount.InvalidInviteCodeError, + ) + + // double check that reversing the takedown action makes the invite code valid again + await agent.api.com.atproto.admin.reverseModerationAction( + { + id: takedownAction.id, + createdBy: 'did:example:admin', + reason: 'Y', + }, + { + encoding: 'application/json', + headers: { authorization: util.adminAuth() }, + }, + ) + // attempt to create account with the previously generated invite code + await createAccountWithInvite(agent, code) + }) + it('fails on used up invite code', async () => { const code = await createInviteCode(agent, 2) await createAccountsWithInvite(agent, code, 2)