@protontech/drive-sdk 0.6.1 → 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 (119) hide show
  1. package/dist/featureFlags.d.ts +7 -0
  2. package/dist/featureFlags.js +14 -0
  3. package/dist/featureFlags.js.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/interface/featureFlags.d.ts +7 -0
  8. package/dist/interface/featureFlags.js +3 -0
  9. package/dist/interface/featureFlags.js.map +1 -0
  10. package/dist/interface/index.d.ts +3 -0
  11. package/dist/interface/index.js.map +1 -1
  12. package/dist/internal/apiService/apiService.d.ts +2 -2
  13. package/dist/internal/apiService/apiService.js.map +1 -1
  14. package/dist/internal/apiService/errors.js +4 -3
  15. package/dist/internal/apiService/errors.js.map +1 -1
  16. package/dist/internal/download/cryptoService.js +8 -6
  17. package/dist/internal/download/cryptoService.js.map +1 -1
  18. package/dist/internal/download/fileDownloader.d.ts +2 -1
  19. package/dist/internal/download/fileDownloader.js +6 -3
  20. package/dist/internal/download/fileDownloader.js.map +1 -1
  21. package/dist/internal/download/index.d.ts +1 -1
  22. package/dist/internal/download/index.js +3 -3
  23. package/dist/internal/download/index.js.map +1 -1
  24. package/dist/internal/nodes/apiService.d.ts +9 -8
  25. package/dist/internal/nodes/apiService.js +14 -5
  26. package/dist/internal/nodes/apiService.js.map +1 -1
  27. package/dist/internal/nodes/apiService.test.js +5 -5
  28. package/dist/internal/nodes/apiService.test.js.map +1 -1
  29. package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
  30. package/dist/internal/nodes/cryptoReporter.js.map +1 -1
  31. package/dist/internal/nodes/cryptoService.d.ts +12 -21
  32. package/dist/internal/nodes/cryptoService.js +45 -14
  33. package/dist/internal/nodes/cryptoService.js.map +1 -1
  34. package/dist/internal/nodes/cryptoService.test.js +262 -17
  35. package/dist/internal/nodes/cryptoService.test.js.map +1 -1
  36. package/dist/internal/nodes/interface.d.ts +13 -3
  37. package/dist/internal/nodes/nodesAccess.d.ts +8 -1
  38. package/dist/internal/nodes/nodesAccess.js +13 -0
  39. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  40. package/dist/internal/nodes/nodesManagement.d.ts +4 -4
  41. package/dist/internal/nodes/nodesManagement.js +16 -20
  42. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  43. package/dist/internal/nodes/nodesManagement.test.js +21 -10
  44. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  45. package/dist/internal/photos/upload.d.ts +2 -2
  46. package/dist/internal/photos/upload.js +1 -1
  47. package/dist/internal/photos/upload.js.map +1 -1
  48. package/dist/internal/sharingPublic/index.d.ts +6 -6
  49. package/dist/internal/sharingPublic/index.js +8 -7
  50. package/dist/internal/sharingPublic/index.js.map +1 -1
  51. package/dist/internal/sharingPublic/nodes.d.ts +16 -3
  52. package/dist/internal/sharingPublic/nodes.js +34 -2
  53. package/dist/internal/sharingPublic/nodes.js.map +1 -1
  54. package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
  55. package/dist/internal/sharingPublic/unauthApiService.js +31 -0
  56. package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
  57. package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
  58. package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
  59. package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
  60. package/dist/internal/upload/apiService.d.ts +4 -3
  61. package/dist/internal/upload/apiService.js.map +1 -1
  62. package/dist/internal/upload/cryptoService.d.ts +8 -3
  63. package/dist/internal/upload/cryptoService.js +45 -9
  64. package/dist/internal/upload/cryptoService.js.map +1 -1
  65. package/dist/internal/upload/fileUploader.d.ts +5 -2
  66. package/dist/internal/upload/fileUploader.js +7 -4
  67. package/dist/internal/upload/fileUploader.js.map +1 -1
  68. package/dist/internal/upload/fileUploader.test.js +1 -1
  69. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  70. package/dist/internal/upload/interface.d.ts +25 -13
  71. package/dist/internal/upload/manager.js +7 -4
  72. package/dist/internal/upload/manager.js.map +1 -1
  73. package/dist/internal/upload/manager.test.js +5 -4
  74. package/dist/internal/upload/manager.test.js.map +1 -1
  75. package/dist/internal/upload/streamUploader.js +9 -4
  76. package/dist/internal/upload/streamUploader.js.map +1 -1
  77. package/dist/internal/upload/streamUploader.test.js +8 -5
  78. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  79. package/dist/protonDriveClient.d.ts +2 -2
  80. package/dist/protonDriveClient.js +7 -2
  81. package/dist/protonDriveClient.js.map +1 -1
  82. package/dist/protonDrivePublicLinkClient.d.ts +2 -1
  83. package/dist/protonDrivePublicLinkClient.js +7 -5
  84. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/featureFlags.ts +11 -0
  87. package/src/index.ts +1 -0
  88. package/src/interface/featureFlags.ts +7 -0
  89. package/src/interface/index.ts +3 -0
  90. package/src/internal/apiService/apiService.ts +2 -2
  91. package/src/internal/apiService/errors.ts +5 -4
  92. package/src/internal/download/cryptoService.ts +13 -6
  93. package/src/internal/download/fileDownloader.ts +4 -2
  94. package/src/internal/download/index.ts +3 -0
  95. package/src/internal/nodes/apiService.test.ts +5 -5
  96. package/src/internal/nodes/apiService.ts +23 -10
  97. package/src/internal/nodes/cryptoReporter.ts +3 -3
  98. package/src/internal/nodes/cryptoService.test.ts +370 -18
  99. package/src/internal/nodes/cryptoService.ts +73 -32
  100. package/src/internal/nodes/interface.ts +16 -2
  101. package/src/internal/nodes/nodesAccess.ts +17 -0
  102. package/src/internal/nodes/nodesManagement.test.ts +21 -10
  103. package/src/internal/nodes/nodesManagement.ts +20 -24
  104. package/src/internal/photos/upload.ts +3 -3
  105. package/src/internal/sharingPublic/index.ts +7 -3
  106. package/src/internal/sharingPublic/nodes.ts +43 -2
  107. package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
  108. package/src/internal/sharingPublic/unauthApiService.ts +32 -0
  109. package/src/internal/upload/apiService.ts +4 -3
  110. package/src/internal/upload/cryptoService.ts +73 -12
  111. package/src/internal/upload/fileUploader.test.ts +1 -1
  112. package/src/internal/upload/fileUploader.ts +18 -11
  113. package/src/internal/upload/interface.ts +24 -13
  114. package/src/internal/upload/manager.test.ts +5 -4
  115. package/src/internal/upload/manager.ts +7 -4
  116. package/src/internal/upload/streamUploader.test.ts +8 -5
  117. package/src/internal/upload/streamUploader.ts +10 -4
  118. package/src/protonDriveClient.ts +12 -2
  119. package/src/protonDrivePublicLinkClient.ts +8 -3
