diff --git a/lib/package.json b/lib/package.json --- a/lib/package.json +++ b/lib/package.json @@ -42,6 +42,7 @@ "fast-json-stable-stringify": "^2.0.0", "file-type": "^12.3.0", "focus-trap-react": "^10.1.4", + "idna-uts46-hx": "^2.3.1", "invariant": "^2.2.4", "just-clone": "^3.2.1", "lodash": "^4.17.21", diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -347,6 +347,8 @@ ); } +const ensRegex: RegExp = /^[a-zA-Z0-9-]{3,}\.eth$/; + export { paragraphRegex, paragraphStripTrailingNewlineRegex, @@ -372,4 +374,5 @@ stripSpoilersFromNotifications, stripSpoilersFromMarkdownAST, parseChatMention, + ensRegex, }; diff --git a/lib/utils/ens-helpers.js b/lib/utils/ens-helpers.js --- a/lib/utils/ens-helpers.js +++ b/lib/utils/ens-helpers.js @@ -1,7 +1,9 @@ // @flow +import uts46 from 'idna-uts46-hx'; import { ENSCache } from './ens-cache.js'; import { getETHAddressForUserInfo } from '../shared/account-utils.js'; +import { ensRegex } from '../shared/markdown.js'; type BaseUserInfo = { +username?: ?string, ... }; export type GetENSNames = ( @@ -70,4 +72,27 @@ }); } -export { getENSNames }; +function isValidENSName(name: string): boolean { + // While the Ethereum spec allows for more flexibility in ENS names + // (see https://eips.ethereum.org/EIPS/eip-137#name-syntax), we want to only + // perform lookups on names that adhere to two specific rules: + // 1. TLD should be .eth + // 2. SLD should be at least three characters in length + // Here, we enforce these rules and also use a library similar to the one + // reccomended by the Ethereum spec to perform the 'heavy lifting' of + // making sure the name adheres to all of the specific limitations. + try { + // Ethereum spec guidelines + const convertedName = uts46.toAscii(name, { + transitional: false, + useStd3ASCII: true, + }); + + // Our specific rules on TLDs and SLDs + return !!convertedName.match(ensRegex); + } catch (e) { + return false; + } +} + +export { getENSNames, isValidENSName }; diff --git a/lib/utils/ens-helpers.test.js b/lib/utils/ens-helpers.test.js new file mode 100644 --- /dev/null +++ b/lib/utils/ens-helpers.test.js @@ -0,0 +1,31 @@ +// @flow + +import { isValidENSName } from './ens-helpers.js'; + +describe('it should correctly validate ENS names', () => { + it('should match all valid ENS names', () => { + expect(isValidENSName('foo.eth')).toBe(true); + expect(isValidENSName('jack.eth')).toBe(true); + expect(isValidENSName('thisuserhasareallylongname.eth')).toBe(true); + expect(isValidENSName('hello-world.eth')).toBe(true); + }); + + it('should not match any SLDs less than 3 characters', () => { + expect(isValidENSName('fo.eth')).toBe(false); + expect(isValidENSName('f.eth')).toBe(false); + expect(isValidENSName('')).toBe(false); + expect(isValidENSName('a.eth')).toBe(false); + }); + + it('should not match any TLDs other than .eth', () => { + expect(isValidENSName('foo.com')).toBe(false); + expect(isValidENSName('foo.')).toBe(false); + expect(isValidENSName('foo')).toBe(false); + }); + + it('should not match any names with special characters', () => { + expect(isValidENSName('foo.eth!')).toBe(false); + expect(isValidENSName('foo.eth#')).toBe(false); + expect(isValidENSName('foo$.eth')).toBe(false); + }); +});