@idos-network/client 1.0.0-rc.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,22 +1,40 @@
1
+ import type {
2
+ BaseProvider,
3
+ EncryptionPasswordStore,
4
+ PublicEncryptionProfile,
5
+ } from "@idos-network/enclave";
6
+ import type { KwilSigner } from "@idos-network/kwil-js";
7
+
8
+ import {
9
+ buildInsertableIDOSCredential,
10
+ matchLevelOrHigher,
11
+ recordFilter,
12
+ } from "@idos-network/credentials/utils";
13
+ import {
14
+ createClientKwilSigner,
15
+ createWebKwilClient,
16
+ type KwilActionClient,
17
+ signNearMessage,
18
+ type Wallet,
19
+ } from "@idos-network/kwil-infra";
1
20
  import {
2
21
  type AddAttributeInput,
3
22
  type AddWalletInput,
4
23
  addWallet,
5
24
  addWallets,
6
25
  addAttribute as createAttribute,
7
- createCredentialCopy,
8
26
  type DagMessageInput,
9
27
  dagMessage,
10
28
  dwgMessage,
11
- type GetGrantsPaginatedInput,
29
+ type GetAccessGrantsGrantedInput,
12
30
  type GetWalletsOutput,
31
+ getAccessGrantsGrantedCount,
13
32
  getAccessGrantsOwned,
14
33
  getAttributes,
15
34
  getCredentialOwned,
35
+ getCredentialShared,
16
36
  getCredentials,
17
37
  getGrants,
18
- getGrantsCount,
19
- getSharedCredential,
20
38
  getUser,
21
39
  getWallets,
22
40
  hasProfile,
@@ -29,20 +47,11 @@ import {
29
47
  type idOSWallet,
30
48
  removeCredential,
31
49
  removeWallet,
32
- removeWallets,
33
50
  revokeAccessGrant,
34
51
  type ShareCredentialInput,
35
52
  shareCredential,
36
- } from "@idos-network/core/kwil-actions";
37
- import {
38
- createClientKwilSigner,
39
- createWebKwilClient,
40
- type KwilActionClient,
41
- signNearMessage,
42
- } from "@idos-network/core/kwil-infra";
43
- import type { Wallet } from "@idos-network/core/types";
44
- import { buildInsertableIDOSCredential } from "@idos-network/core/utils";
45
- import type { KwilSigner } from "@idos-network/kwil-js";
53
+ type WalletType,
54
+ } from "@idos-network/kwil-infra/actions";
46
55
  import {
47
56
  base64Decode,
48
57
  base64Encode,
@@ -50,17 +59,14 @@ import {
50
59
  utf8Decode,
51
60
  utf8Encode,
52
61
  } from "@idos-network/utils/codecs";
53
- import type { BaseProvider, PublicEncryptionProfile } from "@idos-network/utils/enclave";
54
62
  import { LocalStorageStore, type Store } from "@idos-network/utils/store";
55
- import { negate } from "es-toolkit";
56
- import { every, get } from "es-toolkit/compat";
57
63
  import invariant from "tiny-invariant";
58
64
 
59
65
  import { IframeEnclave } from "./enclave/iframe-enclave";
60
66
  export { IframeEnclave };
61
67
 
62
68
  type Properties<T> = {
63
- // biome-ignore lint/complexity/noBannedTypes: All functions are to be removed.
69
+ // oxlint-disable-next-line typescript/no-restricted-types -- All functions are to be removed.
64
70
  [K in keyof T as Exclude<T[K], Function> extends never ? never : K]: T[K];
65
71
  };
66
72
 
@@ -137,16 +143,34 @@ export class idOSClientIdle {
137
143
  return hasProfile(this.kwilClient, { address }).then((res) => res.has_profile);
138
144
  }
139
145
 
140
- async withUserSigner(signer: Wallet): Promise<idOSClientWithUserSigner> {
141
- const [kwilSigner, walletIdentifier] = await createClientKwilSigner(
142
- this.store,
143
- this.kwilClient,
144
- signer,
145
- );
146
+ async withUserSigner(_signer: Wallet): Promise<idOSClientWithUserSigner> {
147
+ let signer = _signer;
148
+ const [kwilSigner, walletIdentifier, walletPublicKey, walletType] =
149
+ await createClientKwilSigner(this.store, this.kwilClient, signer);
146
150
 
147
151
  this.kwilClient.setSigner(kwilSigner);
148
152
 
149
- return new idOSClientWithUserSigner(this, signer, kwilSigner, walletIdentifier);
153
+ if (walletType === "NEAR") {
154
+ const originalSigner = signer;
155
+ signer = {
156
+ signMessage: async (message: string) => {
157
+ const signature = await signNearMessage(
158
+ originalSigner as Parameters<typeof signNearMessage>[0],
159
+ message,
160
+ );
161
+ return { signedMessage: signature } as { signedMessage: string };
162
+ },
163
+ } as unknown as Wallet;
164
+ }
165
+
166
+ return new idOSClientWithUserSigner(
167
+ this,
168
+ signer,
169
+ kwilSigner,
170
+ walletIdentifier,
171
+ walletPublicKey,
172
+ walletType,
173
+ );
150
174
  }
151
175
 
152
176
  async logOut(): Promise<idOSClientIdle> {
@@ -162,12 +186,16 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
162
186
  readonly signer: Wallet;
163
187
  readonly kwilSigner: KwilSigner;
164
188
  readonly walletIdentifier: string;
189
+ readonly walletPublicKey: string | undefined;
190
+ readonly walletType: WalletType;
165
191
 
166
192
  constructor(
167
193
  idOSClientIdle: idOSClientIdle,
168
194
  signer: Wallet,
169
195
  kwilSigner: KwilSigner,
170
196
  walletIdentifier: string,
197
+ walletPublicKey: string | undefined,
198
+ walletType: WalletType,
171
199
  ) {
172
200
  this.state = "with-user-signer";
173
201
  this.store = idOSClientIdle.store;
@@ -176,7 +204,8 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
176
204
  this.signer = signer;
177
205
  this.kwilSigner = kwilSigner;
178
206
  this.walletIdentifier = walletIdentifier;
179
-
207
+ this.walletPublicKey = walletPublicKey;
208
+ this.walletType = walletType;
180
209
  // @ts-expect-error - TODO: Fix this
181
210
  this.enclaveProvider.setSigner(this.signer);
182
211
  }
@@ -192,12 +221,17 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
192
221
  );
193
222
  }
194
223
 
195
- async createUserEncryptionProfile(userId: string): Promise<PublicEncryptionProfile> {
224
+ async createUserEncryptionProfile(
225
+ userId: string,
226
+ forceEncryptionPasswordStore?: EncryptionPasswordStore,
227
+ ): Promise<PublicEncryptionProfile> {
196
228
  await this.enclaveProvider.reconfigure({
197
229
  mode: "new",
198
230
  userId,
199
231
  walletAddress: this.walletIdentifier,
200
- encryptionPasswordStore: undefined,
232
+ walletPublicKey: this.walletPublicKey,
233
+ walletType: this.walletType,
234
+ encryptionPasswordStore: forceEncryptionPasswordStore,
201
235
  expectedUserEncryptionPublicKey: undefined,
202
236
  });
203
237
 
@@ -214,7 +248,9 @@ export class idOSClientWithUserSigner implements Omit<Properties<idOSClientIdle>
214
248
  userId: kwilUser.id,
215
249
  expectedUserEncryptionPublicKey: kwilUser.recipient_encryption_public_key,
216
250
  walletAddress: this.walletIdentifier,
251
+ walletType: this.walletType,
217
252
  encryptionPasswordStore: kwilUser.encryption_password_store,
253
+ walletPublicKey: this.walletPublicKey,
218
254
  });
219
255
 
220
256
  return new idOSClientLoggedIn(this, kwilUser);
@@ -229,6 +265,8 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
229
265
  readonly signer: Wallet;
230
266
  readonly kwilSigner: KwilSigner;
231
267
  readonly walletIdentifier: string;
268
+ readonly walletPublicKey: string | undefined;
269
+ readonly walletType: WalletType;
232
270
  readonly user: idOSUser;
233
271
 
234
272
  constructor(idOSClientWithUserSigner: idOSClientWithUserSigner, user: idOSUser) {
@@ -239,6 +277,8 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
239
277
  this.signer = idOSClientWithUserSigner.signer;
240
278
  this.kwilSigner = idOSClientWithUserSigner.kwilSigner;
241
279
  this.walletIdentifier = idOSClientWithUserSigner.walletIdentifier;
280
+ this.walletPublicKey = idOSClientWithUserSigner.walletPublicKey;
281
+ this.walletType = idOSClientWithUserSigner.walletType;
242
282
  this.user = user;
243
283
  }
244
284
 
@@ -309,52 +349,7 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
309
349
  base64Decode(credential.encryptor_public_key),
310
350
  );
