Suite of AT Protocol TypeScript libraries built on web standards
1/** Human-readable constraints:
2 * - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)
3 * - entire URI is ASCII: [a-zA-Z0-9._:%-]
4 * - always starts "did:" (lower-case)
5 * - method name is one or more lower-case letters, followed by ":"
6 * - remaining identifier can have any of the above chars, but can not end in ":"
7 * - it seems that a bunch of ":" can be included, and don't need spaces between
8 * - "%" is used only for "percent encoding" and must be followed by two hex characters (and thus can't end in "%")
9 * - query ("?") and fragment ("#") stuff is defined for "DID URIs", but not as part of identifier itself
10 * - "The current specification does not take a position on the maximum length of a DID"
11 * - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer
12 * - hard length limit of 8KBytes
13 * - not going to validate "percent encoding" here
14 * @param did - DID to validate
15 * @throws {InvalidDidError} if the DID is invalid
16 */
17export const ensureValidDid = (did: string): void => {
18 if (!did.startsWith("did:")) {
19 throw new InvalidDidError("DID requires 'did:' prefix");
20 }
21
22 // check that all chars are boring ASCII
23 if (!/^[a-zA-Z0-9._:%-]*$/.test(did)) {
24 throw new InvalidDidError(
25 "Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)",
26 );
27 }
28
29 const { length, 1: method } = did.split(":");
30 if (length < 3) {
31 throw new InvalidDidError(
32 "DID requires prefix, method, and method-specific content",
33 );
34 }
35
36 if (!/^[a-z]+$/.test(method)) {
37 throw new InvalidDidError("DID method must be lower-case letters");
38 }
39
40 if (did.endsWith(":") || did.endsWith("%")) {
41 throw new InvalidDidError("DID can not end with ':' or '%'");
42 }
43
44 if (did.length > 2 * 1024) {
45 throw new InvalidDidError("DID is too long (2048 chars max)");
46 }
47};
48
49/**
50 * Simple regex version of {@linkcode ensureValidDid} constraints
51 * @param did - DID to validate
52 * @throws {InvalidDidError} - if the DID is invalid
53 */
54export const ensureValidDidRegex = (did: string): void => {
55 if (!/^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/.test(did)) {
56 throw new InvalidDidError("DID didn't validate via regex");
57 }
58
59 if (did.length > 2 * 1024) {
60 throw new InvalidDidError("DID is too long (2048 chars max)");
61 }
62};
63
64export class InvalidDidError extends Error {}