@twin.org/identity-connector-entity-storage 0.0.1-next.3
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 +201 -0
- package/README.md +21 -0
- package/dist/cjs/index.cjs +1223 -0
- package/dist/esm/index.mjs +1219 -0
- package/dist/types/entities/identityDocument.d.ts +22 -0
- package/dist/types/entities/identityProfile.d.ts +17 -0
- package/dist/types/entityStorageIdentityConnector.d.ts +163 -0
- package/dist/types/entityStorageIdentityProfileConnector.d.ts +86 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/schema.d.ts +10 -0
- package/docs/changelog.md +5 -0
- package/docs/examples.md +1 -0
- package/docs/reference/classes/EntityStorageIdentityConnector.md +593 -0
- package/docs/reference/classes/EntityStorageIdentityProfileConnector.md +239 -0
- package/docs/reference/classes/IdentityDocument.md +45 -0
- package/docs/reference/classes/IdentityProfile.md +37 -0
- package/docs/reference/functions/initSchema.md +23 -0
- package/docs/reference/index.md +12 -0
- package/locales/en.json +41 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var entity = require('@twin.org/entity');
|
|
4
|
+
var core = require('@twin.org/core');
|
|
5
|
+
var crypto = require('@twin.org/crypto');
|
|
6
|
+
var entityStorageModels = require('@twin.org/entity-storage-models');
|
|
7
|
+
var identityModels = require('@twin.org/identity-models');
|
|
8
|
+
var standardsW3cDid = require('@twin.org/standards-w3c-did');
|
|
9
|
+
var vaultModels = require('@twin.org/vault-models');
|
|
10
|
+
var web = require('@twin.org/web');
|
|
11
|
+
|
|
12
|
+
// Copyright 2024 IOTA Stiftung.
|
|
13
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
14
|
+
/**
|
|
15
|
+
* Class describing the identity document.
|
|
16
|
+
*/
|
|
17
|
+
exports.IdentityDocument = class IdentityDocument {
|
|
18
|
+
/**
|
|
19
|
+
* The identity of the document.
|
|
20
|
+
*/
|
|
21
|
+
id;
|
|
22
|
+
/**
|
|
23
|
+
* The DID document.
|
|
24
|
+
*/
|
|
25
|
+
document;
|
|
26
|
+
/**
|
|
27
|
+
* The signature of the document.
|
|
28
|
+
*/
|
|
29
|
+
signature;
|
|
30
|
+
/**
|
|
31
|
+
* The controller of the document.
|
|
32
|
+
*/
|
|
33
|
+
controller;
|
|
34
|
+
};
|
|
35
|
+
__decorate([
|
|
36
|
+
entity.property({ type: "string", isPrimary: true }),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], exports.IdentityDocument.prototype, "id", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
entity.property({ type: "object" }),
|
|
41
|
+
__metadata("design:type", Object)
|
|
42
|
+
], exports.IdentityDocument.prototype, "document", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
entity.property({ type: "string" }),
|
|
45
|
+
__metadata("design:type", String)
|
|
46
|
+
], exports.IdentityDocument.prototype, "signature", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
entity.property({ type: "string" }),
|
|
49
|
+
__metadata("design:type", String)
|
|
50
|
+
], exports.IdentityDocument.prototype, "controller", void 0);
|
|
51
|
+
exports.IdentityDocument = __decorate([
|
|
52
|
+
entity.entity()
|
|
53
|
+
], exports.IdentityDocument);
|
|
54
|
+
|
|
55
|
+
// Copyright 2024 IOTA Stiftung.
|
|
56
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
57
|
+
/**
|
|
58
|
+
* Class representing profile details for the identity.
|
|
59
|
+
*/
|
|
60
|
+
exports.IdentityProfile = class IdentityProfile {
|
|
61
|
+
/**
|
|
62
|
+
* The id for the identity.
|
|
63
|
+
*/
|
|
64
|
+
identity;
|
|
65
|
+
/**
|
|
66
|
+
* The public profile data.
|
|
67
|
+
*/
|
|
68
|
+
publicProfile;
|
|
69
|
+
/**
|
|
70
|
+
* The private profile data.
|
|
71
|
+
*/
|
|
72
|
+
privateProfile;
|
|
73
|
+
};
|
|
74
|
+
__decorate([
|
|
75
|
+
entity.property({ type: "string", isPrimary: true }),
|
|
76
|
+
__metadata("design:type", String)
|
|
77
|
+
], exports.IdentityProfile.prototype, "identity", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
entity.property({ type: "object" }),
|
|
80
|
+
__metadata("design:type", Object)
|
|
81
|
+
], exports.IdentityProfile.prototype, "publicProfile", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
entity.property({ type: "object" }),
|
|
84
|
+
__metadata("design:type", Object)
|
|
85
|
+
], exports.IdentityProfile.prototype, "privateProfile", void 0);
|
|
86
|
+
exports.IdentityProfile = __decorate([
|
|
87
|
+
entity.entity()
|
|
88
|
+
], exports.IdentityProfile);
|
|
89
|
+
|
|
90
|
+
// Copyright 2024 IOTA Stiftung.
|
|
91
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
92
|
+
/**
|
|
93
|
+
* Class for performing identity operations using entity storage.
|
|
94
|
+
*/
|
|
95
|
+
class EntityStorageIdentityConnector {
|
|
96
|
+
/**
|
|
97
|
+
* The namespace supported by the identity connector.
|
|
98
|
+
*/
|
|
99
|
+
static NAMESPACE = "entity-storage";
|
|
100
|
+
/**
|
|
101
|
+
* The size of the revocation bitmap in bits (16Kb).
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
static _REVOCATION_BITS_SIZE = 131072;
|
|
105
|
+
/**
|
|
106
|
+
* Runtime name for the class.
|
|
107
|
+
*/
|
|
108
|
+
CLASS_NAME = "EntityStorageIdentityConnector";
|
|
109
|
+
/**
|
|
110
|
+
* The entity storage for identities.
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
_didDocumentEntityStorage;
|
|
114
|
+
/**
|
|
115
|
+
* The vault for the keys.
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
_vaultConnector;
|
|
119
|
+
/**
|
|
120
|
+
* Create a new instance of EntityStorageIdentityConnector.
|
|
121
|
+
* @param options The dependencies for the identity connector.
|
|
122
|
+
* @param options.didDocumentEntityStorageType The entity storage for the did documents, defaults to "identity-document".
|
|
123
|
+
* @param options.vaultConnectorType The vault for the private keys, defaults to "vault".
|
|
124
|
+
*/
|
|
125
|
+
constructor(options) {
|
|
126
|
+
this._didDocumentEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(options?.didDocumentEntityStorageType ?? "identity-document");
|
|
127
|
+
this._vaultConnector = vaultModels.VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a new document.
|
|
131
|
+
* @param controller The controller of the identity who can make changes.
|
|
132
|
+
* @returns The created document.
|
|
133
|
+
*/
|
|
134
|
+
async createDocument(controller) {
|
|
135
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
136
|
+
try {
|
|
137
|
+
const did = `did:${EntityStorageIdentityConnector.NAMESPACE}:${core.Converter.bytesToHex(core.RandomHelper.generate(32), true)}`;
|
|
138
|
+
await this._vaultConnector.createKey(this.buildVaultKey(did, did), vaultModels.VaultKeyType.Ed25519);
|
|
139
|
+
const bitString = new core.BitString(EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
140
|
+
const compressed = await core.Compression.compress(bitString.getBits(), core.CompressionType.Gzip);
|
|
141
|
+
const didDocument = {
|
|
142
|
+
id: did,
|
|
143
|
+
service: [
|
|
144
|
+
{
|
|
145
|
+
id: `${did}#revocation`,
|
|
146
|
+
type: "BitstringStatusList",
|
|
147
|
+
serviceEndpoint: `data:application/octet-stream;base64,${core.Converter.bytesToBase64Url(compressed)}`
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
};
|
|
151
|
+
await this.updateDocument(controller, didDocument);
|
|
152
|
+
return didDocument;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new core.GeneralError(this.CLASS_NAME, "createDocumentFailed", undefined, error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve a document from its id.
|
|
160
|
+
* @param documentId The id of the document to resolve.
|
|
161
|
+
* @returns The resolved document.
|
|
162
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
163
|
+
*/
|
|
164
|
+
async resolveDocument(documentId) {
|
|
165
|
+
core.Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
|
|
166
|
+
try {
|
|
167
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
168
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
169
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", documentId);
|
|
170
|
+
}
|
|
171
|
+
await this.verifyDocument(didIdentityDocument);
|
|
172
|
+
return didIdentityDocument.document;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
throw new core.GeneralError(this.CLASS_NAME, "resolveDocumentFailed", undefined, error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Add a verification method to the document in JSON Web key Format.
|
|
180
|
+
* @param controller The controller of the identity who can make changes.
|
|
181
|
+
* @param documentId The id of the document to add the verification method to.
|
|
182
|
+
* @param verificationMethodType The type of the verification method to add.
|
|
183
|
+
* @param verificationMethodId The id of the verification method, if undefined uses the kid of the generated JWK.
|
|
184
|
+
* @returns The verification method.
|
|
185
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
186
|
+
* @throws NotSupportedError if the platform does not support multiple keys.
|
|
187
|
+
*/
|
|
188
|
+
async addVerificationMethod(controller, documentId, verificationMethodType, verificationMethodId) {
|
|
189
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
190
|
+
core.Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
|
|
191
|
+
core.Guards.arrayOneOf(this.CLASS_NAME, "verificationMethodType", verificationMethodType, Object.values(standardsW3cDid.DidVerificationMethodType));
|
|
192
|
+
try {
|
|
193
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
194
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
195
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", documentId);
|
|
196
|
+
}
|
|
197
|
+
await this.verifyDocument(didIdentityDocument);
|
|
198
|
+
const didDocument = didIdentityDocument.document;
|
|
199
|
+
const tempKeyId = `temp-${core.Converter.bytesToBase64Url(core.RandomHelper.generate(32))}`;
|
|
200
|
+
const verificationPublicKey = await this._vaultConnector.createKey(this.buildVaultKey(didDocument.id, tempKeyId), vaultModels.VaultKeyType.Ed25519);
|
|
201
|
+
const jwkParams = {
|
|
202
|
+
alg: "EdDSA",
|
|
203
|
+
kty: "OKP",
|
|
204
|
+
crv: "Ed25519",
|
|
205
|
+
x: core.Converter.bytesToBase64Url(verificationPublicKey)
|
|
206
|
+
};
|
|
207
|
+
const kid = core.Converter.bytesToBase64Url(crypto.Sha256.sum256(core.Converter.utf8ToBytes(JSON.stringify(jwkParams))));
|
|
208
|
+
const methodId = `${documentId}#${verificationMethodId ?? kid}`;
|
|
209
|
+
await this._vaultConnector.renameKey(this.buildVaultKey(didDocument.id, tempKeyId), this.buildVaultKey(didDocument.id, methodId));
|
|
210
|
+
const methods = this.getAllMethods(didDocument);
|
|
211
|
+
const existingMethodIndex = methods.findIndex(m => {
|
|
212
|
+
if (core.Is.string(m.method)) {
|
|
213
|
+
return m.method === methodId;
|
|
214
|
+
}
|
|
215
|
+
return m.method.id === methodId;
|
|
216
|
+
});
|
|
217
|
+
if (existingMethodIndex >= 0) {
|
|
218
|
+
const methodArray = didDocument[methods[existingMethodIndex].arrayKey];
|
|
219
|
+
if (core.Is.array(methodArray)) {
|
|
220
|
+
methodArray.splice(existingMethodIndex, 1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const didVerificationMethod = {
|
|
224
|
+
id: methodId,
|
|
225
|
+
controller: documentId,
|
|
226
|
+
type: "JsonWebKey",
|
|
227
|
+
publicKeyJwk: {
|
|
228
|
+
...jwkParams,
|
|
229
|
+
kid
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
didDocument[verificationMethodType] ??= [];
|
|
233
|
+
didDocument[verificationMethodType]?.push(didVerificationMethod);
|
|
234
|
+
await this.updateDocument(controller, didDocument);
|
|
235
|
+
return didVerificationMethod;
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
throw new core.GeneralError(this.CLASS_NAME, "addVerificationMethodFailed", undefined, error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Remove a verification method from the document.
|
|
243
|
+
* @param controller The controller of the identity who can make changes.
|
|
244
|
+
* @param verificationMethodId The id of the verification method.
|
|
245
|
+
* @returns Nothing.
|
|
246
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
247
|
+
* @throws NotSupportedError if the platform does not support multiple revocable keys.
|
|
248
|
+
*/
|
|
249
|
+
async removeVerificationMethod(controller, verificationMethodId) {
|
|
250
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
251
|
+
core.Guards.stringValue(this.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
252
|
+
try {
|
|
253
|
+
const idParts = identityModels.DocumentHelper.parse(verificationMethodId);
|
|
254
|
+
if (core.Is.empty(idParts.hash)) {
|
|
255
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", verificationMethodId);
|
|
256
|
+
}
|
|
257
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
258
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
259
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
260
|
+
}
|
|
261
|
+
await this.verifyDocument(didIdentityDocument);
|
|
262
|
+
const didDocument = didIdentityDocument.document;
|
|
263
|
+
const methods = this.getAllMethods(didDocument);
|
|
264
|
+
const existingMethodIndex = methods.findIndex(m => {
|
|
265
|
+
if (core.Is.string(m.method)) {
|
|
266
|
+
return m.method === verificationMethodId;
|
|
267
|
+
}
|
|
268
|
+
return m.method.id === verificationMethodId;
|
|
269
|
+
});
|
|
270
|
+
if (existingMethodIndex >= 0) {
|
|
271
|
+
const methodArray = didDocument[methods[existingMethodIndex].arrayKey];
|
|
272
|
+
if (core.Is.array(methodArray)) {
|
|
273
|
+
methodArray.splice(existingMethodIndex, 1);
|
|
274
|
+
if (methodArray.length === 0) {
|
|
275
|
+
delete didDocument[methods[existingMethodIndex].arrayKey];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
throw new core.NotFoundError(this.CLASS_NAME, "verificationMethodNotFound", verificationMethodId);
|
|
281
|
+
}
|
|
282
|
+
await this.updateDocument(controller, didDocument);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
throw new core.GeneralError(this.CLASS_NAME, "removeVerificationMethodFailed", undefined, error);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Add a service to the document.
|
|
290
|
+
* @param controller The controller of the identity who can make changes.
|
|
291
|
+
* @param documentId The id of the document to add the service to.
|
|
292
|
+
* @param serviceId The id of the service.
|
|
293
|
+
* @param serviceType The type of the service.
|
|
294
|
+
* @param serviceEndpoint The endpoint for the service.
|
|
295
|
+
* @returns The service.
|
|
296
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
297
|
+
*/
|
|
298
|
+
async addService(controller, documentId, serviceId, serviceType, serviceEndpoint) {
|
|
299
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
300
|
+
core.Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
|
|
301
|
+
core.Guards.stringValue(this.CLASS_NAME, "serviceId", serviceId);
|
|
302
|
+
core.Guards.stringValue(this.CLASS_NAME, "serviceType", serviceType);
|
|
303
|
+
core.Guards.stringValue(this.CLASS_NAME, "serviceEndpoint", serviceEndpoint);
|
|
304
|
+
try {
|
|
305
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(documentId);
|
|
306
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
307
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", documentId);
|
|
308
|
+
}
|
|
309
|
+
await this.verifyDocument(didIdentityDocument);
|
|
310
|
+
const didDocument = didIdentityDocument.document;
|
|
311
|
+
const fullServiceId = serviceId.includes("#") ? serviceId : `${documentId}#${serviceId}`;
|
|
312
|
+
if (core.Is.array(didDocument.service)) {
|
|
313
|
+
const existingServiceIndex = didDocument.service.findIndex(s => s.id === fullServiceId);
|
|
314
|
+
if (existingServiceIndex >= 0) {
|
|
315
|
+
didDocument.service?.splice(existingServiceIndex, 1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const didService = {
|
|
319
|
+
id: fullServiceId,
|
|
320
|
+
type: serviceType,
|
|
321
|
+
serviceEndpoint
|
|
322
|
+
};
|
|
323
|
+
didDocument.service ??= [];
|
|
324
|
+
didDocument.service.push(didService);
|
|
325
|
+
await this.updateDocument(controller, didDocument);
|
|
326
|
+
return didService;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
throw new core.GeneralError(this.CLASS_NAME, "addServiceFailed", undefined, error);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Remove a service from the document.
|
|
334
|
+
* @param controller The controller of the identity who can make changes.
|
|
335
|
+
* @param serviceId The id of the service.
|
|
336
|
+
* @returns Nothing.
|
|
337
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
338
|
+
*/
|
|
339
|
+
async removeService(controller, serviceId) {
|
|
340
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
341
|
+
core.Guards.stringValue(this.CLASS_NAME, "serviceId", serviceId);
|
|
342
|
+
try {
|
|
343
|
+
const idParts = identityModels.DocumentHelper.parse(serviceId);
|
|
344
|
+
if (core.Is.empty(idParts.hash)) {
|
|
345
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", serviceId);
|
|
346
|
+
}
|
|
347
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
348
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
349
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
350
|
+
}
|
|
351
|
+
await this.verifyDocument(didIdentityDocument);
|
|
352
|
+
const didDocument = didIdentityDocument.document;
|
|
353
|
+
if (core.Is.array(didDocument.service)) {
|
|
354
|
+
const existingServiceIndex = didDocument.service.findIndex(s => s.id === serviceId);
|
|
355
|
+
if (existingServiceIndex >= 0) {
|
|
356
|
+
didDocument.service?.splice(existingServiceIndex, 1);
|
|
357
|
+
if (didDocument.service?.length === 0) {
|
|
358
|
+
delete didDocument.service;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
throw new core.NotFoundError(this.CLASS_NAME, "serviceNotFound", serviceId);
|
|
364
|
+
}
|
|
365
|
+
await this.updateDocument(controller, didDocument);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
throw new core.GeneralError(this.CLASS_NAME, "removeServiceFailed", undefined, error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Create a verifiable credential for a verification method.
|
|
373
|
+
* @param controller The controller of the identity who can make changes.
|
|
374
|
+
* @param verificationMethodId The verification method id to use.
|
|
375
|
+
* @param credentialId The id of the credential.
|
|
376
|
+
* @param types The type for the data stored in the verifiable credential.
|
|
377
|
+
* @param subject The subject data to store for the credential.
|
|
378
|
+
* @param contexts Additional contexts to include in the credential.
|
|
379
|
+
* @param revocationIndex The bitmap revocation index of the credential, if undefined will not have revocation status.
|
|
380
|
+
* @returns The created verifiable credential and its token.
|
|
381
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
382
|
+
*/
|
|
383
|
+
async createVerifiableCredential(controller, verificationMethodId, credentialId, types, subject, contexts, revocationIndex) {
|
|
384
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
385
|
+
core.Guards.stringValue(this.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
386
|
+
if (!core.Is.undefined(credentialId)) {
|
|
387
|
+
core.Guards.stringValue(this.CLASS_NAME, "credentialId", credentialId);
|
|
388
|
+
}
|
|
389
|
+
if (core.Is.array(types)) {
|
|
390
|
+
core.Guards.array(this.CLASS_NAME, "types", types);
|
|
391
|
+
}
|
|
392
|
+
else if (!core.Is.undefined(types)) {
|
|
393
|
+
core.Guards.stringValue(this.CLASS_NAME, "types", types);
|
|
394
|
+
}
|
|
395
|
+
if (core.Is.array(subject)) {
|
|
396
|
+
core.Guards.arrayValue(this.CLASS_NAME, "subject", subject);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
core.Guards.object(this.CLASS_NAME, "subject", subject);
|
|
400
|
+
}
|
|
401
|
+
if (core.Is.array(contexts)) {
|
|
402
|
+
core.Guards.array(this.CLASS_NAME, "contexts", contexts);
|
|
403
|
+
}
|
|
404
|
+
else if (!core.Is.undefined(contexts)) {
|
|
405
|
+
core.Guards.stringValue(this.CLASS_NAME, "contexts", contexts);
|
|
406
|
+
}
|
|
407
|
+
if (!core.Is.undefined(revocationIndex)) {
|
|
408
|
+
core.Guards.number(this.CLASS_NAME, "revocationIndex", revocationIndex);
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const idParts = identityModels.DocumentHelper.parse(verificationMethodId);
|
|
412
|
+
if (core.Is.empty(idParts.hash)) {
|
|
413
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", verificationMethodId);
|
|
414
|
+
}
|
|
415
|
+
const issuerIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
416
|
+
if (core.Is.undefined(issuerIdentityDocument)) {
|
|
417
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
418
|
+
}
|
|
419
|
+
await this.verifyDocument(issuerIdentityDocument);
|
|
420
|
+
const issuerDidDocument = issuerIdentityDocument.document;
|
|
421
|
+
const methods = this.getAllMethods(issuerDidDocument);
|
|
422
|
+
const methodAndArray = methods.find(m => {
|
|
423
|
+
if (core.Is.string(m.method)) {
|
|
424
|
+
return m.method === verificationMethodId;
|
|
425
|
+
}
|
|
426
|
+
return m.method.id === verificationMethodId;
|
|
427
|
+
});
|
|
428
|
+
if (!methodAndArray) {
|
|
429
|
+
throw new core.GeneralError(this.CLASS_NAME, "methodMissing");
|
|
430
|
+
}
|
|
431
|
+
const verificationDidMethod = methodAndArray.method;
|
|
432
|
+
if (!core.Is.stringValue(verificationDidMethod.publicKeyJwk?.x)) {
|
|
433
|
+
throw new core.GeneralError(this.CLASS_NAME, "publicKeyJwkMissing");
|
|
434
|
+
}
|
|
435
|
+
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
436
|
+
const finalTypes = ["VerifiableCredential"];
|
|
437
|
+
if (core.Is.array(types)) {
|
|
438
|
+
finalTypes.push(...types);
|
|
439
|
+
}
|
|
440
|
+
else if (core.Is.stringValue(types)) {
|
|
441
|
+
finalTypes.push(types);
|
|
442
|
+
}
|
|
443
|
+
const finalContexts = ["https://www.w3.org/2018/credentials/v1"];
|
|
444
|
+
if (core.Is.array(contexts)) {
|
|
445
|
+
finalContexts.push(...contexts);
|
|
446
|
+
}
|
|
447
|
+
else if (core.Is.stringValue(contexts)) {
|
|
448
|
+
finalContexts.push(contexts);
|
|
449
|
+
}
|
|
450
|
+
const verifiableCredential = {
|
|
451
|
+
"@context": finalContexts,
|
|
452
|
+
id: credentialId,
|
|
453
|
+
type: finalTypes,
|
|
454
|
+
credentialSubject: subject,
|
|
455
|
+
issuer: issuerDidDocument.id,
|
|
456
|
+
issuanceDate: new Date().toISOString(),
|
|
457
|
+
credentialStatus: revocationService && !core.Is.undefined(revocationIndex)
|
|
458
|
+
? {
|
|
459
|
+
id: revocationService.id,
|
|
460
|
+
type: core.Is.array(revocationService.type)
|
|
461
|
+
? revocationService.type[0]
|
|
462
|
+
: revocationService.type,
|
|
463
|
+
revocationBitmapIndex: revocationIndex.toString()
|
|
464
|
+
}
|
|
465
|
+
: undefined
|
|
466
|
+
};
|
|
467
|
+
const jwtHeader = {
|
|
468
|
+
kid: verificationDidMethod.id,
|
|
469
|
+
typ: "JWT",
|
|
470
|
+
alg: "EdDSA"
|
|
471
|
+
};
|
|
472
|
+
const jwtVc = core.ObjectHelper.pick(core.ObjectHelper.clone(verifiableCredential), [
|
|
473
|
+
"@context",
|
|
474
|
+
"type",
|
|
475
|
+
"credentialSubject",
|
|
476
|
+
"credentialStatus"
|
|
477
|
+
]);
|
|
478
|
+
if (core.Is.array(jwtVc.credentialSubject)) {
|
|
479
|
+
jwtVc.credentialSubject = jwtVc.credentialSubject.map(c => {
|
|
480
|
+
core.ObjectHelper.propertyDelete(c, "id");
|
|
481
|
+
return c;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
core.ObjectHelper.propertyDelete(jwtVc.credentialSubject, "id");
|
|
486
|
+
}
|
|
487
|
+
const jwtPayload = {
|
|
488
|
+
iss: idParts.id,
|
|
489
|
+
nbf: Math.floor(Date.now() / 1000),
|
|
490
|
+
jti: verifiableCredential.id,
|
|
491
|
+
sub: core.Is.array(subject)
|
|
492
|
+
? core.ObjectHelper.propertyGet(subject[0], "id")
|
|
493
|
+
: core.ObjectHelper.propertyGet(subject, "id"),
|
|
494
|
+
vc: jwtVc
|
|
495
|
+
};
|
|
496
|
+
const signature = await web.Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (alg, key, payload) => {
|
|
497
|
+
const sig = await this._vaultConnector.sign(this.buildVaultKey(idParts.id, verificationMethodId), payload);
|
|
498
|
+
return sig;
|
|
499
|
+
});
|
|
500
|
+
return {
|
|
501
|
+
verifiableCredential,
|
|
502
|
+
jwt: signature
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
throw new core.GeneralError(this.CLASS_NAME, "createVerifiableCredentialFailed", undefined, error);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Check a verifiable credential is valid.
|
|
511
|
+
* @param credentialJwt The credential to verify.
|
|
512
|
+
* @returns The credential stored in the jwt and the revocation status.
|
|
513
|
+
*/
|
|
514
|
+
async checkVerifiableCredential(credentialJwt) {
|
|
515
|
+
core.Guards.stringValue(this.CLASS_NAME, "credentialJwt", credentialJwt);
|
|
516
|
+
try {
|
|
517
|
+
const jwtDecoded = await web.Jwt.decode(credentialJwt);
|
|
518
|
+
const jwtHeader = jwtDecoded.header;
|
|
519
|
+
const jwtPayload = jwtDecoded.payload;
|
|
520
|
+
const jwtSignature = jwtDecoded.signature;
|
|
521
|
+
if (core.Is.undefined(jwtHeader) ||
|
|
522
|
+
core.Is.undefined(jwtPayload) ||
|
|
523
|
+
core.Is.undefined(jwtPayload.iss) ||
|
|
524
|
+
core.Is.undefined(jwtSignature)) {
|
|
525
|
+
throw new core.NotFoundError(this.CLASS_NAME, "jwkSignatureFailed");
|
|
526
|
+
}
|
|
527
|
+
const issuerDocumentId = jwtPayload.iss;
|
|
528
|
+
const issuerIdentityDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
|
|
529
|
+
if (core.Is.undefined(issuerIdentityDocument)) {
|
|
530
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
531
|
+
}
|
|
532
|
+
await this.verifyDocument(issuerIdentityDocument);
|
|
533
|
+
const issuerDidDocument = issuerIdentityDocument.document;
|
|
534
|
+
const methods = this.getAllMethods(issuerDidDocument);
|
|
535
|
+
const methodAndArray = methods.find(m => {
|
|
536
|
+
if (core.Is.string(m.method)) {
|
|
537
|
+
return m.method === jwtHeader.kid;
|
|
538
|
+
}
|
|
539
|
+
return m.method.id === jwtHeader.kid;
|
|
540
|
+
});
|
|
541
|
+
if (!methodAndArray) {
|
|
542
|
+
throw new core.GeneralError(this.CLASS_NAME, "methodMissing");
|
|
543
|
+
}
|
|
544
|
+
const didMethod = methodAndArray.method;
|
|
545
|
+
if (!core.Is.stringValue(didMethod.publicKeyJwk?.x)) {
|
|
546
|
+
throw new core.GeneralError(this.CLASS_NAME, "publicKeyJwkMissing");
|
|
547
|
+
}
|
|
548
|
+
const verified = web.Jwt.verifySignature(jwtHeader, jwtPayload, jwtSignature, core.Converter.base64UrlToBytes(didMethod.publicKeyJwk.x));
|
|
549
|
+
if (!verified) {
|
|
550
|
+
throw new core.GeneralError(this.CLASS_NAME, "jwkSignatureFailed");
|
|
551
|
+
}
|
|
552
|
+
const verifiableCredential = jwtPayload.vc;
|
|
553
|
+
if (core.Is.object(verifiableCredential)) {
|
|
554
|
+
if (core.Is.string(jwtPayload.jti)) {
|
|
555
|
+
verifiableCredential.id = jwtPayload.jti;
|
|
556
|
+
}
|
|
557
|
+
verifiableCredential.issuer = issuerDocumentId;
|
|
558
|
+
if (core.Is.number(jwtPayload.nbf)) {
|
|
559
|
+
verifiableCredential.issuanceDate = new Date(jwtPayload.nbf * 1000).toISOString();
|
|
560
|
+
}
|
|
561
|
+
if (core.Is.array(verifiableCredential.credentialSubject)) {
|
|
562
|
+
verifiableCredential.credentialSubject = verifiableCredential.credentialSubject.map(c => {
|
|
563
|
+
core.ObjectHelper.propertySet(c, "id", jwtPayload.sub);
|
|
564
|
+
return c;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
else if (core.Is.object(verifiableCredential.credentialSubject)) {
|
|
568
|
+
core.ObjectHelper.propertySet(verifiableCredential.credentialSubject, "id", jwtPayload.sub);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const revoked = await this.checkRevocation(issuerDidDocument, verifiableCredential.credentialStatus?.revocationBitmapIndex);
|
|
572
|
+
return {
|
|
573
|
+
revoked,
|
|
574
|
+
verifiableCredential: revoked ? undefined : verifiableCredential
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
throw new core.GeneralError(this.CLASS_NAME, "checkingVerifiableCredentialFailed", undefined, error);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Revoke verifiable credential(s).
|
|
583
|
+
* @param controller The controller of the identity who can make changes.
|
|
584
|
+
* @param issuerDocumentId The id of the document to update the revocation list for.
|
|
585
|
+
* @param credentialIndices The revocation bitmap index or indices to revoke.
|
|
586
|
+
* @returns Nothing.
|
|
587
|
+
*/
|
|
588
|
+
async revokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
|
|
589
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
590
|
+
core.Guards.stringValue(this.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
|
|
591
|
+
core.Guards.arrayValue(this.CLASS_NAME, "credentialIndices", credentialIndices);
|
|
592
|
+
try {
|
|
593
|
+
const issuerIdentityDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
|
|
594
|
+
if (core.Is.undefined(issuerIdentityDocument)) {
|
|
595
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
596
|
+
}
|
|
597
|
+
await this.verifyDocument(issuerIdentityDocument);
|
|
598
|
+
const issuerDidDocument = issuerIdentityDocument.document;
|
|
599
|
+
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
600
|
+
if (revocationService &&
|
|
601
|
+
core.Is.string(revocationService.serviceEndpoint) &&
|
|
602
|
+
revocationService.type === "BitstringStatusList") {
|
|
603
|
+
const revocationParts = revocationService.serviceEndpoint.split(",");
|
|
604
|
+
if (revocationParts.length === 2) {
|
|
605
|
+
const compressedRevocationBytes = core.Converter.base64UrlToBytes(revocationParts[1]);
|
|
606
|
+
const decompressed = await core.Compression.decompress(compressedRevocationBytes, core.CompressionType.Gzip);
|
|
607
|
+
const bitString = core.BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
608
|
+
for (const credentialIndex of credentialIndices) {
|
|
609
|
+
bitString.setBit(credentialIndex, true);
|
|
610
|
+
}
|
|
611
|
+
const compressed = await core.Compression.compress(bitString.getBits(), core.CompressionType.Gzip);
|
|
612
|
+
revocationService.serviceEndpoint = `data:application/octet-stream;base64,${core.Converter.bytesToBase64Url(compressed)}`;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
await this.updateDocument(controller, issuerDidDocument);
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
throw new core.GeneralError(this.CLASS_NAME, "revokeVerifiableCredentialsFailed", undefined, error);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Unrevoke verifiable credential(s).
|
|
623
|
+
* @param controller The controller of the identity who can make changes.
|
|
624
|
+
* @param issuerDocumentId The id of the document to update the revocation list for.
|
|
625
|
+
* @param credentialIndices The revocation bitmap index or indices to un revoke.
|
|
626
|
+
* @returns Nothing.
|
|
627
|
+
*/
|
|
628
|
+
async unrevokeVerifiableCredentials(controller, issuerDocumentId, credentialIndices) {
|
|
629
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
630
|
+
core.Guards.stringValue(this.CLASS_NAME, "issuerDocumentId", issuerDocumentId);
|
|
631
|
+
core.Guards.arrayValue(this.CLASS_NAME, "credentialIndices", credentialIndices);
|
|
632
|
+
try {
|
|
633
|
+
const issuerIdentityDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
|
|
634
|
+
if (core.Is.undefined(issuerIdentityDocument)) {
|
|
635
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
636
|
+
}
|
|
637
|
+
await this.verifyDocument(issuerIdentityDocument);
|
|
638
|
+
const issuerDidDocument = issuerIdentityDocument.document;
|
|
639
|
+
const revocationService = issuerDidDocument.service?.find(s => s.id.endsWith("#revocation"));
|
|
640
|
+
if (revocationService &&
|
|
641
|
+
core.Is.string(revocationService.serviceEndpoint) &&
|
|
642
|
+
revocationService.type === "BitstringStatusList") {
|
|
643
|
+
const revocationParts = revocationService.serviceEndpoint.split(",");
|
|
644
|
+
if (revocationParts.length === 2) {
|
|
645
|
+
const compressedRevocationBytes = core.Converter.base64UrlToBytes(revocationParts[1]);
|
|
646
|
+
const decompressed = await core.Compression.decompress(compressedRevocationBytes, core.CompressionType.Gzip);
|
|
647
|
+
const bitString = core.BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
648
|
+
for (const credentialIndex of credentialIndices) {
|
|
649
|
+
bitString.setBit(credentialIndex, false);
|
|
650
|
+
}
|
|
651
|
+
const compressed = await core.Compression.compress(bitString.getBits(), core.CompressionType.Gzip);
|
|
652
|
+
revocationService.serviceEndpoint = `data:application/octet-stream;base64,${core.Converter.bytesToBase64Url(compressed)}`;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
await this.updateDocument(controller, issuerDidDocument);
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
throw new core.GeneralError(this.CLASS_NAME, "unrevokeVerifiableCredentialsFailed", undefined, error);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Create a verifiable presentation from the supplied verifiable credentials.
|
|
663
|
+
* @param controller The controller of the identity who can make changes.
|
|
664
|
+
* @param presentationMethodId The method to associate with the presentation.
|
|
665
|
+
* @param types The types for the data stored in the verifiable credential.
|
|
666
|
+
* @param verifiableCredentials The credentials to use for creating the presentation in jwt format.
|
|
667
|
+
* @param contexts Additional contexts to include in the presentation.
|
|
668
|
+
* @param expiresInMinutes The time in minutes for the presentation to expire.
|
|
669
|
+
* @returns The created verifiable presentation and its token.
|
|
670
|
+
* @throws NotFoundError if the id can not be resolved.
|
|
671
|
+
*/
|
|
672
|
+
async createVerifiablePresentation(controller, presentationMethodId, types, verifiableCredentials, contexts, expiresInMinutes) {
|
|
673
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
674
|
+
core.Guards.stringValue(this.CLASS_NAME, "presentationMethodId", presentationMethodId);
|
|
675
|
+
if (core.Is.array(types)) {
|
|
676
|
+
core.Guards.arrayValue(this.CLASS_NAME, "types", types);
|
|
677
|
+
}
|
|
678
|
+
else if (core.Is.string(types)) {
|
|
679
|
+
core.Guards.stringValue(this.CLASS_NAME, "types", types);
|
|
680
|
+
}
|
|
681
|
+
core.Guards.arrayValue(this.CLASS_NAME, "verifiableCredentials", verifiableCredentials);
|
|
682
|
+
if (core.Is.array(contexts)) {
|
|
683
|
+
core.Guards.arrayValue(this.CLASS_NAME, "contexts", contexts);
|
|
684
|
+
}
|
|
685
|
+
else if (core.Is.string(contexts)) {
|
|
686
|
+
core.Guards.stringValue(this.CLASS_NAME, "contexts", contexts);
|
|
687
|
+
}
|
|
688
|
+
if (!core.Is.undefined(expiresInMinutes)) {
|
|
689
|
+
core.Guards.integer(this.CLASS_NAME, "expiresInMinutes", expiresInMinutes);
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const idParts = identityModels.DocumentHelper.parse(presentationMethodId);
|
|
693
|
+
if (core.Is.empty(idParts.hash)) {
|
|
694
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", presentationMethodId);
|
|
695
|
+
}
|
|
696
|
+
const holderIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
697
|
+
if (core.Is.undefined(holderIdentityDocument)) {
|
|
698
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
699
|
+
}
|
|
700
|
+
await this.verifyDocument(holderIdentityDocument);
|
|
701
|
+
const holderDidDocument = holderIdentityDocument.document;
|
|
702
|
+
const methods = this.getAllMethods(holderDidDocument);
|
|
703
|
+
const methodAndArray = methods.find(m => {
|
|
704
|
+
if (core.Is.string(m.method)) {
|
|
705
|
+
return m.method === presentationMethodId;
|
|
706
|
+
}
|
|
707
|
+
return m.method.id === presentationMethodId;
|
|
708
|
+
});
|
|
709
|
+
if (!methodAndArray) {
|
|
710
|
+
throw new core.GeneralError(this.CLASS_NAME, "methodMissing");
|
|
711
|
+
}
|
|
712
|
+
const didMethod = methodAndArray.method;
|
|
713
|
+
if (!core.Is.stringValue(didMethod.publicKeyJwk?.x)) {
|
|
714
|
+
throw new core.GeneralError(this.CLASS_NAME, "publicKeyJwkMissing");
|
|
715
|
+
}
|
|
716
|
+
const finalTypes = ["VerifiablePresentation"];
|
|
717
|
+
if (core.Is.array(types)) {
|
|
718
|
+
finalTypes.push(...types);
|
|
719
|
+
}
|
|
720
|
+
else if (core.Is.stringValue(types)) {
|
|
721
|
+
finalTypes.push(types);
|
|
722
|
+
}
|
|
723
|
+
const finalContexts = ["https://www.w3.org/2018/credentials/v1"];
|
|
724
|
+
if (core.Is.array(contexts)) {
|
|
725
|
+
finalContexts.push(...contexts);
|
|
726
|
+
}
|
|
727
|
+
else if (core.Is.stringValue(contexts)) {
|
|
728
|
+
finalContexts.push(contexts);
|
|
729
|
+
}
|
|
730
|
+
const verifiablePresentation = {
|
|
731
|
+
"@context": finalContexts,
|
|
732
|
+
type: finalTypes,
|
|
733
|
+
verifiableCredential: verifiableCredentials,
|
|
734
|
+
holder: idParts.id
|
|
735
|
+
};
|
|
736
|
+
const jwtHeader = {
|
|
737
|
+
kid: didMethod.id,
|
|
738
|
+
typ: "JWT",
|
|
739
|
+
alg: "EdDSA"
|
|
740
|
+
};
|
|
741
|
+
const jwtVp = core.ObjectHelper.pick(core.ObjectHelper.clone(verifiablePresentation), [
|
|
742
|
+
"@context",
|
|
743
|
+
"type",
|
|
744
|
+
"verifiableCredential"
|
|
745
|
+
]);
|
|
746
|
+
const jwtPayload = {
|
|
747
|
+
iss: idParts.id,
|
|
748
|
+
nbf: Math.floor(Date.now() / 1000),
|
|
749
|
+
vp: jwtVp
|
|
750
|
+
};
|
|
751
|
+
if (core.Is.integer(expiresInMinutes)) {
|
|
752
|
+
const expiresInSeconds = expiresInMinutes * 60;
|
|
753
|
+
jwtPayload.exp = Math.floor(Date.now() / 1000) + expiresInSeconds;
|
|
754
|
+
}
|
|
755
|
+
const signature = await web.Jwt.encodeWithSigner(jwtHeader, jwtPayload, async (alg, key, payload) => {
|
|
756
|
+
const sig = await this._vaultConnector.sign(this.buildVaultKey(idParts.id, presentationMethodId), payload);
|
|
757
|
+
return sig;
|
|
758
|
+
});
|
|
759
|
+
return {
|
|
760
|
+
verifiablePresentation,
|
|
761
|
+
jwt: signature
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
catch (error) {
|
|
765
|
+
throw new core.GeneralError(this.CLASS_NAME, "createVerifiablePresentationFailed", undefined, error);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Check a verifiable presentation is valid.
|
|
770
|
+
* @param presentationJwt The presentation to verify.
|
|
771
|
+
* @returns The presentation stored in the jwt and the revocation status.
|
|
772
|
+
*/
|
|
773
|
+
async checkVerifiablePresentation(presentationJwt) {
|
|
774
|
+
core.Guards.stringValue(this.CLASS_NAME, "presentationJwt", presentationJwt);
|
|
775
|
+
try {
|
|
776
|
+
const jwtDecoded = await web.Jwt.decode(presentationJwt);
|
|
777
|
+
const jwtHeader = jwtDecoded.header;
|
|
778
|
+
const jwtPayload = jwtDecoded.payload;
|
|
779
|
+
const jwtSignature = jwtDecoded.signature;
|
|
780
|
+
if (core.Is.undefined(jwtHeader) ||
|
|
781
|
+
core.Is.undefined(jwtPayload) ||
|
|
782
|
+
core.Is.undefined(jwtPayload.iss) ||
|
|
783
|
+
core.Is.undefined(jwtSignature)) {
|
|
784
|
+
throw new core.NotFoundError(this.CLASS_NAME, "jwkSignatureFailed");
|
|
785
|
+
}
|
|
786
|
+
const holderDocumentId = jwtPayload.iss;
|
|
787
|
+
const holderIdentityDocument = await this._didDocumentEntityStorage.get(holderDocumentId);
|
|
788
|
+
if (core.Is.undefined(holderIdentityDocument)) {
|
|
789
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", holderDocumentId);
|
|
790
|
+
}
|
|
791
|
+
await this.verifyDocument(holderIdentityDocument);
|
|
792
|
+
const issuers = [];
|
|
793
|
+
const tokensRevoked = [];
|
|
794
|
+
const verifiablePresentation = jwtPayload?.vp;
|
|
795
|
+
if (core.Is.object(verifiablePresentation) &&
|
|
796
|
+
core.Is.array(verifiablePresentation.verifiableCredential)) {
|
|
797
|
+
for (const vcJwt of verifiablePresentation.verifiableCredential) {
|
|
798
|
+
const jwt = await web.Jwt.decode(vcJwt);
|
|
799
|
+
let revoked = true;
|
|
800
|
+
if (core.Is.string(jwt.payload?.iss)) {
|
|
801
|
+
const issuerDocumentId = jwt.payload.iss;
|
|
802
|
+
verifiablePresentation.holder = issuerDocumentId;
|
|
803
|
+
const issuerDidDocument = await this._didDocumentEntityStorage.get(issuerDocumentId);
|
|
804
|
+
if (core.Is.undefined(issuerDidDocument)) {
|
|
805
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", issuerDocumentId);
|
|
806
|
+
}
|
|
807
|
+
await this.verifyDocument(issuerDidDocument);
|
|
808
|
+
issuers.push(issuerDidDocument);
|
|
809
|
+
const vc = jwt.payload.vc;
|
|
810
|
+
if (core.Is.object(vc)) {
|
|
811
|
+
revoked = await this.checkRevocation(issuerDidDocument, vc.credentialStatus?.revocationBitmapIndex);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
tokensRevoked.push(revoked);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
revoked: tokensRevoked.some(Boolean),
|
|
819
|
+
verifiablePresentation,
|
|
820
|
+
issuers
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
if (error instanceof Error && error.message.toLowerCase().includes("revoked")) {
|
|
825
|
+
return {
|
|
826
|
+
revoked: true
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
throw new core.GeneralError(this.CLASS_NAME, "checkingVerifiablePresentationFailed", undefined, error);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Create a proof for arbitrary data with the specified verification method.
|
|
834
|
+
* @param controller The controller of the identity who can make changes.
|
|
835
|
+
* @param verificationMethodId The verification method id to use.
|
|
836
|
+
* @param bytes The data bytes to sign.
|
|
837
|
+
* @returns The proof signature type and value.
|
|
838
|
+
*/
|
|
839
|
+
async createProof(controller, verificationMethodId, bytes) {
|
|
840
|
+
core.Guards.stringValue(this.CLASS_NAME, "controller", controller);
|
|
841
|
+
core.Guards.stringValue(this.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
842
|
+
core.Guards.uint8Array(this.CLASS_NAME, "bytes", bytes);
|
|
843
|
+
try {
|
|
844
|
+
const idParts = identityModels.DocumentHelper.parse(verificationMethodId);
|
|
845
|
+
if (core.Is.empty(idParts.hash)) {
|
|
846
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", verificationMethodId);
|
|
847
|
+
}
|
|
848
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
849
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
850
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
851
|
+
}
|
|
852
|
+
await this.verifyDocument(didIdentityDocument);
|
|
853
|
+
const didDocument = didIdentityDocument.document;
|
|
854
|
+
const methods = this.getAllMethods(didDocument);
|
|
855
|
+
const methodAndArray = methods.find(m => {
|
|
856
|
+
if (core.Is.string(m.method)) {
|
|
857
|
+
return m.method === verificationMethodId;
|
|
858
|
+
}
|
|
859
|
+
return m.method.id === verificationMethodId;
|
|
860
|
+
});
|
|
861
|
+
if (!methodAndArray) {
|
|
862
|
+
throw new core.GeneralError(this.CLASS_NAME, "methodMissing");
|
|
863
|
+
}
|
|
864
|
+
const didMethod = methodAndArray.method;
|
|
865
|
+
if (!core.Is.stringValue(didMethod.publicKeyJwk?.x)) {
|
|
866
|
+
throw new core.GeneralError(this.CLASS_NAME, "publicKeyJwkMissing");
|
|
867
|
+
}
|
|
868
|
+
const signature = await this._vaultConnector.sign(this.buildVaultKey(didDocument.id, verificationMethodId), bytes);
|
|
869
|
+
return {
|
|
870
|
+
type: "Ed25519",
|
|
871
|
+
value: signature
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
throw new core.GeneralError(this.CLASS_NAME, "createProofFailed", undefined, error);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Verify proof for arbitrary data with the specified verification method.
|
|
880
|
+
* @param verificationMethodId The verification method id to use.
|
|
881
|
+
* @param bytes The data bytes to verify.
|
|
882
|
+
* @param signatureType The type of the signature for the proof.
|
|
883
|
+
* @param signatureValue The value of the signature for the proof.
|
|
884
|
+
* @returns True if the signature is valid.
|
|
885
|
+
*/
|
|
886
|
+
async verifyProof(verificationMethodId, bytes, signatureType, signatureValue) {
|
|
887
|
+
core.Guards.stringValue(this.CLASS_NAME, "verificationMethodId", verificationMethodId);
|
|
888
|
+
core.Guards.uint8Array(this.CLASS_NAME, "bytes", bytes);
|
|
889
|
+
core.Guards.stringValue(this.CLASS_NAME, "signatureType", signatureType);
|
|
890
|
+
core.Guards.uint8Array(this.CLASS_NAME, "signatureValue", signatureValue);
|
|
891
|
+
try {
|
|
892
|
+
const idParts = identityModels.DocumentHelper.parse(verificationMethodId);
|
|
893
|
+
if (core.Is.empty(idParts.hash)) {
|
|
894
|
+
throw new core.NotFoundError(this.CLASS_NAME, "missingDid", verificationMethodId);
|
|
895
|
+
}
|
|
896
|
+
const didIdentityDocument = await this._didDocumentEntityStorage.get(idParts.id);
|
|
897
|
+
if (core.Is.undefined(didIdentityDocument)) {
|
|
898
|
+
throw new core.NotFoundError(this.CLASS_NAME, "documentNotFound", idParts.id);
|
|
899
|
+
}
|
|
900
|
+
await this.verifyDocument(didIdentityDocument);
|
|
901
|
+
const didDocument = didIdentityDocument.document;
|
|
902
|
+
const methods = this.getAllMethods(didDocument);
|
|
903
|
+
const methodAndArray = methods.find(m => {
|
|
904
|
+
if (core.Is.string(m.method)) {
|
|
905
|
+
return m.method === verificationMethodId;
|
|
906
|
+
}
|
|
907
|
+
return m.method.id === verificationMethodId;
|
|
908
|
+
});
|
|
909
|
+
if (!methodAndArray) {
|
|
910
|
+
throw new core.GeneralError(this.CLASS_NAME, "methodMissing");
|
|
911
|
+
}
|
|
912
|
+
const didMethod = methodAndArray.method;
|
|
913
|
+
if (!core.Is.stringValue(didMethod.publicKeyJwk?.x)) {
|
|
914
|
+
throw new core.GeneralError(this.CLASS_NAME, "publicKeyJwkMissing");
|
|
915
|
+
}
|
|
916
|
+
return this._vaultConnector.verify(this.buildVaultKey(didIdentityDocument.id, verificationMethodId), bytes, signatureValue);
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
throw new core.GeneralError(this.CLASS_NAME, "verifyProofFailed", undefined, error);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Get all the methods from a document.
|
|
924
|
+
* @param document The document to get the methods from.
|
|
925
|
+
* @returns The methods.
|
|
926
|
+
* @internal
|
|
927
|
+
*/
|
|
928
|
+
getAllMethods(document) {
|
|
929
|
+
const methods = [];
|
|
930
|
+
const methodTypes = Object.values(standardsW3cDid.DidVerificationMethodType);
|
|
931
|
+
for (const methodType of methodTypes) {
|
|
932
|
+
const mt = document[methodType];
|
|
933
|
+
if (core.Is.arrayValue(mt)) {
|
|
934
|
+
methods.push(...mt.map(m => ({
|
|
935
|
+
arrayKey: methodType,
|
|
936
|
+
method: core.Is.string(m) ? { id: m } : m
|
|
937
|
+
})));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return methods;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Check if a revocation index is revoked.
|
|
944
|
+
* @param document The document to check.
|
|
945
|
+
* @param revocationBitmapIndex The revocation index to check.
|
|
946
|
+
* @returns True if the index is revoked.
|
|
947
|
+
* @internal
|
|
948
|
+
*/
|
|
949
|
+
async checkRevocation(document, revocationBitmapIndex) {
|
|
950
|
+
if (core.Is.stringValue(revocationBitmapIndex)) {
|
|
951
|
+
const revocationIndex = core.Coerce.number(revocationBitmapIndex);
|
|
952
|
+
if (core.Is.number(revocationIndex)) {
|
|
953
|
+
const revocationService = document.service?.find(s => s.id.endsWith("#revocation"));
|
|
954
|
+
if (revocationService &&
|
|
955
|
+
core.Is.string(revocationService.serviceEndpoint) &&
|
|
956
|
+
revocationService.type === "BitstringStatusList") {
|
|
957
|
+
const revocationParts = revocationService.serviceEndpoint.split(",");
|
|
958
|
+
if (revocationParts.length === 2) {
|
|
959
|
+
const compressedRevocationBytes = core.Converter.base64UrlToBytes(revocationParts[1]);
|
|
960
|
+
const decompressed = await core.Compression.decompress(compressedRevocationBytes, core.CompressionType.Gzip);
|
|
961
|
+
const bitString = core.BitString.fromBits(decompressed, EntityStorageIdentityConnector._REVOCATION_BITS_SIZE);
|
|
962
|
+
return bitString.getBit(revocationIndex);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Verify the document in storage.
|
|
971
|
+
* @param didDocument The did document that was stored.
|
|
972
|
+
* @internal
|
|
973
|
+
*/
|
|
974
|
+
async verifyDocument(didDocument) {
|
|
975
|
+
const stringifiedDocument = core.JsonHelper.canonicalize(didDocument.document);
|
|
976
|
+
const docBytes = core.Converter.utf8ToBytes(stringifiedDocument);
|
|
977
|
+
const verified = await this._vaultConnector.verify(this.buildVaultKey(didDocument.id, didDocument.id), docBytes, core.Converter.base64ToBytes(didDocument.signature));
|
|
978
|
+
if (!verified) {
|
|
979
|
+
throw new core.GeneralError(this.CLASS_NAME, "signatureVerificationFailed");
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Update the document in storage.
|
|
984
|
+
* @param controller The controller of the document.
|
|
985
|
+
* @param didDocument The did document to store.
|
|
986
|
+
* @internal
|
|
987
|
+
*/
|
|
988
|
+
async updateDocument(controller, didDocument) {
|
|
989
|
+
const stringifiedDocument = core.JsonHelper.canonicalize(didDocument);
|
|
990
|
+
const docBytes = core.Converter.utf8ToBytes(stringifiedDocument);
|
|
991
|
+
const signature = await this._vaultConnector.sign(this.buildVaultKey(didDocument.id, didDocument.id), docBytes);
|
|
992
|
+
await this._didDocumentEntityStorage.set({
|
|
993
|
+
id: didDocument.id,
|
|
994
|
+
document: didDocument,
|
|
995
|
+
signature: core.Converter.bytesToBase64(signature),
|
|
996
|
+
controller
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Build the key name to access the specified key in the vault.
|
|
1001
|
+
* @param identity The identity of the user to access the vault keys.
|
|
1002
|
+
* @returns The vault key.
|
|
1003
|
+
* @internal
|
|
1004
|
+
*/
|
|
1005
|
+
buildVaultKey(identity, key) {
|
|
1006
|
+
return `${identity}/${key}`;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Copyright 2024 IOTA Stiftung.
|
|
1011
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
1012
|
+
/**
|
|
1013
|
+
* Class which implements the identity profile connector contract.
|
|
1014
|
+
*/
|
|
1015
|
+
class EntityStorageIdentityProfileConnector {
|
|
1016
|
+
/**
|
|
1017
|
+
* The namespace supported by the identity profile connector.
|
|
1018
|
+
*/
|
|
1019
|
+
static NAMESPACE = "entity-storage";
|
|
1020
|
+
/**
|
|
1021
|
+
* Runtime name for the class.
|
|
1022
|
+
*/
|
|
1023
|
+
CLASS_NAME = "EntityStorageIdentityProfileConnector";
|
|
1024
|
+
/**
|
|
1025
|
+
* The storage connector for the profiles.
|
|
1026
|
+
* @internal
|
|
1027
|
+
*/
|
|
1028
|
+
_profileEntityStorage;
|
|
1029
|
+
/**
|
|
1030
|
+
* Create a new instance of Identity.
|
|
1031
|
+
* @param options The dependencies for the identity service.
|
|
1032
|
+
* @param options.profileEntityStorageType The storage connector for the profiles, default to "identity-profile".
|
|
1033
|
+
*/
|
|
1034
|
+
constructor(options) {
|
|
1035
|
+
this._profileEntityStorage = entityStorageModels.EntityStorageConnectorFactory.get(options?.profileEntityStorageType ?? "identity-profile");
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Create the profile properties for an identity.
|
|
1039
|
+
* @param identity The identity of the profile to create.
|
|
1040
|
+
* @param publicProfile The public profile data.
|
|
1041
|
+
* @param privateProfile The private profile data.
|
|
1042
|
+
* @returns Nothing.
|
|
1043
|
+
*/
|
|
1044
|
+
async create(identity, publicProfile, privateProfile) {
|
|
1045
|
+
core.Guards.stringValue(this.CLASS_NAME, "identity", identity);
|
|
1046
|
+
try {
|
|
1047
|
+
const profile = await this._profileEntityStorage.get(identity);
|
|
1048
|
+
if (!core.Is.empty(profile)) {
|
|
1049
|
+
throw new core.AlreadyExistsError(this.CLASS_NAME, "alreadyExists", identity);
|
|
1050
|
+
}
|
|
1051
|
+
await this._profileEntityStorage.set({
|
|
1052
|
+
identity,
|
|
1053
|
+
publicProfile,
|
|
1054
|
+
privateProfile
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
if (core.BaseError.someErrorClass(error, this.CLASS_NAME)) {
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
throw new core.GeneralError(this.CLASS_NAME, "createFailed", { identity }, error);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get the profile properties for an identity.
|
|
1066
|
+
* @param identity The identity of the item to get.
|
|
1067
|
+
* @param publicPropertyNames The public properties to get for the profile, defaults to all.
|
|
1068
|
+
* @param privatePropertyNames The private properties to get for the profile, defaults to all.
|
|
1069
|
+
* @returns The items properties.
|
|
1070
|
+
*/
|
|
1071
|
+
async get(identity, publicPropertyNames, privatePropertyNames) {
|
|
1072
|
+
try {
|
|
1073
|
+
const profile = await this._profileEntityStorage.get(identity);
|
|
1074
|
+
if (!profile) {
|
|
1075
|
+
throw new core.NotFoundError(this.CLASS_NAME, "getFailed", identity);
|
|
1076
|
+
}
|
|
1077
|
+
return this.pickProperties(profile, publicPropertyNames, privatePropertyNames);
|
|
1078
|
+
}
|
|
1079
|
+
catch (error) {
|
|
1080
|
+
if (core.BaseError.someErrorClass(error, this.CLASS_NAME)) {
|
|
1081
|
+
throw error;
|
|
1082
|
+
}
|
|
1083
|
+
throw new core.GeneralError(this.CLASS_NAME, "getFailed", undefined, error);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Update the profile properties of an identity.
|
|
1088
|
+
* @param identity The identity to update.
|
|
1089
|
+
* @param publicProfile The public profile data.
|
|
1090
|
+
* @param privateProfile The private profile data.
|
|
1091
|
+
* @returns Nothing.
|
|
1092
|
+
*/
|
|
1093
|
+
async update(identity, publicProfile, privateProfile) {
|
|
1094
|
+
core.Guards.stringValue(this.CLASS_NAME, "identity", identity);
|
|
1095
|
+
try {
|
|
1096
|
+
const profile = await this._profileEntityStorage.get(identity);
|
|
1097
|
+
if (core.Is.empty(profile)) {
|
|
1098
|
+
throw new core.NotFoundError(this.CLASS_NAME, "notFound", identity);
|
|
1099
|
+
}
|
|
1100
|
+
profile.publicProfile = publicProfile ?? profile.publicProfile;
|
|
1101
|
+
profile.privateProfile = privateProfile ?? profile.privateProfile;
|
|
1102
|
+
await this._profileEntityStorage.set(profile);
|
|
1103
|
+
}
|
|
1104
|
+
catch (error) {
|
|
1105
|
+
if (core.BaseError.someErrorClass(error, this.CLASS_NAME)) {
|
|
1106
|
+
throw error;
|
|
1107
|
+
}
|
|
1108
|
+
throw new core.GeneralError(this.CLASS_NAME, "updateFailed", { identity }, error);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Delete the profile for an identity.
|
|
1113
|
+
* @param identity The identity to delete.
|
|
1114
|
+
* @returns Nothing.
|
|
1115
|
+
*/
|
|
1116
|
+
async remove(identity) {
|
|
1117
|
+
core.Guards.stringValue(this.CLASS_NAME, "identity", identity);
|
|
1118
|
+
try {
|
|
1119
|
+
const profile = await this._profileEntityStorage.get(identity);
|
|
1120
|
+
if (core.Is.empty(profile)) {
|
|
1121
|
+
throw new core.NotFoundError(this.CLASS_NAME, "notFound", identity);
|
|
1122
|
+
}
|
|
1123
|
+
await this._profileEntityStorage.remove(identity);
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
if (core.BaseError.someErrorClass(error, this.CLASS_NAME)) {
|
|
1127
|
+
throw error;
|
|
1128
|
+
}
|
|
1129
|
+
throw new core.GeneralError(this.CLASS_NAME, "removeFailed", { identity }, error);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get a list of the requested types.
|
|
1134
|
+
* @param publicFilters The filters to apply to the identities public profiles.
|
|
1135
|
+
* @param privateFilters The filters to apply to the identities private profiles.
|
|
1136
|
+
* @param publicPropertyNames The public properties to get for the profile, defaults to all.
|
|
1137
|
+
* @param privatePropertyNames The private properties to get for the profile, defaults to all.
|
|
1138
|
+
* @param cursor The cursor for paged requests.
|
|
1139
|
+
* @param pageSize The maximum number of items in a page.
|
|
1140
|
+
* @returns The list of items and cursor for paging.
|
|
1141
|
+
*/
|
|
1142
|
+
async list(publicFilters, privateFilters, publicPropertyNames, privatePropertyNames, cursor, pageSize) {
|
|
1143
|
+
try {
|
|
1144
|
+
const conditions = [];
|
|
1145
|
+
if (core.Is.arrayValue(publicFilters)) {
|
|
1146
|
+
for (const filter of publicFilters) {
|
|
1147
|
+
conditions.push({
|
|
1148
|
+
property: `publicProfile.${filter.propertyName}`,
|
|
1149
|
+
value: filter.propertyValue,
|
|
1150
|
+
comparison: entity.ComparisonOperator.Equals
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (core.Is.arrayValue(privateFilters)) {
|
|
1155
|
+
for (const filter of privateFilters) {
|
|
1156
|
+
conditions.push({
|
|
1157
|
+
property: `privateProfile.${filter.propertyName}`,
|
|
1158
|
+
value: filter.propertyValue,
|
|
1159
|
+
comparison: entity.ComparisonOperator.Equals
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const result = await this._profileEntityStorage.query(core.Is.arrayValue(conditions)
|
|
1164
|
+
? {
|
|
1165
|
+
conditions
|
|
1166
|
+
}
|
|
1167
|
+
: undefined, undefined, undefined, cursor, pageSize);
|
|
1168
|
+
const items = [];
|
|
1169
|
+
for (const resultEntity of result.entities) {
|
|
1170
|
+
items.push({
|
|
1171
|
+
identity: resultEntity.identity ?? "",
|
|
1172
|
+
...this.pickProperties(resultEntity, publicPropertyNames, privatePropertyNames)
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
items,
|
|
1177
|
+
cursor: result.cursor
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
catch (error) {
|
|
1181
|
+
throw new core.GeneralError(this.CLASS_NAME, "listFailed", undefined, error);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Get the profile properties for an identity.
|
|
1186
|
+
* @param profile The profile to pick the properties from.
|
|
1187
|
+
* @param publicPropertyNames The public properties to get for the profile, defaults to all.
|
|
1188
|
+
* @param privatePropertyNames The private properties to get for the profile, defaults to all.
|
|
1189
|
+
* @returns The identity profile, will only return private data if you have correct permissions.
|
|
1190
|
+
* @internal
|
|
1191
|
+
*/
|
|
1192
|
+
pickProperties(profile, publicPropertyNames, privatePropertyNames) {
|
|
1193
|
+
return {
|
|
1194
|
+
publicProfile: core.Is.array(publicPropertyNames)
|
|
1195
|
+
? core.ObjectHelper.pick(profile.publicProfile, publicPropertyNames)
|
|
1196
|
+
: profile.publicProfile,
|
|
1197
|
+
privateProfile: core.Is.array(privatePropertyNames)
|
|
1198
|
+
? core.ObjectHelper.pick(profile.privateProfile, privatePropertyNames)
|
|
1199
|
+
: profile.privateProfile
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Copyright 2024 IOTA Stiftung.
|
|
1205
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
1206
|
+
/**
|
|
1207
|
+
* Initialize the schema for the identity entity storage connector.
|
|
1208
|
+
* @param options Options for which entities to register.
|
|
1209
|
+
* @param options.includeDocument Whether to include the document entity, defaults to true.
|
|
1210
|
+
* @param options.includeProfile Whether to include the profile entity, defaults to true.
|
|
1211
|
+
*/
|
|
1212
|
+
function initSchema(options) {
|
|
1213
|
+
if (options?.includeDocument ?? true) {
|
|
1214
|
+
entity.EntitySchemaFactory.register("IdentityDocument", () => entity.EntitySchemaHelper.getSchema(exports.IdentityDocument));
|
|
1215
|
+
}
|
|
1216
|
+
if (options?.includeProfile ?? true) {
|
|
1217
|
+
entity.EntitySchemaFactory.register("IdentityProfile", () => entity.EntitySchemaHelper.getSchema(exports.IdentityProfile));
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
exports.EntityStorageIdentityConnector = EntityStorageIdentityConnector;
|
|
1222
|
+
exports.EntityStorageIdentityProfileConnector = EntityStorageIdentityProfileConnector;
|
|
1223
|
+
exports.initSchema = initSchema;
|