311
351
 
312
- return plaintext as any;
313
- }
314
-
315
- async createCredentialCopy(
316
- id: string,
317
- consumerRecipientEncryptionPublicKey: string,
318
- consumerAddress: string,
319
- lockedUntil: number,
320
- ): Promise<{ id: string }> {
321
- const originalCredential = await this.getCredentialById(id);
322
- invariant(originalCredential, `"idOSCredential" with id ${id} not found`);
323
-
324
- await this.enclaveProvider.ensureUserEncryptionProfile();
325
-
326
- const decryptedContent = await this.enclaveProvider.decrypt(
327
- base64Decode(originalCredential.content),
328
- base64Decode(originalCredential.encryptor_public_key),
329
- );
330
-
331
- const { content, encryptorPublicKey } = await this.enclaveProvider.encrypt(
332
- decryptedContent,
333
- base64Decode(consumerRecipientEncryptionPublicKey),
334
- );
335
-
336
- const insertableCredential = {
337
- ...(await buildInsertableIDOSCredential(
338
- originalCredential.user_id,
339
- "",
340
- base64Encode(content),
341
- consumerRecipientEncryptionPublicKey,
342
- base64Encode(encryptorPublicKey),
343
- )),
344
- grantee_wallet_identifier: consumerAddress,
345
- locked_until: lockedUntil,
346
- };
347
-
348
- const copyId = crypto.randomUUID();
349
-
350
- await createCredentialCopy(this.kwilClient, {
351
- original_credential_id: originalCredential.id,
352
- ...originalCredential,
353
- ...insertableCredential,
354
- id: copyId,
355
- });
356
-
357
- return { id: copyId };
352
+ return utf8Decode(plaintext);
358
353
  }
