@protontech/drive-sdk 0.6.2 → 0.7.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 (99) hide show
  1. package/dist/internal/apiService/apiService.d.ts +2 -2
  2. package/dist/internal/apiService/apiService.js.map +1 -1
  3. package/dist/internal/apiService/errors.js +4 -3
  4. package/dist/internal/apiService/errors.js.map +1 -1
  5. package/dist/internal/download/cryptoService.js +8 -6
  6. package/dist/internal/download/cryptoService.js.map +1 -1
  7. package/dist/internal/download/fileDownloader.d.ts +2 -1
  8. package/dist/internal/download/fileDownloader.js +6 -3
  9. package/dist/internal/download/fileDownloader.js.map +1 -1
  10. package/dist/internal/download/index.d.ts +1 -1
  11. package/dist/internal/download/index.js +3 -3
  12. package/dist/internal/download/index.js.map +1 -1
  13. package/dist/internal/nodes/apiService.d.ts +9 -8
  14. package/dist/internal/nodes/apiService.js +14 -5
  15. package/dist/internal/nodes/apiService.js.map +1 -1
  16. package/dist/internal/nodes/apiService.test.js +5 -5
  17. package/dist/internal/nodes/apiService.test.js.map +1 -1
  18. package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
  19. package/dist/internal/nodes/cryptoReporter.js.map +1 -1
  20. package/dist/internal/nodes/cryptoService.d.ts +12 -21
  21. package/dist/internal/nodes/cryptoService.js +45 -14
  22. package/dist/internal/nodes/cryptoService.js.map +1 -1
  23. package/dist/internal/nodes/cryptoService.test.js +262 -17
  24. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  25. package/dist/internal/nodes/interface.d.ts +13 -3
  26. package/dist/internal/nodes/nodesAccess.d.ts +8 -1
  27. package/dist/internal/nodes/nodesAccess.js +13 -0
  28. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  29. package/dist/internal/nodes/nodesManagement.d.ts +4 -4
  30. package/dist/internal/nodes/nodesManagement.js +16 -20
  31. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  32. package/dist/internal/nodes/nodesManagement.test.js +21 -10
  33. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  34. package/dist/internal/photos/upload.d.ts +2 -2
  35. package/dist/internal/photos/upload.js.map +1 -1
  36. package/dist/internal/sharingPublic/index.d.ts +6 -6
  37. package/dist/internal/sharingPublic/index.js +8 -7
  38. package/dist/internal/sharingPublic/index.js.map +1 -1
  39. package/dist/internal/sharingPublic/nodes.d.ts +16 -3
  40. package/dist/internal/sharingPublic/nodes.js +34 -2
  41. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  42. package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
  43. package/dist/internal/sharingPublic/unauthApiService.js +31 -0
  44. package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
  45. package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
  46. package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
  47. package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
  48. package/dist/internal/upload/apiService.d.ts +4 -3
  49. package/dist/internal/upload/apiService.js.map +1 -1
  50. package/dist/internal/upload/cryptoService.d.ts +8 -3
  51. package/dist/internal/upload/cryptoService.js +45 -9
  52. package/dist/internal/upload/cryptoService.js.map +1 -1
  53. package/dist/internal/upload/fileUploader.test.js +1 -1
  54. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  55. package/dist/internal/upload/interface.d.ts +25 -13
  56. package/dist/internal/upload/manager.js +7 -4
  57. package/dist/internal/upload/manager.js.map +1 -1
  58. package/dist/internal/upload/manager.test.js +5 -4
  59. package/dist/internal/upload/manager.test.js.map +1 -1
  60. package/dist/internal/upload/streamUploader.js +9 -4
  61. package/dist/internal/upload/streamUploader.js.map +1 -1
  62. package/dist/internal/upload/streamUploader.test.js +8 -5
  63. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  64. package/dist/protonDriveClient.d.ts +1 -1
  65. package/dist/protonDriveClient.js +2 -1
  66. package/dist/protonDriveClient.js.map +1 -1
  67. package/dist/protonDrivePublicLinkClient.d.ts +2 -1
  68. package/dist/protonDrivePublicLinkClient.js +7 -5
  69. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/internal/apiService/apiService.ts +2 -2
  72. package/src/internal/apiService/errors.ts +5 -4
  73. package/src/internal/download/cryptoService.ts +13 -6
  74. package/src/internal/download/fileDownloader.ts +4 -2
  75. package/src/internal/download/index.ts +3 -0
  76. package/src/internal/nodes/apiService.test.ts +5 -5
  77. package/src/internal/nodes/apiService.ts +23 -10
  78. package/src/internal/nodes/cryptoReporter.ts +3 -3
  79. package/src/internal/nodes/cryptoService.test.ts +370 -18
  80. package/src/internal/nodes/cryptoService.ts +73 -32
  81. package/src/internal/nodes/interface.ts +16 -2
  82. package/src/internal/nodes/nodesAccess.ts +17 -0
  83. package/src/internal/nodes/nodesManagement.test.ts +21 -10
  84. package/src/internal/nodes/nodesManagement.ts +20 -24
  85. package/src/internal/photos/upload.ts +3 -3
  86. package/src/internal/sharingPublic/index.ts +7 -3
  87. package/src/internal/sharingPublic/nodes.ts +43 -2
  88. package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
  89. package/src/internal/sharingPublic/unauthApiService.ts +32 -0
  90. package/src/internal/upload/apiService.ts +4 -3
  91. package/src/internal/upload/cryptoService.ts +73 -12
  92. package/src/internal/upload/fileUploader.test.ts +1 -1
  93. package/src/internal/upload/interface.ts +24 -13
  94. package/src/internal/upload/manager.test.ts +5 -4
  95. package/src/internal/upload/manager.ts +7 -4
  96. package/src/internal/upload/streamUploader.test.ts +8 -5
  97. package/src/internal/upload/streamUploader.ts +10 -4
  98. package/src/protonDriveClient.ts +7 -2
  99. package/src/protonDrivePublicLinkClient.ts +8 -3
