@atcute/identity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to deal
3
+ in the Software without restriction, including without limitation the rights
4
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5
+ copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all
9
+ copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @atcute/identity
2
+
3
+ syntax, type definitions and schemas for atproto handles, DIDs and DID documents.
package/dist/did.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { AtprotoDid, Did } from './types.js';
2
+ export declare const DID_RE: RegExp;
3
+ /**
4
+ * checks if it's a DID identifier
5
+ */
6
+ export declare const isDid: (input: string) => input is Did;
7
+ /**
8
+ * checks if it's a DID identifier that is supported by atproto
9
+ */
10
+ export declare const isAtprotoDid: (input: string) => input is AtprotoDid;
11
+ /**
12
+ * returns the DID's method
13
+ */
14
+ export declare const extractDidMethod: <M extends string>(did: Did<M>) => M;
package/dist/did.js ADDED
@@ -0,0 +1,24 @@
1
+ import { isPlcDid } from './methods/plc.js';
2
+ import { isAtprotoWebDid } from './methods/web.js';
3
+ export const DID_RE = /^(?=.{7,2048}$)did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
4
+ /**
5
+ * checks if it's a DID identifier
6
+ */
7
+ export const isDid = (input) => {
8
+ return input.length >= 7 && input.length <= 2048 && DID_RE.test(input);
9
+ };
10
+ /**
11
+ * checks if it's a DID identifier that is supported by atproto
12
+ */
13
+ export const isAtprotoDid = (input) => {
14
+ return isPlcDid(input) || isAtprotoWebDid(input);
15
+ };
16
+ /**
17
+ * returns the DID's method
18
+ */
19
+ export const extractDidMethod = (did) => {
20
+ const isep = did.indexOf(':', 4);
21
+ const method = did.slice(4, isep);
22
+ return method;
23
+ };
24
+ //# sourceMappingURL=did.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"did.js","sourceRoot":"","sources":["../lib/did.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,CAAC,MAAM,MAAM,GAAG,kEAAkE,CAAC;AAEzF;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAgB,EAAE;IACpD,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAuB,EAAE;IAClE,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAmB,GAAW,EAAK,EAAE;IACpE,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,MAAW,CAAC;AACpB,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Handle } from './types.js';
2
+ export declare const HANDLE_RE: RegExp;
3
+ export declare const isHandle: (str: string) => str is Handle;
package/dist/handle.js ADDED
@@ -0,0 +1,5 @@
1
+ export const HANDLE_RE = /^(?=.{4,253}$)(?:[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])$/;
2
+ export const isHandle = (str) => {
3
+ return str.length >= 3 && str.length <= 253 && HANDLE_RE.test(str);
4
+ };
5
+ //# sourceMappingURL=handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.js","sourceRoot":"","sources":["../lib/handle.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GACrB,4GAA4G,CAAC;AAE9G,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAiB,EAAE;IACtD,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpE,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * as defs from './typedefs.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
4
+ export * from './did.js';
5
+ export * from './methods/plc.js';
6
+ export * from './methods/web.js';
7
+ export * from './handle.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * as defs from './typedefs.js';
2
+ export * from './types.js';
3
+ export * from './utils.js';
4
+ export * from './did.js';
5
+ export * from './methods/plc.js';
6
+ export * from './methods/web.js';
7
+ export * from './handle.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,cAAc,YAAY,CAAC;AAE3B,cAAc,YAAY,CAAC;AAE3B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AAEjC,cAAc,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Did } from '../types.js';
2
+ export declare const PLC_DID_RE: RegExp;
3
+ /**
4
+ * checks if input is a did:plc identifier
5
+ */
6
+ export declare const isPlcDid: (input: string) => input is Did<"plc">;
@@ -0,0 +1,8 @@
1
+ export const PLC_DID_RE = /^did:plc:([a-z2-7]{24})$/;
2
+ /**
3
+ * checks if input is a did:plc identifier
4
+ */
5
+ export const isPlcDid = (input) => {
6
+ return input.length === 32 && PLC_DID_RE.test(input);
7
+ };
8
+ //# sourceMappingURL=plc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plc.js","sourceRoot":"","sources":["../../lib/methods/plc.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAErD;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAuB,EAAE;IAC9D,OAAO,KAAK,CAAC,MAAM,KAAK,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACtD,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { Did } from '../types.js';
2
+ export declare const WEB_DID_RE: RegExp;
3
+ export declare const ATPROTO_WEB_DID_RE: RegExp;
4
+ /**
5
+ * checks if input is a did:web identifier, note that you should probably use
6
+ * `isAtprotoDidWeb` for atproto-related cases as atproto only supports a subset
7
+ * of the did:web specification (namely, no custom paths)
8
+ */
9
+ export declare const isWebDid: (input: string) => input is Did<"web">;
10
+ /**
11
+ * checks if input is a did:web identifier that is supported by atproto
12
+ */
13
+ export declare const isAtprotoWebDid: (input: string) => input is Did<"web">;
14
+ /**
15
+ * normalize a did:web identifier
16
+ */
17
+ export declare const normalizeWebDid: (did: Did<"web">) => Did<"web">;
18
+ /**
19
+ * converts did:web identifier into the DID document's URL
20
+ */
21
+ export declare const webDidToDocumentUrl: (did: Did<"web">) => URL;
@@ -0,0 +1,46 @@
1
+ export const WEB_DID_RE = /^did:web:([a-zA-Z0-9%\-]+(?:(?:\.[a-zA-Z0-9%\-]+)*(?:\.[a-zA-Z]{2,}))?)?((?::[a-zA-Z0-9\-%.]+)+)?$/;
2
+ export const ATPROTO_WEB_DID_RE = /^did:web:([a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})|localhost(?:%3[aA]\d+)?)$/;
3
+ /**
4
+ * checks if input is a did:web identifier, note that you should probably use
5
+ * `isAtprotoDidWeb` for atproto-related cases as atproto only supports a subset
6
+ * of the did:web specification (namely, no custom paths)
7
+ */
8
+ export const isWebDid = (input) => {
9
+ return input.length >= 9 && WEB_DID_RE.test(input);
10
+ };
11
+ /**
12
+ * checks if input is a did:web identifier that is supported by atproto
13
+ */
14
+ export const isAtprotoWebDid = (input) => {
15
+ return input.length >= 12 && ATPROTO_WEB_DID_RE.test(input);
16
+ };
17
+ /**
18
+ * normalize a did:web identifier
19
+ */
20
+ export const normalizeWebDid = (did) => {
21
+ const [host, ...paths] = did.slice(8).split(':').map(decodeURIComponent);
22
+ let normalized = `did:web:${encodeURIComponent(host.toLowerCase())}`;
23
+ if (paths.length > 0) {
24
+ normalized += `:${paths.join(':')}`;
25
+ }
26
+ return normalized;
27
+ };
28
+ /**
29
+ * converts did:web identifier into the DID document's URL
30
+ */
31
+ export const webDidToDocumentUrl = (did) => {
32
+ const [host, ...paths] = did.slice(8).split(':').map(decodeURIComponent);
33
+ let pathname = '/' + paths.join('/');
34
+ if (pathname === '/') {
35
+ pathname = `/.well-known/did.json`;
36
+ }
37
+ else {
38
+ pathname += `/did.json`;
39
+ }
40
+ const url = new URL(`https://${host}${pathname}`);
41
+ if (url.hostname === 'localhost') {
42
+ url.protocol = 'http:';
43
+ }
44
+ return url;
45
+ };
46
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../lib/methods/web.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GACtB,oGAAoG,CAAC;AAEtG,MAAM,CAAC,MAAM,kBAAkB,GAC9B,2FAA2F,CAAC;AAE7F;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAuB,EAAE;IAC9D,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpD,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAuB,EAAE;IACrE,OAAO,KAAK,CAAC,MAAM,IAAI,EAAE,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAe,EAAc,EAAE;IAC9D,MAAM,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAEzE,IAAI,UAAU,GAAG,WAAW,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;IACrE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,UAAU,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACrC,CAAC;IAED,OAAO,UAAwB,CAAC;AACjC,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAe,EAAO,EAAE;IAC3D,MAAM,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAEzE,IAAI,QAAQ,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACtB,QAAQ,GAAG,uBAAuB,CAAC;IACpC,CAAC;SAAM,CAAC;QACP,QAAQ,IAAI,WAAW,CAAC;IACzB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC;IAClD,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QAClC,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,OAAO,GAAG,CAAC;AACZ,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import * as v from '@badrap/valita';
2
+ import * as t from './types.js';
3
+ export declare const FRAGMENT_RE: RegExp;
4
+ export declare const MULTIBASE_RE: RegExp;
5
+ export declare const rfc3968UriSchema: v.Type<string>;
6
+ export declare const didRelativeUri: v.Type<string>;
7
+ export declare const multibaseString: v.Type<string>;
8
+ export declare const didString: v.Type<`did:${string}:${string}`>;
9
+ export declare const verificationMethod: v.Type<t.VerificationMethod>;
10
+ export declare const service: v.Type<t.Service>;
11
+ export declare const didDocument: v.Type<t.DidDocument>;
@@ -0,0 +1,122 @@
1
+ import * as v from '@badrap/valita';
2
+ import { isDid } from './did.js';
3
+ import * as t from './types.js';
4
+ export const FRAGMENT_RE = /^#[^#]+$/;
5
+ export const MULTIBASE_RE = /^z[a-km-zA-HJ-NP-Z1-9]+$/;
6
+ export const rfc3968UriSchema = v.string().assert((input) => {
7
+ return URL.canParse(input);
8
+ }, `must be a url`);
9
+ export const didRelativeUri = v.string().assert((input) => {
10
+ return FRAGMENT_RE.test(input) || URL.canParse(input);
11
+ }, `must be a did relative uri`);
12
+ export const multibaseString = v.string().assert((input) => {
13
+ return MULTIBASE_RE.test(input);
14
+ }, `must be a base58 multibase`);
15
+ export const didString = v.string().assert(isDid, `must be a did`);
16
+ export const verificationMethod = v
17
+ .object({
18
+ id: didRelativeUri,
19
+ type: v.string(),
20
+ controller: didString,
21
+ publicKeyMultibase: multibaseString.optional(),
22
+ publicKeyJwk: v.record().optional(),
23
+ })
24
+ .chain((input) => {
25
+ switch (input.type) {
26
+ case 'Multikey': {
27
+ if (input.publicKeyMultibase === undefined) {
28
+ return v.err({ message: `missing multikey`, path: ['publicKeyMultibase'] });
29
+ }
30
+ break;
31
+ }
32
+ case 'EcdsaSecp256k1VerificationKey2019':
33
+ case 'EcdsaSecp256r1VerificationKey2019': {
34
+ if (input.publicKeyMultibase === undefined) {
35
+ return v.err({ message: `missing multibase key`, path: ['publicKeyMultibase'] });
36
+ }
37
+ break;
38
+ }
39
+ }
40
+ return v.ok(input);
41
+ });
42
+ export const service = v.object({
43
+ // should've only been RFC3968, but did:plc uses relative URIs.
44
+ id: didRelativeUri,
45
+ type: v.union(v.string(), v.array(v.string())),
46
+ serviceEndpoint: v.union(rfc3968UriSchema, v.record(rfc3968UriSchema), v.array(v.union(rfc3968UriSchema, v.record(rfc3968UriSchema)))),
47
+ });
48
+ export const didDocument = v
49
+ .object({
50
+ '@context': v.array(rfc3968UriSchema),
51
+ id: didString,
52
+ alsoKnownAs: v
53
+ .array(rfc3968UriSchema)
54
+ .chain((input) => {
55
+ for (let i = 0, len = input.length; i < len; i++) {
56
+ const aka = input[i];
57
+ for (let j = 0; j < i; j++) {
58
+ if (aka === input[j]) {
59
+ return v.err({
60
+ message: `duplicate "${aka}" aka entry`,
61
+ path: [i],
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return v.ok(input);
67
+ })
68
+ .optional(),
69
+ verificationMethod: v
70
+ .array(verificationMethod)
71
+ .chain((input) => {
72
+ for (let i = 0, len = input.length; i < len; i++) {
73
+ const method = input[i];
74
+ const methodId = method.id;
75
+ for (let j = 0; j < i; j++) {
76
+ if (methodId === input[i].id) {
77
+ return v.err({
78
+ message: `duplicate "${methodId}" verification method`,
79
+ path: [i, 'id'],
80
+ });
81
+ }
82
+ }
83
+ }
84
+ return v.ok(input);
85
+ })
86
+ .optional(),
87
+ service: v.array(service).optional(),
88
+ controller: v.union(didString, v.array(didString)).optional(),
89
+ authentication: v.array(v.union(didRelativeUri, verificationMethod)).optional(),
90
+ })
91
+ .chain((input) => {
92
+ const { id: did, service: services } = input;
93
+ let newServices;
94
+ if (services) {
95
+ for (let i = 0, len = services.length; i < len; i++) {
96
+ const service = services[i];
97
+ let id = service.id;
98
+ if (id[0] === '#') {
99
+ id = did + id;
100
+ if (newServices !== undefined) {
101
+ newServices[i] = { ...service, id };
102
+ }
103
+ else {
104
+ newServices = services.with(i, { ...service, id });
105
+ }
106
+ }
107
+ for (let j = 0; j < i; j++) {
108
+ if (id === (newServices ?? services)[j].id) {
109
+ return v.err({
110
+ message: `duplicate "${id}" service`,
111
+ path: ['service', i, 'id'],
112
+ });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ if (newServices !== undefined) {
118
+ return v.ok({ ...input, service: newServices });
119
+ }
120
+ return v.ok(input);
121
+ });
122
+ //# sourceMappingURL=typedefs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typedefs.js","sourceRoot":"","sources":["../lib/typedefs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export type Handle = `${string}.${string}`;
2
+ export type Did<TMethod extends string = string> = `did:${TMethod}:${string}`;
3
+ export type AtprotoDid = Did<'plc' | 'web'>;
4
+ export interface VerificationMethod {
5
+ id: string;
6
+ type: string;
7
+ controller: Did;
8
+ publicKeyMultibase?: string;
9
+ publicKeyJwk?: Record<string, unknown>;
10
+ }
11
+ export interface Service {
12
+ id: string;
13
+ type: string | string[];
14
+ serviceEndpoint: string | Record<string, string> | (string | Record<string, string>)[];
15
+ }
16
+ export interface DidDocument {
17
+ '@context': string[];
18
+ id: Did;
19
+ controller?: Did | Did[];
20
+ alsoKnownAs?: string[];
21
+ verificationMethod?: VerificationMethod[];
22
+ authentication?: (string | VerificationMethod)[];
23
+ service?: Service[];
24
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../lib/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import * as t from './types.js';
2
+ export interface VerificationMaterial {
3
+ type: string;
4
+ publicKeyMultibase: string;
5
+ }
6
+ export declare const isAtprotoServiceEndpoint: (input: string) => boolean;
7
+ export declare const getAtprotoVerificationMaterial: (doc: t.DidDocument) => VerificationMaterial | undefined;
8
+ export declare const getAtprotoServiceEndpoint: (doc: t.DidDocument, predicate: {
9
+ id: `#${string}`;
10
+ type?: string;
11
+ }) => string | undefined;
12
+ export declare const getPdsEndpoint: (doc: t.DidDocument) => string | undefined;
13
+ export declare const getLabelerEndpoint: (doc: t.DidDocument) => string | undefined;
14
+ export declare const getBlueskyChatEndpoint: (doc: t.DidDocument) => string | undefined;
15
+ export declare const getBlueskyFeedgenEndpoint: (doc: t.DidDocument) => string | undefined;
16
+ export declare const getBlueskyNotificationEndpoint: (doc: t.DidDocument) => string | undefined;
package/dist/utils.js ADDED
@@ -0,0 +1,87 @@
1
+ import * as t from './types.js';
2
+ export const isAtprotoServiceEndpoint = (input) => {
3
+ const url = URL.parse(input);
4
+ return (url !== null &&
5
+ (url.protocol === 'https:' || url.protocol === 'http:') &&
6
+ url.pathname === '/' &&
7
+ url.search === '' &&
8
+ url.hash === '');
9
+ };
10
+ export const getAtprotoVerificationMaterial = (doc) => {
11
+ const verificationMethods = doc.verificationMethod;
12
+ if (!verificationMethods) {
13
+ return;
14
+ }
15
+ const expectedId = `${doc.id}#atproto`;
16
+ for (let idx = 0, len = verificationMethods.length; idx < len; idx++) {
17
+ const { id, type, publicKeyMultibase } = verificationMethods[idx];
18
+ if (id !== expectedId) {
19
+ continue;
20
+ }
21
+ if (publicKeyMultibase === undefined) {
22
+ continue;
23
+ }
24
+ return { type, publicKeyMultibase };
25
+ }
26
+ };
27
+ export const getAtprotoServiceEndpoint = (doc, predicate) => {
28
+ const services = doc.service;
29
+ if (!services) {
30
+ return;
31
+ }
32
+ const expectedId = doc.id + predicate.id;
33
+ const expectedType = predicate.type;
34
+ for (let idx = 0, len = services.length; idx < len; idx++) {
35
+ const { id, type, serviceEndpoint } = services[idx];
36
+ if (id !== expectedId) {
37
+ continue;
38
+ }
39
+ if (expectedType !== undefined) {
40
+ if (Array.isArray(type)) {
41
+ if (!type.includes(expectedType)) {
42
+ continue;
43
+ }
44
+ }
45
+ else {
46
+ if (type !== expectedType) {
47
+ continue;
48
+ }
49
+ }
50
+ }
51
+ if (typeof serviceEndpoint !== 'string' || !isAtprotoServiceEndpoint(serviceEndpoint)) {
52
+ continue;
53
+ }
54
+ return serviceEndpoint;
55
+ }
56
+ };
57
+ export const getPdsEndpoint = (doc) => {
58
+ return getAtprotoServiceEndpoint(doc, {
59
+ id: '#atproto_pds',
60
+ type: 'AtprotoPersonalDataServer',
61
+ });
62
+ };
63
+ export const getLabelerEndpoint = (doc) => {
64
+ return getAtprotoServiceEndpoint(doc, {
65
+ id: '#atproto_labeler',
66
+ type: 'AtprotoLabeler',
67
+ });
68
+ };
69
+ export const getBlueskyChatEndpoint = (doc) => {
70
+ return getAtprotoServiceEndpoint(doc, {
71
+ id: '#bsky_chat',
72
+ type: 'BskyChatService',
73
+ });
74
+ };
75
+ export const getBlueskyFeedgenEndpoint = (doc) => {
76
+ return getAtprotoServiceEndpoint(doc, {
77
+ id: '#bsky_fg',
78
+ type: 'BskyFeedGenerator',
79
+ });
80
+ };
81
+ export const getBlueskyNotificationEndpoint = (doc) => {
82
+ return getAtprotoServiceEndpoint(doc, {
83
+ id: '#bsky_notif',
84
+ type: 'BskyNotificationService',
85
+ });
86
+ };
87
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../lib/utils.ts"],"names":[],"mappings":"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"}
package/lib/did.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { AtprotoDid, Did } from './types.js';
2
+
3
+ import { isPlcDid } from './methods/plc.js';
4
+ import { isAtprotoWebDid } from './methods/web.js';
5
+
6
+ export const DID_RE = /^(?=.{7,2048}$)did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
7
+
8
+ /**
9
+ * checks if it's a DID identifier
10
+ */
11
+ export const isDid = (input: string): input is Did => {
12
+ return input.length >= 7 && input.length <= 2048 && DID_RE.test(input);
13
+ };
14
+
15
+ /**
16
+ * checks if it's a DID identifier that is supported by atproto
17
+ */
18
+ export const isAtprotoDid = (input: string): input is AtprotoDid => {
19
+ return isPlcDid(input) || isAtprotoWebDid(input);
20
+ };
21
+
22
+ /**
23
+ * returns the DID's method
24
+ */
25
+ export const extractDidMethod = <M extends string>(did: Did<M>): M => {
26
+ const isep = did.indexOf(':', 4);
27
+ const method = did.slice(4, isep);
28
+ return method as M;
29
+ };
package/lib/handle.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { Handle } from './types.js';
2
+
3
+ export const HANDLE_RE =
4
+ /^(?=.{4,253}$)(?:[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])$/;
5
+
6
+ export const isHandle = (str: string): str is Handle => {
7
+ return str.length >= 3 && str.length <= 253 && HANDLE_RE.test(str);
8
+ };
package/lib/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * as defs from './typedefs.js';
2
+ export * from './types.js';
3
+
4
+ export * from './utils.js';
5
+
6
+ export * from './did.js';
7
+ export * from './methods/plc.js';
8
+ export * from './methods/web.js';
9
+
10
+ export * from './handle.js';
@@ -0,0 +1,10 @@
1
+ import type { Did } from '../types.js';
2
+
3
+ export const PLC_DID_RE = /^did:plc:([a-z2-7]{24})$/;
4
+
5
+ /**
6
+ * checks if input is a did:plc identifier
7
+ */
8
+ export const isPlcDid = (input: string): input is Did<'plc'> => {
9
+ return input.length === 32 && PLC_DID_RE.test(input);
10
+ };
@@ -0,0 +1,58 @@
1
+ import type { Did } from '../types.js';
2
+
3
+ export const WEB_DID_RE =
4
+ /^did:web:([a-zA-Z0-9%\-]+(?:(?:\.[a-zA-Z0-9%\-]+)*(?:\.[a-zA-Z]{2,}))?)?((?::[a-zA-Z0-9\-%.]+)+)?$/;
5
+
6
+ export const ATPROTO_WEB_DID_RE =
7
+ /^did:web:([a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})|localhost(?:%3[aA]\d+)?)$/;
8
+
9
+ /**
10
+ * checks if input is a did:web identifier, note that you should probably use
11
+ * `isAtprotoDidWeb` for atproto-related cases as atproto only supports a subset
12
+ * of the did:web specification (namely, no custom paths)
13
+ */
14
+ export const isWebDid = (input: string): input is Did<'web'> => {
15
+ return input.length >= 9 && WEB_DID_RE.test(input);
16
+ };
17
+
18
+ /**
19
+ * checks if input is a did:web identifier that is supported by atproto
20
+ */
21
+ export const isAtprotoWebDid = (input: string): input is Did<'web'> => {
22
+ return input.length >= 12 && ATPROTO_WEB_DID_RE.test(input);
23
+ };
24
+
25
+ /**
26
+ * normalize a did:web identifier
27
+ */
28
+ export const normalizeWebDid = (did: Did<'web'>): Did<'web'> => {
29
+ const [host, ...paths] = did.slice(8).split(':').map(decodeURIComponent);
30
+
31
+ let normalized = `did:web:${encodeURIComponent(host.toLowerCase())}`;
32
+ if (paths.length > 0) {
33
+ normalized += `:${paths.join(':')}`;
34
+ }
35
+
36
+ return normalized as Did<'web'>;
37
+ };
38
+
39
+ /**
40
+ * converts did:web identifier into the DID document's URL
41
+ */
42
+ export const webDidToDocumentUrl = (did: Did<'web'>): URL => {
43
+ const [host, ...paths] = did.slice(8).split(':').map(decodeURIComponent);
44
+
45
+ let pathname = '/' + paths.join('/');
46
+ if (pathname === '/') {
47
+ pathname = `/.well-known/did.json`;
48
+ } else {
49
+ pathname += `/did.json`;
50
+ }
51
+
52
+ const url = new URL(`https://${host}${pathname}`);
53
+ if (url.hostname === 'localhost') {
54
+ url.protocol = 'http:';
55
+ }
56
+
57
+ return url;
58
+ };
@@ -0,0 +1,150 @@
1
+ import * as v from '@badrap/valita';
2
+
3
+ import { isDid } from './did.js';
4
+ import * as t from './types.js';
5
+
6
+ export const FRAGMENT_RE = /^#[^#]+$/;
7
+ export const MULTIBASE_RE = /^z[a-km-zA-HJ-NP-Z1-9]+$/;
8
+
9
+ export const rfc3968UriSchema = v.string().assert((input) => {
10
+ return URL.canParse(input);
11
+ }, `must be a url`);
12
+
13
+ export const didRelativeUri = v.string().assert((input) => {
14
+ return FRAGMENT_RE.test(input) || URL.canParse(input);
15
+ }, `must be a did relative uri`);
16
+
17
+ export const multibaseString = v.string().assert((input) => {
18
+ return MULTIBASE_RE.test(input);
19
+ }, `must be a base58 multibase`);
20
+
21
+ export const didString = v.string().assert(isDid, `must be a did`);
22
+
23
+ export const verificationMethod: v.Type<t.VerificationMethod> = v
24
+ .object({
25
+ id: didRelativeUri,
26
+ type: v.string(),
27
+ controller: didString,
28
+ publicKeyMultibase: multibaseString.optional(),
29
+ publicKeyJwk: v.record().optional(),
30
+ })
31
+ .chain((input) => {
32
+ switch (input.type) {
33
+ case 'Multikey': {
34
+ if (input.publicKeyMultibase === undefined) {
35
+ return v.err({ message: `missing multikey`, path: ['publicKeyMultibase'] });
36
+ }
37
+
38
+ break;
39
+ }
40
+ case 'EcdsaSecp256k1VerificationKey2019':
41
+ case 'EcdsaSecp256r1VerificationKey2019': {
42
+ if (input.publicKeyMultibase === undefined) {
43
+ return v.err({ message: `missing multibase key`, path: ['publicKeyMultibase'] });
44
+ }
45
+
46
+ break;
47
+ }
48
+ }
49
+
50
+ return v.ok(input);
51
+ });
52
+
53
+ export const service: v.Type<t.Service> = v.object({
54
+ // should've only been RFC3968, but did:plc uses relative URIs.
55
+ id: didRelativeUri,
56
+ type: v.union(v.string(), v.array(v.string())),
57
+ serviceEndpoint: v.union(
58
+ rfc3968UriSchema,
59
+ v.record(rfc3968UriSchema),
60
+ v.array(v.union(rfc3968UriSchema, v.record(rfc3968UriSchema))),
61
+ ),
62
+ });
63
+
64
+ export const didDocument: v.Type<t.DidDocument> = v
65
+ .object({
66
+ '@context': v.array(rfc3968UriSchema),
67
+
68
+ id: didString,
69
+
70
+ alsoKnownAs: v
71
+ .array(rfc3968UriSchema)
72
+ .chain((input) => {
73
+ for (let i = 0, len = input.length; i < len; i++) {
74
+ const aka = input[i];
75
+
76
+ for (let j = 0; j < i; j++) {
77
+ if (aka === input[j]) {
78
+ return v.err({
79
+ message: `duplicate "${aka}" aka entry`,
80
+ path: [i],
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ return v.ok(input);
87
+ })
88
+ .optional(),
89
+ verificationMethod: v
90
+ .array(verificationMethod)
91
+ .chain((input) => {
92
+ for (let i = 0, len = input.length; i < len; i++) {
93
+ const method = input[i];
94
+ const methodId = method.id;
95
+
96
+ for (let j = 0; j < i; j++) {
97
+ if (methodId === input[i].id) {
98
+ return v.err({
99
+ message: `duplicate "${methodId}" verification method`,
100
+ path: [i, 'id'],
101
+ });
102
+ }
103
+ }
104
+ }
105
+
106
+ return v.ok(input);
107
+ })
108
+ .optional(),
109
+ service: v.array(service).optional(),
110
+
111
+ controller: v.union(didString, v.array(didString)).optional(),
112
+ authentication: v.array(v.union(didRelativeUri, verificationMethod)).optional(),
113
+ })
114
+ .chain((input) => {
115
+ const { id: did, service: services } = input;
116
+
117
+ let newServices: t.Service[] | undefined;
118
+
119
+ if (services) {
120
+ for (let i = 0, len = services.length; i < len; i++) {
121
+ const service = services[i];
122
+
123
+ let id = service.id;
124
+ if (id[0] === '#') {
125
+ id = did + id;
126
+
127
+ if (newServices !== undefined) {
128
+ newServices[i] = { ...service, id };
129
+ } else {
130
+ newServices = services.with(i, { ...service, id });
131
+ }
132
+ }
133
+
134
+ for (let j = 0; j < i; j++) {
135
+ if (id === (newServices ?? services)[j].id) {
136
+ return v.err({
137
+ message: `duplicate "${id}" service`,
138
+ path: ['service', i, 'id'],
139
+ });
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ if (newServices !== undefined) {
146
+ return v.ok({ ...input, service: newServices });
147
+ }
148
+
149
+ return v.ok(input);
150
+ });
package/lib/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ export type Handle = `${string}.${string}`;
2
+
3
+ export type Did<TMethod extends string = string> = `did:${TMethod}:${string}`;
4
+
5
+ export type AtprotoDid = Did<'plc' | 'web'>;
6
+
7
+ export interface VerificationMethod {
8
+ id: string;
9
+ type: string;
10
+ controller: Did;
11
+ publicKeyMultibase?: string;
12
+ publicKeyJwk?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface Service {
16
+ id: string;
17
+ type: string | string[];
18
+ serviceEndpoint: string | Record<string, string> | (string | Record<string, string>)[];
19
+ }
20
+
21
+ export interface DidDocument {
22
+ '@context': string[];
23
+ id: Did;
24
+ controller?: Did | Did[];
25
+ alsoKnownAs?: string[];
26
+ verificationMethod?: VerificationMethod[];
27
+ authentication?: (string | VerificationMethod)[];
28
+ service?: Service[];
29
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,115 @@
1
+ import * as t from './types.js';
2
+
3
+ export interface VerificationMaterial {
4
+ type: string;
5
+ publicKeyMultibase: string;
6
+ }
7
+
8
+ export const isAtprotoServiceEndpoint = (input: string): boolean => {
9
+ const url = URL.parse(input);
10
+
11
+ return (
12
+ url !== null &&
13
+ (url.protocol === 'https:' || url.protocol === 'http:') &&
14
+ url.pathname === '/' &&
15
+ url.search === '' &&
16
+ url.hash === ''
17
+ );
18
+ };
19
+
20
+ export const getAtprotoVerificationMaterial = (doc: t.DidDocument): VerificationMaterial | undefined => {
21
+ const verificationMethods = doc.verificationMethod;
22
+ if (!verificationMethods) {
23
+ return;
24
+ }
25
+
26
+ const expectedId = `${doc.id}#atproto`;
27
+
28
+ for (let idx = 0, len = verificationMethods.length; idx < len; idx++) {
29
+ const { id, type, publicKeyMultibase } = verificationMethods[idx];
30
+
31
+ if (id !== expectedId) {
32
+ continue;
33
+ }
34
+
35
+ if (publicKeyMultibase === undefined) {
36
+ continue;
37
+ }
38
+
39
+ return { type, publicKeyMultibase };
40
+ }
41
+ };
42
+
43
+ export const getAtprotoServiceEndpoint = (
44
+ doc: t.DidDocument,
45
+ predicate: { id: `#${string}`; type?: string },
46
+ ): string | undefined => {
47
+ const services = doc.service;
48
+ if (!services) {
49
+ return;
50
+ }
51
+
52
+ const expectedId = doc.id + predicate.id;
53
+ const expectedType = predicate.type;
54
+
55
+ for (let idx = 0, len = services.length; idx < len; idx++) {
56
+ const { id, type, serviceEndpoint } = services[idx];
57
+
58
+ if (id !== expectedId) {
59
+ continue;
60
+ }
61
+
62
+ if (expectedType !== undefined) {
63
+ if (Array.isArray(type)) {
64
+ if (!type.includes(expectedType)) {
65
+ continue;
66
+ }
67
+ } else {
68
+ if (type !== expectedType) {
69
+ continue;
70
+ }
71
+ }
72
+ }
73
+
74
+ if (typeof serviceEndpoint !== 'string' || !isAtprotoServiceEndpoint(serviceEndpoint)) {
75
+ continue;
76
+ }
77
+
78
+ return serviceEndpoint;
79
+ }
80
+ };
81
+
82
+ export const getPdsEndpoint = (doc: t.DidDocument): string | undefined => {
83
+ return getAtprotoServiceEndpoint(doc, {
84
+ id: '#atproto_pds',
85
+ type: 'AtprotoPersonalDataServer',
86
+ });
87
+ };
88
+
89
+ export const getLabelerEndpoint = (doc: t.DidDocument): string | undefined => {
90
+ return getAtprotoServiceEndpoint(doc, {
91
+ id: '#atproto_labeler',
92
+ type: 'AtprotoLabeler',
93
+ });
94
+ };
95
+
96
+ export const getBlueskyChatEndpoint = (doc: t.DidDocument): string | undefined => {
97
+ return getAtprotoServiceEndpoint(doc, {
98
+ id: '#bsky_chat',
99
+ type: 'BskyChatService',
100
+ });
101
+ };
102
+
103
+ export const getBlueskyFeedgenEndpoint = (doc: t.DidDocument): string | undefined => {
104
+ return getAtprotoServiceEndpoint(doc, {
105
+ id: '#bsky_fg',
106
+ type: 'BskyFeedGenerator',
107
+ });
108
+ };
109
+
110
+ export const getBlueskyNotificationEndpoint = (doc: t.DidDocument): string | undefined => {
111
+ return getAtprotoServiceEndpoint(doc, {
112
+ id: '#bsky_notif',
113
+ type: 'BskyNotificationService',
114
+ });
115
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@atcute/identity",
4
+ "version": "0.1.0",
5
+ "description": "syntax, type definitions and schemas for atproto handles, DIDs and DID documents",
6
+ "keywords": [
7
+ "atproto",
8
+ "did"
9
+ ],
10
+ "license": "MIT",
11
+ "repository": {
12
+ "url": "https://github.com/mary-ext/atcute",
13
+ "directory": "packages/identity/identity"
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "lib/",
18
+ "!lib/**/*.bench.ts",
19
+ "!lib/**/*.test.ts"
20
+ ],
21
+ "exports": {
22
+ ".": "./dist/index.js"
23
+ },
24
+ "sideEffects": false,
25
+ "devDependencies": {
26
+ "@types/bun": "^1.2.1"
27
+ },
28
+ "dependencies": {
29
+ "@badrap/valita": "^0.4.2"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc --project tsconfig.build.json",
33
+ "test": "bun test --coverage",
34
+ "prepublish": "rm -rf dist; pnpm run build"
35
+ }
36
+ }