359
354
 
360
355
  async requestDAGMessage(params: DagMessageInput): Promise<string> {
@@ -362,7 +357,7 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
362
357
  }
363
358
 
364
359
  async getGrants(
365
- params: GetGrantsPaginatedInput,
360
+ params: Partial<GetAccessGrantsGrantedInput>,
366
361
  ): Promise<{ grants: idOSGrant[]; totalCount: number }> {
367
362
  return {
368
363
  grants: await getGrants(this.kwilClient, params),
@@ -376,13 +371,13 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
376
371
  }
377
372
 
378
373
  async getGrantsCount(): Promise<number> {
379
- return getGrantsCount(this.kwilClient, {
374
+ return getAccessGrantsGrantedCount(this.kwilClient, {
380
375
  user_id: null,
381
376
  }).then((res) => res.count);
382
377
  }
383
378
 
384
- async getSharedCredential(id: string): Promise<idOSCredential | undefined> {
385
- return getSharedCredential(this.kwilClient, { id }).then((res) => res[0]);
379
+ async getCredentialShared(id: string): Promise<idOSCredential | undefined> {
380
+ return getCredentialShared(this.kwilClient, { id }).then((res) => res[0]);
386
381
  }
387
382
 
388
383
  async revokeAccessGrant(id: string): Promise<{ id: string }> {
@@ -392,6 +387,39 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
392
387
 
393
388
  async addWallet(params: AddWalletInput): Promise<AddWalletInput> {
394
389
  await addWallet(this.kwilClient, params);
390
+
391
+ // we don't need to add the wallet to MPC if the user is not using MPC
392
+ if (this.user.encryption_password_store !== "mpc") {
393
+ console.log("MPC is not enabled or the user is not using MPC");
394
+ return params;
395
+ }
396
+
397
+ if (!params.wallet_type) {
398
+ throw new Error("Wallet type is required for MPC users");
399
+ }
400
+
401
+ const messageToSign = await this.enclaveProvider.addAddressMessageToSign(
402
+ params.address,
403
+ params.public_key ?? undefined,
404
+ params.wallet_type,
405
+ );
406
+
407
+ const signature = await this.enclaveProvider.signTypedData(
408
+ messageToSign.domain,
409
+ messageToSign.types,
410
+ messageToSign.value,
411
+ );
412
+
413
+ const result = await this.enclaveProvider.addAddressToMpcSecret(
414
+ this.user.id,
415
+ messageToSign.value,
416
+ signature,
417
+ );
418
+
419
+ if (result !== "success") {
420
+ console.error(`Failed to add wallet to MPC: ${result}`);
421
+ }
422
+
395
423
  return params;
396
424
  }
397
425
 
@@ -400,12 +428,47 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
400
428
  }
401
429
 
402
430
  async removeWallet(id: string): Promise<{ id: string }> {
431
+ const wallets = await this.getWallets();
432
+ const wallet = wallets.find((wallet) => wallet.id === id);
433
+ if (!wallet) {
434
+ throw new Error(`Wallet with id ${id} not found`);
435
+ }
436
+
403
437
  await removeWallet(this.kwilClient, { id });
438
+
439
+ // we don't need to add the wallet to MPC if the user is not using MPC
440
+ if (this.user.encryption_password_store !== "mpc") {
441
+ console.log("MPC is not enabled or the user is not using MPC");
442
+ return { id };
443
+ }
444
+
445
+ const messageToSign = await this.enclaveProvider.removeAddressMessageToSign(
446
+ wallet.address,
447
+ wallet.public_key ?? undefined,
448
+ wallet.wallet_type,
449
+ );
450
+ const signature = await this.enclaveProvider.signTypedData(
451
+ messageToSign.domain,
452
+ messageToSign.types,
453
+ messageToSign.value,
454
+ );
455
+ const result = await this.enclaveProvider.removeAddressFromMpcSecret(
456
+ wallet.user_id,
457
+ messageToSign.value,
458
+ signature,
459
+ );
460
+ if (result !== "success") {
461
+ console.error(`Failed to add wallet to MPC: ${result}`);
462
+ }
463
+
404
464
  return { id };
405
465
  }
406
466
 
407
467
  async removeWallets(ids: string[]): Promise<string[]> {
408
- await removeWallets(this.kwilClient, ids);
468
+ for (const id of ids) {
469
+ await this.removeWallet(id);
470
+ }
471
+
409
472
  return ids;
410
473
  }
411
474
 
@@ -414,19 +477,18 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
414
477
  authPublicKey: string;
415
478
  }[];
416
479
  publicNotesFieldFilters?: {
417
- pick: Record<string, unknown[]>;
418
- omit: Record<string, unknown[]>;
480
+ pick: Parameters<typeof recordFilter>[1];
481
+ omit: Parameters<typeof recordFilter>[2];
419
482
  };
420
483
  privateFieldFilters?: {
421
- pick: Record<string, unknown[]>;
422
- omit: Record<string, unknown[]>;
484
+ pick: Parameters<typeof recordFilter>[1];
485
+ omit: Parameters<typeof recordFilter>[2];
423
486
  };
424
- }): Promise<idOSCredential[]> {
425
- const matchCriteria = (content: Record<string, unknown>, criteria: Record<string, unknown[]>) =>
426
- every(Object.entries(criteria), ([path, targetSet]) =>
427
- targetSet.includes(get(content, path)),
428
- );
429
-
487
+ credentialLevelOrHigherFilter?: {
488
+ userLevel: "basic" | "plus";
489
+ requiredAddons: ("liveness" | "email" | "phoneNumber")[];
490
+ };
491
+ }): Promise<idOSCredentialListItem[]> {
430
492
  const credentials = await this.getAllCredentials();
431
493
  const originalCredentials = credentials.filter(
432
494
  (cred) => !cred.original_id && !!cred.public_notes,
@@ -440,24 +502,74 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
440
502
  });
