@protontech/drive-sdk 0.4.1 → 0.5.1

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 (197) hide show
  1. package/dist/diagnostic/{sdkDiagnosticFull.d.ts → diagnostic.d.ts} +5 -4
  2. package/dist/diagnostic/{sdkDiagnosticFull.js → diagnostic.js} +13 -10
  3. package/dist/diagnostic/diagnostic.js.map +1 -0
  4. package/dist/diagnostic/index.js +2 -4
  5. package/dist/diagnostic/index.js.map +1 -1
  6. package/dist/diagnostic/interface.d.ts +22 -1
  7. package/dist/diagnostic/sdkDiagnostic.d.ts +3 -2
  8. package/dist/diagnostic/sdkDiagnostic.js +80 -8
  9. package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
  10. package/dist/interface/download.d.ts +4 -4
  11. package/dist/interface/index.d.ts +1 -1
  12. package/dist/interface/index.js.map +1 -1
  13. package/dist/interface/nodes.d.ts +9 -0
  14. package/dist/interface/telemetry.d.ts +4 -1
  15. package/dist/interface/telemetry.js.map +1 -1
  16. package/dist/interface/upload.d.ts +6 -3
  17. package/dist/internal/apiService/apiService.d.ts +3 -0
  18. package/dist/internal/apiService/apiService.js +25 -2
  19. package/dist/internal/apiService/apiService.js.map +1 -1
  20. package/dist/internal/apiService/apiService.test.js +38 -0
  21. package/dist/internal/apiService/apiService.test.js.map +1 -1
  22. package/dist/internal/apiService/driveTypes.d.ts +2595 -2397
  23. package/dist/internal/apiService/errors.js +3 -0
  24. package/dist/internal/apiService/errors.js.map +1 -1
  25. package/dist/internal/apiService/errors.test.js +15 -7
  26. package/dist/internal/apiService/errors.test.js.map +1 -1
  27. package/dist/internal/asyncIteratorMap.d.ts +1 -1
  28. package/dist/internal/asyncIteratorMap.js +6 -1
  29. package/dist/internal/asyncIteratorMap.js.map +1 -1
  30. package/dist/internal/asyncIteratorMap.test.js +9 -0
  31. package/dist/internal/asyncIteratorMap.test.js.map +1 -1
  32. package/dist/internal/download/controller.d.ts +2 -0
  33. package/dist/internal/download/controller.js +15 -1
  34. package/dist/internal/download/controller.js.map +1 -1
  35. package/dist/internal/download/fileDownloader.d.ts +3 -3
  36. package/dist/internal/download/fileDownloader.js +11 -6
  37. package/dist/internal/download/fileDownloader.js.map +1 -1
  38. package/dist/internal/download/fileDownloader.test.js +8 -8
  39. package/dist/internal/download/fileDownloader.test.js.map +1 -1
  40. package/dist/internal/nodes/apiService.d.ts +6 -1
  41. package/dist/internal/nodes/apiService.js +71 -44
  42. package/dist/internal/nodes/apiService.js.map +1 -1
  43. package/dist/internal/nodes/apiService.test.js +204 -15
  44. package/dist/internal/nodes/apiService.test.js.map +1 -1
  45. package/dist/internal/nodes/debouncer.d.ts +24 -0
  46. package/dist/internal/nodes/debouncer.js +92 -0
  47. package/dist/internal/nodes/debouncer.js.map +1 -0
  48. package/dist/internal/nodes/debouncer.test.d.ts +1 -0
  49. package/dist/internal/nodes/debouncer.test.js +108 -0
  50. package/dist/internal/nodes/debouncer.test.js.map +1 -0
  51. package/dist/internal/nodes/extendedAttributes.js +2 -2
  52. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  53. package/dist/internal/nodes/index.js +1 -1
  54. package/dist/internal/nodes/index.js.map +1 -1
  55. package/dist/internal/nodes/nodesAccess.d.ts +6 -4
  56. package/dist/internal/nodes/nodesAccess.js +29 -9
  57. package/dist/internal/nodes/nodesAccess.js.map +1 -1
  58. package/dist/internal/nodes/nodesAccess.test.js +19 -7
  59. package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
  60. package/dist/internal/nodes/nodesManagement.d.ts +2 -2
  61. package/dist/internal/nodes/nodesManagement.js +5 -3
  62. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  63. package/dist/internal/nodes/nodesManagement.test.js +3 -1
  64. package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
  65. package/dist/internal/photos/apiService.js +9 -20
  66. package/dist/internal/photos/apiService.js.map +1 -1
  67. package/dist/internal/photos/upload.d.ts +2 -1
  68. package/dist/internal/photos/upload.js +9 -3
  69. package/dist/internal/photos/upload.js.map +1 -1
  70. package/dist/internal/sharing/apiService.d.ts +1 -1
  71. package/dist/internal/sharing/apiService.js +2 -2
  72. package/dist/internal/sharing/apiService.js.map +1 -1
  73. package/dist/internal/sharing/sharingManagement.d.ts +4 -1
  74. package/dist/internal/sharing/sharingManagement.js +7 -4
  75. package/dist/internal/sharing/sharingManagement.js.map +1 -1
  76. package/dist/internal/sharingPublic/apiService.d.ts +8 -10
  77. package/dist/internal/sharingPublic/apiService.js +9 -125
  78. package/dist/internal/sharingPublic/apiService.js.map +1 -1
  79. package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
  80. package/dist/internal/sharingPublic/{cryptoService.js → cryptoReporter.js} +3 -16
  81. package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
  82. package/dist/internal/sharingPublic/index.d.ts +22 -4
  83. package/dist/internal/sharingPublic/index.js +37 -12
  84. package/dist/internal/sharingPublic/index.js.map +1 -1
  85. package/dist/internal/sharingPublic/nodes.d.ts +18 -0
  86. package/dist/internal/sharingPublic/nodes.js +46 -0
  87. package/dist/internal/sharingPublic/nodes.js.map +1 -0
  88. package/dist/internal/sharingPublic/session/apiService.d.ts +7 -5
  89. package/dist/internal/sharingPublic/session/apiService.js +25 -4
  90. package/dist/internal/sharingPublic/session/apiService.js.map +1 -1
  91. package/dist/internal/sharingPublic/session/interface.d.ts +17 -0
  92. package/dist/internal/sharingPublic/session/manager.d.ts +12 -4
  93. package/dist/internal/sharingPublic/session/manager.js +14 -4
  94. package/dist/internal/sharingPublic/session/manager.js.map +1 -1
  95. package/dist/internal/sharingPublic/session/session.d.ts +7 -4
  96. package/dist/internal/sharingPublic/session/session.js +7 -3
  97. package/dist/internal/sharingPublic/session/session.js.map +1 -1
  98. package/dist/internal/sharingPublic/session/url.test.js +3 -3
  99. package/dist/internal/sharingPublic/shares.d.ts +27 -0
  100. package/dist/internal/sharingPublic/shares.js +46 -0
  101. package/dist/internal/sharingPublic/shares.js.map +1 -0
  102. package/dist/internal/upload/apiService.js +10 -1
  103. package/dist/internal/upload/apiService.js.map +1 -1
  104. package/dist/internal/upload/controller.d.ts +11 -3
  105. package/dist/internal/upload/controller.js +16 -2
  106. package/dist/internal/upload/controller.js.map +1 -1
  107. package/dist/internal/upload/fileUploader.d.ts +6 -3
  108. package/dist/internal/upload/fileUploader.js +4 -4
  109. package/dist/internal/upload/fileUploader.js.map +1 -1
  110. package/dist/internal/upload/fileUploader.test.js +23 -11
  111. package/dist/internal/upload/fileUploader.test.js.map +1 -1
  112. package/dist/internal/upload/streamUploader.d.ts +9 -4
  113. package/dist/internal/upload/streamUploader.js +67 -20
  114. package/dist/internal/upload/streamUploader.js.map +1 -1
  115. package/dist/internal/upload/streamUploader.test.js +43 -13
  116. package/dist/internal/upload/streamUploader.test.js.map +1 -1
  117. package/dist/protonDriveClient.d.ts +11 -6
  118. package/dist/protonDriveClient.js +11 -10
  119. package/dist/protonDriveClient.js.map +1 -1
  120. package/dist/protonDrivePublicLinkClient.d.ts +34 -6
  121. package/dist/protonDrivePublicLinkClient.js +52 -9
  122. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  123. package/dist/tests/telemetry.d.ts +4 -2
  124. package/dist/tests/telemetry.js +3 -1
  125. package/dist/tests/telemetry.js.map +1 -1
  126. package/dist/transformers.d.ts +3 -2
  127. package/dist/transformers.js +6 -0
  128. package/dist/transformers.js.map +1 -1
  129. package/package.json +1 -1
  130. package/src/diagnostic/{sdkDiagnosticFull.ts → diagnostic.ts} +10 -6
  131. package/src/diagnostic/index.ts +3 -5
  132. package/src/diagnostic/interface.ts +39 -0
  133. package/src/diagnostic/sdkDiagnostic.ts +111 -10
  134. package/src/interface/download.ts +4 -4
  135. package/src/interface/index.ts +1 -0
  136. package/src/interface/nodes.ts +3 -0
  137. package/src/interface/telemetry.ts +5 -0
  138. package/src/interface/upload.ts +3 -3
  139. package/src/internal/apiService/apiService.test.ts +50 -0
  140. package/src/internal/apiService/apiService.ts +33 -2
  141. package/src/internal/apiService/driveTypes.ts +2713 -2561
  142. package/src/internal/apiService/errors.test.ts +10 -0
  143. package/src/internal/apiService/errors.ts +5 -1
  144. package/src/internal/asyncIteratorMap.test.ts +12 -0
  145. package/src/internal/asyncIteratorMap.ts +8 -0
  146. package/src/internal/download/controller.ts +13 -1
  147. package/src/internal/download/fileDownloader.test.ts +8 -8
  148. package/src/internal/download/fileDownloader.ts +13 -6
  149. package/src/internal/nodes/apiService.test.ts +261 -14
  150. package/src/internal/nodes/apiService.ts +99 -65
  151. package/src/internal/nodes/debouncer.test.ts +141 -0
  152. package/src/internal/nodes/debouncer.ts +109 -0
  153. package/src/internal/nodes/extendedAttributes.ts +2 -2
  154. package/src/internal/nodes/index.ts +1 -8
  155. package/src/internal/nodes/nodesAccess.test.ts +19 -7
  156. package/src/internal/nodes/nodesAccess.ts +44 -9
  157. package/src/internal/nodes/nodesManagement.test.ts +3 -1
  158. package/src/internal/nodes/nodesManagement.ts +11 -5
  159. package/src/internal/photos/apiService.ts +12 -29
  160. package/src/internal/photos/upload.ts +22 -1
  161. package/src/internal/sharing/apiService.ts +2 -2
  162. package/src/internal/sharing/sharingManagement.ts +7 -4
  163. package/src/internal/sharingPublic/apiService.ts +23 -160
  164. package/src/internal/sharingPublic/{cryptoService.ts → cryptoReporter.ts} +2 -27
  165. package/src/internal/sharingPublic/index.ts +76 -13
  166. package/src/internal/sharingPublic/nodes.ts +59 -0
  167. package/src/internal/sharingPublic/session/apiService.ts +32 -10
  168. package/src/internal/sharingPublic/session/interface.ts +20 -0
  169. package/src/internal/sharingPublic/session/manager.ts +31 -8
  170. package/src/internal/sharingPublic/session/session.ts +12 -7
  171. package/src/internal/sharingPublic/session/url.test.ts +3 -3
  172. package/src/internal/sharingPublic/shares.ts +50 -0
  173. package/src/internal/upload/apiService.ts +12 -1
  174. package/src/internal/upload/controller.ts +16 -4
  175. package/src/internal/upload/fileUploader.test.ts +25 -11
  176. package/src/internal/upload/fileUploader.ts +6 -5
  177. package/src/internal/upload/streamUploader.test.ts +56 -12
  178. package/src/internal/upload/streamUploader.ts +78 -20
  179. package/src/protonDriveClient.ts +29 -11
  180. package/src/protonDrivePublicLinkClient.ts +100 -16
  181. package/src/tests/telemetry.ts +6 -3
  182. package/src/transformers.ts +8 -0
  183. package/dist/diagnostic/sdkDiagnosticFull.js.map +0 -1
  184. package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -19
  185. package/dist/internal/sharingPublic/cryptoCache.js +0 -72
  186. package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
  187. package/dist/internal/sharingPublic/cryptoService.d.ts +0 -9
  188. package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
  189. package/dist/internal/sharingPublic/interface.d.ts +0 -6
  190. package/dist/internal/sharingPublic/interface.js +0 -3
  191. package/dist/internal/sharingPublic/interface.js.map +0 -1
  192. package/dist/internal/sharingPublic/manager.d.ts +0 -19
  193. package/dist/internal/sharingPublic/manager.js +0 -81
  194. package/dist/internal/sharingPublic/manager.js.map +0 -1
  195. package/src/internal/sharingPublic/cryptoCache.ts +0 -79
  196. package/src/internal/sharingPublic/interface.ts +0 -14
  197. package/src/internal/sharingPublic/manager.ts +0 -86
