@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
@@ -0,0 +1,29 @@
1
+ import { getUnauthEndpoint } from './unauthApiService';
2
+
3
+ describe('getUnauthEndpoint', () => {
4
+ it('should not change urls endpoints', () => {
5
+ expect(getUnauthEndpoint('drive/urls/anything')).toBe('drive/urls/anything');
6
+ expect(getUnauthEndpoint('drive/urls/drive/anything')).toBe('drive/urls/drive/anything');
7
+ expect(getUnauthEndpoint('drive/urls/drive/v2/anything')).toBe('drive/urls/drive/v2/anything');
8
+ });
9
+
10
+ it('should not change v2/urls endpoints', () => {
11
+ expect(getUnauthEndpoint('drive/v2/urls/anything')).toBe('drive/v2/urls/anything');
12
+ expect(getUnauthEndpoint('drive/v2/urls/drive/anything')).toBe('drive/v2/urls/drive/anything');
13
+ expect(getUnauthEndpoint('drive/v2/urls/drive/v2/anything')).toBe('drive/v2/urls/drive/v2/anything');
14
+ });
15
+
16
+ it('should put unauth prefix for v2 endpoints', () => {
17
+ expect(getUnauthEndpoint('drive/v2/anything')).toBe('drive/unauth/v2/anything');
18
+ expect(getUnauthEndpoint('drive/v2/drive/anything')).toBe('drive/unauth/v2/drive/anything');
19
+ expect(getUnauthEndpoint('drive/v2/drive/v2/anything')).toBe('drive/unauth/v2/drive/v2/anything');
20
+ });
21
+
22
+ it('should put unauth prefix for non-v2 endpoints', () => {
23
+ expect(getUnauthEndpoint('drive/anything')).toBe('drive/unauth/anything');
24
+ expect(getUnauthEndpoint('drive/anything/v2/anything')).toBe('drive/unauth/anything/v2/anything');
25
+ expect(getUnauthEndpoint('drive/anything/drive/anything')).toBe('drive/unauth/anything/drive/anything');
26
+ expect(getUnauthEndpoint('drive/anything/drive/v2/anything')).toBe('drive/unauth/anything/drive/v2/anything');
27
+ });
28
+ });
29
+
@@ -0,0 +1,32 @@
1
+ import { DriveAPIService } from '../apiService';
2
+
3
+ /**
4
+ * Drive API Service for public links.
5
+ *
6
+ * This service is used to make requests to the Drive API without
7
+ * authentication. The unauth context uses the same endpoint, but
8
+ * with an `unauth` prefix. The goal is to avoid the need to use
9
+ * different path and use the exact endpoint for both contexts.
10
+ * However, API has global logic for handling expired sessions that
11
+ * is not compatible with the unauth context. For this reason, this
12
+ * service is used to make requests to the Drive API for public
13
+ * link context in the mean time.
14
+ */
15
+ export class UnauthDriveAPIService extends DriveAPIService {
16
+ protected async makeRequest<RequestPayload, ResponsePayload>(
17
+ url: string,
18
+ method = 'GET',
19
+ data?: RequestPayload,
20
+ signal?: AbortSignal,
21
+ ): Promise<ResponsePayload> {
22
+ const unauthUrl = getUnauthEndpoint(url);
23
+ return super.makeRequest(unauthUrl, method, data, signal);
24
+ }
25
+ }
26
+
27
+ export function getUnauthEndpoint(url: string): string {
28
+ if (url.startsWith('drive/urls/') || url.startsWith('drive/v2/urls/')) {
29
+ return url;
30
+ }
31
+ return url.replace(/^drive\//, 'drive/unauth/');
32
+ }
@@ -1,6 +1,7 @@
1
1
  import { c } from 'ttag';
2
2
 
3
3
  import { base64StringToUint8Array, uint8ArrayToBase64String } from '../../crypto';
4
+ import { AnonymousUser } from '../../interface';
4
5
  import { APICodeError, DriveAPIService, drivePaths, isCodeOk } from '../apiService';
5
6
  import { splitNodeUid, makeNodeUid, splitNodeRevisionUid, makeNodeRevisionUid } from '../uids';
6
7
  import { UploadTokens } from './interface';
@@ -65,7 +66,7 @@ export class UploadAPIService {
65
66
  armoredNodePassphraseSignature: string;
66
67
  base64ContentKeyPacket: string;
67
68
  armoredContentKeyPacketSignature: string;
68
- signatureEmail: string;
69
+ signatureEmail: string | AnonymousUser;
69
70
  },
70
71
  ): Promise<{
71
72
  nodeUid: string;
@@ -150,7 +151,7 @@ export class UploadAPIService {
150
151
 
151
152
  async requestBlockUpload(
152
153
  draftNodeRevisionUid: string,
153
- addressId: string,
154
+ addressId: string | AnonymousUser,
154
155
  blocks: {
155
156
  contentBlocks: {
156
157
  index: number;
@@ -211,7 +212,7 @@ export class UploadAPIService {
211
212
  draftNodeRevisionUid: string,
212
213
  options: {
213
214
  armoredManifestSignature: string;
214
- signatureEmail: string;
215
+ signatureEmail: string | AnonymousUser;
215
216
  armoredExtendedAttributes?: string;
216
217
  },
217
218
  ): Promise<void> {
@@ -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
 
@@ -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
  {
@@ -106,7 +106,11 @@ export class ProtonDriveClient {
106
106
  * Experimental feature to authenticate a public link and
107
107
  * return the client for the public link to access it.
108
108
  */
109
- authPublicLink: (url: string, customPassword?: string) => Promise<ProtonDrivePublicLinkClient>;
109
+ authPublicLink: (
110
+ url: string,
111
+ customPassword?: string,
112
+ isAnonymousContext?: boolean,
113
+ ) => Promise<ProtonDrivePublicLinkClient>;
110
114
  };
111
115
 
112
116
  constructor({
@@ -222,7 +226,7 @@ export class ProtonDriveClient {
222
226
  this.logger.info(`Getting info for public link ${url}`);
223
227
  return this.publicSessionManager.getInfo(url);
224
228
  },
225
- authPublicLink: async (url: string, customPassword?: string) => {
229
+ authPublicLink: async (url: string, customPassword?: string, isAnonymousContext: boolean = false) => {
226
230
  this.logger.info(`Authenticating public link ${url}`);
227
231
  const { httpClient, token, shareKey, rootUid } = await this.publicSessionManager.auth(
228
232
  url,
@@ -239,6 +243,7 @@ export class ProtonDriveClient {
239
243
  token,
240
244
  publicShareKey: shareKey,
241
245
  publicRootNodeUid: rootUid,
246
+ isAnonymousContext,
242
247
  });
243
248
  },
244
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,