@protontech/drive-sdk 0.3.0 → 0.3.2
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/dist/crypto/driveCrypto.d.ts +1 -1
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +6 -1
- package/dist/crypto/openPGPCrypto.d.ts +1 -1
- package/dist/crypto/openPGPCrypto.js +4 -1
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/diagnostic/httpClient.d.ts +3 -3
- package/dist/interface/httpClient.d.ts +5 -5
- package/dist/interface/index.d.ts +15 -5
- package/dist/internal/apiService/apiService.js +1 -1
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errorCodes.js.map +1 -1
- package/dist/internal/apiService/errors.d.ts +4 -3
- package/dist/internal/apiService/errors.js +7 -4
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +2 -1
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/download/cryptoService.js +2 -2
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.js +2 -2
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +3 -1
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/events/index.d.ts +1 -1
- package/dist/internal/nodes/cache.js +3 -1
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.js +6 -7
- package/dist/internal/nodes/cryptoCache.js.map +1 -1
- package/dist/internal/nodes/cryptoCache.test.js +4 -7
- package/dist/internal/nodes/cryptoCache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoReporter.d.ts +20 -0
- package/dist/internal/nodes/cryptoReporter.js +96 -0
- package/dist/internal/nodes/cryptoReporter.js.map +1 -0
- package/dist/internal/nodes/cryptoService.d.ts +17 -12
- package/dist/internal/nodes/cryptoService.js +17 -97
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +3 -1
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/index.js +3 -1
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +1 -1
- package/dist/internal/nodes/nodesAccess.d.ts +2 -2
- package/dist/internal/nodes/nodesAccess.js +52 -54
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/shares/cryptoCache.d.ts +4 -3
- package/dist/internal/shares/cryptoCache.js +23 -6
- package/dist/internal/shares/cryptoCache.js.map +1 -1
- package/dist/internal/shares/cryptoCache.test.js +3 -2
- package/dist/internal/shares/cryptoCache.test.js.map +1 -1
- package/dist/internal/shares/index.js +1 -1
- package/dist/internal/shares/index.js.map +1 -1
- package/dist/internal/sharing/cache.d.ts +3 -0
- package/dist/internal/sharing/cache.js +17 -2
- package/dist/internal/sharing/cache.js.map +1 -1
- package/dist/internal/sharing/cryptoService.js +8 -6
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.test.js +13 -0
- package/dist/internal/sharing/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/index.js +1 -1
- package/dist/internal/sharing/index.js.map +1 -1
- package/dist/internal/sharing/interface.d.ts +1 -1
- package/dist/internal/sharing/interface.js +1 -1
- package/dist/internal/sharing/sharingAccess.js +6 -0
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +242 -33
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +3 -1
- package/dist/internal/sharing/sharingManagement.js +10 -1
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.test.js +32 -1
- package/dist/internal/sharing/sharingManagement.test.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +19 -0
- package/dist/internal/sharingPublic/apiService.js +141 -0
- package/dist/internal/sharingPublic/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoCache.d.ts +19 -0
- package/dist/internal/sharingPublic/cryptoCache.js +72 -0
- package/dist/internal/sharingPublic/cryptoCache.js.map +1 -0
- package/dist/internal/sharingPublic/cryptoService.d.ts +9 -0
- package/dist/internal/sharingPublic/cryptoService.js +57 -0
- package/dist/internal/sharingPublic/cryptoService.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +15 -0
- package/dist/internal/sharingPublic/index.js +27 -0
- package/dist/internal/sharingPublic/index.js.map +1 -0
- package/dist/internal/sharingPublic/interface.d.ts +6 -0
- package/dist/internal/sharingPublic/interface.js +3 -0
- package/dist/internal/sharingPublic/interface.js.map +1 -0
- package/dist/internal/sharingPublic/manager.d.ts +19 -0
- package/dist/internal/sharingPublic/manager.js +81 -0
- package/dist/internal/sharingPublic/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +28 -0
- package/dist/internal/sharingPublic/session/apiService.js +55 -0
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -0
- package/dist/internal/sharingPublic/session/httpClient.d.ts +16 -0
- package/dist/internal/sharingPublic/session/httpClient.js +41 -0
- package/dist/internal/sharingPublic/session/httpClient.js.map +1 -0
- package/dist/internal/sharingPublic/session/index.d.ts +1 -0
- package/dist/internal/sharingPublic/session/index.js +6 -0
- package/dist/internal/sharingPublic/session/index.js.map +1 -0
- package/dist/internal/sharingPublic/session/interface.d.ts +18 -0
- package/dist/internal/sharingPublic/session/interface.js +3 -0
- package/dist/internal/sharingPublic/session/interface.js.map +1 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +49 -0
- package/dist/internal/sharingPublic/session/manager.js +75 -0
- package/dist/internal/sharingPublic/session/manager.js.map +1 -0
- package/dist/internal/sharingPublic/session/session.d.ts +34 -0
- package/dist/internal/sharingPublic/session/session.js +67 -0
- package/dist/internal/sharingPublic/session/session.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.d.ts +12 -0
- package/dist/internal/sharingPublic/session/url.js +23 -0
- package/dist/internal/sharingPublic/session/url.js.map +1 -0
- package/dist/internal/sharingPublic/session/url.test.d.ts +1 -0
- package/dist/internal/sharingPublic/session/url.test.js +59 -0
- package/dist/internal/sharingPublic/session/url.test.js.map +1 -0
- package/dist/internal/upload/streamUploader.js +1 -1
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +3 -1
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +18 -3
- package/dist/protonDriveClient.js +31 -8
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +57 -0
- package/dist/protonDrivePublicLinkClient.js +73 -0
- package/dist/protonDrivePublicLinkClient.js.map +1 -0
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +1 -1
- package/src/crypto/interface.ts +12 -1
- package/src/crypto/openPGPCrypto.ts +5 -2
- package/src/diagnostic/httpClient.ts +4 -4
- package/src/interface/httpClient.ts +5 -5
- package/src/interface/index.ts +18 -6
- package/src/internal/apiService/apiService.ts +1 -1
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.test.ts +2 -1
- package/src/internal/apiService/errors.ts +15 -4
- package/src/internal/download/cryptoService.ts +2 -2
- package/src/internal/download/fileDownloader.test.ts +3 -1
- package/src/internal/download/fileDownloader.ts +2 -2
- package/src/internal/events/index.ts +1 -1
- package/src/internal/nodes/cache.ts +3 -1
- package/src/internal/nodes/cryptoCache.test.ts +4 -7
- package/src/internal/nodes/cryptoCache.ts +6 -7
- package/src/internal/nodes/cryptoReporter.ts +145 -0
- package/src/internal/nodes/cryptoService.test.ts +3 -1
- package/src/internal/nodes/cryptoService.ts +44 -137
- package/src/internal/nodes/index.ts +3 -1
- package/src/internal/nodes/interface.ts +3 -1
- package/src/internal/nodes/nodesAccess.ts +59 -61
- package/src/internal/shares/cryptoCache.test.ts +3 -2
- package/src/internal/shares/cryptoCache.ts +26 -7
- package/src/internal/shares/index.ts +1 -1
- package/src/internal/sharing/cache.ts +19 -2
- package/src/internal/sharing/cryptoService.test.ts +22 -1
- package/src/internal/sharing/cryptoService.ts +8 -6
- package/src/internal/sharing/index.ts +1 -0
- package/src/internal/sharing/interface.ts +1 -1
- package/src/internal/sharing/sharingAccess.test.ts +282 -34
- package/src/internal/sharing/sharingAccess.ts +6 -0
- package/src/internal/sharing/sharingManagement.test.ts +33 -0
- package/src/internal/sharing/sharingManagement.ts +9 -0
- package/src/internal/sharingPublic/apiService.ts +173 -0
- package/src/internal/sharingPublic/cryptoCache.ts +79 -0
- package/src/internal/sharingPublic/cryptoService.ts +98 -0
- package/src/internal/sharingPublic/index.ts +41 -0
- package/src/internal/sharingPublic/interface.ts +14 -0
- package/src/internal/sharingPublic/manager.ts +86 -0
- package/src/internal/sharingPublic/session/apiService.ts +74 -0
- package/src/internal/sharingPublic/session/httpClient.ts +48 -0
- package/src/internal/sharingPublic/session/index.ts +1 -0
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +97 -0
- package/src/internal/sharingPublic/session/session.ts +78 -0
- package/src/internal/sharingPublic/session/url.test.ts +72 -0
- package/src/internal/sharingPublic/session/url.ts +23 -0
- package/src/internal/upload/streamUploader.test.ts +3 -1
- package/src/internal/upload/streamUploader.ts +1 -1
- package/src/protonDriveClient.ts +48 -11
- package/src/protonDrivePublicLinkClient.ts +135 -0
|
@@ -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
|
|
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
|
-
|
|
49
|
-
|
|
67
|
+
telemetry: ProtonDriveTelemetry,
|
|
68
|
+
protected driveCrypto: DriveCrypto,
|
|
50
69
|
private account: ProtonDriveAccount,
|
|
51
|
-
private
|
|
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.
|
|
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;
|
|
@@ -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(
|
|
@@ -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
|
|
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,
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
interface BaseNode {
|
|
19
19
|
// Internal metadata
|
|
20
20
|
hash?: string; // root node doesn't have any hash
|
|
21
|
+
// ecnryptedName should not be needed to keep, nameSessionKey should be enough.
|
|
22
|
+
// We will improve this in the future.
|
|
21
23
|
encryptedName: string;
|
|
22
24
|
|
|
23
25
|
// Basic node metadata
|
|
@@ -54,7 +56,7 @@ export interface EncryptedNodeCrypto {
|
|
|
54
56
|
nameSignatureEmail?: string;
|
|
55
57
|
armoredKey: string;
|
|
56
58
|
armoredNodePassphrase: string;
|
|
57
|
-
armoredNodePassphraseSignature
|
|
59
|
+
armoredNodePassphraseSignature?: string;
|
|
58
60
|
membership?: {
|
|
59
61
|
inviterEmail: string;
|
|
60
62
|
base64MemberSharePassphraseKeyPacket: string;
|
|
@@ -289,7 +289,7 @@ export class NodesAccess {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
const { node: unparsedNode, keys } = await this.cryptoService.decryptNode(encryptedNode, parentKey);
|
|
292
|
-
const node = await this.
|
|
292
|
+
const node = await parseNode(this.logger, unparsedNode);
|
|
293
293
|
try {
|
|
294
294
|
await this.cache.setNode(node);
|
|
295
295
|
} catch (error: unknown) {
|
|
@@ -305,65 +305,6 @@ export class NodesAccess {
|
|
|
305
305
|
return { node, keys };
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
private async parseNode(unparsedNode: DecryptedUnparsedNode): Promise<DecryptedNode> {
|
|
309
|
-
let nodeName: Result<string, Error | InvalidNameError> = unparsedNode.name;
|
|
310
|
-
if (unparsedNode.name.ok) {
|
|
311
|
-
try {
|
|
312
|
-
validateNodeName(unparsedNode.name.value);
|
|
313
|
-
} catch (error: unknown) {
|
|
314
|
-
this.logger.warn(`Node name validation failed: ${error instanceof Error ? error.message : error}`);
|
|
315
|
-
nodeName = resultError({
|
|
316
|
-
name: unparsedNode.name.value,
|
|
317
|
-
error: error instanceof Error ? error.message : c('Error').t`Unknown error`,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (unparsedNode.type === NodeType.File) {
|
|
323
|
-
const extendedAttributes = unparsedNode.activeRevision?.ok
|
|
324
|
-
? parseFileExtendedAttributes(
|
|
325
|
-
this.logger,
|
|
326
|
-
unparsedNode.activeRevision.value.creationTime,
|
|
327
|
-
unparsedNode.activeRevision.value.extendedAttributes,
|
|
328
|
-
)
|
|
329
|
-
: undefined;
|
|
330
|
-
|
|
331
|
-
return {
|
|
332
|
-
...unparsedNode,
|
|
333
|
-
isStale: false,
|
|
334
|
-
activeRevision: !unparsedNode.activeRevision?.ok
|
|
335
|
-
? unparsedNode.activeRevision
|
|
336
|
-
: resultOk({
|
|
337
|
-
uid: unparsedNode.activeRevision.value.uid,
|
|
338
|
-
state: unparsedNode.activeRevision.value.state,
|
|
339
|
-
creationTime: unparsedNode.activeRevision.value.creationTime,
|
|
340
|
-
storageSize: unparsedNode.activeRevision.value.storageSize,
|
|
341
|
-
contentAuthor: unparsedNode.activeRevision.value.contentAuthor,
|
|
342
|
-
thumbnails: unparsedNode.activeRevision.value.thumbnails,
|
|
343
|
-
...extendedAttributes,
|
|
344
|
-
}),
|
|
345
|
-
folder: undefined,
|
|
346
|
-
treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const extendedAttributes = unparsedNode.folder?.extendedAttributes
|
|
351
|
-
? parseFolderExtendedAttributes(this.logger, unparsedNode.folder.extendedAttributes)
|
|
352
|
-
: undefined;
|
|
353
|
-
return {
|
|
354
|
-
...unparsedNode,
|
|
355
|
-
name: nodeName,
|
|
356
|
-
isStale: false,
|
|
357
|
-
activeRevision: undefined,
|
|
358
|
-
folder: extendedAttributes
|
|
359
|
-
? {
|
|
360
|
-
...extendedAttributes,
|
|
361
|
-
}
|
|
362
|
-
: undefined,
|
|
363
|
-
treeEventScopeId: splitNodeUid(unparsedNode.uid).volumeId,
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
308
|
async getParentKeys(
|
|
368
309
|
node: Pick<DecryptedNode, 'parentUid' | 'shareId'>,
|
|
369
310
|
): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
|
|
@@ -375,7 +316,7 @@ export class NodesAccess {
|
|
|
375
316
|
// Change the error message to be more specific.
|
|
376
317
|
// Original error message is referring to node, while here
|
|
377
318
|
// it referes to as parent to follow the method context.
|
|
378
|
-
throw new DecryptionError(c('Error').t`Parent cannot be decrypted
|
|
319
|
+
throw new DecryptionError(c('Error').t`Parent cannot be decrypted`, { cause: error });
|
|
379
320
|
}
|
|
380
321
|
throw error;
|
|
381
322
|
}
|
|
@@ -458,3 +399,60 @@ export class NodesAccess {
|
|
|
458
399
|
return node.parentUid ? this.getRootNode(node.parentUid) : node;
|
|
459
400
|
}
|
|
460
401
|
}
|
|
402
|
+
|
|
403
|
+
export async function parseNode(logger: Logger, unparsedNode: DecryptedUnparsedNode): Promise<DecryptedNode> {
|
|
404
|
+
let nodeName: Result<string, Error | InvalidNameError> = unparsedNode.name;
|
|
405
|
+
if (unparsedNode.name.ok) {
|
|
406
|
+
try {
|
|
407
|
+
validateNodeName(unparsedNode.name.value);
|
|
408
|
+
} catch (error: unknown) {
|
|
409
|
+
logger.warn(`Node name validation failed: ${error instanceof Error ? error.message : error}`);
|
|
410
|
+
nodeName = resultError({
|
|
411
|
+
name: unparsedNode.name.value,
|
|
412
|
+
error: error instanceof Error ? error.message : c('Error').t`Unknown error`,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const treeEventScopeId = splitNodeUid(unparsedNode.uid).volumeId;
|
|
418
|
+
|
|
419
|
+
if (unparsedNode.type === NodeType.File) {
|
|
420
|
+
const extendedAttributes = unparsedNode.activeRevision?.ok
|
|
421
|
+
? parseFileExtendedAttributes(
|
|
422
|
+
logger,
|
|
423
|
+
unparsedNode.activeRevision.value.creationTime,
|
|
424
|
+
unparsedNode.activeRevision.value.extendedAttributes,
|
|
425
|
+
)
|
|
426
|
+
: undefined;
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
...unparsedNode,
|
|
430
|
+
isStale: false,
|
|
431
|
+
activeRevision: !unparsedNode.activeRevision?.ok
|
|
432
|
+
? unparsedNode.activeRevision
|
|
433
|
+
: resultOk({
|
|
434
|
+
uid: unparsedNode.activeRevision.value.uid,
|
|
435
|
+
state: unparsedNode.activeRevision.value.state,
|
|
436
|
+
creationTime: unparsedNode.activeRevision.value.creationTime,
|
|
437
|
+
storageSize: unparsedNode.activeRevision.value.storageSize,
|
|
438
|
+
contentAuthor: unparsedNode.activeRevision.value.contentAuthor,
|
|
439
|
+
thumbnails: unparsedNode.activeRevision.value.thumbnails,
|
|
440
|
+
...extendedAttributes,
|
|
441
|
+
}),
|
|
442
|
+
folder: undefined,
|
|
443
|
+
treeEventScopeId,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const extendedAttributes = unparsedNode.folder?.extendedAttributes
|
|
448
|
+
? parseFolderExtendedAttributes(logger, unparsedNode.folder.extendedAttributes)
|
|
449
|
+
: undefined;
|
|
450
|
+
return {
|
|
451
|
+
...unparsedNode,
|
|
452
|
+
name: nodeName,
|
|
453
|
+
isStale: false,
|
|
454
|
+
activeRevision: undefined,
|
|
455
|
+
folder: extendedAttributes,
|
|
456
|
+
treeEventScopeId,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PrivateKey, SessionKey } from '../../crypto';
|
|
2
2
|
import { MemoryCache } from '../../cache';
|
|
3
3
|
import { CachedCryptoMaterial } from '../../interface';
|
|
4
|
+
import { getMockLogger } from '../../tests/logger';
|
|
4
5
|
import { SharesCryptoCache } from './cryptoCache';
|
|
5
6
|
|
|
6
7
|
describe('sharesCryptoCache', () => {
|
|
@@ -17,7 +18,7 @@ describe('sharesCryptoCache', () => {
|
|
|
17
18
|
|
|
18
19
|
beforeEach(() => {
|
|
19
20
|
memoryCache = new MemoryCache();
|
|
20
|
-
cache = new SharesCryptoCache(memoryCache);
|
|
21
|
+
cache = new SharesCryptoCache(getMockLogger(), memoryCache);
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
it('should store and retrieve keys', async () => {
|
|
@@ -53,7 +54,7 @@ describe('sharesCryptoCache', () => {
|
|
|
53
54
|
const keys = { key: generatePrivateKey('privateKey'), passphraseSessionKey: generateSessionKey('sessionKey') };
|
|
54
55
|
|
|
55
56
|
await cache.setShareKey(shareId, keys);
|
|
56
|
-
await cache.
|
|
57
|
+
await cache.removeShareKeys([shareId]);
|
|
57
58
|
|
|
58
59
|
try {
|
|
59
60
|
await cache.getShareKey(shareId);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProtonDriveCryptoCache } from '../../interface';
|
|
1
|
+
import { Logger, ProtonDriveCryptoCache } from '../../interface';
|
|
2
2
|
import { DecryptedShareKey } from './interface';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -13,23 +13,42 @@ import { DecryptedShareKey } from './interface';
|
|
|
13
13
|
* only the root node, thus share cache is not needed.
|
|
14
14
|
*/
|
|
15
15
|
export class SharesCryptoCache {
|
|
16
|
-
constructor(
|
|
16
|
+
constructor(
|
|
17
|
+
private logger: Logger,
|
|
18
|
+
private driveCache: ProtonDriveCryptoCache,
|
|
19
|
+
) {
|
|
20
|
+
this.logger = logger;
|
|
17
21
|
this.driveCache = driveCache;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
async setShareKey(shareId: string, key: DecryptedShareKey): Promise<void> {
|
|
21
|
-
await this.driveCache.setEntity(
|
|
25
|
+
await this.driveCache.setEntity(getCacheKey(shareId), {
|
|
26
|
+
shareKey: key,
|
|
27
|
+
});
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
async getShareKey(shareId: string): Promise<DecryptedShareKey> {
|
|
25
|
-
|
|
31
|
+
const nodeKeysData = await this.driveCache.getEntity(getCacheKey(shareId));
|
|
32
|
+
if (!nodeKeysData.shareKey) {
|
|
33
|
+
try {
|
|
34
|
+
await this.removeShareKeys([shareId]);
|
|
35
|
+
} catch (removingError: unknown) {
|
|
36
|
+
// The node keys will not be returned, thus SDK will re-fetch
|
|
37
|
+
// and re-cache it. Setting it again should then fix the problem.
|
|
38
|
+
this.logger.warn(
|
|
39
|
+
`Failed to remove corrupted node keys from the cache: ${removingError instanceof Error ? removingError.message : removingError}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Failed to deserialize node keys`);
|
|
43
|
+
}
|
|
44
|
+
return nodeKeysData.shareKey;
|
|
26
45
|
}
|
|
27
46
|
|
|
28
|
-
async
|
|
29
|
-
await this.driveCache.removeEntities(shareIds.map(
|
|
47
|
+
async removeShareKeys(shareIds: string[]): Promise<void> {
|
|
48
|
+
await this.driveCache.removeEntities(shareIds.map(getCacheKey));
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
51
|
|
|
33
|
-
function
|
|
52
|
+
function getCacheKey(shareId: string) {
|
|
34
53
|
return `shareKey-${shareId}`;
|
|
35
54
|
}
|
|
@@ -33,7 +33,7 @@ export function initSharesModule(
|
|
|
33
33
|
) {
|
|
34
34
|
const api = new SharesAPIService(apiService);
|
|
35
35
|
const cache = new SharesCache(telemetry.getLogger('shares-cache'), driveEntitiesCache);
|
|
36
|
-
const cryptoCache = new SharesCryptoCache(driveCryptoCache);
|
|
36
|
+
const cryptoCache = new SharesCryptoCache(telemetry.getLogger('shares-cache'), driveCryptoCache);
|
|
37
37
|
const cryptoService = new SharesCryptoService(telemetry, crypto, account);
|
|
38
38
|
const sharesManager = new SharesManager(
|
|
39
39
|
telemetry.getLogger('shares'),
|
|
@@ -44,11 +44,28 @@ export class SharingCache {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async getSharedWithMeNodeUids(): Promise<string[]> {
|
|
47
|
-
return this.getNodeUids(SharingType.
|
|
47
|
+
return this.getNodeUids(SharingType.SharedWithMe);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async hasSharedWithMeNodeUidsLoaded(): Promise<boolean> {
|
|
51
|
+
try {
|
|
52
|
+
await this.getNodeUids(SharingType.SharedWithMe);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async addSharedWithMeNodeUid(nodeUid: string): Promise<void> {
|
|
60
|
+
return this.addNodeUid(SharingType.SharedWithMe, nodeUid);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async removeSharedWithMeNodeUid(nodeUid: string): Promise<void> {
|
|
64
|
+
return this.removeNodeUid(SharingType.SharedWithMe, nodeUid);
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
async setSharedWithMeNodeUids(nodeUids: string[] | undefined): Promise<void> {
|
|
51
|
-
return this.setNodeUids(SharingType.
|
|
68
|
+
return this.setNodeUids(SharingType.SharedWithMe, nodeUids);
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '../../interface';
|
|
10
10
|
import { getMockTelemetry } from '../../tests/telemetry';
|
|
11
11
|
import { SharesService } from './interface';
|
|
12
|
-
import { SharingCryptoService } from './cryptoService';
|
|
12
|
+
import { PUBLIC_LINK_GENERATED_PASSWORD_LENGTH, SharingCryptoService } from './cryptoService';
|
|
13
13
|
|
|
14
14
|
describe('SharingCryptoService', () => {
|
|
15
15
|
let telemetry: ProtonDriveTelemetry;
|
|
@@ -87,6 +87,27 @@ describe('SharingCryptoService', () => {
|
|
|
87
87
|
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
it('should decrypt bookmark with custom password', async () => {
|
|
91
|
+
// First 12 characters are the generated password. Anything beyond is the custom password.
|
|
92
|
+
driveCrypto.decryptShareUrlPassword = jest.fn().mockResolvedValue('urlPassword1WithCustomPassword');
|
|
93
|
+
|
|
94
|
+
const result = await cryptoService.decryptBookmark(encryptedBookmark);
|
|
95
|
+
|
|
96
|
+
expect(result).toMatchObject({
|
|
97
|
+
url: resultOk('https://drive.proton.me/urls/tokenId#urlPassword1'),
|
|
98
|
+
nodeName: resultOk('nodeName'),
|
|
99
|
+
});
|
|
100
|
+
expect(driveCrypto.decryptShareUrlPassword).toHaveBeenCalledWith('encryptedUrlPassword', ['addressKey']);
|
|
101
|
+
expect(driveCrypto.decryptKeyWithSrpPassword).toHaveBeenCalledWith(
|
|
102
|
+
'urlPassword1WithCustomPassword',
|
|
103
|
+
'base64SharePasswordSalt',
|
|
104
|
+
'armoredKey',
|
|
105
|
+
'armoredPassphrase',
|
|
106
|
+
);
|
|
107
|
+
expect(driveCrypto.decryptNodeName).toHaveBeenCalledWith('encryptedName', 'decryptedKey', []);
|
|
108
|
+
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
90
111
|
it('should handle undecryptable URL password', async () => {
|
|
91
112
|
const error = new Error('Failed to decrypt URL password');
|
|
92
113
|
driveCrypto.decryptShareUrlPassword = jest.fn().mockRejectedValue(error);
|