@@ -1,10 +1,19 @@
1
- import { DriveCrypto } from '../../crypto';
2
- import { ProtonDriveCryptoCache, ProtonDriveTelemetry, ProtonDriveAccount } from '../../interface';
1
+ import { DriveCrypto, PrivateKey } from '../../crypto';
2
+ import {
3
+ ProtonDriveCryptoCache,
4
+ ProtonDriveTelemetry,
5
+ ProtonDriveAccount,
6
+ ProtonDriveEntitiesCache,
7
+ } from '../../interface';
3
8
  import { DriveAPIService } from '../apiService';
4
- import { SharingPublicAPIService } from './apiService';
5
- import { SharingPublicCryptoCache } from './cryptoCache';
6
- import { SharingPublicCryptoService } from './cryptoService';
7
- import { SharingPublicManager } from './manager';
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';
14
+ import { SharingPublicCryptoReporter } from './cryptoReporter';
15
+ import { SharingPublicNodesAccess } from './nodes';
16
+ import { SharingPublicSharesManager } from './shares';
8
17
 
9
18
  export { SharingPublicSessionManager } from './session/manager';
10
19
 
@@ -20,22 +29,76 @@ export { SharingPublicSessionManager } from './session/manager';
20
29
  export function initSharingPublicModule(
21
30
  telemetry: ProtonDriveTelemetry,
22
31
  apiService: DriveAPIService,
32
+ driveEntitiesCache: ProtonDriveEntitiesCache,
23
33
  driveCryptoCache: ProtonDriveCryptoCache,
24
34
  driveCrypto: DriveCrypto,
25
35
  account: ProtonDriveAccount,
36
+ url: string,
26
37
  token: string,
27
- password: string,
38
+ publicShareKey: PrivateKey,
39
+ publicRootNodeUid: string,
28
40
  ) {
29
- const api = new SharingPublicAPIService(telemetry.getLogger('sharingPublic-api'), apiService);
30
- 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'),
41
+ const shares = new SharingPublicSharesManager(account, publicShareKey, publicRootNodeUid);
42
+ const nodes = initSharingPublicNodesModule(
43
+ telemetry,
44
+ apiService,
45
+ driveEntitiesCache,
46
+ driveCryptoCache,
47
+ driveCrypto,
48
+ account,
49
+ shares,
50
+ url,
51
+ token,
52
+ publicShareKey,
53
+ publicRootNodeUid,
54
+ );
55
+
56
+ return {
57
+ shares,
58
+ nodes,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Provides facade for the public link nodes module.
64
+ *
65
+ * The public link nodes initializes the core nodes module, but uses public
66
+ * link shares or crypto reporter instead.
67
+ */
68
+ export function initSharingPublicNodesModule(
69
+ telemetry: ProtonDriveTelemetry,
70
+ apiService: DriveAPIService,
71
+ driveEntitiesCache: ProtonDriveEntitiesCache,
72
+ driveCryptoCache: ProtonDriveCryptoCache,
73
+ driveCrypto: DriveCrypto,
74
+ account: ProtonDriveAccount,
75
+ sharesService: SharingPublicSharesManager,
76
+ url: string,
77
+ token: string,
78
+ publicShareKey: PrivateKey,
79
+ publicRootNodeUid: string,
80
+ ) {
81
+ const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService);
82
+ const cache = new NodesCache(telemetry.getLogger('nodes-cache'), driveEntitiesCache);
83
+ const cryptoCache = new NodesCryptoCache(telemetry.getLogger('nodes-cache'), driveCryptoCache);
84
+ const cryptoReporter = new SharingPublicCryptoReporter(telemetry);
85
+ const cryptoService = new NodesCryptoService(telemetry, driveCrypto, account, cryptoReporter);
86
+ const nodesAccess = new SharingPublicNodesAccess(
87
+ telemetry,
34
88
  api,
89
+ cache,
35
90
  cryptoCache,
36
91
  cryptoService,
92
+ sharesService,
93
+ url,
37
94
  token,
95
+ publicShareKey,
96
+ publicRootNodeUid,
38
97
  );
98
+ const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
39
99
 
40
- return manager;
100
+ return {
101
+ access: nodesAccess,
102
+ revisions: nodesRevisions,
103
+ };
41
104
  }
@@ -0,0 +1,59 @@
1
+ import { ProtonDriveTelemetry } 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
+ import { DecryptedNode, DecryptedNodeKeys } from '../nodes/interface';
11
+ import { PrivateKey } from '../../crypto';
12
+
13
+ export class SharingPublicNodesAccess extends NodesAccess {
14
+ constructor(
15
+ telemetry: ProtonDriveTelemetry,
16
+ apiService: NodeAPIService,
17
+ cache: NodesCache,
18
+ cryptoCache: NodesCryptoCache,
19
+ cryptoService: NodesCryptoService,
20
+ sharesService: SharingPublicSharesManager,
21
+ private url: string,
22
+ private token: string,
23
+ private publicShareKey: PrivateKey,
24
+ private publicRootNodeUid: string,
25
+ ) {
26
+ super(telemetry, apiService, cache, cryptoCache, cryptoService, sharesService);
27
+ this.token = token;
28
+ this.publicShareKey = publicShareKey;
29
+ this.publicRootNodeUid = publicRootNodeUid;
30
+ }
31
+
32
+ async getParentKeys(
33
+ node: Pick<DecryptedNode, 'uid' | 'parentUid' | 'shareId'>,
34
+ ): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
35
+ // If we reached the root node of the public link, return the public
36
+ // share key even if user has access to the parent node. We do not
37
+ // support access to nodes outside of the public link context.
38
+ // For other nodes, the client must use the main SDK.
39
+ if (node.uid === this.publicRootNodeUid) {
40
+ return {
41
+ key: this.publicShareKey,
42
+ };
43
+ }
44
+
45
+ return super.getParentKeys(node);
46
+ }
47
+
48
+ async getNodeUrl(nodeUid: string): Promise<string> {
49
+ const node = await this.getNode(nodeUid);
50
+ if (isProtonDocument(node.mediaType) || isProtonSheet(node.mediaType)) {
51
+ const { nodeId } = splitNodeUid(nodeUid);
52
+ const type = isProtonDocument(node.mediaType) ? 'doc' : 'sheet';
53
+ return `https://docs.proton.me/doc?type=${type}&mode=open-url&token=${this.token}&linkId=${nodeId}`;
54
+ }
55
+
56
+ // Public link doesn't support specific node URLs.
57
+ return this.url;
58
+ }
59
+ }
@@ -1,5 +1,7 @@
1
- import { DriveAPIService, drivePaths } from '../../apiService';
2
- import { PublicLinkInfo, PublicLinkSrpAuth } from './interface';
1
+ import { Logger } from '../../../interface';
2
+ import { DriveAPIService, drivePaths, permissionsToMemberRole } from '../../apiService';
3
+ import { makeNodeUid } from '../../uids';
4
+ import { PublicLinkInfo, PublicLinkSrpAuth, PublicLinkSession, EncryptedShareCrypto } from './interface';
3
5
 
