Skip to content

Commit

Permalink
Merge pull request #196 from unstoppabledomains/refat/add_domain_vali…
Browse files Browse the repository at this point in the history
…dation

Added validation for domain name and test
  • Loading branch information
jarvis3d authored Jan 4, 2022
2 parents 053afe8 + 30a7e3b commit a853912
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 7.1.0

- Throw `ResolutionErrorCode.InvalidDomainAddress` error if domain contains special characters
- Domain name is being validated according to the following regular expression: `^[.a-z0-9-]+$`
## 7.0.0

- ENS support is completely removed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unstoppabledomains/resolution",
"version": "7.0.2",
"version": "7.1.0",
"description": "Domain Resolution for blockchain domains",
"main": "./build/index.js",
"directories": {
Expand Down
37 changes: 17 additions & 20 deletions src/Resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {findNamingServiceName, signedInfuraLink} from './utils';
import {Eip1993Factories as Eip1193Factories} from './utils/Eip1993Factories';
import {NamingService} from './NamingService';
import Networking from './utils/Networking';
import {prepareAndValidateDomain} from "./utils/prepareAndValidate";

/**
* Blockchain domain Resolution library - Resolution.
Expand Down Expand Up @@ -373,7 +374,7 @@ export default class Resolution {
ticker: string,
chain: string,
): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);

const recordKey = `crypto.${ticker.toUpperCase()}.version.${chain.toUpperCase()}.address`;
Expand All @@ -388,7 +389,7 @@ export default class Resolution {
* @returns A promise that resolves in a verified twitter handle
*/
async twitter(domain: string): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return method.twitter(domain);
}
Expand Down Expand Up @@ -419,7 +420,7 @@ export default class Resolution {
* @throws [[ResolutionError]]
*/
async ipfsHash(domain: string): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.getPreferableNewRecord(
domain,
'dweb.ipfs.hash',
Expand All @@ -432,7 +433,7 @@ export default class Resolution {
* @param domain - domain name
*/
async httpUrl(domain: string): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.getPreferableNewRecord(
domain,
'browser.redirect_url',
Expand All @@ -455,7 +456,7 @@ export default class Resolution {
* @param domain - domain to look for
*/
async resolver(domain: string): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const resolver = await this.getNamingMethodOrThrow(domain).resolver(domain);
if (!resolver) {
throw new ResolutionError(ResolutionErrorCode.UnspecifiedResolver, {
Expand All @@ -470,7 +471,7 @@ export default class Resolution {
* @returns An owner address of the domain
*/
async owner(domain: string): Promise<string | null> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return (await method.owner(domain)) || null;
}
Expand All @@ -481,7 +482,7 @@ export default class Resolution {
* @returns A record value promise for a given record name
*/
async record(domain: string, recordKey: string): Promise<string> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return method.record(domain, recordKey);
}
Expand All @@ -492,7 +493,7 @@ export default class Resolution {
* @returns A Promise with key-value mapping of domain records
*/
async records(domain: string, keys: string[]): Promise<CryptoRecords> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return method.records(domain, keys);
}
Expand All @@ -502,7 +503,7 @@ export default class Resolution {
* @returns A Promise of whether or not the domain belongs to a wallet
*/
async isRegistered(domain: string): Promise<Boolean> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return method.isRegistered(domain);
}
Expand All @@ -512,7 +513,7 @@ export default class Resolution {
* @returns A Promise of whether or not the domain is available
*/
async isAvailable(domain: string): Promise<Boolean> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
return method.isAvailable(domain);
}
Expand All @@ -528,7 +529,7 @@ export default class Resolution {
domain: string,
options: NamehashOptions = NamehashOptionsDefault,
): string {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.formatNamehash(
this.getNamingMethodOrThrow(domain).namehash(domain),
options,
Expand Down Expand Up @@ -572,7 +573,7 @@ export default class Resolution {
* @param hash - hash obtained from the blockchain
*/
isValidHash(domain: string, hash: string): boolean {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.namehash(domain) === hash;
}

Expand All @@ -582,7 +583,7 @@ export default class Resolution {
* @param domain - domain name to be checked
*/
async isSupportedDomain(domain: string): Promise<boolean> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const namingMethod = this.getNamingMethod(domain);
return namingMethod ? await namingMethod.isSupportedDomain(domain) : false;
}
Expand All @@ -592,7 +593,7 @@ export default class Resolution {
* @param domain - domain name to look for
*/
serviceName(domain: string): ResolutionMethod {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.getNamingMethodOrThrow(domain).serviceName();
}

Expand All @@ -602,7 +603,7 @@ export default class Resolution {
* @param domain - domain name
*/
async allRecords(domain: string): Promise<CryptoRecords> {
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
return this.getNamingMethodOrThrow(domain).allRecords(domain);
}

Expand All @@ -619,7 +620,7 @@ export default class Resolution {

async dns(domain: string, types: DnsRecordType[]): Promise<DnsRecord[]> {
const dnsUtils = new DnsUtils();
domain = this.prepareDomain(domain);
domain = prepareAndValidateDomain(domain);
const method = this.getNamingMethodOrThrow(domain);
const dnsRecordKeys = this.getDnsRecordKeys(types);
const blockchainData = await method.records(domain, dnsRecordKeys);
Expand Down Expand Up @@ -743,10 +744,6 @@ export default class Resolution {

return method;
}

private prepareDomain(domain: string): string {
return domain ? domain.trim().toLowerCase() : '';
}
}

export {Resolution};
Expand Down
3 changes: 3 additions & 0 deletions src/errors/resolutionError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum ResolutionErrorCode {
ServiceProviderError = 'ServiceProviderError',
InvalidTwitterVerification = 'InvalidTwitterVerification',
InconsistentDomainArray = 'InconsistentDomainArray',
InvalidDomainAddress = 'InvalidDomainAddress',
}

/**
Expand Down Expand Up @@ -86,6 +87,8 @@ const HandlersByCode = {
`Failed to query tokenUri ${params.tokenUri}. Error: ${params.errorMessage}`,
[ResolutionErrorCode.UnsupportedService]: (params: {namingService: string}) =>
`Naming service ${params.namingService} is not supported`,
[ResolutionErrorCode.InvalidDomainAddress]: (params: {domain: string}) =>
`Domain address ${params.domain} is invalid`,
};

/**
Expand Down
47 changes: 47 additions & 0 deletions src/tests/Resolution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1634,5 +1634,52 @@ describe('Resolution', () => {
ResolutionErrorCode.UnsupportedMethod,
);
});

it('should check all methods for domain validation', async () => {
await expectResolutionErrorCode(
() => resolution.twitter('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.ipfsHash('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.httpUrl('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.resolver('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.owner('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.isRegistered('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.isAvailable('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.namehash('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.isSupportedDomain('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.serviceName('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => resolution.allRecords('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
});
});
});
34 changes: 34 additions & 0 deletions src/tests/prepareAndValidate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {expectResolutionErrorCode} from './helpers';
import {prepareAndValidateDomain} from '../utils/prepareAndValidate';
import {ResolutionErrorCode} from '../errors/resolutionError';

it('should throw exception for invalid domains', async () => {
await expectResolutionErrorCode(
() => prepareAndValidateDomain('#[email protected]'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => prepareAndValidateDomain('hello123#.blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => prepareAndValidateDomain('hello#blockchain'),
ResolutionErrorCode.InvalidDomainAddress,
);
await expectResolutionErrorCode(
() => prepareAndValidateDomain('helloblockchain#'),
ResolutionErrorCode.InvalidDomainAddress,
);
});

it('should convert domain name to lower case', async () => {
expect(prepareAndValidateDomain(' HELLO.Blockchain ')).toEqual(
'hello.blockchain',
);
expect(prepareAndValidateDomain(' HELLO123.Blockchain ')).toEqual(
'hello123.blockchain',
);
expect(prepareAndValidateDomain(' HELLO1.Blockchain1 ')).toEqual(
'hello1.blockchain1',
);
});
17 changes: 17 additions & 0 deletions src/utils/prepareAndValidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ResolutionError, {ResolutionErrorCode} from '../errors/resolutionError';

/**
* Checks domain name for special symbols and returns address in lowercase without spaces
* @throws Will throw an error if domain address contains special symbols
* @param domain - a domain address
*/
const reg = RegExp('^[.a-z0-9-]+$');
export function prepareAndValidateDomain(domain: string): string {
const retVal: string = domain ? domain.trim().toLowerCase() : '';
if (!reg.test(retVal)) {
throw new ResolutionError(ResolutionErrorCode.InvalidDomainAddress, {
domain,
});
}
return retVal;
}

0 comments on commit a853912

Please sign in to comment.