@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.
- package/dist/diagnostic/{sdkDiagnosticFull.d.ts → diagnostic.d.ts} +5 -4
- package/dist/diagnostic/{sdkDiagnosticFull.js → diagnostic.js} +13 -10
- package/dist/diagnostic/diagnostic.js.map +1 -0
- package/dist/diagnostic/index.js +2 -4
- package/dist/diagnostic/index.js.map +1 -1
- package/dist/diagnostic/interface.d.ts +22 -1
- package/dist/diagnostic/sdkDiagnostic.d.ts +3 -2
- package/dist/diagnostic/sdkDiagnostic.js +80 -8
- package/dist/diagnostic/sdkDiagnostic.js.map +1 -1
- package/dist/interface/download.d.ts +4 -4
- package/dist/interface/index.d.ts +1 -1
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +9 -0
- package/dist/interface/telemetry.d.ts +4 -1
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/interface/upload.d.ts +6 -3
- package/dist/internal/apiService/apiService.d.ts +3 -0
- package/dist/internal/apiService/apiService.js +25 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/apiService.test.js +38 -0
- package/dist/internal/apiService/apiService.test.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +2595 -2397
- package/dist/internal/apiService/errors.js +3 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/errors.test.js +15 -7
- package/dist/internal/apiService/errors.test.js.map +1 -1
- package/dist/internal/asyncIteratorMap.d.ts +1 -1
- package/dist/internal/asyncIteratorMap.js +6 -1
- package/dist/internal/asyncIteratorMap.js.map +1 -1
- package/dist/internal/asyncIteratorMap.test.js +9 -0
- package/dist/internal/asyncIteratorMap.test.js.map +1 -1
- package/dist/internal/download/controller.d.ts +2 -0
- package/dist/internal/download/controller.js +15 -1
- package/dist/internal/download/controller.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +3 -3
- package/dist/internal/download/fileDownloader.js +11 -6
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +8 -8
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +6 -1
- package/dist/internal/nodes/apiService.js +71 -44
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +204 -15
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/debouncer.d.ts +24 -0
- package/dist/internal/nodes/debouncer.js +92 -0
- package/dist/internal/nodes/debouncer.js.map +1 -0
- package/dist/internal/nodes/debouncer.test.d.ts +1 -0
- package/dist/internal/nodes/debouncer.test.js +108 -0
- package/dist/internal/nodes/debouncer.test.js.map +1 -0
- package/dist/internal/nodes/extendedAttributes.js +2 -2
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/index.js +1 -1
- package/dist/internal/nodes/index.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.d.ts +6 -4
- package/dist/internal/nodes/nodesAccess.js +29 -9
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesAccess.test.js +19 -7
- package/dist/internal/nodes/nodesAccess.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -2
- package/dist/internal/nodes/nodesManagement.js +5 -3
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +3 -1
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/apiService.js +9 -20
- package/dist/internal/photos/apiService.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -1
- package/dist/internal/photos/upload.js +9 -3
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharing/apiService.d.ts +1 -1
- package/dist/internal/sharing/apiService.js +2 -2
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/sharingManagement.d.ts +4 -1
- package/dist/internal/sharing/sharingManagement.js +7 -4
- package/dist/internal/sharing/sharingManagement.js.map +1 -1
- package/dist/internal/sharingPublic/apiService.d.ts +8 -10
- package/dist/internal/sharingPublic/apiService.js +9 -125
- package/dist/internal/sharingPublic/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/cryptoReporter.d.ts +16 -0
- package/dist/internal/sharingPublic/{cryptoService.js → cryptoReporter.js} +3 -16
- package/dist/internal/sharingPublic/cryptoReporter.js.map +1 -0
- package/dist/internal/sharingPublic/index.d.ts +22 -4
- package/dist/internal/sharingPublic/index.js +37 -12
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +18 -0
- package/dist/internal/sharingPublic/nodes.js +46 -0
- package/dist/internal/sharingPublic/nodes.js.map +1 -0
- package/dist/internal/sharingPublic/session/apiService.d.ts +7 -5
- package/dist/internal/sharingPublic/session/apiService.js +25 -4
- package/dist/internal/sharingPublic/session/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/session/interface.d.ts +17 -0
- package/dist/internal/sharingPublic/session/manager.d.ts +12 -4
- package/dist/internal/sharingPublic/session/manager.js +14 -4
- package/dist/internal/sharingPublic/session/manager.js.map +1 -1
- package/dist/internal/sharingPublic/session/session.d.ts +7 -4
- package/dist/internal/sharingPublic/session/session.js +7 -3
- package/dist/internal/sharingPublic/session/session.js.map +1 -1
- package/dist/internal/sharingPublic/session/url.test.js +3 -3
- package/dist/internal/sharingPublic/shares.d.ts +27 -0
- package/dist/internal/sharingPublic/shares.js +46 -0
- package/dist/internal/sharingPublic/shares.js.map +1 -0
- package/dist/internal/upload/apiService.js +10 -1
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/controller.d.ts +11 -3
- package/dist/internal/upload/controller.js +16 -2
- package/dist/internal/upload/controller.js.map +1 -1
- package/dist/internal/upload/fileUploader.d.ts +6 -3
- package/dist/internal/upload/fileUploader.js +4 -4
- package/dist/internal/upload/fileUploader.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +23 -11
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.d.ts +9 -4
- package/dist/internal/upload/streamUploader.js +67 -20
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +43 -13
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -6
- package/dist/protonDriveClient.js +11 -10
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +34 -6
- package/dist/protonDrivePublicLinkClient.js +52 -9
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/tests/telemetry.d.ts +4 -2
- package/dist/tests/telemetry.js +3 -1
- package/dist/tests/telemetry.js.map +1 -1
- package/dist/transformers.d.ts +3 -2
- package/dist/transformers.js +6 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostic/{sdkDiagnosticFull.ts → diagnostic.ts} +10 -6
- package/src/diagnostic/index.ts +3 -5
- package/src/diagnostic/interface.ts +39 -0
- package/src/diagnostic/sdkDiagnostic.ts +111 -10
- package/src/interface/download.ts +4 -4
- package/src/interface/index.ts +1 -0
- package/src/interface/nodes.ts +3 -0
- package/src/interface/telemetry.ts +5 -0
- package/src/interface/upload.ts +3 -3
- package/src/internal/apiService/apiService.test.ts +50 -0
- package/src/internal/apiService/apiService.ts +33 -2
- package/src/internal/apiService/driveTypes.ts +2713 -2561
- package/src/internal/apiService/errors.test.ts +10 -0
- package/src/internal/apiService/errors.ts +5 -1
- package/src/internal/asyncIteratorMap.test.ts +12 -0
- package/src/internal/asyncIteratorMap.ts +8 -0
- package/src/internal/download/controller.ts +13 -1
- package/src/internal/download/fileDownloader.test.ts +8 -8
- package/src/internal/download/fileDownloader.ts +13 -6
- package/src/internal/nodes/apiService.test.ts +261 -14
- package/src/internal/nodes/apiService.ts +99 -65
- package/src/internal/nodes/debouncer.test.ts +141 -0
- package/src/internal/nodes/debouncer.ts +109 -0
- package/src/internal/nodes/extendedAttributes.ts +2 -2
- package/src/internal/nodes/index.ts +1 -8
- package/src/internal/nodes/nodesAccess.test.ts +19 -7
- package/src/internal/nodes/nodesAccess.ts +44 -9
- package/src/internal/nodes/nodesManagement.test.ts +3 -1
- package/src/internal/nodes/nodesManagement.ts +11 -5
- package/src/internal/photos/apiService.ts +12 -29
- package/src/internal/photos/upload.ts +22 -1
- package/src/internal/sharing/apiService.ts +2 -2
- package/src/internal/sharing/sharingManagement.ts +7 -4
- package/src/internal/sharingPublic/apiService.ts +23 -160
- package/src/internal/sharingPublic/{cryptoService.ts → cryptoReporter.ts} +2 -27
- package/src/internal/sharingPublic/index.ts +76 -13
- package/src/internal/sharingPublic/nodes.ts +59 -0
- package/src/internal/sharingPublic/session/apiService.ts +32 -10
- package/src/internal/sharingPublic/session/interface.ts +20 -0
- package/src/internal/sharingPublic/session/manager.ts +31 -8
- package/src/internal/sharingPublic/session/session.ts +12 -7
- package/src/internal/sharingPublic/session/url.test.ts +3 -3
- package/src/internal/sharingPublic/shares.ts +50 -0
- package/src/internal/upload/apiService.ts +12 -1
- package/src/internal/upload/controller.ts +16 -4
- package/src/internal/upload/fileUploader.test.ts +25 -11
- package/src/internal/upload/fileUploader.ts +6 -5
- package/src/internal/upload/streamUploader.test.ts +56 -12
- package/src/internal/upload/streamUploader.ts +78 -20
- package/src/protonDriveClient.ts +29 -11
- package/src/protonDrivePublicLinkClient.ts +100 -16
- package/src/tests/telemetry.ts +6 -3
- package/src/transformers.ts +8 -0
- package/dist/diagnostic/sdkDiagnosticFull.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoCache.d.ts +0 -19
- package/dist/internal/sharingPublic/cryptoCache.js +0 -72
- package/dist/internal/sharingPublic/cryptoCache.js.map +0 -1
- package/dist/internal/sharingPublic/cryptoService.d.ts +0 -9
- package/dist/internal/sharingPublic/cryptoService.js.map +0 -1
- package/dist/internal/sharingPublic/interface.d.ts +0 -6
- package/dist/internal/sharingPublic/interface.js +0 -3
- package/dist/internal/sharingPublic/interface.js.map +0 -1
- package/dist/internal/sharingPublic/manager.d.ts +0 -19
- package/dist/internal/sharingPublic/manager.js +0 -81
- package/dist/internal/sharingPublic/manager.js.map +0 -1
- package/src/internal/sharingPublic/cryptoCache.ts +0 -79
- package/src/internal/sharingPublic/interface.ts +0 -14
- package/src/internal/sharingPublic/manager.ts +0 -86
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { DriveCrypto } from '../../crypto';
|
|
2
|
-
import {
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
38
|
+
publicShareKey: PrivateKey,
|
|
39
|
+
publicRootNodeUid: string,
|
|
28
40
|
) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
import { SharingPublicSessionAPIService } from
|
|
3
|
-
import { PublicLinkInfo, PublicLinkSrpInfo } from
|
|
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<
|
|
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:
|
|
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
|
-
|
|
8
|
-
|
|
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(
|
|
135
|
+
startUploadSpy = jest.spyOn(uploader as any, 'startUpload').mockReturnValue(Promise.resolve({
|
|
136
|
+
nodeRevisionUid: 'revisionUid',
|
|
137
|
+
nodeUid: 'nodeUid'
|
|
138
|
+
}));
|
|
135
139
|
});
|
|
136
140
|
|
|
137
|
-
describe('
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
174
|
+
await uploader.uploadFromFile(file, thumbnails, onProgress);
|
|
171
175
|
|
|
172
|
-
await expect(uploader.
|
|
176
|
+
await expect(uploader.uploadFromFile(file, thumbnails, onProgress)).rejects.toThrow('Upload already started');
|
|
173
177
|
});
|
|
174
178
|
});
|
|
175
179
|
|
|
176
|
-
describe('
|
|
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.
|
|
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.
|
|
192
|
+
await uploader.uploadFromStream(stream, thumbnails, onProgress);
|
|
189
193
|
|
|
190
|
-
await expect(uploader.
|
|
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
|
});
|