4
6
  type GetPublicLinkInfoResponse =
5
7
  drivePaths['/drive/urls/{token}/info']['get']['responses']['200']['content']['application/json'];
@@ -9,7 +11,7 @@ type PostPublicLinkAuthRequest = Extract<
9
11
  { content: object }
10
12
  >['content']['application/json'];
11
13
  type PostPublicLinkAuthResponse =
12
- drivePaths['/drive/urls/{token}/auth']['post']['responses']['200']['content']['application/json'];
14
+ drivePaths['/drive/urls/{token}/auth']['post']['responses']['200']['content']['application/json'];
13
15
 
14
16
  /**
15
17
  * Provides API communication for managing public link session (not data).
@@ -18,7 +20,11 @@ type PostPublicLinkAuthResponse =
18
20
  * and vice versa. It should not contain any business logic.
19
21
  */
20
22
  export class SharingPublicSessionAPIService {
21
- constructor(private apiService: DriveAPIService) {
23
+ constructor(
24
+ private logger: Logger,
25
+ private apiService: DriveAPIService,
26
+ ) {
27
+ this.logger = logger;
22
28
  this.apiService = apiService;
23
29
  }
24
30
 
@@ -38,6 +44,13 @@ export class SharingPublicSessionAPIService {
38
44
  isCustomPasswordProtected: (response.Flags & 1) === 1,
39
45
  isLegacy: response.Flags === 0 || response.Flags === 1,
40
46
  vendorType: response.VendorType,
47
+ directAccess: response.DirectAccess
48
+ ? {
49
+ nodeUid: makeNodeUid(response.DirectAccess.VolumeID, response.DirectAccess.LinkID),
50
+ directRole: permissionsToMemberRole(this.logger, response.DirectAccess.DirectPermissions),
51
+ publicRole: permissionsToMemberRole(this.logger, response.DirectAccess.PublicPermissions),
52
+ }
53
+ : undefined,
41
54
  };
42
55
  }
43
56
 
@@ -52,9 +65,9 @@ export class SharingPublicSessionAPIService {
52
65
  token: string,
53
66
  srp: PublicLinkSrpAuth,
54
67
  ): Promise<{
55
- serverProof: string;
56
- sessionUid: string;
57
- sessionAccessToken?: string;
68
+ session: PublicLinkSession;
69
+ encryptedShare: EncryptedShareCrypto;
70
+ rootUid: string;
58
71
  }> {
59
72
  const response = await this.apiService.post<PostPublicLinkAuthRequest, PostPublicLinkAuthResponse>(
60
73
  `drive/urls/${token}/auth`,
@@ -66,9 +79,18 @@ export class SharingPublicSessionAPIService {
66
79
  );
67
80
 
68
81
  return {
69
- serverProof: response.ServerProof,
70
- sessionUid: response.UID,
71
- sessionAccessToken: response.AccessToken,
82
+ session: {
83
+ serverProof: response.ServerProof,
84
+ sessionUid: response.UID,
85
+ sessionAccessToken: response.AccessToken,
86
+ },
87
+ encryptedShare: {
88
+ base64UrlPasswordSalt: response.Share.SharePasswordSalt,
89
+ armoredKey: response.Share.ShareKey,
90
+ armoredPassphrase: response.Share.SharePassphrase,
91
+ publicRole: permissionsToMemberRole(this.logger, response.Share.PublicPermissions),
92
+ },
93
+ rootUid: makeNodeUid(response.Share.VolumeID, response.Share.LinkID),
72
94
  };
73
95
  }
74
96
  }
@@ -1,8 +1,15 @@
1
+ import { MemberRole } from '../../../interface';
2
+
1
3
  export type PublicLinkInfo = {
2
4
  srp: PublicLinkSrpInfo;
3
5
  isCustomPasswordProtected: boolean;
4
6
  isLegacy: boolean;
5
7
  vendorType: number;
8
+ directAccess?: {
9
+ nodeUid: string;
10
+ directRole: MemberRole;
11
+ publicRole: MemberRole;
12
+ };
6
13
  };
7
14
 
8
15
  export type PublicLinkSrpInfo = {
@@ -18,3 +25,16 @@ export type PublicLinkSrpAuth = {
18
25
  clientEphemeral: string;
19
26
  srpSession: string;
20
27
  };
28
+
29
+ export type PublicLinkSession = {
30
+ serverProof: string;
31
+ sessionUid: string;
32
+ sessionAccessToken?: string;
33
+ };
34
+
35
+ export type EncryptedShareCrypto = {
36
+ base64UrlPasswordSalt: string;
37
+ armoredKey: string;
38
+ armoredPassphrase: string;
39
+ publicRole: MemberRole;
40
+ };
@@ -1,9 +1,9 @@
1
- import { ProtonDriveHTTPClient } from '../../../interface';
2
- import { SRPModule } from '../../../crypto';
1
+ import { MemberRole, ProtonDriveHTTPClient, ProtonDriveTelemetry } from '../../../interface';
2
+ import { DriveCrypto, PrivateKey, SRPModule } from '../../../crypto';
3
3
  import { DriveAPIService } from '../../apiService';
4
4
  import { SharingPublicSessionAPIService } from './apiService';
5
5
  import { SharingPublicSessionHttpClient } from './httpClient';
6
- import { PublicLinkInfo } from './interface';
6
+ import { EncryptedShareCrypto, PublicLinkInfo } from './interface';
7
7
  import { SharingPublicLinkSession } from './session';
8
8
  import { getTokenAndPasswordFromUrl } from './url';
9
9
 
@@ -18,14 +18,17 @@ export class SharingPublicSessionManager {
18
18
  private infosPerToken: Map<string, PublicLinkInfo> = new Map();
19
19
 
20
20
  constructor(
21
+ telemetry: ProtonDriveTelemetry,
21
22
  private httpClient: ProtonDriveHTTPClient,
22
- apiService: DriveAPIService,
23
+ private driveCrypto: DriveCrypto,
23
24
  private srpModule: SRPModule,
25
+ apiService: DriveAPIService,
24
26
  ) {
25
27
  this.httpClient = httpClient;
28
+ this.driveCrypto = driveCrypto;
26
29
  this.srpModule = srpModule;
27
30
 
28
- this.api = new SharingPublicSessionAPIService(apiService);
31
+ this.api = new SharingPublicSessionAPIService(telemetry.getLogger('sharingPublicSession'), apiService);
29
32
  }
30
33
 
31
34
  /**
@@ -42,6 +45,11 @@ export class SharingPublicSessionManager {
42
45
  isCustomPasswordProtected: boolean;
43
46
  isLegacy: boolean;
44
47
  vendorType: number;
48
+ directAccess?: {
49
+ nodeUid: string;
50
+ directRole: MemberRole;
51
+ publicRole: MemberRole;
52
+ };
45
53
  }> {
46
54
  const { token } = getTokenAndPasswordFromUrl(url);
47
55
 
@@ -52,6 +60,7 @@ export class SharingPublicSessionManager {
52
60
  isCustomPasswordProtected: info.isCustomPasswordProtected,
53
61
  isLegacy: info.isLegacy,
54
62
  vendorType: info.vendorType,
63
+ directAccess: info.directAccess,
55
64
  };
56
65
  }
57
66
 
@@ -73,8 +82,9 @@ export class SharingPublicSessionManager {
73
82
  customPassword?: string,
74
83
  ): Promise<{
75
84
  token: string;
76
- password: string;
77
85
  httpClient: SharingPublicSessionHttpClient;
86
+ shareKey: PrivateKey;
87
+ rootUid: string;
78
88
  }> {
79
89
  const { token, password: urlPassword } = getTokenAndPasswordFromUrl(url);
80
90
 
@@ -86,12 +96,25 @@ export class SharingPublicSessionManager {
86
96
  const password = `${urlPassword}${customPassword || ''}`;
87
97
 
88
98
  const session = new SharingPublicLinkSession(this.api, this.srpModule, token, password);
89
- await session.auth(info.srp);
99
+ const { encryptedShare, rootUid } = await session.auth(info.srp);
100
+
101
+ const shareKey = await this.decryptShareKey(encryptedShare, password);
90
102
 
91
103
  return {
92
104
  token,
93
- password,
94
105
  httpClient: new SharingPublicSessionHttpClient(this.httpClient, session),
106
+ shareKey,
107
+ rootUid,
95
108
  };
96
109
  }
110
+
111
+ private async decryptShareKey(encryptedShare: EncryptedShareCrypto, password: string): Promise<PrivateKey> {
112
+ const { key: shareKey } = await this.driveCrypto.decryptKeyWithSrpPassword(
113
+ password,
114
+ encryptedShare.base64UrlPasswordSalt,
115
+ encryptedShare.armoredKey,
116
+ encryptedShare.armoredPassphrase,
117
+ );
118
+ return shareKey;
119
+ }
97
120
  }
@@ -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 { EncryptedShareCrypto, PublicLinkInfo, PublicLinkSrpInfo } from './interface';
4
4
 
5
5
  /**
6
6
  * Session for a public link.
@@ -33,7 +33,7 @@ export class SharingPublicLinkSession {
33
33
  return this.apiService.initPublicLinkSession(this.token);
34
34
  }
35
35
 
36
- async auth(srp: PublicLinkSrpInfo): Promise<void> {
36
+ async auth(srp: PublicLinkSrpInfo): Promise<{ encryptedShare: EncryptedShareCrypto; rootUid: string }> {
37
37
  const { expectedServerProof, clientProof, clientEphemeral } = await this.srpModule.getSrp(
38
38
  srp.version,
39
39
  srp.modulus,
@@ -48,12 +48,17 @@ export class SharingPublicLinkSession {
48
48
  srpSession: srp.srpSession,
49
49
  });
50
50
 
51
- if (auth.serverProof !== expectedServerProof) {
51
+ if (auth.session.serverProof !== expectedServerProof) {
52
52
  throw new Error('Invalid server proof');
53
53
  }
54
54
 
55
- this.sessionUid = auth.sessionUid;
56
- this.sessionAccessToken = auth.sessionAccessToken;
55
+ this.sessionUid = auth.session.sessionUid;
56
+ this.sessionAccessToken = auth.session.sessionAccessToken;
57
+
58
+ return {
59
+ encryptedShare: auth.encryptedShare,
60
+ rootUid: auth.rootUid,
61
+ };
57
62
  }
58
63
 
59
64
  /**
@@ -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,50 @@
1
+ import { PrivateKey } from '../../crypto';
2
+ import { MetricVolumeType, ProtonDriveAccount } from '../../interface';
3
+ import { splitNodeUid } from '../uids';
4
+
5
+ /**
6
+ * Provides high-level actions for managing public link share.
7
+ *
8
+ * The public link share manager provides the same interface as the code share
9
+ * service so it can be used in the same way in various modules that use shares.
10
+ */
11
+ export class SharingPublicSharesManager {
12
+ constructor(
13
+ private account: ProtonDriveAccount,
14
+ private publicShareKey: PrivateKey,
15
+ private publicRootNodeUid: string,
16
+ ) {
17
+ this.account = account;
18
+ this.publicShareKey = publicShareKey;
19
+ this.publicRootNodeUid = publicRootNodeUid;
20
+ }
21
+
22
+ // TODO: Rename to getRootIDs everywhere.
23
+ async getOwnVolumeIDs(): Promise<{ volumeId: string; rootNodeId: string; rootNodeUid: string }> {
24
+ const { volumeId, nodeId: rootNodeId } = splitNodeUid(this.publicRootNodeUid);
25
+ return { volumeId, rootNodeId, rootNodeUid: this.publicRootNodeUid };
26
+ }
27
+
28
+ async getSharePrivateKey(): Promise<PrivateKey> {
29
+ return this.publicShareKey;
30
+ }
31
+
32
+ async getContextShareMemberEmailKey(): Promise<{
33
+ email: string;
34
+ addressId: string;
35
+ addressKey: PrivateKey;
36
+ addressKeyId: string;
37
+ }> {
38
+ const address = await this.account.getOwnPrimaryAddress();
39
+ return {
40
+ email: address.email,
41
+ addressId: address.addressId,
42
+ addressKey: address.keys[address.primaryKeyIndex].key,
43
+ addressKeyId: address.keys[address.primaryKeyIndex].id,
44
+ };
45
+ }
46
+
47
+ async getVolumeMetricContext(): Promise<MetricVolumeType> {
48
+ return MetricVolumeType.SharedPublic;
49
+ }
50
+ }
@@ -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,
@@ -1,11 +1,23 @@
1
+ import { AbortError } from '../../errors';
1
2
  import { waitForCondition } from '../wait';
2
3
 
3
4
  export class UploadController {
4
5
  private paused = false;
5
- public promise?: Promise<string>;
6
+ public promise?: Promise<{ nodeRevisionUid: string; nodeUid: string }>;
6
7
 
7
- async waitIfPaused(): Promise<void> {
8
- await waitForCondition(() => !this.paused);
8
+ constructor(private signal?: AbortSignal) {
9
+ this.signal = signal;
10
+ }
11
+
12
+ async waitWhilePaused(): Promise<void> {
13
+ try {
14
+ await waitForCondition(() => !this.paused, this.signal);
15
+ } catch (error) {
16
+ if (error instanceof AbortError) {
17
+ return;
18
+ }
19
+ throw error;
20
+ }
9
21
  }
10
22
 
11
23
  pause(): void {
@@ -16,7 +28,7 @@ export class UploadController {
16
28
  this.paused = false;
17
29
  }
18
30
 
19
- async completion(): Promise<string> {
31
+ async completion(): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
20
32
  if (!this.promise) {
21
33
  throw new Error('UploadController.completion() called before upload started');
22
34
  }
@@ -108,6 +108,7 @@ describe('FileUploader', () => {
108
108
 
109
109
  revisionDraft = {
110
110
  nodeRevisionUid: 'revisionUid',
111
+ nodeUid: 'nodeUid',
111
112
  nodeKeys: {
112
113
  signatureAddress: { addressId: 'addressId' },
113
114
  },
@@ -131,10 +132,13 @@ describe('FileUploader', () => {
131
132
  abortController.signal,
132
133
  );
133
134
 
134
- startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve('revisionUid'));
135
+ startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve({
136
+ nodeRevisionUid: 'revisionUid',
137
+ nodeUid: 'nodeUid'
138
+ }));
135
139
  });
136
140
 
137
- describe('writeFile', () => {
141
+ describe('uploadFromFile', () => {
138
142
  // @ts-expect-error Ignore mocking File
139
143
  const file = {
140
144
  type: 'image/png',
@@ -146,50 +150,60 @@ describe('FileUploader', () => {
146
150
  const onProgress = jest.fn();
147
151
 
148
152
  it('should set media type if not set', async () => {
149
- await uploader.writeFile(file, thumbnails, onProgress);
153
+ await uploader.uploadFromFile(file, thumbnails, onProgress);
150
154
 
151
155
  expect(metadata.mediaType).toEqual('image/png');
152
156
  expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
153
157
  });
154
158
 
155
159
  it('should set expected size if not set', async () => {
156
- await uploader.writeFile(file, thumbnails, onProgress);
160
+ await uploader.uploadFromFile(file, thumbnails, onProgress);
157
161
 
158
162
  expect(metadata.expectedSize).toEqual(file.size);
159
163
  expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
160
164
  });
161
165
 
162
166
  it('should set modification time if not set', async () => {
163
- await uploader.writeFile(file, thumbnails, onProgress);
167
+ await uploader.uploadFromFile(file, thumbnails, onProgress);
164
168
 
165
169
  expect(metadata.modificationTime).toEqual(new Date(123456789));
166
170
  expect(startUploadSpy).toHaveBeenCalledWith('stream', thumbnails, onProgress);
167
171
  });
168
172
 
169
173
  it('should throw an error if upload already started', async () => {
170
- await uploader.writeFile(file, thumbnails, onProgress);
174
+ await uploader.uploadFromFile(file, thumbnails, onProgress);
171
175
 
172
- await expect(uploader.writeFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
176
+ await expect(uploader.uploadFromFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
173
177
  });
174
178
  });
175
179
 
176
- describe('writeStream', () => {
180
+ describe('uploadFromStream', () => {
177
181
  const stream = new ReadableStream();
178
182
  const thumbnails: Thumbnail[] = [];
179
183
  const onProgress = jest.fn();
180
184
 
181
185
  it('should start the upload process', async () => {
182
- await uploader.writeStream(stream, thumbnails, onProgress);
186
+ await uploader.uploadFromStream(stream, thumbnails, onProgress);
183
187
 
184
188
  expect(startUploadSpy).toHaveBeenCalledWith(stream, thumbnails, onProgress);
185
189
  });
186
190
 
187
191
  it('should throw an error if upload already started', async () => {
188
- await uploader.writeStream(stream, thumbnails, onProgress);
192
+ await uploader.uploadFromStream(stream, thumbnails, onProgress);
189
193
 
190
- await expect(uploader.writeStream(stream, thumbnails, onProgress)).rejects.toThrow(
194
+ await expect(uploader.uploadFromStream(stream, thumbnails, onProgress)).rejects.toThrow(
191
195
  'Upload already started',
192
196
  );
193
197
  });
198
+
199
+ it('should return correct nodeUid and nodeRevisionUid via controller completion', async () => {
200
+ const controller = await uploader.uploadFromStream(stream, thumbnails, onProgress);
201
+ const result = await controller.completion();
202
+
203
+ expect(result).toEqual({
204
+ nodeRevisionUid: 'revisionUid',
205
+ nodeUid: 'nodeUid'
206
+ });
207
+ });
194
208
  });
195
209
  });