From f3816ec8cdac5be08a1bcc78eba0eccd1b70634b Mon Sep 17 00:00:00 2001 From: Shane da Silva Date: Wed, 11 Oct 2023 12:05:10 -0700 Subject: [PATCH] feat: Require admin to transfer/assign reserved usernames We're getting a number of users claiming fnames for obvious/well-known names that they don't actually represent or belong to. Require these names to be transferred by an admin. --- src/reserved.ts | 110 ++++++++++++++++++++++++++++++++++++++++ src/transfers.ts | 7 ++- tests/transfers.test.ts | 10 ++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/reserved.ts diff --git a/src/reserved.ts b/src/reserved.ts new file mode 100644 index 000000000..32f8960a1 --- /dev/null +++ b/src/reserved.ts @@ -0,0 +1,110 @@ +// Usernames that we want only admins to be able to assign to an FID, in order +// to prevent abuse and obvious username squatting. +const RESERVED_USERNAMES = new Set([ + 'adidas', + 'amazon', + 'apple', + 'arbitrum', + 'barackobama', + 'bbc', + 'billgates', + 'binance', + 'bitcoin', + 'blockchain', + 'bmw', + 'cia', + 'cloud', + 'cnn', + 'coinbase', + 'consensys', + 'context', + 'crypto', + 'data', + 'dell', + 'disney', + 'drake', + 'elonmusk', + 'ethereum', + 'f1', + 'facebook', + 'fashion', + 'fbi', + 'fifa', + 'finance', + 'fitness', + 'food', + 'fwb', + 'fwbdao', + 'gallery', + 'games', + 'ge', + 'gem', + 'gm', + 'gnosis', + 'gnosissafe', + 'google', + 'health', + 'hp', + 'hyperverse', + 'ibm', + 'intel', + 'invest', + 'jpmorgan', + 'kraken', + 'kucoin', + 'lebronjames', + 'lionelmessi', + 'luxury', + 'messi', + 'metamask', + 'microsoft', + 'midjourney', + 'mirror', + 'ml', + 'money', + 'nasa', + 'nba', + 'netflix', + 'news', + 'nfl', + 'nike', + 'nounsdao', + 'obsidian', + 'openai', + 'opensea', + 'optimism', + 'oracle', + 'ourzora', + 'pga', + 'poap', + 'polygon', + 'privacy', + 'rainbow', + 'rarible', + 'ronaldo', + 'samsung', + 'sony', + 'starknet', + 'stripe', + 'sudoswap', + 'superrare', + 'sushiswap', + 'syndicate', + 'tesla', + 'tezos', + 'travel', + 'twitter', + 'uber', + 'un', + 'uniswap', + 'urbit', + 'viamirror', + 'vitalik', + 'youtube', + 'zora', + 'zoraco', +]); + +export function reservedUsername(username: string) { + return RESERVED_USERNAMES.has(username); +} diff --git a/src/transfers.ts b/src/transfers.ts index b4fde36f8..76ebcd913 100644 --- a/src/transfers.ts +++ b/src/transfers.ts @@ -1,6 +1,7 @@ import { Kysely, Selectable } from 'kysely'; import { Database, TransfersTable } from './db.js'; import { ADMIN_KEYS, generateSignature, signer, verifySignature } from './signature.js'; +import { reservedUsername } from './reserved.js'; import { bytesToHex, currentTimestamp, hexToBytes } from './util.js'; import { bytesCompare, validations } from '@farcaster/hub-nodejs'; import { log } from './log.js'; @@ -30,6 +31,7 @@ export type TransferHistoryFilter = { type ErrorCode = | 'USERNAME_TAKEN' + | 'USERNAME_RESERVED' | 'TOO_MANY_NAMES' | 'UNAUTHORIZED' | 'USERNAME_NOT_FOUND' @@ -100,10 +102,13 @@ async function getAndValidateVerifierAddress(req: TransferRequest, idContract: I export async function validateTransfer(req: TransferRequest, db: Kysely, idContract: IdRegistry) { const verifierAddress = await getAndValidateVerifierAddress(req, idContract); if (!verifierAddress) { - // Only admin transfers are allowed until we finish migrating throw new ValidationError('UNAUTHORIZED'); } + if (reservedUsername(req.username) && !ADMIN_KEYS[req.userFid]) { + throw new ValidationError('USERNAME_RESERVED'); + } + if (!verifySignature(req.username, req.timestamp, req.owner, req.userSignature, verifierAddress)) { log.error(`Invalid signature for req ${JSON.stringify(req)}`); throw new ValidationError('INVALID_SIGNATURE'); diff --git a/tests/transfers.test.ts b/tests/transfers.test.ts index 6ac63fa7e..9f96a08b0 100644 --- a/tests/transfers.test.ts +++ b/tests/transfers.test.ts @@ -33,6 +33,16 @@ describe('transfers', () => { await expect(createTestTransfer(db, { username: 'test3', to: 4 })).rejects.toThrow('USERNAME_TAKEN'); }); + test('cannot register a reserved name with a non-admin account', async () => { + await expect(createTestTransfer(db, { username: 'apple', to: 123, userFid: 123 })).rejects.toThrow( + 'USERNAME_RESERVED' + ); + }); + + test('can register a reserved name with an admin account', async () => { + expect(await createTestTransfer(db, { username: 'apple', to: 123 })); + }); + test('same fid cannot register twice', async () => { await expect(createTestTransfer(db, { username: 'test1234', to: 2 })).rejects.toThrow('TOO_MANY_NAMES'); });