// Human-readable constraints: // - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax) // - entire URI is ASCII: [a-zA-Z0-9._:%-] // - always starts "did:" (lower-case) // - method name is one or more lower-case letters, followed by ":" // - remaining identifier can have any of the above chars, but can not end in ":" // - it seems that a bunch of ":" can be included, and don't need spaces between // - "%" is used only for "percent encoding" and must be followed by two hex characters (and thus can't end in "%") // - query ("?") and fragment ("#") stuff is defined for "DID URIs", but not as part of identifier itself // - "The current specification does not take a position on the maximum length of a DID" // - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer // - hard length limit of 8KBytes // - not going to validate "percent encoding" here export type DidString = `did:${M}:${string}` export function ensureValidDid( input: I, ): asserts input is I & DidString { if (!input.startsWith('did:')) { throw new InvalidDidError('DID requires "did:" prefix') } if (input.length > 2048) { throw new InvalidDidError('DID is too long (2048 chars max)') } if (input.endsWith(':') || input.endsWith('%')) { throw new InvalidDidError('DID can not end with ":" or "%"') } // check that all chars are boring ASCII if (!/^[a-zA-Z0-9._:%-]*$/.test(input)) { throw new InvalidDidError( 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)', ) } const { length, 1: method } = input.split(':') if (length < 3) { throw new InvalidDidError( 'DID requires prefix, method, and method-specific content', ) } if (!/^[a-z]+$/.test(method)) { throw new InvalidDidError('DID method must be lower-case letters') } } const DID_REGEX = /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/ export function ensureValidDidRegex( input: I, ): asserts input is I & DidString { // simple regex to enforce most constraints via just regex and length. // hand wrote this regex based on above constraints if (!DID_REGEX.test(input)) { throw new InvalidDidError("DID didn't validate via regex") } if (input.length > 2048) { throw new InvalidDidError('DID is too long (2048 chars max)') } } export function isValidDid(input: I): input is I & DidString { return input.length <= 2048 && DID_REGEX.test(input) } export class InvalidDidError extends Error {}