@protontech/drive-sdk 0.4.0 → 0.5.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 (176) hide show
  1. package/dist/diagnostic/sdkDiagnostic.js +1 -1
  2. package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
  3. package/dist/interface/download.d.ts +4 -4
  4. package/dist/interface/nodes.d.ts +4 -0
  5. package/dist/interface/nodes.js.map +1 -1
  6. package/dist/interface/upload.d.ts +6 -3
  7. package/dist/internal/apiService/apiService.d.ts +3 -0
  8. package/dist/internal/apiService/apiService.js +25 -2
  9. package/dist/internal/apiService/apiService.js.map +1 -1
  10. package/dist/internal/apiService/apiService.test.js +38 -0
  11. package/dist/internal/apiService/apiService.test.js.map +1 -1
  12. package/dist/internal/apiService/driveTypes.d.ts +31 -48
  13. package/dist/internal/apiService/errors.js +3 -0
  14. package/dist/internal/apiService/errors.js.map +1 -1
  15. package/dist/internal/apiService/errors.test.js +15 -7
  16. package/dist/internal/apiService/errors.test.js.map +1 -1
  17. package/dist/internal/asyncIteratorMap.d.ts +1 -1
  18. package/dist/internal/asyncIteratorMap.js +6 -1
  19. package/dist/internal/asyncIteratorMap.js.map +1 -1
  20. package/dist/internal/asyncIteratorMap.test.js +9 -0
  21. package/dist/internal/asyncIteratorMap.test.js.map +1 -1
  22. package/dist/internal/download/fileDownloader.d.ts +3 -3
  23. package/dist/internal/download/fileDownloader.js +5 -5
  24. package/dist/internal/download/fileDownloader.js.map +1 -1
  25. package/dist/internal/download/fileDownloader.test.js +8 -8
  26. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  27. package/dist/internal/nodes/apiService.d.ts +6 -1
  28. package/dist/internal/nodes/apiService.js +45 -32
  29. package/dist/internal/nodes/apiService.js.map +1 -1
  30. package/dist/internal/nodes/apiService.test.js +164 -17
  31. package/dist/internal/nodes/apiService.test.js.map +1 -1
  32. package/dist/internal/nodes/cache.test.js +1 -0
  33. package/dist/internal/nodes/cache.test.js.map +1 -1
  34. package/dist/internal/nodes/debouncer.d.ts +23 -0
  35. package/dist/internal/nodes/debouncer.js +80 -0
  36. package/dist/internal/nodes/debouncer.js.map +1 -0
  37. package/dist/internal/nodes/debouncer.test.d.ts +1 -0
  38. package/dist/internal/nodes/debouncer.test.js +100 -0
  39. package/dist/internal/nodes/debouncer.test.js.map +1 -0
  40. package/dist/internal/nodes/extendedAttributes.d.ts +2 -2
  41. package/dist/internal/nodes/extendedAttributes.js +15 -11
  42. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  43. package/dist/internal/nodes/extendedAttributes.test.js +19 -1
  44. package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
  45. package/dist/internal/nodes/index.test.js +1 -0
  46. package/dist/internal/nodes/index.test.js.map +1 -1
  47. package/dist/internal/nodes/interface.d.ts +1 -0
  48. package/dist/internal/nodes/nodesAccess.d.ts +2 -1
  49. package/dist/internal/nodes/nodesAccess.js +24 -5
  50. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  51. package/dist/internal/nodes/nodesAccess.test.js +2 -2
  52. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  53. package/dist/internal/nodes/nodesManagement.js +1 -0
  54. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  55. package/dist/internal/photos/index.d.ts +11 -0
  56. package/dist/internal/photos/index.js +27 -0
  57. package/dist/internal/photos/index.js.map +1 -1
  58. package/dist/internal/photos/upload.d.ts +60 -0
  59. package/dist/internal/photos/upload.js +104 -0
  60. package/dist/internal/photos/upload.js.map +1 -0
  61. package/dist/internal/sharingPublic/apiService.d.ts +2 -2
  62. package/dist/internal/sharingPublic/apiService.js +2 -62
  63. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  64. package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -4
  65. package/dist/internal/sharingPublic/cryptoCache.js +0 -28
  66. package/dist/internal/sharingPublic/cryptoCache.js.map +1 -1
  67. package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
  68. package/dist/internal/sharingPublic/cryptoReporter.js +44 -0
  69. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
  70. package/dist/internal/sharingPublic/cryptoService.d.ts +3 -4
  71. package/dist/internal/sharingPublic/cryptoService.js +5 -43
  72. package/dist/internal/sharingPublic/cryptoService.js.map +1 -1
  73. package/dist/internal/sharingPublic/index.d.ts +21 -3
  74. package/dist/internal/sharingPublic/index.js +43 -12
  75. package/dist/internal/sharingPublic/index.js.map +1 -1
  76. package/dist/internal/sharingPublic/interface.d.ts +0 -1
  77. package/dist/internal/sharingPublic/nodes.d.ts +13 -0
  78. package/dist/internal/sharingPublic/nodes.js +28 -0
  79. package/dist/internal/sharingPublic/nodes.js.map +1 -0
  80. package/dist/internal/sharingPublic/session/session.d.ts +3 -3
  81. package/dist/internal/sharingPublic/session/url.test.js +3 -3
  82. package/dist/internal/sharingPublic/shares.d.ts +34 -0
  83. package/dist/internal/sharingPublic/shares.js +69 -0
  84. package/dist/internal/sharingPublic/shares.js.map +1 -0
  85. package/dist/internal/upload/apiService.d.ts +2 -2
  86. package/dist/internal/upload/apiService.js +11 -2
  87. package/dist/internal/upload/apiService.js.map +1 -1
  88. package/dist/internal/upload/controller.d.ts +8 -2
  89. package/dist/internal/upload/controller.js.map +1 -1
  90. package/dist/internal/upload/cryptoService.d.ts +2 -2
  91. package/dist/internal/upload/cryptoService.js.map +1 -1
  92. package/dist/internal/upload/fileUploader.d.ts +7 -3
  93. package/dist/internal/upload/fileUploader.js +6 -3
  94. package/dist/internal/upload/fileUploader.js.map +1 -1
  95. package/dist/internal/upload/fileUploader.test.js +23 -11
  96. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  97. package/dist/internal/upload/interface.d.ts +3 -0
  98. package/dist/internal/upload/manager.d.ts +12 -11
  99. package/dist/internal/upload/manager.js +8 -2
  100. package/dist/internal/upload/manager.js.map +1 -1
  101. package/dist/internal/upload/manager.test.js +8 -0
  102. package/dist/internal/upload/manager.test.js.map +1 -1
  103. package/dist/internal/upload/streamUploader.d.ts +40 -26
  104. package/dist/internal/upload/streamUploader.js +15 -8
  105. package/dist/internal/upload/streamUploader.js.map +1 -1
  106. package/dist/internal/upload/streamUploader.test.js +11 -7
  107. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  108. package/dist/protonDriveClient.d.ts +3 -3
  109. package/dist/protonDriveClient.js +4 -4
  110. package/dist/protonDriveClient.js.map +1 -1
  111. package/dist/protonDrivePhotosClient.d.ts +18 -2
  112. package/dist/protonDrivePhotosClient.js +19 -2
  113. package/dist/protonDrivePhotosClient.js.map +1 -1
  114. package/dist/protonDrivePublicLinkClient.d.ts +31 -4
  115. package/dist/protonDrivePublicLinkClient.js +52 -9
  116. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  117. package/dist/transformers.d.ts +1 -1
  118. package/dist/transformers.js +1 -0
  119. package/dist/transformers.js.map +1 -1
  120. package/package.json +1 -1
  121. package/src/diagnostic/sdkDiagnostic.ts +1 -1
  122. package/src/interface/download.ts +4 -4
  123. package/src/interface/nodes.ts +4 -0
  124. package/src/interface/upload.ts +3 -3
  125. package/src/internal/apiService/apiService.test.ts +50 -0
  126. package/src/internal/apiService/apiService.ts +33 -2
  127. package/src/internal/apiService/driveTypes.ts +31 -48
  128. package/src/internal/apiService/errors.test.ts +10 -0
  129. package/src/internal/apiService/errors.ts +5 -1
  130. package/src/internal/asyncIteratorMap.test.ts +12 -0
  131. package/src/internal/asyncIteratorMap.ts +8 -0
  132. package/src/internal/download/fileDownloader.test.ts +8 -8
  133. package/src/internal/download/fileDownloader.ts +5 -5
  134. package/src/internal/nodes/apiService.test.ts +222 -16
  135. package/src/internal/nodes/apiService.ts +63 -49
  136. package/src/internal/nodes/cache.test.ts +1 -0
  137. package/src/internal/nodes/debouncer.test.ts +129 -0
  138. package/src/internal/nodes/debouncer.ts +93 -0
  139. package/src/internal/nodes/extendedAttributes.test.ts +23 -1
  140. package/src/internal/nodes/extendedAttributes.ts +26 -18
  141. package/src/internal/nodes/index.test.ts +1 -0
  142. package/src/internal/nodes/interface.ts +1 -0
  143. package/src/internal/nodes/nodesAccess.test.ts +2 -2
  144. package/src/internal/nodes/nodesAccess.ts +30 -5
  145. package/src/internal/nodes/nodesManagement.ts +1 -0
  146. package/src/internal/photos/index.ts +62 -0
  147. package/src/internal/photos/upload.ts +212 -0
  148. package/src/internal/sharingPublic/apiService.ts +5 -86
  149. package/src/internal/sharingPublic/cryptoCache.ts +0 -34
  150. package/src/internal/sharingPublic/cryptoReporter.ts +73 -0
  151. package/src/internal/sharingPublic/cryptoService.ts +4 -80
  152. package/src/internal/sharingPublic/index.ts +68 -6
  153. package/src/internal/sharingPublic/interface.ts +0 -9
  154. package/src/internal/sharingPublic/nodes.ts +37 -0
  155. package/src/internal/sharingPublic/session/apiService.ts +1 -1
  156. package/src/internal/sharingPublic/session/session.ts +3 -3
  157. package/src/internal/sharingPublic/session/url.test.ts +3 -3
  158. package/src/internal/sharingPublic/shares.ts +86 -0
  159. package/src/internal/upload/apiService.ts +15 -4
  160. package/src/internal/upload/controller.ts +2 -2
  161. package/src/internal/upload/cryptoService.ts +2 -2
  162. package/src/internal/upload/fileUploader.test.ts +25 -11
  163. package/src/internal/upload/fileUploader.ts +16 -3
  164. package/src/internal/upload/interface.ts +3 -0
  165. package/src/internal/upload/manager.test.ts +8 -0
  166. package/src/internal/upload/manager.ts +20 -10
  167. package/src/internal/upload/streamUploader.test.ts +32 -15
  168. package/src/internal/upload/streamUploader.ts +43 -30
  169. package/src/protonDriveClient.ts +4 -4
  170. package/src/protonDrivePhotosClient.ts +46 -6
  171. package/src/protonDrivePublicLinkClient.ts +93 -12
  172. package/src/transformers.ts +2 -0
  173. package/dist/internal/sharingPublic/manager.d.ts +0 -19
  174. package/dist/internal/sharingPublic/manager.js +0 -81
  175. package/dist/internal/sharingPublic/manager.js.map +0 -1
  176. package/src/internal/sharingPublic/manager.ts +0 -86