@@ -2,8 +2,15 @@ import { c } from 'ttag';
2
2
 
3
3
  import { DriveCrypto, PrivateKey, SessionKey } from '../../crypto';
4
4
  import { IntegrityError } from '../../errors';
5
- import { Thumbnail } from '../../interface';
6
- import { EncryptedBlock, EncryptedThumbnail, NodeCrypto, NodeRevisionDraftKeys, NodesService } from './interface';
5
+ import { Thumbnail, AnonymousUser } from '../../interface';
6
+ import {
7
+ EncryptedBlock,
8
+ EncryptedThumbnail,
9
+ NodeCrypto,
10
+ NodeCryptoSigningKeys,
11
+ NodeRevisionDraftKeys,
12
+ NodesService,
13
+ } from './interface';
7
14
 
8
15
  export class UploadCryptoService {
9
16
  constructor(
@@ -19,11 +26,15 @@ export class UploadCryptoService {
19
26
  parentKeys: { key: PrivateKey; hashKey: Uint8Array },
20
27
  name: string,
21
28
  ): Promise<NodeCrypto> {
22
- const signatureAddress = await this.nodesService.getRootNodeEmailKey(parentUid);
29
+ const signingKeys = await this.getSigningKeys({ parentNodeUid: parentUid });
30
+
31
+ if (!signingKeys.nameAndPassphraseSigningKey) {
32
+ throw new Error('Cannot create new node without a name and passphrase signing key');
33
+ }
23
34
 
24
35
  const [nodeKeys, { armoredNodeName }, hash] = await Promise.all([
25
- this.driveCrypto.generateKey([parentKeys.key], signatureAddress.addressKey),
26
- this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, signatureAddress.addressKey),
36
+ this.driveCrypto.generateKey([parentKeys.key], signingKeys.nameAndPassphraseSigningKey),
37
+ this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, signingKeys.nameAndPassphraseSigningKey),
27
38
  this.driveCrypto.generateLookupHash(name, parentKeys.hashKey),
28
39
  ]);
29
40
 
@@ -36,7 +47,57 @@ export class UploadCryptoService {
36
47
  encryptedName: armoredNodeName,
37
48
  hash,
38
49
  },
39
- signatureAddress,
50
+ signingKeys: {
51
+ email: signingKeys.email,
52
+ addressId: signingKeys.addressId,
53
+ nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
54
+ contentSigningKey: signingKeys.contentSigningKey || nodeKeys.decrypted.key,
55
+ },
56
+ };
57
+ }
58
+
59
+ async getSigningKeysForExistingNode(uids: {
60
+ nodeUid: string;
61
+ parentNodeUid?: string;
62
+ }): Promise<NodeCryptoSigningKeys> {
63
+ const signingKeys = await this.getSigningKeys(uids);
64
+
65
+ if (!signingKeys.nameAndPassphraseSigningKey) {
66
+ throw new Error('Cannot get name and passphrase signing key for existing node');
67
+ }
68
+ if (!signingKeys.contentSigningKey) {
69
+ throw new Error('Cannot get content signing key for existing node');
70
+ }
71
+
72
+ return {
73
+ email: signingKeys.email,
74
+ addressId: signingKeys.addressId,
75
+ nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
76
+ contentSigningKey: signingKeys.contentSigningKey,
77
+ };
78
+ }
79
+
80
+ private async getSigningKeys(
81
+ uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
82
+ ): Promise<
83
+ Omit<NodeCryptoSigningKeys, 'nameAndPassphraseSigningKey' | 'contentSigningKey'> & {
84
+ nameAndPassphraseSigningKey?: PrivateKey;
85
+ contentSigningKey?: PrivateKey;
86
+ }
87
+ > {
88
+ const signingKeys = await this.nodesService.getNodeSigningKeys(uids);
89
+
90
+ const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
91
+ const addressId = signingKeys.type === 'userAddress' ? signingKeys.addressId : null;
92
+ const nameAndPassphraseSigningKey =
93
+ signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.parentNodeKey;
94
+ const contentSigningKey = signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.nodeKey;
95
+
96
+ return {
97
+ email,
98
+ addressId,
99
+ nameAndPassphraseSigningKey,
100
+ contentSigningKey,
40
101
  };
41
102
  }