@@ -21,6 +21,7 @@ export function initDownloadModule(
21
21
  sharesService: SharesService,
22
22
  nodesService: NodesService,
23
23
  revisionsService: RevisionsService,
24
+ ignoreManifestVerification = false,
24
25
  ) {
25
26
  const queue = new DownloadQueue();
26
27
  const api = new DownloadAPIService(apiService);
@@ -63,6 +64,7 @@ export function initDownloadModule(
63
64
  node.activeRevision.value,
64
65
  signal,
65
66
  onFinish,
67
+ ignoreManifestVerification,
66
68
  );
67
69
  }
68
70
 
@@ -102,6 +104,7 @@ export function initDownloadModule(
102
104
  revision,
103
105
  signal,
104
106
  onFinish,
107
+ ignoreManifestVerification,
105
108
  );
106
109
  }
107
110
 
@@ -577,8 +577,8 @@ describe('nodeAPIService', () => {
577
577
  });
578
578
  });
579
579
 
580
- describe('deleteNodes', () => {
581
- it('should delete nodes', async () => {
580
+ describe('deleteTrashedNodes', () => {
581
+ it('should delete trashed nodes', async () => {
582
582
  // @ts-expect-error Mocking for testing purposes
583
583
  apiMock.post = jest.fn(async () =>
584
584
  Promise.resolve({
@@ -600,14 +600,14 @@ describe('nodeAPIService', () => {
600
600
  }),
601
601
  );
602
602
 
603
- const result = await Array.fromAsync(api.deleteNodes(['volumeId~nodeId1', 'volumeId~nodeId2']));
603
+ const result = await Array.fromAsync(api.deleteTrashedNodes(['volumeId~nodeId1', 'volumeId~nodeId2']));
604
604
  expect(result).toEqual([
605
605
  { uid: 'volumeId~nodeId1', ok: true },
606
606
  { uid: 'volumeId~nodeId2', ok: false, error: 'INSUFFICIENT_SCOPE' },
607
607
  ]);
608
608
  });
609
609
 
610
- it('should delete nodes from multiple volumes', async () => {
610
+ it('should delete trashed nodes from multiple volumes', async () => {
611
611
  // @ts-expect-error Mocking for testing purposes
612
612
  apiMock.post = jest.fn(async (_, { LinkIDs }) =>
613
613
  Promise.resolve({
@@ -620,7 +620,7 @@ describe('nodeAPIService', () => {
620
620
  }),
621
621
  );
622
622
 
623
- const result = await Array.fromAsync(api.deleteNodes(['volumeId1~nodeId1', 'volumeId2~nodeId2']));
623
+ const result = await Array.fromAsync(api.deleteTrashedNodes(['volumeId1~nodeId1', 'volumeId2~nodeId2']));
624
624
  expect(result).toEqual([
625
625
  { uid: 'volumeId1~nodeId1', ok: true },
626
626
  { uid: 'volumeId2~nodeId2', ok: true },
@@ -1,8 +1,7 @@
1
1
  import { c } from 'ttag';
2
2
 
3
3
  import { NodeWithSameNameExistsValidationError, ProtonDriveError, ValidationError } from '../../errors';
4
- import { Logger, NodeResult } from '../../interface';
5
- import { MemberRole, RevisionState } from '../../interface/nodes';
4
+ import { Logger, NodeResult, MemberRole, RevisionState, AnonymousUser } from '../../interface';
6
5
  import {
7
6
  DriveAPIService,
8
7
  drivePaths,
@@ -102,7 +101,6 @@ type PostRestoreRevisionResponse =
102
101
  type DeleteRevisionResponse =
103
102
  drivePaths['/drive/v2/volumes/{volumeID}/files/{linkID}/revisions/{revisionID}']['delete']['responses']['200']['content']['application/json'];
104
103
 
105
-
106
104
  type PostCheckAvailableHashesRequest = Extract<
107
105
  drivePaths['/drive/v2/volumes/{volumeID}/links/{linkID}/checkAvailableHashes']['post']['requestBody'],
108
106
  { content: object }
@@ -292,7 +290,7 @@ export class NodeAPIService {
292
290
  },
293
291
  newNode: {
294
292
  encryptedName: string;
295
- nameSignatureEmail: string;
293
+ nameSignatureEmail: string | AnonymousUser;
296
294
  hash?: string;
297
295
  },
298
296
  signal?: AbortSignal,
@@ -332,9 +330,9 @@ export class NodeAPIService {
332
330
  parentUid: string;
333
331
  armoredNodePassphrase: string;
334
332
  armoredNodePassphraseSignature?: string;
335
- signatureEmail?: string;
333
+ signatureEmail?: string | AnonymousUser;
336
334
  encryptedName: string;
337
- nameSignatureEmail?: string;
335
+ nameSignatureEmail?: string | AnonymousUser;
338
336
  hash: string;
339
337
  contentHash?: string;
340
338
  },
@@ -369,9 +367,9 @@ export class NodeAPIService {
369
367
  parentUid: string;
370
368
  armoredNodePassphrase: string;
371
369
  armoredNodePassphraseSignature?: string;
372
- signatureEmail?: string;
370
+ signatureEmail?: string | AnonymousUser;
373
371
  encryptedName: string;
374
- nameSignatureEmail?: string;
372
+ nameSignatureEmail?: string | AnonymousUser;
375
373
  hash: string;
376
374
  },
377
375
  signal?: AbortSignal,
@@ -430,7 +428,7 @@ export class NodeAPIService {
430
428
  }
431
429
  }
432
430
 
433
- async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
431
+ async *deleteTrashedNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
434
432
  for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
435
433
  const response = await this.apiService.post<PostDeleteNodesRequest, PostDeleteNodesResponse>(
436
434
  `drive/v2/volumes/${volumeId}/trash/delete_multiple`,
@@ -445,6 +443,21 @@ export class NodeAPIService {
445
443
  }
446
444
  }
447
445
 
446
+ async *deleteExistingNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
447
+ for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
448
+ const response = await this.apiService.post<PostDeleteNodesRequest, PostDeleteNodesResponse>(
449
+ `drive/v2/volumes/${volumeId}/delete_multiple`,
450
+ {
451
+ LinkIDs: batchNodeIds,
452
+ },
453
+ signal,
454
+ );
455
+
456
+ // TODO: remove `as` when backend fixes OpenAPI schema.
457
+ yield* handleResponseErrors(batchNodeUids, volumeId, response.Responses as LinkResponse[]);
458
+ }
459
+ }
460
+
448
461
  async createFolder(
449
462
  parentUid: string,
450
463
  newNode: {
@@ -452,7 +465,7 @@ export class NodeAPIService {
452
465
  armoredHashKey: string;
453
466
  armoredNodePassphrase: string;
454
467
  armoredNodePassphraseSignature: string;
455
- signatureEmail: string;
468
+ signatureEmail: string | AnonymousUser;
456
469
  encryptedName: string;
457
470
  hash: string;
458
471
  armoredExtendedAttributes?: string;
@@ -34,7 +34,7 @@ export class NodesCryptoReporter {
34
34
  signatureType: string,
35
35
  verified: VERIFICATION_STATUS,
36
36
  verificationErrors?: Error[],
37
- claimedAuthor?: string,
37
+ claimedAuthor?: string | AnonymousUser,
38
38
  notAvailableVerificationKeys = false,
39
39
  ): Promise<Author> {
40
40
  const author = handleClaimedAuthor(
@@ -54,7 +54,7 @@ export class NodesCryptoReporter {
54
54
  node: { uid: string; creationTime: Date },
55
55
  field: MetricVerificationErrorField,
56
56
  verificationErrors?: Error[],
57
- claimedAuthor?: string,
57
+ claimedAuthor?: string | AnonymousUser,
58
58
  ) {
59
59
  if (this.reportedVerificationErrors.has(node.uid)) {
60
60
  return;
@@ -128,7 +128,7 @@ function handleClaimedAuthor(
128
128
  signatureType: string,
129
129
  verified: VERIFICATION_STATUS,
130
130
  verificationErrors?: Error[],
131
- claimedAuthor?: string,
131
+ claimedAuthor?: string | AnonymousUser,
132
132
  notAvailableVerificationKeys = false,
133
133
  ): Author {
134
134
  if (!claimedAuthor && notAvailableVerificationKeys) {
@@ -1,7 +1,14 @@
1
1
  import { DriveCrypto, PrivateKey, SessionKey, VERIFICATION_STATUS } from '../../crypto';
2
2
  import { MemberRole, ProtonDriveAccount, ProtonDriveTelemetry, RevisionState } from '../../interface';
3
3
  import { getMockTelemetry } from '../../tests/telemetry';
4
- import { DecryptedNode, DecryptedNodeKeys, DecryptedUnparsedNode, EncryptedNode, SharesService } from './interface';
4
+ import {
5
+ DecryptedNode,
6
+ DecryptedNodeKeys,
7
+ DecryptedUnparsedNode,
8
+ EncryptedNode,
9
+ NodeSigningKeys,
10
+ SharesService,
11
+ } from './interface';
5
12
  import { NodesCryptoService } from './cryptoService';
6
13
  import { NodesCryptoReporter } from './cryptoReporter';
7
14
 
@@ -250,6 +257,48 @@ describe('nodesCryptoService', () => {
250
257
  });
251
258
  });
252
259
 
260
+ it('on older node name ignores NOT_SIGNED', async () => {
261
+ encryptedNode.creationTime = new Date('2020-12-31');
262
+ driveCrypto.decryptNodeName = jest.fn(async () =>
263
+ Promise.resolve({
264
+ name: 'name',
265
+ verified: VERIFICATION_STATUS.NOT_SIGNED,
266
+ verificationErrors: [new Error('missing signature')],
267
+ }),
268
+ );
269
+
270
+ const result = await cryptoService.decryptNode(encryptedNode, parentKey);
271
+ verifyResult(result, {
272
+ nameAuthor: {
273
+ ok: true,
274
+ value: 'nameSignatureEmail',
275
+ },
276
+ });
277
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
278
+ });
279
+
280
+ it('on newer node name does not ignore NOT_SIGNED', async () => {
281
+ encryptedNode.creationTime = new Date('2021-01-01');
282
+ driveCrypto.decryptNodeName = jest.fn(async () =>
283
+ Promise.resolve({
284
+ name: 'name',
285
+ verified: VERIFICATION_STATUS.NOT_SIGNED,
286
+ verificationErrors: [new Error('missing signature')],
287
+ }),
288
+ );
289
+
290
+ const result = await cryptoService.decryptNode(encryptedNode, parentKey);
291
+ verifyResult(result, {
292
+ nameAuthor: {
293
+ ok: false,
294
+ error: {
295
+ claimedAuthor: 'nameSignatureEmail',
296
+ error: 'Missing signature for name',
297
+ },
298
+ },
299
+ });
300
+ });
301
+
253
302
  it('on hash key', async () => {
254
303
  driveCrypto.decryptNodeHashKey = jest.fn(async () =>
255
304
  Promise.resolve({
@@ -275,6 +324,48 @@ describe('nodesCryptoService', () => {
275
324
  });
276
325
  });
277
326
 
327
+ it('on older node hash key ignores NOT_SIGNED', async () => {
328
+ encryptedNode.creationTime = new Date('2021-07-31');
329
+ driveCrypto.decryptNodeHashKey = jest.fn(async () =>
330
+ Promise.resolve({
331
+ hashKey: new Uint8Array(),
332
+ verified: VERIFICATION_STATUS.NOT_SIGNED,
333
+ verificationErrors: [new Error('missing signature')],
334
+ }),
335
+ );
336
+
337
+ const result = await cryptoService.decryptNode(encryptedNode, parentKey);
338
+ verifyResult(result, {
339
+ keyAuthor: {
340
+ ok: true,
341
+ value: 'signatureEmail',
342
+ },
343
+ });
344
+ expect(telemetry.recordMetric).not.toHaveBeenCalled();
345
+ });
346
+
347
+ it('on newer node hash key does not ignore NOT_SIGNED', async () => {
348
+ encryptedNode.creationTime = new Date('2021-08-01');
349
+ driveCrypto.decryptNodeHashKey = jest.fn(async () =>
350
+ Promise.resolve({
351
+ hashKey: new Uint8Array(),
352
+ verified: VERIFICATION_STATUS.NOT_SIGNED,
353
+ verificationErrors: [new Error('missing signature')],
354
+ }),
355
+ );
356
+
357
+ const result = await cryptoService.decryptNode(encryptedNode, parentKey);
358
+ verifyResult(result, {
359
+ keyAuthor: {
360
+ ok: false,
361
+ error: {
362
+ claimedAuthor: 'signatureEmail',
363
+ error: 'Missing signature for hash key',
364
+ },
365
+ },
366
+ });
367
+ });
368
+
278
369
  it('on node key and hash key reports error from node key', async () => {
279
370
  driveCrypto.decryptKey = jest.fn(async () =>
280
371
  Promise.resolve({
@@ -985,24 +1076,239 @@ describe('nodesCryptoService', () => {
985
1076
  });
986
1077
  });
987
1078
 
1079
+ describe('createFolder', () => {
1080
+ let parentKeys: any;
1081
+
1082
+ beforeEach(() => {
1083
+ parentKeys = {
1084
+ key: 'parentKey' as any,
1085
+ hashKey: new Uint8Array([1, 2, 3]),
1086
+ };
1087
+ driveCrypto.generateKey = jest.fn().mockResolvedValue({
1088
+ encrypted: {
1089
+ armoredKey: 'encryptedNodeKey',
1090
+ armoredPassphrase: 'encryptedPassphrase',
1091
+ armoredPassphraseSignature: 'passphraseSignature',
1092
+ },
1093
+ decrypted: {
1094
+ key: 'nodeKey' as any,
1095
+ passphrase: 'nodePassphrase',
1096
+ passphraseSessionKey: 'passphraseSessionKey' as any,
1097
+ },
1098
+ });
1099
+ driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
1100
+ armoredNodeName: 'encryptedNodeName',
1101
+ });
1102
+ driveCrypto.generateLookupHash = jest.fn().mockResolvedValue('lookupHash');
1103
+ driveCrypto.generateHashKey = jest.fn().mockResolvedValue({
1104
+ armoredHashKey: 'encryptedHashKey',
1105
+ hashKey: new Uint8Array([4, 5, 6]),
1106
+ });
1107
+ driveCrypto.encryptExtendedAttributes = jest.fn().mockResolvedValue({
1108
+ armoredExtendedAttributes: 'encryptedAttributes',
1109
+ });
1110
+ });
1111
+
1112
+ it('should encrypt new folder with account key', async () => {
1113
+ const signingKeys: NodeSigningKeys = {
1114
+ type: 'userAddress',
1115
+ email: 'test@example.com',
1116
+ addressId: 'addressId',
1117
+ key: 'addressKey' as any,
1118
+ };
1119
+
1120
+ const result = await cryptoService.createFolder(
1121
+ parentKeys,
1122
+ signingKeys,
1123
+ 'New Folder',
1124
+ '{"modificationTime": 1234567890}',
1125
+ );
1126
+
1127
+ expect(result).toEqual({
1128
+ encryptedCrypto: {
1129
+ encryptedName: 'encryptedNodeName',
1130
+ hash: 'lookupHash',
1131
+ armoredKey: 'encryptedNodeKey',
1132
+ armoredNodePassphrase: 'encryptedPassphrase',
1133
+ armoredNodePassphraseSignature: 'passphraseSignature',
1134
+ folder: {
1135
+ armoredExtendedAttributes: 'encryptedAttributes',
1136
+ armoredHashKey: 'encryptedHashKey',
1137
+ },
1138
+ signatureEmail: 'test@example.com',
1139
+ nameSignatureEmail: 'test@example.com',
1140
+ },
1141
+ keys: {
1142
+ passphrase: 'nodePassphrase',
1143
+ key: 'nodeKey',
1144
+ passphraseSessionKey: 'passphraseSessionKey',
1145
+ hashKey: new Uint8Array([4, 5, 6]),
1146
+ },
1147
+ });
1148
+
1149
+ expect(driveCrypto.generateKey).toHaveBeenCalledWith([parentKeys.key], signingKeys.key);
1150
+ expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
1151
+ 'New Folder',
1152
+ undefined,
1153
+ parentKeys.key,
1154
+ signingKeys.key,
1155
+ );
1156
+ expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('New Folder', parentKeys.hashKey);
1157
+ expect(driveCrypto.generateHashKey).toHaveBeenCalledWith('nodeKey');
1158
+ expect(driveCrypto.encryptExtendedAttributes).toHaveBeenCalledWith(
1159
+ '{"modificationTime": 1234567890}',
1160
+ 'nodeKey',
1161
+ signingKeys.key,
1162
+ );
1163
+ });
1164
+
1165
+ it('should encrypt new folder with node key', async () => {
1166
+ const signingKeys: NodeSigningKeys = {
1167
+ type: 'nodeKey',
1168
+ nodeKey: 'nodeSigningKey' as any,
1169
+ parentNodeKey: 'parentNodeKey' as any,
1170
+ };
1171
+
1172
+ const result = await cryptoService.createFolder(
1173
+ parentKeys,
1174
+ signingKeys,
1175
+ 'New Folder',
1176
+ '{"modificationTime": 1234567890}',
1177
+ );
1178
+
1179
+ expect(result).toEqual({
1180
+ encryptedCrypto: {
1181
+ encryptedName: 'encryptedNodeName',
1182
+ hash: 'lookupHash',
1183
+ armoredKey: 'encryptedNodeKey',
1184
+ armoredNodePassphrase: 'encryptedPassphrase',
1185
+ armoredNodePassphraseSignature: 'passphraseSignature',
1186
+ folder: {
1187
+ armoredExtendedAttributes: 'encryptedAttributes',
1188
+ armoredHashKey: 'encryptedHashKey',
1189
+ },
1190
+ signatureEmail: null,
1191
+ nameSignatureEmail: null,
1192
+ },
1193
+ keys: {
1194
+ passphrase: 'nodePassphrase',
1195
+ key: 'nodeKey',
1196
+ passphraseSessionKey: 'passphraseSessionKey',
1197
+ hashKey: new Uint8Array([4, 5, 6]),
1198
+ },
1199
+ });
1200
+
1201
+ expect(driveCrypto.generateKey).toHaveBeenCalledWith([parentKeys.key], signingKeys.parentNodeKey);
1202
+ expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
1203
+ 'New Folder',
1204
+ undefined,
1205
+ parentKeys.key,
1206
+ signingKeys.parentNodeKey,
1207
+ );
1208
+ expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('New Folder', parentKeys.hashKey);
1209
+ expect(driveCrypto.generateHashKey).toHaveBeenCalledWith('nodeKey');
1210
+ expect(driveCrypto.encryptExtendedAttributes).toHaveBeenCalledWith(
1211
+ '{"modificationTime": 1234567890}',
1212
+ 'nodeKey',
1213
+ signingKeys.nodeKey,
1214
+ );
1215
+ });
1216
+ });
1217
+
1218
+ describe('encryptNewName', () => {
1219
+ let parentKeys: any;
1220
+ let nodeNameSessionKey: SessionKey;
1221
+
1222
+ beforeEach(() => {
1223
+ parentKeys = {
1224
+ key: 'parentKey' as any,
1225
+ hashKey: new Uint8Array([1, 2, 3]),
1226
+ };
1227
+ nodeNameSessionKey = 'nameSessionKey' as any;
1228
+ driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
1229
+ armoredNodeName: 'encryptedNewNodeName',
1230
+ });
1231
+ driveCrypto.generateLookupHash = jest.fn().mockResolvedValue('newHash');
1232
+ });
1233
+
1234
+ it('should encrypt new name with account key', async () => {
1235
+ const signingKeys: NodeSigningKeys = {
1236
+ type: 'userAddress',
1237
+ email: 'test@example.com',
1238
+ addressId: 'addressId',
1239
+ key: 'addressKey' as any,
1240
+ };
1241
+
1242
+ const result = await cryptoService.encryptNewName(
1243
+ parentKeys,
1244
+ nodeNameSessionKey,
1245
+ signingKeys,
1246
+ 'Renamed File.txt',
1247
+ );
1248
+
1249
+ expect(result).toEqual({
1250
+ signatureEmail: 'test@example.com',
1251
+ armoredNodeName: 'encryptedNewNodeName',
1252
+ hash: 'newHash',
1253
+ });
1254
+
1255
+ expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
1256
+ 'Renamed File.txt',
1257
+ nodeNameSessionKey,
1258
+ parentKeys.key,
1259
+ signingKeys.key,
1260
+ );
1261
+ expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('Renamed File.txt', parentKeys.hashKey);
1262
+ });
1263
+
1264
+ it('should encrypt new name with node key', async () => {
1265
+ const signingKeys: NodeSigningKeys = {
1266
+ type: 'nodeKey',
1267
+ nodeKey: 'nodeSigningKey' as any,
1268
+ parentNodeKey: 'parentNodeKey' as any,
1269
+ };
1270
+
1271
+ const result = await cryptoService.encryptNewName(
1272
+ parentKeys,
1273
+ nodeNameSessionKey,
1274
+ signingKeys,
1275
+ 'Renamed File.txt',
1276
+ );
1277
+
1278
+ expect(result).toEqual({
1279
+ signatureEmail: null,
1280
+ armoredNodeName: 'encryptedNewNodeName',
1281
+ hash: 'newHash',
1282
+ });
1283
+
1284
+ expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
1285
+ 'Renamed File.txt',
1286
+ nodeNameSessionKey,
1287
+ parentKeys.key,
1288
+ signingKeys.parentNodeKey,
1289
+ );
1290
+ expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('Renamed File.txt', parentKeys.hashKey);
1291
+ });
1292
+ });
1293
+
988
1294
  describe('encryptNodeWithNewParent', () => {
989
- it('should encrypt node data for move operation', async () => {
990
- const node = {
1295
+ let node: DecryptedNode;
1296
+ let keys: any;
1297
+ let parentKeys: any;
1298
+
1299
+ beforeEach(() => {
1300
+ node = {
991
1301
  name: { ok: true, value: 'testFile.txt' },
992
1302
  } as DecryptedNode;
993
- const keys = {
1303
+ keys = {
994
1304
  passphrase: 'nodePassphrase',
995
1305
  passphraseSessionKey: 'nodePassphraseSessionKey',
996
1306
  nameSessionKey: 'nameSessionKey' as any,
997
1307
  };
998
- const parentKeys = {
1308
+ parentKeys = {
999
1309
  key: 'newParentKey' as any,
1000
1310
  hashKey: new Uint8Array([1, 2, 3]),
1001
1311
  };
1002
- const address = {
1003
- email: 'test@example.com',
1004
- addressKey: 'addressKey' as any,
1005
- };
1006
1312
  driveCrypto.encryptNodeName = jest.fn().mockResolvedValue({
1007
1313
  armoredNodeName: 'encryptedNodeName',
1008
1314
  });
@@ -1011,8 +1317,17 @@ describe('nodesCryptoService', () => {
1011
1317
  armoredPassphrase: 'encryptedPassphrase',
1012
1318
  armoredPassphraseSignature: 'passphraseSignature',
1013
1319
  });
1320
+ });
1014
1321
 
1015
- const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address);
1322
+ it('should encrypt node data for move operation with account key (logged in context)', async () => {
1323
+ const signingKeys: NodeSigningKeys = {
1324
+ type: 'userAddress',
1325
+ email: 'test@example.com',
1326
+ addressId: 'addressId',
1327
+ key: 'addressKey' as any,
1328
+ };
1329
+
1330
+ const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys);
1016
1331
 
1017
1332
  expect(result).toEqual({
1018
1333
  encryptedName: 'encryptedNodeName',
@@ -1027,14 +1342,47 @@ describe('nodesCryptoService', () => {
1027
1342
  'testFile.txt',
1028
1343
  keys.nameSessionKey,
1029
1344
  parentKeys.key,
1030
- address.addressKey,
1345
+ signingKeys.key,
1346
+ );
1347
+ expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('testFile.txt', parentKeys.hashKey);
1348
+ expect(driveCrypto.encryptPassphrase).toHaveBeenCalledWith(
1349
+ keys.passphrase,
1350
+ keys.passphraseSessionKey,
1351
+ [parentKeys.key],
1352
+ signingKeys.key,
1353
+ );
1354
+ });
1355
+
1356
+ it('should encrypt node data for move operation with node key (anonymous context)', async () => {
1357
+ const signingKeys: NodeSigningKeys = {
1358
+ type: 'nodeKey',
1359
+ nodeKey: 'addressKey' as any,
1360
+ parentNodeKey: 'parentNodeKey' as any,
1361
+ };
1362
+
1363
+ const result = await cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys);
1364
+
1365
+ expect(result).toEqual({
1366
+ encryptedName: 'encryptedNodeName',
1367
+ hash: 'newHash',
1368
+ armoredNodePassphrase: 'encryptedPassphrase',
1369
+ armoredNodePassphraseSignature: 'passphraseSignature',
1370
+ signatureEmail: null,
1371
+ nameSignatureEmail: null,
1372
+ });
1373
+
1374
+ expect(driveCrypto.encryptNodeName).toHaveBeenCalledWith(
1375
+ 'testFile.txt',
1376
+ keys.nameSessionKey,
1377
+ parentKeys.key,
1378
+ signingKeys.nodeKey,
1031
1379
  );
1032
1380
  expect(driveCrypto.generateLookupHash).toHaveBeenCalledWith('testFile.txt', parentKeys.hashKey);
1033
1381
  expect(driveCrypto.encryptPassphrase).toHaveBeenCalledWith(
1034
1382
  keys.passphrase,
1035
1383
  keys.passphraseSessionKey,
1036
1384
  [parentKeys.key],
1037
- address.addressKey,
1385
+ signingKeys.nodeKey,
1038
1386
  );
1039
1387
  });
1040
1388
 
@@ -1051,13 +1399,15 @@ describe('nodesCryptoService', () => {
1051
1399
  key: 'newParentKey' as any,
1052
1400
  hashKey: undefined,
1053
1401
  } as any;
1054
- const address = {
1402
+ const signingKeys: NodeSigningKeys = {
1403
+ type: 'userAddress',
1055
1404
  email: 'test@example.com',
1056
- addressKey: 'addressKey' as any,
1405
+ addressId: 'addressId',
1406
+ key: 'addressKey' as any,
1057
1407
  };
1058
1408
 
1059
1409
  await expect(
1060
- cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
1410
+ cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys),
1061
1411
  ).rejects.toThrow('Moving item to a non-folder is not allowed');
1062
1412
  });
1063
1413
 
@@ -1074,13 +1424,15 @@ describe('nodesCryptoService', () => {
1074
1424
  key: 'newParentKey' as any,
1075
1425
  hashKey: new Uint8Array([1, 2, 3]),
1076
1426
  };
1077
- const address = {
1427
+ const signingKeys: NodeSigningKeys = {
1428
+ type: 'userAddress',
1078
1429
  email: 'test@example.com',
1079
- addressKey: 'addressKey' as any,
1430
+ addressId: 'addressId',
1431
+ key: 'addressKey' as any,
1080
1432
  };
1081
1433
 
1082
1434
  await expect(
1083
- cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, address),
1435
+ cryptoService.encryptNodeWithNewParent(node, keys as any, parentKeys, signingKeys),
1084
1436
  ).rejects.toThrow('Cannot move item without a valid name, please rename the item first');
1085
1437
  });
1086
1438
  });