@@ -1,15 +1,11 @@
1
1
  import { DriveAPIService, drivePaths, nodeTypeNumberToNodeType } from '../apiService';
2
2
  import { Logger, MemberRole } from '../../interface';
3
- import { makeNodeUid, splitNodeUid } from '../uids';
4
- import { EncryptedShareCrypto, EncryptedNode } from './interface';
5
-
6
- const PAGE_SIZE = 50;
3
+ import { makeNodeUid } from '../uids';
4
+ import { EncryptedNode } from '../nodes/interface';
5
+ import { EncryptedShareCrypto } from './interface';
7
6
 
8
7
  type GetTokenInfoResponse = drivePaths['/drive/urls/{token}']['get']['responses']['200']['content']['application/json'];
9
8
 
10
- type GetTokenFolderChildrenResponse =
11
- drivePaths['/drive/urls/{token}/folders/{linkID}/children']['get']['responses']['200']['content']['application/json'];
12
-
13
9
  /**
14
10
  * Provides API communication for accessing public link data.
15
11
  *
@@ -41,27 +37,6 @@ export class SharingPublicAPIService {
41
37
  },
42
38
  };
43
39
  }
44
-
45
- async *iterateFolderChildren(parentUid: string, signal?: AbortSignal): AsyncGenerator<EncryptedNode> {
46
- const { volumeId: token, nodeId } = splitNodeUid(parentUid);
47
-
48
- let page = 0;
49
- while (true) {
50
- const response = await this.apiService.get<GetTokenFolderChildrenResponse>(
51
- `drive/urls/${token}/folders/${nodeId}/children?Page=${page}&PageSize=${PAGE_SIZE}`,
52
- signal,
53
- );
54
-
55
- for (const link of response.Links) {
56
- yield linkToEncryptedNode(this.logger, token, link);
57
- }
58
-
59
- if (response.Links.length < PAGE_SIZE) {
60
- break;
61
- }
62
- page++;
63
- }
64
- }
65
40
  }
66
41
 
67
42
  function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token']): EncryptedNode {
@@ -70,12 +45,13 @@ function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token
70
45
  encryptedName: token.Name,
71
46
 
72
47
  // Basic node metadata
73
- uid: makeNodeUid(token.Token, token.LinkID),
48
+ uid: makeNodeUid(token.VolumeID, token.LinkID),
74
49
  parentUid: undefined,
75
50
  type: nodeTypeNumberToNodeType(logger, token.LinkType),
76
51
  creationTime: new Date(), // TODO
77
52
 
78
53
  isShared: false,
54
+ isSharedPublicly: false,
79
55
  directRole: MemberRole.Viewer, // TODO
80
56
  };
81
57
 
@@ -114,60 +90,3 @@ function tokenToEncryptedNode(logger: Logger, token: GetTokenInfoResponse['Token
114
90
 
115
91
  throw new Error(`Unknown node type: ${token.LinkType}`);
116
92
  }
117
-
118
- function linkToEncryptedNode(
119
- logger: Logger,
120
- token: string,
121
- link: GetTokenFolderChildrenResponse['Links'][0],
122
- ): EncryptedNode {
123
- const baseNodeMetadata = {
124
- // Internal metadata
125
- hash: link.Hash || undefined,
126
- encryptedName: link.Name,
127
-
128
- // Basic node metadata
129
- uid: makeNodeUid(token, link.LinkID),
130
- parentUid: link.ParentLinkID ? makeNodeUid(token, link.ParentLinkID) : undefined,
131
- type: nodeTypeNumberToNodeType(logger, link.Type),
132
- creationTime: new Date(), // TODO
133
- totalStorageSize: link.TotalSize,
134
-
135
- isShared: false,
136
- directRole: MemberRole.Viewer, // TODO
137
- };
138
-
139
- const baseCryptoNodeMetadata = {
140
- signatureEmail: link.SignatureEmail || undefined,
141
- armoredKey: link.NodeKey,
142
- armoredNodePassphrase: link.NodePassphrase,
143
- armoredNodePassphraseSignature: link.NodePassphraseSignature || undefined,
144
- };
145
-
146
- if (link.Type === 1 && link.FolderProperties) {
147
- return {
148
- ...baseNodeMetadata,
149
- encryptedCrypto: {
150
- ...baseCryptoNodeMetadata,
151
- folder: {
152
- armoredHashKey: link.FolderProperties.NodeHashKey as string,
153
- },
154
- },
155
- };
156
- }
157
-
158
- if (link.Type === 2 && link.FileProperties?.ContentKeyPacket) {
159
- return {
160
- ...baseNodeMetadata,
161
- totalStorageSize: link.FileProperties.ActiveRevision?.Size || undefined,
162
- mediaType: link.MIMEType || undefined,
163
- encryptedCrypto: {
164
- ...baseCryptoNodeMetadata,
165
- file: {
166
- base64ContentKeyPacket: link.FileProperties.ContentKeyPacket,
167
- },
168
- },
169
- };
170
- }
171
-
172
- throw new Error(`Unknown node type: ${link.Type}`);
173
- }
@@ -1,6 +1,5 @@
1
1
  import { PrivateKey } from '../../crypto';
2
2
  import { ProtonDriveCryptoCache, Logger } from '../../interface';
3
- import { DecryptedNodeKeys } from './interface';
4
3
 
5
4
  /**
6
5
  * Provides caching for public link crypto material.
@@ -39,41 +38,8 @@ export class SharingPublicCryptoCache {
39
38
  }
40
39
  return shareKeyData.publicShareKey.key;
41
40
  }
42
-
43
- async setNodeKeys(nodeUid: string, keys: DecryptedNodeKeys): Promise<void> {
44
- const cacheUid = getNodeCacheKey(nodeUid);
45
- await this.driveCache.setEntity(cacheUid, {
46
- nodeKeys: keys,
47
- });
48
- }
49
-
50
- async getNodeKeys(nodeUid: string): Promise<DecryptedNodeKeys> {
51
- const nodeKeysData = await this.driveCache.getEntity(getNodeCacheKey(nodeUid));
52
- if (!nodeKeysData.nodeKeys) {
53
- try {
54
- await this.removeNodeKeys([nodeUid]);
55
- } catch (removingError: unknown) {
56
- // The node keys will not be returned, thus SDK will re-fetch
57
- // and re-cache it. Setting it again should then fix the problem.
58
- this.logger.warn(
59
- `Failed to remove corrupted public node keys from the cache: ${removingError instanceof Error ? removingError.message : removingError}`,
60
- );
61
- }
62
- throw new Error(`Failed to deserialize public node keys`);
63
- }
64
- return nodeKeysData.nodeKeys;
65
- }
66
-
67
- async removeNodeKeys(nodeUids: string[]): Promise<void> {
68
- const cacheUids = nodeUids.map(getNodeCacheKey);
69
- await this.driveCache.removeEntities(cacheUids);
70
- }
71
41
  }
72
42
 
73
43
  function getShareKeyCacheKey() {
74
44
  return 'publicShareKey';
75
45
  }
76
-
77
- function getNodeCacheKey(nodeUid: string) {
78
- return `publicNodeKeys-${nodeUid}`;
79
- }
@@ -0,0 +1,73 @@
1
+ import { c } from 'ttag';
2
+
3
+ import { VERIFICATION_STATUS } from '../../crypto';
4
+ import { getVerificationMessage } from '../errors';
5
+ import {
6
+ resultOk,
7
+ resultError,
8
+ Author,
9
+ AnonymousUser,
10
+ ProtonDriveTelemetry,
11
+ MetricVerificationErrorField,
12
+ MetricVolumeType,
13
+ MetricsDecryptionErrorField,
14
+ Logger,
15
+ } from '../../interface';
16
+
17
+ export class SharingPublicCryptoReporter {
18
+ private logger: Logger;
19
+ private telemetry: ProtonDriveTelemetry;
20
+
21
+ constructor(telemetry: ProtonDriveTelemetry) {
22
+ this.telemetry = telemetry;
23
+ this.logger = telemetry.getLogger('sharingPublic-crypto');
24
+ }
25
+
26
+ async handleClaimedAuthor(
27
+ node: { uid: string; creationTime: Date },
28
+ field: MetricVerificationErrorField,
29
+ signatureType: string,
30
+ verified: VERIFICATION_STATUS,
31
+ verificationErrors?: Error[],
32
+ claimedAuthor?: string,
33
+ notAvailableVerificationKeys = false,
34
+ ): Promise<Author> {
35
+ if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
36
+ return resultOk(claimedAuthor || (null as AnonymousUser));
37
+ }
38
+
39
+ return resultError({
40
+ claimedAuthor,
41
+ error: !claimedAuthor
42
+ ? c('Info').t`Author is not provided on public link`
43
+ : getVerificationMessage(verified, verificationErrors, signatureType, notAvailableVerificationKeys),
44
+ });
45
+ }
46
+
47
+ reportDecryptionError(
48
+ node: { uid: string; creationTime: Date },
49
+ field: MetricsDecryptionErrorField,
50
+ error: unknown,
51
+ ) {
52
+ const fromBefore2024 = node.creationTime < new Date('2024-01-01');
53
+
54
+ this.logger.error(
55
+ `Failed to decrypt public link node ${node.uid} (from before 2024: ${fromBefore2024})`,
56
+ error,
57
+ );
58
+
59
+ this.telemetry.recordMetric({
60
+ eventName: 'decryptionError',
61
+ volumeType: MetricVolumeType.SharedPublic,
62
+ field,
63
+ fromBefore2024,
64
+ error,
65
+ uid: node.uid,
66
+ });
67
+ }
68
+
69
+ reportVerificationError() {
70
+ // Authors or signatures are not provided on public links.
71
+ // We do not report any signature verification errors at this moment.
72
+ }
73
+ }
@@ -1,30 +1,12 @@
1
- import { c } from 'ttag';
2
-
3
- import { DriveCrypto, PrivateKey, VERIFICATION_STATUS } from '../../crypto';
4
- import { getVerificationMessage } from '../errors';
5
- import {
6
- resultOk,
7
- resultError,
8
- Author,
9
- AnonymousUser,
10
- ProtonDriveTelemetry,
11
- MetricVerificationErrorField,
12
- MetricVolumeType,
13
- MetricsDecryptionErrorField,
14
- Logger,
15
- ProtonDriveAccount,
16
- } from '../../interface';
17
- import { NodesCryptoService } from '../nodes/cryptoService';
1
+ import { DriveCrypto, PrivateKey } from '../../crypto';
18
2
  import { EncryptedShareCrypto } from './interface';
19
3
 
20
- export class SharingPublicCryptoService extends NodesCryptoService {
4
+ export class SharingPublicCryptoService {
21
5
  constructor(
22
- telemetry: ProtonDriveTelemetry,
23
- driveCrypto: DriveCrypto,
24
- account: ProtonDriveAccount,
6
+ private driveCrypto: DriveCrypto,
25
7
  private password: string,
26
8
  ) {
27
- super(telemetry, driveCrypto, account, new SharingPublicCryptoReporter(telemetry));
9
+ this.driveCrypto = driveCrypto;
28
10
  this.password = password;
29
11
  }
30
12
 
@@ -38,61 +20,3 @@ export class SharingPublicCryptoService extends NodesCryptoService {
38
20
  return shareKey;
39
21
  }
40
22
  }
41
-
42
- class SharingPublicCryptoReporter {
43
- private logger: Logger;
44
- private telemetry: ProtonDriveTelemetry;
45
-
46
- constructor(telemetry: ProtonDriveTelemetry) {
47
- this.telemetry = telemetry;
48
- this.logger = telemetry.getLogger('sharingPublic-crypto');
49
- }
50
-
51
- async handleClaimedAuthor(
52
- node: { uid: string; creationTime: Date },
53
- field: MetricVerificationErrorField,
54
- signatureType: string,
55
- verified: VERIFICATION_STATUS,
56
- verificationErrors?: Error[],
57
- claimedAuthor?: string,
58
- notAvailableVerificationKeys = false,
59
- ): Promise<Author> {
60
- if (verified === VERIFICATION_STATUS.SIGNED_AND_VALID) {
61
- return resultOk(claimedAuthor || (null as AnonymousUser));
62
- }
63
-
64
- return resultError({
65
- claimedAuthor,
66
- error: !claimedAuthor
67
- ? c('Info').t`Author is not provided on public link`
68
- : getVerificationMessage(verified, verificationErrors, signatureType, notAvailableVerificationKeys),
69
- });
70
- }
71
-
72
- reportDecryptionError(
73
- node: { uid: string; creationTime: Date },
74
- field: MetricsDecryptionErrorField,
75
- error: unknown,
76
- ) {
77
- const fromBefore2024 = node.creationTime < new Date('2024-01-01');
78
-
79
- this.logger.error(
80
- `Failed to decrypt public link node ${node.uid} (from before 2024: ${fromBefore2024})`,
81
- error,
82
- );
83
-
84
- this.telemetry.recordMetric({
85
- eventName: 'decryptionError',
86
- volumeType: MetricVolumeType.SharedPublic,
87
- field,
88
- fromBefore2024,
89
- error,
90
- uid: node.uid,
91
- });
92
- }
93
-
94
- reportVerificationError() {
95
- // Authors or signatures are not provided on public links.
96
- // We do not report any signature verification errors at this moment.
97
- }
98
- }
@@ -1,10 +1,22 @@
1
1
  import { DriveCrypto } from '../../crypto';
2
- import { ProtonDriveCryptoCache, ProtonDriveTelemetry, ProtonDriveAccount } from '../../interface';
2
+ import {
3
+ ProtonDriveCryptoCache,
4
+ ProtonDriveTelemetry,
5
+ ProtonDriveAccount,
6
+ ProtonDriveEntitiesCache,
7
+ } from '../../interface';
3
8
  import { DriveAPIService } from '../apiService';
9
+ import { NodeAPIService } from '../nodes/apiService';
10
+ import { NodesCache } from '../nodes/cache';
11
+ import { NodesCryptoCache } from '../nodes/cryptoCache';
12
+ import { NodesCryptoService } from '../nodes/cryptoService';
13
+ import { NodesRevisons } from '../nodes/nodesRevisions';
4
14
  import { SharingPublicAPIService } from './apiService';
5
15
  import { SharingPublicCryptoCache } from './cryptoCache';
16
+ import { SharingPublicCryptoReporter } from './cryptoReporter';
6
17
  import { SharingPublicCryptoService } from './cryptoService';
7
- import { SharingPublicManager } from './manager';
18
+ import { SharingPublicNodesAccess } from './nodes';
19
+ import { SharingPublicSharesManager } from './shares';
8
20
 
9
21
  export { SharingPublicSessionManager } from './session/manager';
10
22
 
@@ -20,22 +32,72 @@ export { SharingPublicSessionManager } from './session/manager';
20
32
  export function initSharingPublicModule(
21
33
  telemetry: ProtonDriveTelemetry,
22
34
  apiService: DriveAPIService,
35
+ driveEntitiesCache: ProtonDriveEntitiesCache,
23
36
  driveCryptoCache: ProtonDriveCryptoCache,
24
37
  driveCrypto: DriveCrypto,
25
38
  account: ProtonDriveAccount,
39
+ url: string,
26
40
  token: string,
27
41
  password: string,
28
42
  ) {
29
43
  const api = new SharingPublicAPIService(telemetry.getLogger('sharingPublic-api'), apiService);
30
44
  const cryptoCache = new SharingPublicCryptoCache(telemetry.getLogger('sharingPublic-crypto'), driveCryptoCache);
31
- const cryptoService = new SharingPublicCryptoService(telemetry, driveCrypto, account, password);
32
- const manager = new SharingPublicManager(
33
- telemetry.getLogger('sharingPublic-nodes'),
45
+ const cryptoService = new SharingPublicCryptoService(driveCrypto, password);
46
+ const shares = new SharingPublicSharesManager(api, cryptoCache, cryptoService, account, token);
47
+ const nodes = initSharingPublicNodesModule(
48
+ telemetry,
49
+ apiService,
50
+ driveEntitiesCache,
51
+ driveCryptoCache,
52
+ driveCrypto,
53
+ account,
54
+ shares,
55
+ url,
56
+ token,
57
+ );
58
+
59
+ return {
60
+ shares,
61
+ nodes,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Provides facade for the public link nodes module.
67
+ *
68
+ * The public link nodes initializes the core nodes module, but uses public
69
+ * link shares or crypto reporter instead.
70
+ */
71
+ export function initSharingPublicNodesModule(
72
+ telemetry: ProtonDriveTelemetry,
73
+ apiService: DriveAPIService,
74
+ driveEntitiesCache: ProtonDriveEntitiesCache,
75
+ driveCryptoCache: ProtonDriveCryptoCache,
76
+ driveCrypto: DriveCrypto,
77
+ account: ProtonDriveAccount,
78
+ sharesService: SharingPublicSharesManager,
79
+ url: string,
80
+ token: string,
81
+ ) {
82
+ const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService);
83
+ const cache = new NodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
84
+ const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
85
+ const cryptoReporter = new SharingPublicCryptoReporter(telemetry);
86
+ const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, cryptoReporter);
87
+ const nodesAccess = new SharingPublicNodesAccess(
88
+ telemetry.getLogger('nodes'),
34
89
  api,
90
+ cache,
35
91
  cryptoCache,
36
92
  cryptoService,
93
+ sharesService,
94
+ url,
37
95
  token,
38
96
  );
