@protontech/drive-sdk 0.3.1 → 0.4.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.
Files changed (181) hide show
  1. package/dist/crypto/driveCrypto.d.ts +1 -1
  2. package/dist/crypto/driveCrypto.js.map +1 -1
  3. package/dist/crypto/interface.d.ts +1 -1
  4. package/dist/crypto/openPGPCrypto.d.ts +1 -1
  5. package/dist/crypto/openPGPCrypto.js +4 -1
  6. package/dist/crypto/openPGPCrypto.js.map +1 -1
  7. package/dist/internal/apiService/errorCodes.d.ts +1 -0
  8. package/dist/internal/apiService/errors.d.ts +3 -0
  9. package/dist/internal/apiService/errors.js +7 -1
  10. package/dist/internal/apiService/errors.js.map +1 -1
  11. package/dist/internal/devices/interface.d.ts +1 -1
  12. package/dist/internal/devices/manager.js +1 -1
  13. package/dist/internal/devices/manager.js.map +1 -1
  14. package/dist/internal/devices/manager.test.js +3 -3
  15. package/dist/internal/devices/manager.test.js.map +1 -1
  16. package/dist/internal/download/cryptoService.js +2 -2
  17. package/dist/internal/download/cryptoService.js.map +1 -1
  18. package/dist/internal/download/fileDownloader.js +2 -2
  19. package/dist/internal/download/fileDownloader.js.map +1 -1
  20. package/dist/internal/download/fileDownloader.test.js +3 -1
  21. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  22. package/dist/internal/events/apiService.js +1 -1
  23. package/dist/internal/events/apiService.js.map +1 -1
  24. package/dist/internal/events/coreEventManager.js +1 -1
  25. package/dist/internal/events/coreEventManager.js.map +1 -1
  26. package/dist/internal/events/coreEventManager.test.js +18 -24
  27. package/dist/internal/events/coreEventManager.test.js.map +1 -1
  28. package/dist/internal/events/index.d.ts +3 -4
  29. package/dist/internal/events/index.js +4 -4
  30. package/dist/internal/events/index.js.map +1 -1
  31. package/dist/internal/events/interface.d.ts +3 -0
  32. package/dist/internal/nodes/apiService.d.ts +12 -3
  33. package/dist/internal/nodes/apiService.js +53 -13
  34. package/dist/internal/nodes/apiService.js.map +1 -1
  35. package/dist/internal/nodes/apiService.test.js +19 -2
  36. package/dist/internal/nodes/apiService.test.js.map +1 -1
  37. package/dist/internal/nodes/cache.js +3 -1
  38. package/dist/internal/nodes/cache.js.map +1 -1
  39. package/dist/internal/nodes/cryptoReporter.d.ts +20 -0
  40. package/dist/internal/nodes/cryptoReporter.js +96 -0
  41. package/dist/internal/nodes/cryptoReporter.js.map +1 -0
  42. package/dist/internal/nodes/cryptoService.d.ts +18 -13
  43. package/dist/internal/nodes/cryptoService.js +18 -98
  44. package/dist/internal/nodes/cryptoService.js.map +1 -1
  45. package/dist/internal/nodes/cryptoService.test.js +7 -5
  46. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  47. package/dist/internal/nodes/errors.d.ts +4 -0
  48. package/dist/internal/nodes/errors.js +9 -0
  49. package/dist/internal/nodes/errors.js.map +1 -0
  50. package/dist/internal/nodes/index.js +3 -1
  51. package/dist/internal/nodes/index.js.map +1 -1
  52. package/dist/internal/nodes/index.test.js +1 -1
  53. package/dist/internal/nodes/index.test.js.map +1 -1
  54. package/dist/internal/nodes/interface.d.ts +5 -2
  55. package/dist/internal/nodes/nodesAccess.d.ts +4 -4
  56. package/dist/internal/nodes/nodesAccess.js +77 -69
  57. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  58. package/dist/internal/nodes/nodesAccess.test.js +48 -8
  59. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  60. package/dist/internal/nodes/nodesManagement.d.ts +2 -0
  61. package/dist/internal/nodes/nodesManagement.js +86 -9
  62. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  63. package/dist/internal/nodes/nodesManagement.test.js +81 -5
  64. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  65. package/dist/internal/photos/albums.d.ts +9 -7
  66. package/dist/internal/photos/albums.js +26 -13
  67. package/dist/internal/photos/albums.js.map +1 -1
  68. package/dist/internal/photos/apiService.d.ts +34 -3
  69. package/dist/internal/photos/apiService.js +96 -3
  70. package/dist/internal/photos/apiService.js.map +1 -1
  71. package/dist/internal/photos/index.d.ts +20 -4
  72. package/dist/internal/photos/index.js +30 -7
  73. package/dist/internal/photos/index.js.map +1 -1
  74. package/dist/internal/photos/interface.d.ts +25 -1
  75. package/dist/internal/photos/shares.d.ts +43 -0
  76. package/dist/internal/photos/shares.js +112 -0
  77. package/dist/internal/photos/shares.js.map +1 -0
  78. package/dist/internal/photos/timeline.d.ts +15 -0
  79. package/dist/internal/photos/timeline.js +22 -0
  80. package/dist/internal/photos/timeline.js.map +1 -0
  81. package/dist/internal/shares/manager.d.ts +1 -1
  82. package/dist/internal/shares/manager.js +4 -4
  83. package/dist/internal/shares/manager.js.map +1 -1
  84. package/dist/internal/shares/manager.test.js +7 -7
  85. package/dist/internal/shares/manager.test.js.map +1 -1
  86. package/dist/internal/sharing/cache.d.ts +3 -0
  87. package/dist/internal/sharing/cache.js +17 -2
  88. package/dist/internal/sharing/cache.js.map +1 -1
  89. package/dist/internal/sharing/interface.d.ts +2 -2
  90. package/dist/internal/sharing/interface.js +1 -1
  91. package/dist/internal/sharing/sharingAccess.js +7 -1
  92. package/dist/internal/sharing/sharingAccess.js.map +1 -1
  93. package/dist/internal/sharing/sharingAccess.test.js +243 -34
  94. package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
  95. package/dist/internal/sharingPublic/apiService.d.ts +1 -1
  96. package/dist/internal/sharingPublic/apiService.js +9 -2
  97. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  98. package/dist/internal/sharingPublic/cryptoService.d.ts +6 -20
  99. package/dist/internal/sharingPublic/cryptoService.js +40 -103
  100. package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
  101. package/dist/internal/sharingPublic/index.d.ts +2 -2
  102. package/dist/internal/sharingPublic/index.js +2 -2
  103. package/dist/internal/sharingPublic/index.js.map +1 -1
  104. package/dist/internal/sharingPublic/interface.d.ts +1 -43
  105. package/dist/internal/sharingPublic/manager.d.ts +1 -1
  106. package/dist/internal/sharingPublic/manager.js +9 -7
  107. package/dist/internal/sharingPublic/manager.js.map +1 -1
  108. package/dist/internal/upload/streamUploader.js +1 -1
  109. package/dist/internal/upload/streamUploader.js.map +1 -1
  110. package/dist/internal/upload/streamUploader.test.js +3 -1
  111. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  112. package/dist/protonDriveClient.d.ts +20 -3
  113. package/dist/protonDriveClient.js +24 -4
  114. package/dist/protonDriveClient.js.map +1 -1
  115. package/dist/protonDrivePhotosClient.d.ts +86 -12
  116. package/dist/protonDrivePhotosClient.js +132 -29
  117. package/dist/protonDrivePhotosClient.js.map +1 -1
  118. package/dist/protonDrivePublicLinkClient.d.ts +13 -4
  119. package/dist/protonDrivePublicLinkClient.js +13 -11
  120. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  121. package/package.json +1 -1
  122. package/src/crypto/driveCrypto.ts +1 -1
  123. package/src/crypto/interface.ts +1 -1
  124. package/src/crypto/openPGPCrypto.ts +5 -2
  125. package/src/internal/apiService/errorCodes.ts +1 -0
  126. package/src/internal/apiService/errors.ts +6 -0
  127. package/src/internal/devices/interface.ts +1 -1
  128. package/src/internal/devices/manager.test.ts +3 -3
  129. package/src/internal/devices/manager.ts +1 -1
  130. package/src/internal/download/cryptoService.ts +2 -2
  131. package/src/internal/download/fileDownloader.test.ts +3 -1
  132. package/src/internal/download/fileDownloader.ts +2 -2
  133. package/src/internal/events/apiService.ts +1 -1
  134. package/src/internal/events/coreEventManager.test.ts +21 -27
  135. package/src/internal/events/coreEventManager.ts +1 -1
  136. package/src/internal/events/index.ts +3 -4
  137. package/src/internal/events/interface.ts +4 -0
  138. package/src/internal/nodes/apiService.test.ts +35 -1
  139. package/src/internal/nodes/apiService.ts +103 -17
  140. package/src/internal/nodes/cache.ts +3 -1
  141. package/src/internal/nodes/cryptoReporter.ts +145 -0
  142. package/src/internal/nodes/cryptoService.test.ts +11 -9
  143. package/src/internal/nodes/cryptoService.ts +45 -138
  144. package/src/internal/nodes/errors.ts +5 -0
  145. package/src/internal/nodes/index.test.ts +1 -1
  146. package/src/internal/nodes/index.ts +3 -1
  147. package/src/internal/nodes/interface.ts +6 -2
  148. package/src/internal/nodes/nodesAccess.test.ts +68 -8
  149. package/src/internal/nodes/nodesAccess.ts +101 -76
  150. package/src/internal/nodes/nodesManagement.test.ts +100 -5
  151. package/src/internal/nodes/nodesManagement.ts +100 -13
  152. package/src/internal/photos/albums.ts +31 -12
  153. package/src/internal/photos/apiService.ts +159 -4
  154. package/src/internal/photos/index.ts +54 -9
  155. package/src/internal/photos/interface.ts +23 -1
  156. package/src/internal/photos/shares.ts +134 -0
  157. package/src/internal/photos/timeline.ts +24 -0
  158. package/src/internal/shares/manager.test.ts +7 -7
  159. package/src/internal/shares/manager.ts +4 -4
  160. package/src/internal/sharing/cache.ts +19 -2
  161. package/src/internal/sharing/interface.ts +2 -2
  162. package/src/internal/sharing/sharingAccess.test.ts +283 -35
  163. package/src/internal/sharing/sharingAccess.ts +7 -1
  164. package/src/internal/sharingPublic/apiService.ts +11 -2
  165. package/src/internal/sharingPublic/cryptoService.ts +71 -135
  166. package/src/internal/sharingPublic/index.ts +3 -2
  167. package/src/internal/sharingPublic/interface.ts +8 -53
  168. package/src/internal/sharingPublic/manager.ts +9 -8
  169. package/src/internal/upload/streamUploader.test.ts +3 -1
  170. package/src/internal/upload/streamUploader.ts +1 -1
  171. package/src/protonDriveClient.ts +34 -4
  172. package/src/protonDrivePhotosClient.ts +211 -32
  173. package/src/protonDrivePublicLinkClient.ts +26 -12
  174. package/dist/internal/photos/cache.d.ts +0 -6
  175. package/dist/internal/photos/cache.js +0 -15
  176. package/dist/internal/photos/cache.js.map +0 -1
  177. package/dist/internal/photos/photosTimeline.d.ts +0 -10
  178. package/dist/internal/photos/photosTimeline.js +0 -19
  179. package/dist/internal/photos/photosTimeline.js.map +0 -1
  180. package/src/internal/photos/cache.ts +0 -11
  181. package/src/internal/photos/photosTimeline.ts +0 -17
