UNPKG

@bluesky-social/syntax

Version:

Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc

122 lines 5.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DisallowedDomainError = exports.UnsupportedDomainError = exports.ReservedHandleError = exports.InvalidHandleError = exports.isValidTld = exports.isValidHandle = exports.normalizeAndEnsureValidHandle = exports.normalizeHandle = exports.ensureValidHandleRegex = exports.ensureValidHandle = exports.DISALLOWED_TLDS = exports.INVALID_HANDLE = void 0; exports.INVALID_HANDLE = 'handle.invalid'; // Currently these are registration-time restrictions, not protocol-level // restrictions. We have a couple accounts in the wild that we need to clean up // before hard-disallow. // See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains exports.DISALLOWED_TLDS = [ '.local', '.arpa', '.invalid', '.localhost', '.internal', '.example', '.alt', // policy could concievably change on ".onion" some day '.onion', // NOTE: .test is allowed in testing and devopment. In practical terms // "should" "never" actually resolve and get registered in production ]; // Handle constraints, in English: // - must be a possible domain name // - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696, // section 2. and RFC-3986, section 3. can now have leading numbers (eg, // 4chan.org) // - "labels" (sub-names) are made of ASCII letters, digits, hyphens // - can not start or end with a hyphen // - TLD (last component) should not start with a digit // - can't end with a hyphen (can end with digit) // - each segment must be between 1 and 63 characters (not including any periods) // - overall length can't be more than 253 characters // - separated by (ASCII) periods; does not start or end with period // - case insensitive // - domains (handles) are equal if they are the same lower-case // - punycode allowed for internationalization // - no whitespace, null bytes, joining chars, etc // - does not validate whether domain or TLD exists, or is a reserved or // special TLD (eg, .onion or .local) // - does not validate punycode const ensureValidHandle = (handle) => { // check that all chars are boring ASCII if (!/^[a-zA-Z0-9.-]*$/.test(handle)) { throw new InvalidHandleError('Disallowed characters in handle (ASCII letters, digits, dashes, periods only)'); } if (handle.length > 253) { throw new InvalidHandleError('Handle is too long (253 chars max)'); } const labels = handle.split('.'); if (labels.length < 2) { throw new InvalidHandleError('Handle domain needs at least two parts'); } for (let i = 0; i < labels.length; i++) { const l = labels[i]; if (l.length < 1) { throw new InvalidHandleError('Handle parts can not be empty'); } if (l.length > 63) { throw new InvalidHandleError('Handle part too long (max 63 chars)'); } if (l.endsWith('-') || l.startsWith('-')) { throw new InvalidHandleError('Handle parts can not start or end with hyphens'); } if (i + 1 === labels.length && !/^[a-zA-Z]/.test(l)) { throw new InvalidHandleError('Handle final component (TLD) must start with ASCII letter'); } } }; exports.ensureValidHandle = ensureValidHandle; // simple regex translation of above constraints const ensureValidHandleRegex = (handle) => { if (!/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(handle)) { throw new InvalidHandleError("Handle didn't validate via regex"); } if (handle.length > 253) { throw new InvalidHandleError('Handle is too long (253 chars max)'); } }; exports.ensureValidHandleRegex = ensureValidHandleRegex; const normalizeHandle = (handle) => { return handle.toLowerCase(); }; exports.normalizeHandle = normalizeHandle; const normalizeAndEnsureValidHandle = (handle) => { const normalized = (0, exports.normalizeHandle)(handle); (0, exports.ensureValidHandle)(normalized); return normalized; }; exports.normalizeAndEnsureValidHandle = normalizeAndEnsureValidHandle; const isValidHandle = (handle) => { try { (0, exports.ensureValidHandle)(handle); } catch (err) { if (err instanceof InvalidHandleError) { return false; } throw err; } return true; }; exports.isValidHandle = isValidHandle; const isValidTld = (handle) => { return !exports.DISALLOWED_TLDS.some((domain) => handle.endsWith(domain)); }; exports.isValidTld = isValidTld; class InvalidHandleError extends Error { } exports.InvalidHandleError = InvalidHandleError; /** @deprecated Never used */ class ReservedHandleError extends Error { } exports.ReservedHandleError = ReservedHandleError; /** @deprecated Never used */ class UnsupportedDomainError extends Error { } exports.UnsupportedDomainError = UnsupportedDomainError; /** @deprecated Never used */ class DisallowedDomainError extends Error { } exports.DisallowedDomainError = DisallowedDomainError; //# sourceMappingURL=handle.js.map