97
+ const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
39
98
 
40
- return manager;
99
+ return {
100
+ access: nodesAccess,
101
+ revisions: nodesRevisions,
102
+ };
41
103
  }
@@ -1,12 +1,3 @@
1
- // TODO: use them directly, or avoid them completely
2
- export type {
3
- EncryptedNode,
4
- EncryptedNodeFolderCrypto,
5
- EncryptedNodeFileCrypto,
6
- DecryptedNode,
7
- DecryptedNodeKeys,
8
- } from '../nodes/interface';
9
-
10
1
  export interface EncryptedShareCrypto {
11
2
  base64UrlPasswordSalt: string;
12
3
  armoredKey: string;
@@ -0,0 +1,37 @@
1
+ import { Logger } from "../../interface";
2
+ import { NodeAPIService } from "../nodes/apiService";
3
+ import { NodesCache } from "../nodes/cache";
4
+ import { NodesCryptoCache } from "../nodes/cryptoCache";
5
+ import { NodesCryptoService } from "../nodes/cryptoService";
6
+ import { NodesAccess } from "../nodes/nodesAccess";
7
+ import { isProtonDocument, isProtonSheet } from "../nodes/mediaTypes";
8
+ import { splitNodeUid } from "../uids";
9
+ import { SharingPublicSharesManager } from "./shares";
10
+
11
+ export class SharingPublicNodesAccess extends NodesAccess {
12
+ constructor(
13
+ logger: Logger,
14
+ apiService: NodeAPIService,
15
+ cache: NodesCache,
16
+ cryptoCache: NodesCryptoCache,
17
+ cryptoService: NodesCryptoService,
18
+ sharesService: SharingPublicSharesManager,
19
+ private url: string,
20
+ private token: string,
21
+ ) {
22
+ super(logger, apiService, cache, cryptoCache, cryptoService, sharesService);
23
+ this.token = token;
24
+ }
25
+
26
+ async getNodeUrl(nodeUid: string): Promise<string> {
27
+ const node = await this.getNode(nodeUid);
28
+ if (isProtonDocument(node.mediaType) || isProtonSheet(node.mediaType)) {
29
+ const { nodeId } = splitNodeUid(nodeUid);
30
+ const type = isProtonDocument(node.mediaType) ? 'doc' : 'sheet';
31
+ return `https://docs.proton.me/doc?type=${type}&mode=open-url&token=${this.token}&linkId=${nodeId}`;
32
+ }
33
+
34
+ // Public link doesn't support specific node URLs.
35
+ return this.url;
36
+ }
37
+ }
@@ -9,7 +9,7 @@ type PostPublicLinkAuthRequest = Extract<
9
9
  { content: object }
10
10
  >['content']['application/json'];
11
11
  type PostPublicLinkAuthResponse =
12
- drivePaths['/drive/urls/{token}/auth']['post']['responses']['200']['content']['application/json'];
12
+ drivePaths['/drive/urls/{token}/auth']['post']['responses']['200']['content']['application/json'];
13
13
 
14
14
  /**
15
15
  * Provides API communication for managing public link session (not data).
@@ -1,6 +1,6 @@
1
- import { SRPModule } from "../../../crypto";
2
- import { SharingPublicSessionAPIService } from "./apiService";
3
- import { PublicLinkInfo, PublicLinkSrpInfo } from "./interface";
1
+ import { SRPModule } from '../../../crypto';
2
+ import { SharingPublicSessionAPIService } from './apiService';
3
+ import { PublicLinkInfo, PublicLinkSrpInfo } from './interface';
4
4
 
5
5
  /**
6
6
  * Session for a public link.
@@ -9,7 +9,7 @@ describe('getTokenAndPasswordFromUrl', () => {
9
9
 
10
10
  expect(result).toEqual({
11
11
  token: 'abc123',
12
- password: 'def456'
12
+ password: 'def456',
13
13
  });
14
14
  });
15
15
 
@@ -19,7 +19,7 @@ describe('getTokenAndPasswordFromUrl', () => {
19
19
 
20
20
  expect(result).toEqual({
21
21
  token: 'mytoken',
22
- password: 'mypassword'
22
+ password: 'mypassword',
23
23
  });
24
24
  });
25
25
 
@@ -29,7 +29,7 @@ describe('getTokenAndPasswordFromUrl', () => {
29
29
 
30
30
  expect(result).toEqual({
31
31
  token: 'token123',
32
- password: 'password456'
32
+ password: 'password456',
33
33
  });
34
34
  });
35
35
  });
@@ -0,0 +1,86 @@
1
+ import { PrivateKey } from '../../crypto';
2
+ import { MetricVolumeType, ProtonDriveAccount } from '../../interface';
3
+ import { splitNodeUid } from '../uids';
4
+ import { SharingPublicAPIService } from './apiService';
5
+ import { SharingPublicCryptoCache } from './cryptoCache';
6
+ import { SharingPublicCryptoService } from './cryptoService';
7
+
8
+ /**
9
+ * Provides high-level actions for managing public link share.
10
+ *
11
+ * The public link share manager provides the same interface as the code share
12
+ * service so it can be used in the same way in various modules that use shares.
13
+ */
14
+ export class SharingPublicSharesManager {
15
+ private promisePublicLinkRoot?: Promise<{
16
+ rootIds: { volumeId: string; rootNodeId: string; rootNodeUid: string };
17
+ shareKey: PrivateKey;
18
+ }>;
19
+
20
+ constructor(
21
+ private apiService: SharingPublicAPIService,
22
+ private cryptoCache: SharingPublicCryptoCache,
23
+ private cryptoService: SharingPublicCryptoService,
24
+ private account: ProtonDriveAccount,
25
+ private token: string,
26
+ ) {
27
+ this.apiService = apiService;
28
+ this.cryptoCache = cryptoCache;
29
+ this.cryptoService = cryptoService;
30
+ this.account = account;
31
+ this.token = token;
32
+ }
33
+
34
+ // TODO: Rename to getRootIDs everywhere.
35
+ async getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string; rootNodeUid: string }> {
36
+ const { rootIds } = await this.getPublicLinkRoot();
37
+ return rootIds;
38
+ }
39
+
40
+ async getSharePrivateKey(): Promise<PrivateKey> {
41
+ const { shareKey } = await this.getPublicLinkRoot();
42
+ return shareKey;
43
+ }
44
+
45
+ private async getPublicLinkRoot(): Promise<{
46
+ rootIds: { volumeId: string; rootNodeId: string; rootNodeUid: string };
47
+ shareKey: PrivateKey;
48
+ }> {
49
+ if (!this.promisePublicLinkRoot) {
50
+ this.promisePublicLinkRoot = (async () => {
51
+ const { encryptedNode, encryptedShare } = await this.apiService.getPublicLinkRoot(this.token);
52
+
53
+ const { volumeId, nodeId: rootNodeId } = splitNodeUid(encryptedNode.uid);
54
+
55
+ const shareKey = await this.cryptoService.decryptPublicLinkShareKey(encryptedShare);
56
+ await this.cryptoCache.setShareKey(shareKey);
57
+
58
+ return {
59
+ rootIds: { volumeId, rootNodeId, rootNodeUid: encryptedNode.uid },
60
+ shareKey,
61
+ };
62
+ })();
63
+ }
64
+
65
+ return this.promisePublicLinkRoot;
66
+ }
67
+
68
+ async getContextShareMemberEmailKey(): Promise<{
69
+ email: string;
70
+ addressId: string;
71
+ addressKey: PrivateKey;
72
+ addressKeyId: string;
73
+ }> {
74
+ const address = await this.account.getOwnPrimaryAddress();
75
+ return {
76
+ email: address.email,
77
+ addressId: address.addressId,
78
+ addressKey: address.keys[address.primaryKeyIndex].key,
79
+ addressKeyId: address.keys[address.primaryKeyIndex].id,
80
+ };
81
+ }
82
+
83
+ async getVolumeMetricContext(): Promise<MetricVolumeType> {
84
+ return MetricVolumeType.SharedPublic;
85
+ }
86
+ }
@@ -53,8 +53,8 @@ type PostDeleteNodesResponse =
53
53
 