42
103
 
@@ -47,7 +108,7 @@ export class UploadCryptoService {
47
108
  const { encryptedData } = await this.driveCrypto.encryptThumbnailBlock(
48
109
  thumbnail.thumbnail,
49
110
  nodeRevisionDraftKeys.contentKeyPacketSessionKey,
50
- nodeRevisionDraftKeys.signatureAddress.addressKey,
111
+ nodeRevisionDraftKeys.signingKeys.contentSigningKey,
51
112
  );
52
113
 
53
114
  const digest = await crypto.subtle.digest('SHA-256', encryptedData);
@@ -71,7 +132,7 @@ export class UploadCryptoService {
71
132
  block,
72
133
  nodeRevisionDraftKeys.key,
73
134
  nodeRevisionDraftKeys.contentKeyPacketSessionKey,
74
- nodeRevisionDraftKeys.signatureAddress.addressKey,
135
+ nodeRevisionDraftKeys.signingKeys.contentSigningKey,
75
136
  );
76
137
 
77
138
  const digest = await crypto.subtle.digest('SHA-256', encryptedData);
@@ -94,25 +155,25 @@ export class UploadCryptoService {
94
155
  extendedAttributes?: string,
95
156
  ): Promise<{
96
157
  armoredManifestSignature: string;
97
- signatureEmail: string;
158
+ signatureEmail: string | AnonymousUser;
98
159
  armoredExtendedAttributes?: string;
99
160
  }> {
100
161
  const { armoredManifestSignature } = await this.driveCrypto.signManifest(
101
162
  manifest,
102
- nodeRevisionDraftKeys.signatureAddress.addressKey,
163
+ nodeRevisionDraftKeys.signingKeys.contentSigningKey,
103
164
  );
104
165
 
105
166
  const { armoredExtendedAttributes } = extendedAttributes
106
167
  ? await this.driveCrypto.encryptExtendedAttributes(
107
168
  extendedAttributes,
108
169
  nodeRevisionDraftKeys.key,
109
- nodeRevisionDraftKeys.signatureAddress.addressKey,
170
+ nodeRevisionDraftKeys.signingKeys.contentSigningKey,
110
171
  )
111
172
  : { armoredExtendedAttributes: undefined };
112
173
 
113
174
  return {
114
175
  armoredManifestSignature,
115
- signatureEmail: nodeRevisionDraftKeys.signatureAddress.email,
176
+ signatureEmail: nodeRevisionDraftKeys.signingKeys.email,
116
177
  armoredExtendedAttributes,
117
178
  };
118
179
  }
@@ -110,7 +110,7 @@ describe('FileUploader', () => {
110
110
  nodeRevisionUid: 'revisionUid',
111
111
  nodeUid: 'nodeUid',
112
112
  nodeKeys: {
113
- signatureAddress: { addressId: 'addressId' },
113
+ signingKeys: { addressId: 'addressId' },
114
114
  },
115
115
  } as NodeRevisionDraft;
116
116
 
@@ -15,7 +15,7 @@ import { UploadTelemetry } from './telemetry';
15
15
  * This class is not meant to be used directly, but rather to be extended
16
16
  * by `FileUploader` and `FileRevisionUploader`.
17
17
  */
18
- class Uploader {
18
+ abstract class Uploader {
19
19
  protected controller: UploadController;
20
20
  protected abortController: AbortController;
21
21
 
@@ -83,7 +83,7 @@ class Uploader {
83
83
  stream: ReadableStream,
84
84
  thumbnails: Thumbnail[],
85
85
  onProgress?: (uploadedBytes: number) => void,
86
- ): Promise<{ nodeRevisionUid: string, nodeUid: string }> {
86
+ ): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
87
87
  const uploader = await this.initStreamUploader();
88
88
  return uploader.start(stream, thumbnails, onProgress);
89
89
  }
@@ -94,15 +94,11 @@ class Uploader {
94
94
  const onFinish = async (failure: boolean) => {
95
95
  this.onFinish();
96
96
  if (failure) {
97
- await this.manager.deleteDraftNode(revisionDraft.nodeUid);
97
+ await this.deleteRevisionDraft(revisionDraft);
98
98
  }
99
99
  };
100
100
 
101
- return this.newStreamUploader(
102
- blockVerifier,
103
- revisionDraft,
104
- onFinish,
105
- );
101
+ return this.newStreamUploader(blockVerifier, revisionDraft, onFinish);
106
102
  }
107
103
 
108
104
  protected async newStreamUploader(
@@ -124,9 +120,12 @@ class Uploader {
124
120
  );
125
121
  }
126
122
 
127
- protected async createRevisionDraft(): Promise<{ revisionDraft: NodeRevisionDraft; blockVerifier: BlockVerifier }> {
128
- throw new Error('Not implemented');
129
- }
123
+ protected abstract createRevisionDraft(): Promise<{
124
+ revisionDraft: NodeRevisionDraft;
125
+ blockVerifier: BlockVerifier;
126
+ }>;
127
+
128
+ protected abstract deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void>;
130
129
  }
131
130
 
132
131
  /**
@@ -176,6 +175,10 @@ export class FileUploader extends Uploader {
176
175
  blockVerifier,
177
176
  };
178
177
  }
178
+
179
+ protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
180
+ await this.manager.deleteDraftNode(revisionDraft.nodeUid);
181
+ }
179
182
  }
180
183
 
181
184
  /**
@@ -223,4 +226,8 @@ export class FileRevisionUploader extends Uploader {
223
226
  blockVerifier,
224
227
  };
225
228
  }
229
+
230
+ protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
231
+ await this.manager.deleteDraftRevision(revisionDraft.nodeRevisionUid);
232
+ }
226
233
  }
@@ -1,6 +1,6 @@
1
1
  import { PrivateKey, SessionKey } from '../../crypto';
2
2
 
3
- import { MetricVolumeType, ThumbnailType, Result, Revision } from '../../interface';
3
+ import { MetricVolumeType, ThumbnailType, Result, Revision, AnonymousUser } from '../../interface';
4
4
  import { DecryptedNode } from '../nodes';
5
5
 
6
6
  export type NodeRevisionDraft = {
@@ -22,7 +22,7 @@ export type NodeRevisionDraft = {
22
22
  export type NodeRevisionDraftKeys = {
23
23
  key: PrivateKey;
24
24
  contentKeyPacketSessionKey: SessionKey;
25
- signatureAddress: NodeCryptoSignatureAddress;
25
+ signingKeys: NodeCryptoSigningKeys;
26
26
  };
27
27
 
28
28
  export type NodeCrypto = {
@@ -51,13 +51,14 @@ export type NodeCrypto = {
51
51
  encryptedName: string;
52
52
  hash: string;
53
53
  };
54
- signatureAddress: NodeCryptoSignatureAddress;
54
+ signingKeys: NodeCryptoSigningKeys;
55
55
  };
56
56
 
57
- export type NodeCryptoSignatureAddress = {
58
- email: string;
59
- addressId: string;
60
- addressKey: PrivateKey;
57
+ export type NodeCryptoSigningKeys = {
58
+ email: string | AnonymousUser;
59
+ addressId: string | AnonymousUser;
60
+ nameAndPassphraseSigningKey: PrivateKey;
61
+ contentSigningKey: PrivateKey;
61
62
  };
62
63
 
63
64
  export type EncryptedBlockMetadata = {
@@ -102,12 +103,9 @@ export interface NodesService {
102
103
  contentKeyPacketSessionKey?: SessionKey;
103
104
  hashKey?: Uint8Array;
104
105
  }>;
105
- getRootNodeEmailKey(nodeUid: string): Promise<{
106
- email: string;
107
- addressId: string;
108
- addressKey: PrivateKey;
109
- addressKeyId: string;
110
- }>;
106
+ getNodeSigningKeys(
107
+ uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
108
+ ): Promise<NodeSigningKeys>;
111
109
  notifyChildCreated(nodeUid: string): Promise<void>;
112
110
  notifyNodeChanged(nodeUid: string): Promise<void>;
113
111
  }
@@ -126,6 +124,19 @@ export interface NodesServiceNode {
126
124
  activeRevision?: Result<Revision, Error>;
127
125
  }
128
126
 
127
+ export type NodeSigningKeys =
128
+ | {
129
+ type: 'userAddress';
130
+ email: string;
131
+ addressId: string;
132
+ key: PrivateKey;
133
+ }
134
+ | {
135
+ type: 'nodeKey';
136
+ nodeKey?: PrivateKey;
137
+ parentNodeKey?: PrivateKey;
138
+ };
139
+
129
140
  /**
130
141
  * Interface describing the dependencies to the shares module.
131
142
  */
@@ -50,7 +50,7 @@ describe('UploadManager', () => {
50
50
  encryptedName: 'newNode:encryptedName',
51
51
  hash: 'newNode:hash',
52
52
  },
53
- signatureAddress: {
53
+ signingKeys: {
54
54
  email: 'signatureEmail',
55
55
  },
56
56
  }),
@@ -69,7 +69,8 @@ describe('UploadManager', () => {
69
69
  hashKey: 'parentNode:hashKey',
70
70
  key: 'parentNode:nodekey',
71
71
  }),
72
- getRootNodeEmailKey: jest.fn().mockResolvedValue({
72
+ getNodeSigningKeys: jest.fn().mockResolvedValue({
73
+ type: 'userAddress',
73
74
  email: 'signatureEmail',
74
75
  addressId: 'addressId',
75
76
  }),
@@ -100,7 +101,7 @@ describe('UploadManager', () => {
100
101
  nodeKeys: {
101
102
  key: 'newNode:key',
102
103
  contentKeyPacketSessionKey: 'newNode:ContentKeyPacketSessionKey',
103
- signatureAddress: {
104
+ signingKeys: {
104
105
  email: 'signatureEmail',
105
106
  },
106
107
  },
@@ -153,7 +154,7 @@ describe('UploadManager', () => {
153
154
  nodeKeys: {
154
155
  key: 'newNode:key',
155
156
  contentKeyPacketSessionKey: 'newNode:ContentKeyPacketSessionKey',
156
- signatureAddress: {
157
+ signingKeys: {
157
158
  email: 'signatureEmail',
158
159
  },
159
160
  },
@@ -57,7 +57,7 @@ export class UploadManager {
57
57
  nodeKeys: {
58
58
  key: generatedNodeCrypto.nodeKeys.decrypted.key,
59
59
  contentKeyPacketSessionKey: generatedNodeCrypto.contentKey.decrypted.contentKeyPacketSessionKey,
60
- signatureAddress: generatedNodeCrypto.signatureAddress,
60
+ signingKeys: generatedNodeCrypto.signingKeys,
61
61
  },
62
62
  parentNodeKeys: {
63
63
  hashKey: parentKeys.hashKey,
@@ -93,7 +93,7 @@ export class UploadManager {
93
93
  base64ContentKeyPacket: generatedNodeCrypto.contentKey.encrypted.base64ContentKeyPacket,
94
94
  armoredContentKeyPacketSignature:
95
95
  generatedNodeCrypto.contentKey.encrypted.armoredContentKeyPacketSignature,
96
- signatureEmail: generatedNodeCrypto.signatureAddress.email,
96
+ signatureEmail: generatedNodeCrypto.signingKeys.email,
97
97
  });
98
98
  return result;
99
99
  } catch (error: unknown) {
@@ -192,7 +192,10 @@ export class UploadManager {
192
192
  throw new ValidationError(c('Error').t`Creating revisions in non-files is not allowed`);
193
193
  }
194
194
 
195
- const signatureAddress = await this.nodesService.getRootNodeEmailKey(nodeUid);
195
+ const signingKeys = await this.cryptoService.getSigningKeysForExistingNode({
196
+ nodeUid,
197
+ parentNodeUid: node.parentUid,
198
+ });
196
199
 
197
200
  const { nodeRevisionUid } = await this.apiService.createDraftRevision(nodeUid, {
198
201
  currentRevisionUid: node.activeRevision.value.uid,
@@ -205,7 +208,7 @@ export class UploadManager {
205
208
  nodeKeys: {
206
209
  key: nodeKeys.key,
207
210
  contentKeyPacketSessionKey: nodeKeys.contentKeyPacketSessionKey,
208
- signatureAddress: signatureAddress,
211
+ signingKeys,
209
212
  },
210
213
  };
211
214
  }
@@ -110,7 +110,9 @@ describe('StreamUploader', () => {
110
110
  nodeRevisionUid: 'revisionUid',
111
111
  nodeUid: 'nodeUid',
112
112
  nodeKeys: {
113
- signatureAddress: { addressId: 'addressId' },
113
+ signingKeys: {
114
+ addressId: 'addressId',
115
+ },
114
116
  },
115
117
  } as NodeRevisionDraft;
116
118
 
@@ -312,8 +314,9 @@ describe('StreamUploader', () => {
312
314
  throw new Error('Failed to encrypt block');
313
315
  });
314
316
 
315
- // Encrypting thumbnails is before blocks, thus it can be uploaded before failure.
316
- await verifyFailure('Failed to encrypt block', 1024);
317
+ // Thumbnail are uploaded with the first content block. If the
318
+ // content block fails to encrypt, nothing is uploaded.
319
+ await verifyFailure('Failed to encrypt block', 0);
317
320
  // 1 block + 1 retry, others are skipped
318
321
  expect(cryptoService.encryptBlock).toHaveBeenCalledTimes(2);
319
322
  });
@@ -415,7 +418,7 @@ describe('StreamUploader', () => {
415
418
  expect(apiService.requestBlockUpload).toHaveBeenCalledTimes(2);
416
419
  expect(apiService.requestBlockUpload).toHaveBeenCalledWith(
417
420
  revisionDraft.nodeRevisionUid,
418
- revisionDraft.nodeKeys.signatureAddress.addressId,
421
+ revisionDraft.nodeKeys.signingKeys.addressId,
419
422
  {
420
423
  contentBlocks: [
421
424
  {
@@ -436,7 +439,7 @@ describe('StreamUploader', () => {
436
439
  describe('verifyIntegrity', () => {
437
440
  it('should report block verification error', async () => {
438
441
  blockVerifier.verifyBlock = jest.fn().mockRejectedValue(new IntegrityError('Block verification error'));
439
- await verifyFailure('Block verification error', 1024);
442
+ await verifyFailure('Block verification error', 0);
440
443
  expect(telemetry.logBlockVerificationError).toHaveBeenCalledWith(false);
441
444
  });
442
445
 
@@ -319,7 +319,7 @@ export class StreamUploader {
319
319
  this.logger.info(`Requesting upload tokens for ${this.encryptedBlocks.size} blocks`);
320
320
  const uploadTokens = await this.apiService.requestBlockUpload(
321
321
  this.revisionDraft.nodeRevisionUid,
322
- this.revisionDraft.nodeKeys.signatureAddress.addressId,
322
+ this.revisionDraft.nodeKeys.signingKeys.addressId,
323
323
  {
324
324
  contentBlocks: Array.from(
325
325
  this.encryptedBlocks.values().map((block) => ({
@@ -340,6 +340,12 @@ export class StreamUploader {
340
340
  },
341
341
  );
342
342
 
343
+ // If the upload was aborted while requesting next upload tokens,
344
+ // do not schedule any next upload.
345
+ if (this.isUploadAborted) {
346
+ throw this.error || new AbortError();
347
+ }
348
+
343
349
  for (const thumbnailToken of uploadTokens.thumbnailTokens) {
344
350
  let encryptedThumbnail = this.encryptedThumbnails.get(thumbnailToken.type);
345
351
  if (!encryptedThumbnail) {
@@ -418,7 +424,7 @@ export class StreamUploader {
418
424
  break;
419
425
  } catch (error: unknown) {
420
426
  // Do not retry or report anything if the upload was aborted.
421
- if (error instanceof AbortError) {
427
+ if (error instanceof AbortError || this.isUploadAborted) {
422
428
  throw error;
423
429
  }
424
430
 
@@ -485,7 +491,7 @@ export class StreamUploader {
485
491
  break;
486
492
  } catch (error: unknown) {
487
493
  // Do not retry or report anything if the upload was aborted.
488
- if (error instanceof AbortError) {
494
+ if (error instanceof AbortError || this.isUploadAborted) {
489
495
  throw error;
490
496
  }
491
497
 
@@ -501,7 +507,7 @@ export class StreamUploader {
501
507
  logger.warn(`Token expired, fetching new token and retrying`);
502
508
  const uploadTokens = await this.apiService.requestBlockUpload(
503
509
  this.revisionDraft.nodeRevisionUid,
504
- this.revisionDraft.nodeKeys.signatureAddress.addressId,
510
+ this.revisionDraft.nodeKeys.signingKeys.addressId,
505
511
  {
506
512
  contentBlocks: [
507
513
  {
@@ -1,5 +1,6 @@
1
1
  import { getConfig } from './config';
2
2
  import { DriveCrypto, SessionKey } from './crypto';
3
+ import { NullFeatureFlagProvider } from './featureFlags';
3
4
  import {
4
5
  Logger,
5
6
  ProtonDriveClientContructorParameters,
@@ -105,7 +106,11 @@ export class ProtonDriveClient {
105
106
  * Experimental feature to authenticate a public link and
106
107
  * return the client for the public link to access it.
107
108
  */
108
- authPublicLink: (url: string, customPassword?: string) => Promise<ProtonDrivePublicLinkClient>;
109
+ authPublicLink: (
110
+ url: string,
111
+ customPassword?: string,
112
+ isAnonymousContext?: boolean,
113
+ ) => Promise<ProtonDrivePublicLinkClient>;
109
114
  };
110
115
 
111
116
  constructor({
@@ -117,11 +122,15 @@ export class ProtonDriveClient {
117
122
  srpModule,
118
123
  config,
119
124
  telemetry,
125
+ featureFlagProvider,
120
126
  latestEventIdProvider,
121
127
  }: ProtonDriveClientContructorParameters) {
122
128
  if (!telemetry) {
123
129
  telemetry = new Telemetry();
124
130
  }
131
+ if (!featureFlagProvider) {
132
+ featureFlagProvider = new NullFeatureFlagProvider();
133
+ }
125
134
  this.logger = telemetry.getLogger('interface');
126
135
 
127
136
  const fullConfig = getConfig(config);
@@ -217,7 +226,7 @@ export class ProtonDriveClient {
217
226
  this.logger.info(`Getting info for public link ${url}`);
218
227
  return this.publicSessionManager.getInfo(url);
219
228
  },
220
- authPublicLink: async (url: string, customPassword?: string) => {
229
+ authPublicLink: async (url: string, customPassword?: string, isAnonymousContext: boolean = false) => {
221
230
  this.logger.info(`Authenticating public link ${url}`);
222
231
  const { httpClient, token, shareKey, rootUid } = await this.publicSessionManager.auth(
223
232
  url,
@@ -234,6 +243,7 @@ export class ProtonDriveClient {
234
243
  token,
235
244
  publicShareKey: shareKey,
236
245
  publicRootNodeUid: rootUid,
246
+ isAnonymousContext,
237
247
  });
238
248
  },
239
249
  };
@@ -27,10 +27,9 @@ import {
27
27
  convertInternalMissingNodeIterator,
28
28
  getUids,
29
29
  } from './transformers';
30
- import { DriveAPIService } from './internal/apiService';
31
30
  import { initDownloadModule } from './internal/download';
32
31
  import { SDKEvents } from './internal/sdkEvents';
33
- import { initSharingPublicModule } from './internal/sharingPublic';
32
+ import { initSharingPublicModule, UnauthDriveAPIService } from './internal/sharingPublic';
34
33
  import { initUploadModule } from './internal/upload';
35
34
 
36
35
  /**
@@ -81,6 +80,7 @@ export class ProtonDrivePublicLinkClient {
81
80
  token,
82
81
  publicShareKey,
83
82
  publicRootNodeUid,
83
+ isAnonymousContext,
84
84
  }: {
85
85
  httpClient: ProtonDriveHTTPClient;
86
86
  account: ProtonDriveAccount;
@@ -92,6 +92,7 @@ export class ProtonDrivePublicLinkClient {
92
92
  token: string;
93
93
  publicShareKey: PrivateKey;
94
94
  publicRootNodeUid: string;
95
+ isAnonymousContext: boolean;
95
96
  }) {
96
97
  if (!telemetry) {
97
98
  telemetry = new Telemetry();
@@ -105,7 +106,7 @@ export class ProtonDrivePublicLinkClient {
105
106
  const fullConfig = getConfig(config);
106
107
  this.sdkEvents = new SDKEvents(telemetry);
107
108
 
108
- const apiService = new DriveAPIService(
109
+ const apiService = new UnauthDriveAPIService(
109
110
  telemetry,
110
111
  this.sdkEvents,
111
112
  httpClient,
@@ -124,6 +125,7 @@ export class ProtonDrivePublicLinkClient {
124
125
  token,
125
126
  publicShareKey,
126
127
  publicRootNodeUid,
128
+ isAnonymousContext,
127
129
  );
128
130
  this.download = initDownloadModule(
129
131
  telemetry,
@@ -133,6 +135,9 @@ export class ProtonDrivePublicLinkClient {
133
135
  this.sharingPublic.shares,
134
136
  this.sharingPublic.nodes.access,
135
137
  this.sharingPublic.nodes.revisions,
138
+ // Ignore manifest integrity verifications for public links.
139
+ // Anonymous user on public page cannot load public keys of other users (yet).
140
+ true,
136
141
  );
137
142
  this.upload = initUploadModule(
138
143
  telemetry,