@@ -3,6 +3,7 @@ import { MemberRole, ProtonDriveAccount, ProtonDriveTelemetry, RevisionState } f
3
3
  import { getMockTelemetry } from '../../tests/telemetry';
4
4
  import { DecryptedNode, DecryptedNodeKeys, DecryptedUnparsedNode, EncryptedNode, SharesService } from './interface';
5
5
  import { NodesCryptoService } from './cryptoService';
6
+ import { NodesCryptoReporter } from './cryptoReporter';
6
7
 
7
8
  describe('nodesCryptoService', () => {
8
9
  let telemetry: ProtonDriveTelemetry;
@@ -74,7 +75,8 @@ describe('nodesCryptoService', () => {
74
75
  getVolumeMetricContext: jest.fn().mockResolvedValue('own_volume'),
75
76
  };
76
77
 
77
- cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, sharesService);
78
+ const nodesCryptoReporter = new NodesCryptoReporter(telemetry, sharesService);
79
+ cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, nodesCryptoReporter);
78
80
  });
79
81
 
80
82
  const parentKey = 'parentKey' as unknown as PrivateKey;
@@ -983,7 +985,7 @@ describe('nodesCryptoService', () => {
983
985
  });
984
986
  });
985
987
 
986
- describe('moveNode', () => {
988
+ describe('encryptNodeWithNewParent', () => {
987
989
  it('should encrypt node data for move operation', async () => {
988
990
  const node = {
989
991
  name: { ok: true, value: 'testFile.txt' },
@@ -1010,7 +1012,7 @@ describe('nodesCryptoService', () => {
1010
1012
  armoredPassphraseSignature: 'passphraseSignature',
1011
1013
  });
1012
1014
 
1013
- const result = await cryptoService.moveNode(node, keys as any, parentKeys, address);
1015
+ const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address);
1014
1016
 
1015
1017
  expect(result).toEqual({
1016
1018
  encryptedName: 'encryptedNodeName',
@@ -1054,9 +1056,9 @@ describe('nodesCryptoService', () => {
1054
1056
  addressKey: 'addressKey' as any,
1055
1057
  };
1056
1058
 
1057
- await expect(cryptoService.moveNode(node, keys as any, parentKeys, address)).rejects.toThrow(
1058
- 'Moving item to a non-folder is not allowed',
1059
- );
1059
+ await expect(
1060
+ cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
1061
+ ).rejects.toThrow('Moving item to a non-folder is not allowed');
1060
1062
  });
1061
1063
 
1062
1064
  it('should throw error when node has invalid name', async () => {
@@ -1077,9 +1079,9 @@ describe('nodesCryptoService', () => {
1077
1079
  addressKey: 'addressKey' as any,
1078
1080
  };
1079
1081
 
1080
- await expect(cryptoService.moveNode(node, keys as any, parentKeys, address)).rejects.toThrow(
1081
- 'Cannot move item without a valid name, please rename the item first',
1082
- );
1082
+ await expect(
1083
+ cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
1084
+ ).rejects.toThrow('Cannot move item without a valid name, please rename the item first');
1083
1085
  });
1084
1086
  });
1085
1087
  });
@@ -7,27 +7,49 @@ import {
7
7
  Result,
8
8
  Author,
9
9
  AnonymousUser,
10
- ProtonDriveAccount,
11
10
  ProtonDriveTelemetry,
12
11
  Logger,
13
12
  MetricsDecryptionErrorField,
14
13
  MetricVerificationErrorField,
15
14
  Membership,
15
+ ProtonDriveAccount,
16
16
  } from '../../interface';
17
17
  import { ValidationError } from '../../errors';
18
- import { getErrorMessage, getVerificationMessage } from '../errors';
19
- import { splitNodeUid } from '../uids';
18
+ import { getErrorMessage } from '../errors';
20
19
  import {
21
20
  EncryptedNode,
22
21
  EncryptedNodeFolderCrypto,
23
22
  DecryptedUnparsedNode,
24
23
  DecryptedNode,
25
24
  DecryptedNodeKeys,
26
- SharesService,
27
25
  EncryptedRevision,
28
26
  DecryptedUnparsedRevision,
29
27
  } from './interface';
30
28
 
29
+ export interface NodesCryptoReporter {
30
+ handleClaimedAuthor(
31
+ node: NodesCryptoReporterNode,
32
+ field: MetricVerificationErrorField,
33
+ signatureType: string,
34
+ verified: VERIFICATION_STATUS,
35
+ verificationErrors?: Error[],
36
+ claimedAuthor?: string,
37
+ notAvailableVerificationKeys?: boolean,
38
+ ): Promise<Author>;
39
+ reportDecryptionError(node: NodesCryptoReporterNode, field: MetricsDecryptionErrorField, error: unknown): void;
40
+ reportVerificationError(
41
+ node: NodesCryptoReporterNode,
42
+ field: MetricVerificationErrorField,
43
+ verificationErrors?: Error[],
44
+ claimedAuthor?: string,
45
+ ): void;
46
+ }
47
+
48
+ type NodesCryptoReporterNode = {
49
+ uid: string;
50
+ creationTime: Date;
51
+ };
52
+
31
53
  /**
32
54
  * Provides crypto operations for nodes metadata.
33
55
  *
@@ -41,20 +63,16 @@ import {
41
63
  export class NodesCryptoService {
42
64
  private logger: Logger;
43
65
 
44
- private reportedDecryptionErrors = new Set<string>();
45
- private reportedVerificationErrors = new Set<string>();
46
-
47
66
  constructor(
48
- private telemetry: ProtonDriveTelemetry,
49
- private driveCrypto: DriveCrypto,
67
+ telemetry: ProtonDriveTelemetry,
68
+ protected driveCrypto: DriveCrypto,
50
69
  private account: ProtonDriveAccount,
51
- private shareService: SharesService,
70
+ private reporter: NodesCryptoReporter,
52
71
  ) {
53
- this.telemetry = telemetry;
54
72
  this.logger = telemetry.getLogger('nodes-crypto');
55
73
  this.driveCrypto = driveCrypto;
56
74
  this.account = account;
57
- this.shareService = shareService;
75
+ this.reporter = reporter;
58
76
  }
59
77
 
60
78
  async decryptNode(
@@ -107,7 +125,7 @@ export class NodesCryptoService {
107
125
  passphraseSessionKey = keyResult.passphraseSessionKey;
108
126
  keyAuthor = keyResult.author;
109
127
  } catch (error: unknown) {
110
- void this.reportDecryptionError(node, 'nodeKey', error);
128
+ void this.reporter.reportDecryptionError(node, 'nodeKey', error);
111
129
  const message = getErrorMessage(error);
112
130
  const errorMessage = c('Error').t`Failed to decrypt node key: ${message}`;
113
131
  const { name, author: nameAuthor } = await namePromise;
@@ -159,7 +177,7 @@ export class NodesCryptoService {
159
177
  hashKey = hashKeyResult.hashKey;
160
178
  hashKeyAuthor = hashKeyResult.author;
161
179
  } catch (error: unknown) {
162
- void this.reportDecryptionError(node, 'nodeHashKey', error);
180
+ void this.reporter.reportDecryptionError(node, 'nodeHashKey', error);
163
181
  errors.push(error);
164
182
  }
165
183
 
@@ -170,7 +188,7 @@ export class NodesCryptoService {
170
188
  };
171
189
  folderExtendedAttributesAuthor = extendedAttributesResult.author;
172
190
  } catch (error: unknown) {
173
- void this.reportDecryptionError(node, 'nodeExtendedAttributes', error);
191
+ void this.reporter.reportDecryptionError(node, 'nodeExtendedAttributes', error);
174
192
  errors.push(error);
175
193
  }
176
194
  }
@@ -194,7 +212,7 @@ export class NodesCryptoService {
194
212
  try {
195
213
  activeRevision = resultOk(await activeRevisionPromise);
196
214
  } catch (error: unknown) {
197
- void this.reportDecryptionError(node, 'nodeExtendedAttributes', error);
215
+ void this.reporter.reportDecryptionError(node, 'nodeExtendedAttributes', error);
198
216
  const message = getErrorMessage(error);
199
217
  const errorMessage = c('Error').t`Failed to decrypt active revision: ${message}`;
200
218
  activeRevision = resultError(new Error(errorMessage));
@@ -205,7 +223,7 @@ export class NodesCryptoService {
205
223
  contentKeyPacketSessionKey = keySessionKeyResult.sessionKey;
206
224
  contentKeyPacketAuthor =
207
225
  keySessionKeyResult.verified !== undefined &&
208
- (await this.handleClaimedAuthor(
226
+ (await this.reporter.handleClaimedAuthor(
209
227
  node,
210
228
  'nodeContentKey',
211
229
  c('Property').t`content key`,
@@ -214,7 +232,7 @@ export class NodesCryptoService {
214
232
  node.encryptedCrypto.signatureEmail,
215
233
  ));
216
234
  } catch (error: unknown) {
217
- void this.reportDecryptionError(node, 'nodeContentKey', error);
235
+ void this.reporter.reportDecryptionError(node, 'nodeContentKey', error);
218
236
  const message = getErrorMessage(error);
219
237
  const errorMessage = c('Error').t`Failed to decrypt content key: ${message}`;
220
238
  contentKeyPacketAuthor = resultError({
@@ -295,7 +313,7 @@ export class NodesCryptoService {
295
313
  passphrase: key.passphrase,
296
314
  key: key.key,
297
315
  passphraseSessionKey: key.passphraseSessionKey,
298
- author: await this.handleClaimedAuthor(
316
+ author: await this.reporter.handleClaimedAuthor(
299
317
  node,
300
318
  'nodeKey',
301
319
  c('Property').t`key`,
@@ -326,7 +344,7 @@ export class NodesCryptoService {
326
344
 
327
345
  return {
328
346
  name: resultOk(name),
329
- author: await this.handleClaimedAuthor(
347
+ author: await this.reporter.handleClaimedAuthor(
330
348
  node,
331
349
  'nodeName',
332
350
  c('Property').t`name`,
@@ -337,7 +355,7 @@ export class NodesCryptoService {
337
355
  ),
338
356
  };
339
357
  } catch (error: unknown) {
340
- void this.reportDecryptionError(node, 'nodeName', error);
358
+ void this.reporter.reportDecryptionError(node, 'nodeName', error);
341
359
  const errorMessage = getErrorMessage(error);
342
360
  return {
343
361
  name: resultError(new Error(errorMessage)),
@@ -378,7 +396,7 @@ export class NodesCryptoService {
378
396
  inviterEmailKeys || [],
379
397
  );
380
398
 
381
- sharedBy = await this.handleClaimedAuthor(
399
+ sharedBy = await this.reporter.handleClaimedAuthor(
382
400
  node,
383
401
  'membershipInviter',
384
402
  c('Property').t`membership`,
@@ -387,7 +405,7 @@ export class NodesCryptoService {
387
405
  node.encryptedCrypto.membership.inviterEmail,
388
406
  );
389
407
  } catch (error: unknown) {
390
- void this.reportVerificationError(node, 'membershipInviter');
408
+ void this.reporter.reportVerificationError(node, 'membershipInviter');
391
409
  this.logger.error('Failed to verify invitation', error);
392
410
  sharedBy = resultError({
393
411
  claimedAuthor: node.encryptedCrypto.membership.inviterEmail,
@@ -428,7 +446,7 @@ export class NodesCryptoService {
428
446
 
429
447
  return {
430
448
  hashKey,
431
- author: await this.handleClaimedAuthor(
449
+ author: await this.reporter.handleClaimedAuthor(
432
450
  node,
433
451
  'nodeHashKey',
434
452
  c('Property').t`hash key`,
@@ -491,7 +509,7 @@ export class NodesCryptoService {
491
509
 
492
510
  return {
493
511
  extendedAttributes,
494
- author: await this.handleClaimedAuthor(
512
+ author: await this.reporter.handleClaimedAuthor(
495
513
  node,
496
514
  'nodeExtendedAttributes',
497
515
  c('Property').t`attributes`,
@@ -509,6 +527,7 @@ export class NodesCryptoService {
509
527
  extendedAttributes?: string,
510
528
  ): Promise<{
511
529
  encryptedCrypto: EncryptedNodeFolderCrypto & {
530
+ armoredNodePassphraseSignature: string;
512
531
  // signatureEmail and nameSignatureEmail are not optional.
513
532
  signatureEmail: string;
514
533
  nameSignatureEmail: string;
@@ -582,7 +601,7 @@ export class NodesCryptoService {
582
601
  };
583
602
  }
584
603
 
585
- async moveNode(
604
+ async encryptNodeWithNewParent(
586
605
  node: Pick<DecryptedNode, 'name'>,
587
606
  keys: { passphrase: string; passphraseSessionKey: SessionKey; nameSessionKey: SessionKey },
588
607
  parentKeys: { key: PrivateKey; hashKey: Uint8Array },
@@ -626,118 +645,6 @@ export class NodesCryptoService {
626
645
  nameSignatureEmail: email,
627
646
  };
628
647
  }
629
-
630
- private async handleClaimedAuthor(
631
- node: { uid: string; creationTime: Date },
632
- field: MetricVerificationErrorField,
633
- signatureType: string,
634
- verified: VERIFICATION_STATUS,
635
- verificationErrors?: Error[],
636
- claimedAuthor?: string,
637
- notAvailableVerificationKeys = false,
638
- ): Promise<Author> {
639
- const author = handleClaimedAuthor(
640
- signatureType,
641
- verified,
642
- verificationErrors,
643
- claimedAuthor,
644
- notAvailableVerificationKeys,
645
- );
646
- if (!author.ok) {
647
- void this.reportVerificationError(node, field, verificationErrors, claimedAuthor);
648
- }
649
- return author;
650
- }
651
-
652
- private async reportVerificationError(
653
- node: { uid: string; creationTime: Date },
654
- field: MetricVerificationErrorField,
655
- verificationErrors?: Error[],
656
- claimedAuthor?: string,
657
- ) {
658
- if (this.reportedVerificationErrors.has(node.uid)) {
659
- return;
660
- }
661
- this.reportedVerificationErrors.add(node.uid);
662
-
663
- const fromBefore2024 = node.creationTime < new Date('2024-01-01');
664
-
665
- let addressMatchingDefaultShare, volumeType;
666
- try {
667
- const { volumeId } = splitNodeUid(node.uid);
668
- const { email } = await this.shareService.getMyFilesShareMemberEmailKey();
669
- addressMatchingDefaultShare = claimedAuthor ? claimedAuthor === email : undefined;
670
- volumeType = await this.shareService.getVolumeMetricContext(volumeId);
671
- } catch (error: unknown) {
672
- this.logger.error('Failed to check if claimed author matches default share', error);
673
- }
674
-
675
- this.logger.warn(
676
- `Failed to verify ${field} for node ${node.uid} (from before 2024: ${fromBefore2024}, matching address: ${addressMatchingDefaultShare})`,
677
- );
678
-
679
- this.telemetry.recordMetric({
680
- eventName: 'verificationError',
681
- volumeType,
682
- field,
683
- addressMatchingDefaultShare,
684
- fromBefore2024,
685
- error: verificationErrors?.map((e) => e.message).join(', '),
686
- uid: node.uid,
687
- });
688
- }
689
-
690
- private async reportDecryptionError(node: EncryptedNode, field: MetricsDecryptionErrorField, error: unknown) {
691
- if (this.reportedDecryptionErrors.has(node.uid)) {
692
- return;
693
- }
694
-
695
- const fromBefore2024 = node.creationTime < new Date('2024-01-01');
696
-
697
- let volumeType;
698
- try {
699
- const { volumeId } = splitNodeUid(node.uid);
700
- volumeType = await this.shareService.getVolumeMetricContext(volumeId);
701
- } catch (error: unknown) {
702
- this.logger.error('Failed to get metric context', error);
703
- }
704
-
705
- this.logger.error(`Failed to decrypt node ${node.uid} (from before 2024: ${fromBefore2024})`, error);
706
-
707
- this.telemetry.recordMetric({
708
- eventName: 'decryptionError',
709
- volumeType,
710
- field,
711
- fromBefore2024,
712
- error,
713
- uid: node.uid,
714
- });
715
- this.reportedDecryptionErrors.add(node.uid);
716
- }
717
- }
718
-
719
- /**
720
- * @param signatureType - Must be translated before calling this function.
721
- */
722
- function handleClaimedAuthor(
723
- signatureType: string,
724
- verified: VERIFICATION_STATUS,
725
- verificationErrors?: Error[],
726
- claimedAuthor?: string,
727
- notAvailableVerificationKeys = false,
728
- ): Author {
729
- if (!claimedAuthor && notAvailableVerificationKeys) {
730
- return resultOk(null as AnonymousUser);
731
- }
732
-
733
- if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
734
- return resultOk(claimedAuthor || (null as AnonymousUser));
735
- }
736
-
737
- return resultError({
738
- claimedAuthor: claimedAuthor,
739
- error: getVerificationMessage(verified, verificationErrors, signatureType, notAvailableVerificationKeys),
740
- });
741
648
  }
742
649
 
743
650
  function getClaimedAuthor(
@@ -0,0 +1,5 @@
1
+ import { ValidationError } from "../../errors";
2
+
3
+ export class NodeOutOfSyncError extends ValidationError {
4
+ name = 'NodeOutOfSyncError';
5
+ }
@@ -52,7 +52,7 @@ describe('nodesModules integration tests', () => {
52
52
  driveCrypto = {};
53
53
  // @ts-expect-error No need to implement all methods for mocking
54
54
  sharesService = {
55
- getMyFilesIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
55
+ getOwnVolumeIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
56
56
  };
57
57
 
58
58
  nodesModule = initNodesModule(
@@ -10,6 +10,7 @@ import { NodeAPIService } from './apiService';
10
10
  import { NodesCache } from './cache';
11
11
  import { NodesCryptoCache } from './cryptoCache';
12
12
  import { NodesCryptoService } from './cryptoService';
13
+ import { NodesCryptoReporter } from './cryptoReporter';
13
14
  import { SharesService } from './interface';
14
15
  import { NodesAccess } from './nodesAccess';
15
16
  import { NodesManagement } from './nodesManagement';
@@ -40,7 +41,8 @@ export function initNodesModule(
40
41
  const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService);
41
42
  const cache = new NodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
42
43
  const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
43
- const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, sharesService);
44
+ const cryptoReporter = new NodesCryptoReporter(telemetry, sharesService);
45
+ const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, cryptoReporter);
44
46
  const nodesAccess = new NodesAccess(
45
47
  telemetry.getLogger('nodes'),
46
48
  api,
@@ -12,6 +12,10 @@ import {
12
12
  RevisionState,
13
13
  } from '../../interface';
14
14
 
15
+ export type FilterOptions = {
16
+ type?: NodeType;
17
+ };
18
+
15
19
  /**
16
20
  * Internal common node interface for both encrypted or decrypted node.
17
21
  */
@@ -56,7 +60,7 @@ export interface EncryptedNodeCrypto {
56
60
  nameSignatureEmail?: string;
57
61
  armoredKey: string;
58
62
  armoredNodePassphrase: string;
59
- armoredNodePassphraseSignature: string;
63
+ armoredNodePassphraseSignature?: string;
60
64
  membership?: {
61
65
  inviterEmail: string;
62
66
  base64MemberSharePassphraseKeyPacket: string;
@@ -172,7 +176,7 @@ export interface DecryptedRevision extends Revision {
172
176
  * Interface describing the dependencies to the shares module.
173
177
  */
174
178
  export interface SharesService {
175
- getMyFilesIDs(): Promise<{ volumeId: string; rootNodeId: string }>;
179
+ getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string }>;
176
180
  getSharePrivateKey(shareId: string): Promise<PrivateKey>;
177
181
  getMyFilesShareMemberEmailKey(): Promise<{
178
182
  email: string;
@@ -46,7 +46,7 @@ describe('nodesAccess', () => {
46
46
  };
47
47
  // @ts-expect-error No need to implement all methods for mocking
48
48
  shareService = {
49
- getMyFilesIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
49
+ getOwnVolumeIDs: jest.fn().mockResolvedValue({ volumeId: 'volumeId' }),
50
50
  getSharePrivateKey: jest.fn(),
51
51
  };
52
52
 
@@ -209,7 +209,12 @@ describe('nodesAccess', () => {
209
209
 
210
210
  const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
211
211
  expect(result).toMatchObject([node1, node4, node2, node3]);
212
- expect(apiService.iterateNodes).toHaveBeenCalledWith([node2.uid, node3.uid], 'volumeId', undefined);
212
+ expect(apiService.iterateNodes).toHaveBeenCalledWith(
213
+ [node2.uid, node3.uid],
214
+ 'volumeId',
215
+ undefined, // filterOptions
216
+ undefined, // signal
217
+ );
213
218
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(2);
214
219
  expect(cache.setNode).toHaveBeenCalledTimes(2);
215
220
  expect(cryptoCache.setNodeKeys).toHaveBeenCalledTimes(2);
@@ -226,7 +231,11 @@ describe('nodesAccess', () => {
226
231
 
227
232
  const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
228
233
  expect(result).toMatchObject([node1, node2, node3, node4]);
229
- expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
234
+ expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith(
235
+ 'volumeId~parentNodeid',
236
+ false, // onlyFolders
237
+ undefined, // signal
238
+ );
230
239
  expect(apiService.iterateNodes).not.toHaveBeenCalled();
231
240
  expect(cache.setFolderChildrenLoaded).toHaveBeenCalledWith('volumeId~parentNodeid');
232
241
  });
@@ -247,11 +256,16 @@ describe('nodesAccess', () => {
247
256
 
248
257
  const result = await Array.fromAsync(access.iterateFolderChildren('volumeId~parentNodeid'));
249
258
  expect(result).toMatchObject([node1, node2, node3, node4]);
250
- expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith('volumeId~parentNodeid', undefined);
259
+ expect(apiService.iterateChildrenNodeUids).toHaveBeenCalledWith(
260
+ 'volumeId~parentNodeid',
261
+ false, // onlyFolders
262
+ undefined, // signal
263
+ );
251
264
  expect(apiService.iterateNodes).toHaveBeenCalledWith(
252
265
  ['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'],
253
266
  'volumeId',
254
- undefined,
267
+ undefined, // filterOptions
268
+ undefined, // signal
255
269
  );
256
270
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
257
271
  expect(cache.setNode).toHaveBeenCalledTimes(4);
@@ -320,6 +334,50 @@ describe('nodesAccess', () => {
320
334
  expect(error.cause).toEqual([new DecryptionError('Decryption failed')]);
321
335
  }
322
336
  });
337
+
338
+ it('should return only filtered nodes from cache', async () => {
339
+ cache.isFolderChildrenLoaded = jest.fn().mockResolvedValue(true);
340
+ cache.iterateChildren = jest.fn().mockImplementation(async function* () {
341
+ yield { ok: true, node: { ...node1, type: NodeType.Folder } };
342
+ yield { ok: true, node: { ...node2, type: NodeType.Folder } };
343
+ yield { ok: true, node: { ...node3, type: NodeType.File } };
344
+ yield { ok: true, node: { ...node4, type: NodeType.File } };
345
+ });
346
+
347
+ const result = await Array.fromAsync(
348
+ access.iterateFolderChildren('volumeId~parentNodeid', { type: NodeType.Folder }),
349
+ );
350
+
351
+ expect(result).toMatchObject([node1, node2]);
352
+ expect(cache.setFolderChildrenLoaded).not.toHaveBeenCalled();
353
+ });
354
+
355
+ it.only('should return only filtered nodes from API', async () => {
356
+ cache.isFolderChildrenLoaded = jest.fn().mockResolvedValue(false);
357
+ cache.getNode = jest.fn().mockImplementation((uid: string) => {
358
+ if (uid === parentNode.uid) {
359
+ return parentNode;
360
+ }
361
+ throw new Error('Entity not found');
362
+ });
363
+ apiService.iterateChildrenNodeUids = jest.fn().mockImplementation(async function* () {
364
+ yield 'volumeId~node1';
365
+ yield 'volumeId~node2';
366
+ yield 'volumeId~node3';
367
+ yield 'volumeId~node4';
368
+ });
369
+ apiService.iterateNodes = jest.fn().mockImplementation(async function* () {
370
+ yield { ...node1, parentUid: 'volumeId~parentNodeId', type: NodeType.Folder };
371
+ yield { ...node2, parentUid: 'volumeId~parentNodeId', type: NodeType.Folder };
372
+ });
373
+
374
+ const result = await Array.fromAsync(
375
+ access.iterateFolderChildren('volumeId~parentNodeid', { type: NodeType.Folder }),
376
+ );
377
+
378
+ expect(result).toMatchObject([node1, node2]);
379
+ expect(cache.setFolderChildrenLoaded).not.toHaveBeenCalled();
380
+ });
323
381
  });
324
382
 
325
383
  describe('iterateTrashedNodes', () => {
@@ -330,7 +388,7 @@ describe('nodesAccess', () => {
330
388
  const node4 = { uid: 'volumeId~node4', isStale: false } as DecryptedNode;
331
389
 
332
390
  beforeEach(() => {
333
- shareService.getMyFilesIDs = jest.fn().mockResolvedValue({ volumeId });
391
+ shareService.getOwnVolumeIDs = jest.fn().mockResolvedValue({ volumeId });
334
392
  apiService.iterateTrashedNodeUids = jest.fn().mockImplementation(async function* () {
335
393
  yield node1.uid;
336
394
  yield node2.uid;
@@ -359,7 +417,8 @@ describe('nodesAccess', () => {
359
417
  expect(apiService.iterateNodes).toHaveBeenCalledWith(
360
418
  ['volumeId~node1', 'volumeId~node2', 'volumeId~node3', 'volumeId~node4'],
361
419
  volumeId,
362
- undefined,
420
+ undefined, // filterOptions
421
+ undefined, // signal
363
422
  );
364
423
  expect(cryptoService.decryptNode).toHaveBeenCalledTimes(4);
365
424
  expect(cache.setNode).toHaveBeenCalledTimes(4);
@@ -417,7 +476,8 @@ describe('nodesAccess', () => {
417
476
  expect(apiService.iterateNodes).toHaveBeenCalledWith(
418
477
  ['volumeId~node2', 'volumeId~node3'],
419
478
  'volumeId',
420
- undefined,
479
+ undefined, // filterOptions
480
+ undefined, // signal
421
481
  );
422
482
  });
423
483