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