@bluesky-social/syntax
Version:
Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc
122 lines • 5.13 kB
JavaScript
;
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