441
503
 
442
504
  const publicNotesFieldFilters = requirements.publicNotesFieldFilters;
443
- if (publicNotesFieldFilters) {
505
+ const credentialLevelOrHigherFilter = requirements.credentialLevelOrHigherFilter;
506
+ const privateFieldFilters = requirements.privateFieldFilters;
507
+
508
+ if (credentialLevelOrHigherFilter) {
444
509
  result = result.filter((credential) => {
445
510
  let publicNotes: Record<string, string>;
511
+
446
512
  try {
447
513
  publicNotes = JSON.parse(credential.public_notes);
448
514
  } catch (_) {
449
- throw new Error(`Credential ${credential.id} has non-JSON public notes".replace("{}`);
515
+ throw new Error(`Credential ${credential.id} has non-JSON public notes.`);
450
516
  }
451
- return (
452
- matchCriteria(publicNotes, publicNotesFieldFilters.pick) &&
453
- negate(() => matchCriteria(publicNotes, publicNotesFieldFilters.omit))
517
+
518
+ return matchLevelOrHigher(
519
+ credentialLevelOrHigherFilter.userLevel,
520
+ credentialLevelOrHigherFilter.requiredAddons,
521
+ publicNotes.level,
454
522
  );
455
523
  });
456
524
  }
457
525
 
458
- const privateFieldFilters = requirements.privateFieldFilters;
459
- if (privateFieldFilters) {
460
- result = await this.enclaveProvider.filterCredentials(result, privateFieldFilters);
526
+ if (
527
+ publicNotesFieldFilters &&
528
+ (Object.keys(publicNotesFieldFilters.pick).length > 0 ||
529
+ Object.keys(publicNotesFieldFilters.omit).length > 0)
530
+ ) {
531
+ result = result.filter((credential) => {
532
+ let publicNotes: Record<string, string>;
533
+
534
+ try {
535
+ publicNotes = JSON.parse(credential.public_notes);
536
+ } catch (_) {
537
+ throw new Error(`Credential ${credential.id} has non-JSON public notes.`);
538
+ }
539
+
540
+ return recordFilter(
541
+ publicNotes,
542
+ publicNotesFieldFilters.pick,
543
+ publicNotesFieldFilters.omit,
544
+ );
545
+ });
546
+ }
547
+
548
+ // Private field filtering requires decryption and the full idOS credential content
549
+ // it's most "expensive", since it requires a roundtrip to the nodes and roundtrip to enclave.
550
+ if (
551
+ privateFieldFilters &&
552
+ (Object.keys(privateFieldFilters.pick).length > 0 ||
553
+ Object.keys(privateFieldFilters.omit).length > 0)
554
+ ) {
555
+ const fullCredentials = await Promise.all(
556
+ result.map((credential) => this.getCredentialById(credential.id)),
557
+ );
558
+
559
+ // Check for undefined, it will be sign of some internal error
560
+ invariant(
561
+ fullCredentials.every((cred) => cred !== undefined),
562
+ "Some credentials could not be fetched in full form for private field filtering",
563
+ );
564
+
565
+ const matches = await this.enclaveProvider.filterCredentials(
566
+ fullCredentials,
567
+ privateFieldFilters,
568
+ );
569
+
570
+ result = result.filter((credential) =>
571
+ matches.some((matchedCredential) => matchedCredential.id === credential.id),
572
+ );
461
573
  }
462
574
 
463
575
  return result;
@@ -468,8 +580,13 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
468
580
  {
469
581
  consumerEncryptionPublicKey,
470
582
  consumerAuthPublicKey,
471
- }: { consumerEncryptionPublicKey: string; consumerAuthPublicKey: string },
472
- ): Promise<idOSCredential> {
583
+ lockedUntil = 0,
584
+ }: {
585
+ consumerEncryptionPublicKey: string;
586
+ consumerAuthPublicKey: string;
587
+ lockedUntil?: number;
588
+ },
589
+ ): Promise<ShareCredentialInput> {
473
590
  const credential = await this.getCredentialById(credentialId);
474
591
  const contentHash = await this.getCredentialContentSha256Hash(credentialId);
475
592
 
@@ -489,19 +606,18 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
489
606
  base64Decode(consumerEncryptionPublicKey),
490
607
  );
491
608
 
492
- const insertableCredential = {
609
+ const insertableCredential: ShareCredentialInput = {
493
610
  ...credential,
494
- ...(await buildInsertableIDOSCredential(
611
+ ...buildInsertableIDOSCredential(
495
612
  credential.user_id,
496
613
  "",
497
614
  base64Encode(content),
498
- consumerAuthPublicKey,
499
615
  base64Encode(encryptorPublicKey),
500
- )),
616
+ ),
501
617
  original_credential_id: credential.id,
502
618
  id: crypto.randomUUID(),
503
619
  grantee_wallet_identifier: consumerAuthPublicKey,
504
- locked_until: 0,
620
+ locked_until: lockedUntil,
505
621
  content_hash: contentHash,
506
622
  };
507
623
 
@@ -514,6 +630,7 @@ export class idOSClientLoggedIn implements Omit<Properties<idOSClientWithUserSig
514
630
  export type {
515
631
  idOSDelegatedWriteGrant,
516
632
  idOSCredential,
633
+ idOSCredentialListItem,
517
634
  idOSGrant,
518
635
  idOSUser,
519
636
  idOSUserAttribute,