@atproto/identity 0.2.1 → 0.3.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/CHANGELOG.md +13 -0
- package/dist/did/atproto-data.d.ts +2 -5
- package/dist/did/base-resolver.d.ts +2 -2
- package/dist/index.js +108 -100
- package/dist/index.js.map +3 -3
- package/dist/types.d.ts +6 -94
- package/package.json +2 -3
- package/src/did/atproto-data.ts +22 -92
- package/src/did/base-resolver.ts +20 -9
- package/src/did/memory-cache.ts +1 -2
- package/src/handle/index.ts +1 -1
- package/src/types.ts +11 -24
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DidDocument } from '@atproto/common-web';
|
|
2
|
+
export { didDocument } from '@atproto/common-web';
|
|
3
|
+
export type { DidDocument } from '@atproto/common-web';
|
|
2
4
|
export declare type IdentityResolverOpts = {
|
|
3
5
|
timeout?: number;
|
|
4
6
|
plcUrl?: string;
|
|
@@ -25,102 +27,12 @@ export declare type CacheResult = {
|
|
|
25
27
|
doc: DidDocument;
|
|
26
28
|
updatedAt: number;
|
|
27
29
|
stale: boolean;
|
|
30
|
+
expired: boolean;
|
|
28
31
|
};
|
|
29
32
|
export interface DidCache {
|
|
30
|
-
cacheDid(did: string, doc: DidDocument): Promise<void>;
|
|
33
|
+
cacheDid(did: string, doc: DidDocument, prevResult?: CacheResult): Promise<void>;
|
|
31
34
|
checkCache(did: string): Promise<CacheResult | null>;
|
|
32
|
-
refreshCache(did: string, getDoc: () => Promise<DidDocument | null
|
|
35
|
+
refreshCache(did: string, getDoc: () => Promise<DidDocument | null>, prevResult?: CacheResult): Promise<void>;
|
|
33
36
|
clearEntry(did: string): Promise<void>;
|
|
34
37
|
clear(): Promise<void>;
|
|
35
38
|
}
|
|
36
|
-
export declare const verificationMethod: z.ZodObject<{
|
|
37
|
-
id: z.ZodString;
|
|
38
|
-
type: z.ZodString;
|
|
39
|
-
controller: z.ZodString;
|
|
40
|
-
publicKeyMultibase: z.ZodOptional<z.ZodString>;
|
|
41
|
-
}, "strip", z.ZodTypeAny, {
|
|
42
|
-
id: string;
|
|
43
|
-
type: string;
|
|
44
|
-
controller: string;
|
|
45
|
-
publicKeyMultibase?: string | undefined;
|
|
46
|
-
}, {
|
|
47
|
-
id: string;
|
|
48
|
-
type: string;
|
|
49
|
-
controller: string;
|
|
50
|
-
publicKeyMultibase?: string | undefined;
|
|
51
|
-
}>;
|
|
52
|
-
export declare const service: z.ZodObject<{
|
|
53
|
-
id: z.ZodString;
|
|
54
|
-
type: z.ZodString;
|
|
55
|
-
serviceEndpoint: z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
56
|
-
}, "strip", z.ZodTypeAny, {
|
|
57
|
-
id: string;
|
|
58
|
-
type: string;
|
|
59
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
60
|
-
}, {
|
|
61
|
-
id: string;
|
|
62
|
-
type: string;
|
|
63
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
64
|
-
}>;
|
|
65
|
-
export declare const didDocument: z.ZodObject<{
|
|
66
|
-
id: z.ZodString;
|
|
67
|
-
alsoKnownAs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
68
|
-
verificationMethod: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
69
|
-
id: z.ZodString;
|
|
70
|
-
type: z.ZodString;
|
|
71
|
-
controller: z.ZodString;
|
|
72
|
-
publicKeyMultibase: z.ZodOptional<z.ZodString>;
|
|
73
|
-
}, "strip", z.ZodTypeAny, {
|
|
74
|
-
id: string;
|
|
75
|
-
type: string;
|
|
76
|
-
controller: string;
|
|
77
|
-
publicKeyMultibase?: string | undefined;
|
|
78
|
-
}, {
|
|
79
|
-
id: string;
|
|
80
|
-
type: string;
|
|
81
|
-
controller: string;
|
|
82
|
-
publicKeyMultibase?: string | undefined;
|
|
83
|
-
}>, "many">>;
|
|
84
|
-
service: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
85
|
-
id: z.ZodString;
|
|
86
|
-
type: z.ZodString;
|
|
87
|
-
serviceEndpoint: z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
88
|
-
}, "strip", z.ZodTypeAny, {
|
|
89
|
-
id: string;
|
|
90
|
-
type: string;
|
|
91
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
92
|
-
}, {
|
|
93
|
-
id: string;
|
|
94
|
-
type: string;
|
|
95
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
96
|
-
}>, "many">>;
|
|
97
|
-
}, "strip", z.ZodTypeAny, {
|
|
98
|
-
id: string;
|
|
99
|
-
alsoKnownAs?: string[] | undefined;
|
|
100
|
-
verificationMethod?: {
|
|
101
|
-
id: string;
|
|
102
|
-
type: string;
|
|
103
|
-
controller: string;
|
|
104
|
-
publicKeyMultibase?: string | undefined;
|
|
105
|
-
}[] | undefined;
|
|
106
|
-
service?: {
|
|
107
|
-
id: string;
|
|
108
|
-
type: string;
|
|
109
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
110
|
-
}[] | undefined;
|
|
111
|
-
}, {
|
|
112
|
-
id: string;
|
|
113
|
-
alsoKnownAs?: string[] | undefined;
|
|
114
|
-
verificationMethod?: {
|
|
115
|
-
id: string;
|
|
116
|
-
type: string;
|
|
117
|
-
controller: string;
|
|
118
|
-
publicKeyMultibase?: string | undefined;
|
|
119
|
-
}[] | undefined;
|
|
120
|
-
service?: {
|
|
121
|
-
id: string;
|
|
122
|
-
type: string;
|
|
123
|
-
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
|
|
124
|
-
}[] | undefined;
|
|
125
|
-
}>;
|
|
126
|
-
export declare type DidDocument = z.infer<typeof didDocument>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Library for decentralized identities in atproto using DIDs and handles",
|
|
6
6
|
"keywords": [
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"main": "dist/index.js",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"axios": "^0.27.2",
|
|
20
|
-
"
|
|
21
|
-
"@atproto/common-web": "^0.2.1",
|
|
20
|
+
"@atproto/common-web": "^0.2.2",
|
|
22
21
|
"@atproto/crypto": "^0.2.2"
|
|
23
22
|
},
|
|
24
23
|
"devDependencies": {
|
package/src/did/atproto-data.ts
CHANGED
|
@@ -1,73 +1,39 @@
|
|
|
1
1
|
import * as crypto from '@atproto/crypto'
|
|
2
2
|
import { DidDocument, AtprotoData } from '../types'
|
|
3
|
+
import {
|
|
4
|
+
getDid,
|
|
5
|
+
getHandle,
|
|
6
|
+
getPdsEndpoint,
|
|
7
|
+
getFeedGenEndpoint,
|
|
8
|
+
getNotifEndpoint,
|
|
9
|
+
getSigningKey,
|
|
10
|
+
} from '@atproto/common-web'
|
|
3
11
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
export {
|
|
13
|
+
getDid,
|
|
14
|
+
getHandle,
|
|
15
|
+
getPdsEndpoint as getPds,
|
|
16
|
+
getFeedGenEndpoint as getFeedGen,
|
|
17
|
+
getNotifEndpoint as getNotif,
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
export const getKey = (doc: DidDocument): string | undefined => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
if (!keys) return undefined
|
|
16
|
-
if (typeof keys !== 'object') return undefined
|
|
17
|
-
if (!Array.isArray(keys)) {
|
|
18
|
-
keys = [keys]
|
|
19
|
-
}
|
|
20
|
-
const found = keys.find(
|
|
21
|
-
(key) => key.id === '#atproto' || key.id === `${did}#atproto`,
|
|
22
|
-
)
|
|
23
|
-
if (!found) return undefined
|
|
21
|
+
const key = getSigningKey(doc)
|
|
22
|
+
if (!key) return undefined
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
// should we be surfacing errors here or returning undefined?
|
|
27
|
-
if (!found.publicKeyMultibase) return undefined
|
|
28
|
-
const keyBytes = crypto.multibaseToBytes(found.publicKeyMultibase)
|
|
24
|
+
const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
|
|
29
25
|
let didKey: string | undefined = undefined
|
|
30
|
-
if (
|
|
26
|
+
if (key.type === 'EcdsaSecp256r1VerificationKey2019') {
|
|
31
27
|
didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
|
|
32
|
-
} else if (
|
|
28
|
+
} else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {
|
|
33
29
|
didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
|
|
34
|
-
} else if (
|
|
35
|
-
const parsed = crypto.parseMultikey(
|
|
30
|
+
} else if (key.type === 'Multikey') {
|
|
31
|
+
const parsed = crypto.parseMultikey(key.publicKeyMultibase)
|
|
36
32
|
didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)
|
|
37
33
|
}
|
|
38
34
|
return didKey
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
export const getHandle = (doc: DidDocument): string | undefined => {
|
|
42
|
-
const aka = doc.alsoKnownAs
|
|
43
|
-
if (!aka) return undefined
|
|
44
|
-
const found = aka.find((name) => name.startsWith('at://'))
|
|
45
|
-
if (!found) return undefined
|
|
46
|
-
// strip off at:// prefix
|
|
47
|
-
return found.slice(5)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const getPds = (doc: DidDocument): string | undefined => {
|
|
51
|
-
return getServiceEndpoint(doc, {
|
|
52
|
-
id: '#atproto_pds',
|
|
53
|
-
type: 'AtprotoPersonalDataServer',
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const getFeedGen = (doc: DidDocument): string | undefined => {
|
|
58
|
-
return getServiceEndpoint(doc, {
|
|
59
|
-
id: '#bsky_fg',
|
|
60
|
-
type: 'BskyFeedGenerator',
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const getNotif = (doc: DidDocument): string | undefined => {
|
|
65
|
-
return getServiceEndpoint(doc, {
|
|
66
|
-
id: '#bsky_notif',
|
|
67
|
-
type: 'BskyNotificationService',
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
37
|
export const parseToAtprotoDocument = (
|
|
72
38
|
doc: DidDocument,
|
|
73
39
|
): Partial<AtprotoData> => {
|
|
@@ -76,7 +42,7 @@ export const parseToAtprotoDocument = (
|
|
|
76
42
|
did,
|
|
77
43
|
signingKey: getKey(doc),
|
|
78
44
|
handle: getHandle(doc),
|
|
79
|
-
pds:
|
|
45
|
+
pds: getPdsEndpoint(doc),
|
|
80
46
|
}
|
|
81
47
|
}
|
|
82
48
|
|
|
@@ -96,39 +62,3 @@ export const ensureAtpDocument = (doc: DidDocument): AtprotoData => {
|
|
|
96
62
|
}
|
|
97
63
|
return { did, signingKey, handle, pds }
|
|
98
64
|
}
|
|
99
|
-
|
|
100
|
-
// Check protocol and hostname to prevent potential SSRF
|
|
101
|
-
const validateUrl = (url: string) => {
|
|
102
|
-
const { hostname, protocol } = new URL(url)
|
|
103
|
-
if (!['http:', 'https:'].includes(protocol)) {
|
|
104
|
-
throw new Error('Invalid pds protocol')
|
|
105
|
-
}
|
|
106
|
-
if (!hostname) {
|
|
107
|
-
throw new Error('Invalid pds hostname')
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const getServiceEndpoint = (
|
|
112
|
-
doc: DidDocument,
|
|
113
|
-
opts: { id: string; type: string },
|
|
114
|
-
) => {
|
|
115
|
-
const did = getDid(doc)
|
|
116
|
-
let services = doc.service
|
|
117
|
-
if (!services) return undefined
|
|
118
|
-
if (typeof services !== 'object') return undefined
|
|
119
|
-
if (!Array.isArray(services)) {
|
|
120
|
-
services = [services]
|
|
121
|
-
}
|
|
122
|
-
const found = services.find(
|
|
123
|
-
(service) => service.id === opts.id || service.id === `${did}${opts.id}`,
|
|
124
|
-
)
|
|
125
|
-
if (!found) return undefined
|
|
126
|
-
if (found.type !== opts.type) {
|
|
127
|
-
return undefined
|
|
128
|
-
}
|
|
129
|
-
if (typeof found.serviceEndpoint !== 'string') {
|
|
130
|
-
return undefined
|
|
131
|
-
}
|
|
132
|
-
validateUrl(found.serviceEndpoint)
|
|
133
|
-
return found.serviceEndpoint
|
|
134
|
-
}
|
package/src/did/base-resolver.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as crypto from '@atproto/crypto'
|
|
2
2
|
import { check } from '@atproto/common-web'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DidCache,
|
|
5
|
+
AtprotoData,
|
|
6
|
+
DidDocument,
|
|
7
|
+
didDocument,
|
|
8
|
+
CacheResult,
|
|
9
|
+
} from '../types'
|
|
4
10
|
import * as atprotoData from './atproto-data'
|
|
5
11
|
import { DidNotFoundError, PoorlyFormattedDidDocumentError } from '../errors'
|
|
6
12
|
|
|
@@ -25,20 +31,25 @@ export abstract class BaseResolver {
|
|
|
25
31
|
return this.validateDidDoc(did, got)
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
async refreshCache(did: string): Promise<void> {
|
|
29
|
-
await this.cache?.refreshCache(
|
|
34
|
+
async refreshCache(did: string, prevResult?: CacheResult): Promise<void> {
|
|
35
|
+
await this.cache?.refreshCache(
|
|
36
|
+
did,
|
|
37
|
+
() => this.resolveNoCache(did),
|
|
38
|
+
prevResult,
|
|
39
|
+
)
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
async resolve(
|
|
33
43
|
did: string,
|
|
34
44
|
forceRefresh = false,
|
|
35
45
|
): Promise<DidDocument | null> {
|
|
46
|
+
let fromCache: CacheResult | null = null
|
|
36
47
|
if (this.cache && !forceRefresh) {
|
|
37
|
-
|
|
38
|
-
if (fromCache
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
fromCache = await this.cache.checkCache(did)
|
|
49
|
+
if (fromCache && !fromCache.expired) {
|
|
50
|
+
if (fromCache?.stale) {
|
|
51
|
+
await this.refreshCache(did, fromCache)
|
|
52
|
+
}
|
|
42
53
|
return fromCache.doc
|
|
43
54
|
}
|
|
44
55
|
}
|
|
@@ -48,7 +59,7 @@ export abstract class BaseResolver {
|
|
|
48
59
|
await this.cache?.clearEntry(did)
|
|
49
60
|
return null
|
|
50
61
|
}
|
|
51
|
-
await this.cache?.cacheDid(did, got)
|
|
62
|
+
await this.cache?.cacheDid(did, got, fromCache ?? undefined)
|
|
52
63
|
return got
|
|
53
64
|
}
|
|
54
65
|
|
package/src/did/memory-cache.ts
CHANGED
|
@@ -35,13 +35,12 @@ export class MemoryCache implements DidCache {
|
|
|
35
35
|
if (!val) return null
|
|
36
36
|
const now = Date.now()
|
|
37
37
|
const expired = now > val.updatedAt + this.maxTTL
|
|
38
|
-
if (expired) return null
|
|
39
|
-
|
|
40
38
|
const stale = now > val.updatedAt + this.staleTTL
|
|
41
39
|
return {
|
|
42
40
|
...val,
|
|
43
41
|
did,
|
|
44
42
|
stale,
|
|
43
|
+
expired,
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
package/src/handle/index.ts
CHANGED
|
@@ -50,7 +50,7 @@ export class HandleResolver {
|
|
|
50
50
|
const url = new URL('/.well-known/atproto-did', `https://${handle}`)
|
|
51
51
|
try {
|
|
52
52
|
const res = await fetch(url, { signal })
|
|
53
|
-
const did = await res.text()
|
|
53
|
+
const did = (await res.text()).split('\n')[0].trim()
|
|
54
54
|
if (typeof did === 'string' && did.startsWith('did:')) {
|
|
55
55
|
return did
|
|
56
56
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DidDocument } from '@atproto/common-web'
|
|
2
|
+
|
|
3
|
+
export { didDocument } from '@atproto/common-web'
|
|
4
|
+
export type { DidDocument } from '@atproto/common-web'
|
|
2
5
|
|
|
3
6
|
export type IdentityResolverOpts = {
|
|
4
7
|
timeout?: number
|
|
@@ -30,37 +33,21 @@ export type CacheResult = {
|
|
|
30
33
|
doc: DidDocument
|
|
31
34
|
updatedAt: number
|
|
32
35
|
stale: boolean
|
|
36
|
+
expired: boolean
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export interface DidCache {
|
|
36
|
-
cacheDid(
|
|
40
|
+
cacheDid(
|
|
41
|
+
did: string,
|
|
42
|
+
doc: DidDocument,
|
|
43
|
+
prevResult?: CacheResult,
|
|
44
|
+
): Promise<void>
|
|
37
45
|
checkCache(did: string): Promise<CacheResult | null>
|
|
38
46
|
refreshCache(
|
|
39
47
|
did: string,
|
|
40
48
|
getDoc: () => Promise<DidDocument | null>,
|
|
49
|
+
prevResult?: CacheResult,
|
|
41
50
|
): Promise<void>
|
|
42
51
|
clearEntry(did: string): Promise<void>
|
|
43
52
|
clear(): Promise<void>
|
|
44
53
|
}
|
|
45
|
-
|
|
46
|
-
export const verificationMethod = z.object({
|
|
47
|
-
id: z.string(),
|
|
48
|
-
type: z.string(),
|
|
49
|
-
controller: z.string(),
|
|
50
|
-
publicKeyMultibase: z.string().optional(),
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
export const service = z.object({
|
|
54
|
-
id: z.string(),
|
|
55
|
-
type: z.string(),
|
|
56
|
-
serviceEndpoint: z.union([z.string(), z.record(z.unknown())]),
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
export const didDocument = z.object({
|
|
60
|
-
id: z.string(),
|
|
61
|
-
alsoKnownAs: z.array(z.string()).optional(),
|
|
62
|
-
verificationMethod: z.array(verificationMethod).optional(),
|
|
63
|
-
service: z.array(service).optional(),
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
export type DidDocument = z.infer<typeof didDocument>
|