54
54
  export class UploadAPIService {
55
55
  constructor(
56
- private apiService: DriveAPIService,
57
- private clientUid: string | undefined,
56
+ protected apiService: DriveAPIService,
57
+ protected clientUid: string | undefined,
58
58
  ) {
59
59
  this.apiService = apiService;
60
60
  this.clientUid = clientUid;
@@ -110,6 +110,17 @@ export class UploadAPIService {
110
110
  nodeUid: string;
111
111
  nodeRevisionUid: string;
112
112
  }> {
113
+ // The client shouldn't send the clear text size of the file.
114
+ // The intented upload size is needed only for early validation that
115
+ // the file can fit in the remaining quota to avoid data transfer when
116
+ // the upload would be rejected. The backend will still validate
117
+ // the quota during block upload and revision commit.
118
+ const precision = 100_000; // bytes
119
+ const intendedUploadSize =
120
+ node.intendedUploadSize && node.intendedUploadSize > precision
121
+ ? Math.floor(node.intendedUploadSize / precision) * precision
122
+ : null;
123
+
113
124
  const { volumeId, nodeId: parentNodeId } = splitNodeUid(parentNodeUid);
114
125
  const result = await this.apiService.post<PostCreateDraftRequest, PostCreateDraftResponse>(
115
126
  `drive/v2/volumes/${volumeId}/files`,
@@ -119,7 +130,7 @@ export class UploadAPIService {
119
130
  Hash: node.hash,
120
131
  MIMEType: node.mediaType,
121
132
  ClientUID: this.clientUid || null,
122
- IntendedUploadSize: node.intendedUploadSize || null,
133
+ IntendedUploadSize: intendedUploadSize,
123
134
  NodeKey: node.armoredNodeKey,
124
135
  NodePassphrase: node.armoredNodePassphrase,
125
136
  NodePassphraseSignature: node.armoredNodePassphraseSignature,
@@ -252,7 +263,7 @@ export class UploadAPIService {
252
263
  ManifestSignature: options.armoredManifestSignature,
253
264
  SignatureAddress: options.signatureEmail,
254
265
  XAttr: options.armoredExtendedAttributes || null,
255
- Photo: null, // TODO
266
+ Photo: null, // Only used for photos in the Photo volume.
256
267
  });
257
268
  }
258
269