forked from decentralized-identity/did-resolver
-
Notifications
You must be signed in to change notification settings - Fork 2
/
resolver.ts
192 lines (173 loc) · 4.82 KB
/
resolver.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Copyright 2018 ConsenSys AG
// Licensed under the Apache License, Version 2.0(the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export interface DIDDocument {
'@context': 'https://w3id.org/did/v1' | string | string[]
id: string
publicKey: PublicKey[]
authentication?: (string | PublicKey | Authentication)[]
/**
* @deprecated This does not appear in the did-core spec
*/
uportProfile?: any
service?: ServiceEndpoint[]
/**
* @deprecated this property has been removed from the did-core spec
*/
created?: string
/**
* @deprecated this property has been removed from the did-core spec
*/
updated?: string
/**
* @deprecated this property has been removed from the did-core spec
*/
proof?: LinkedDataProof
keyAgreement?: (string | PublicKey)[]
}
export interface ServiceEndpoint {
id: string
type: string
serviceEndpoint: string
description?: string
}
export interface PublicKey {
id: string
type: string
controller: string
ethereumAddress?: string
publicKeyBase64?: string
publicKeyBase58?: string
publicKeyHex?: string
publicKeyPem?: string
}
/**
* @deprecated The `authentication` array should be an array of strings or `PublicKey`
*/
export interface Authentication {
type: string
publicKey: string
}
export interface LinkedDataProof {
type: string
created: string
creator: string
nonce: string
signatureValue: string
}
export interface Params {
[index: string]: string
}
export interface ParsedDID {
did: string
didUrl: string
method: string
id: string
path?: string
fragment?: string
query?: string
params?: Params
}
export type DIDResolver = (
did: string,
parsed: ParsedDID,
resolver: Resolver
) => Promise<null | DIDDocument>
export type WrappedResolver = () => Promise<null | DIDDocument>
export type DIDCache = (
parsed: ParsedDID,
resolve: WrappedResolver
) => Promise<null | DIDDocument>
interface ResolverRegistry {
[index: string]: DIDResolver
}
export function inMemoryCache(): DIDCache {
const cache: Map<string, DIDDocument | null> = new Map()
return async (parsed, resolve) => {
if (parsed.params && parsed.params['no-cache'] === 'true')
return await resolve()
const cached = cache.get(parsed.did)
if (cached !== undefined) return cached
const doc = await resolve()
if (doc !== null) {
cache.set(parsed.did, doc)
}
return doc
}
}
export function noCache(
parsed: ParsedDID,
resolve: WrappedResolver
): Promise<null | DIDDocument> {
return resolve()
}
const ID_CHAR = '[a-zA-Z0-9_.-]'
const METHOD = '([a-zA-Z0-9_]+)'
const METHOD_ID = `(${ID_CHAR}+(:${ID_CHAR}+)*)`
const PARAM_CHAR = '[a-zA-Z0-9_.:%-]'
const PARAM = `;${PARAM_CHAR}+=${PARAM_CHAR}*`
const PARAMS = `((${PARAM})*)`
const PATH = `(\/[^#?]*)?`
const QUERY = `([?][^#]*)?`
const FRAGMENT = `(\#.*)?`
const DID_MATCHER = new RegExp(
`^did:${METHOD}:${METHOD_ID}${PARAMS}${PATH}${QUERY}${FRAGMENT}$`
)
export function parse(didUrl: string): ParsedDID {
if (didUrl === '' || !didUrl) throw new Error('Missing DID')
const sections = didUrl.match(DID_MATCHER)
if (sections) {
const parts: ParsedDID = {
did: `did:${sections[1]}:${sections[2]}`,
method: sections[1],
id: sections[2],
didUrl
}
if (sections[4]) {
const params = sections[4].slice(1).split(';')
parts.params = {}
for (const p of params) {
const kv = p.split('=')
parts.params[kv[0]] = kv[1]
}
}
if (sections[6]) parts.path = sections[6]
if (sections[7]) parts.query = sections[7].slice(1)
if (sections[8]) parts.fragment = sections[8].slice(1)
return parts
}
throw new Error(`Invalid DID ${didUrl}`)
}
export class Resolver {
private registry: ResolverRegistry
private cache: DIDCache
constructor(
registry: ResolverRegistry = {},
cache?: DIDCache | boolean | undefined
) {
this.registry = registry
this.cache = cache === true ? inMemoryCache() : cache || noCache
}
async resolve(didUrl: string): Promise<DIDDocument> {
const parsed = parse(didUrl)
const resolver = this.registry[parsed.method]
if (resolver) {
const doc = await this.cache(parsed, () =>
resolver(parsed.did, parsed, this)
)
if (doc == null) {
throw new Error(`resolver returned null for ${parsed.did}`)
} else {
return doc
}
}
throw new Error(`Unsupported DID method: '${parsed.method}'`)
}
}