@protontech/drive-sdk 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/apiService/apiService.d.ts +2 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/errors.js +4 -3
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/download/cryptoService.js +8 -6
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -1
- package/dist/internal/download/fileDownloader.js +6 -3
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/index.d.ts +1 -1
- package/dist/internal/download/index.js +3 -3
- package/dist/internal/download/index.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +9 -8
- package/dist/internal/nodes/apiService.js +14 -5
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +5 -5
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
- package/dist/internal/nodes/cryptoReporter.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +12 -21
- package/dist/internal/nodes/cryptoService.js +45 -14
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +262 -17
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +13 -3
- package/dist/internal/nodes/nodesAccess.d.ts +8 -1
- package/dist/internal/nodes/nodesAccess.js +13 -0
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +4 -4
- package/dist/internal/nodes/nodesManagement.js +16 -20
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +21 -10
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +6 -6
- package/dist/internal/sharingPublic/index.js +8 -7
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +16 -3
- package/dist/internal/sharingPublic/nodes.js +34 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
- package/dist/internal/sharingPublic/unauthApiService.js +31 -0
- package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
- package/dist/internal/upload/apiService.d.ts +4 -3
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +8 -3
- package/dist/internal/upload/cryptoService.js +45 -9
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +1 -1
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +25 -13
- package/dist/internal/upload/manager.js +7 -4
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +5 -4
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +9 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +8 -5
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +1 -1
- package/dist/protonDriveClient.js +2 -1
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +2 -1
- package/dist/protonDrivePublicLinkClient.js +7 -5
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/apiService/apiService.ts +2 -2
- package/src/internal/apiService/errors.ts +5 -4
- package/src/internal/download/cryptoService.ts +13 -6
- package/src/internal/download/fileDownloader.ts +4 -2
- package/src/internal/download/index.ts +3 -0
- package/src/internal/nodes/apiService.test.ts +5 -5
- package/src/internal/nodes/apiService.ts +23 -10
- package/src/internal/nodes/cryptoReporter.ts +3 -3
- package/src/internal/nodes/cryptoService.test.ts +370 -18
- package/src/internal/nodes/cryptoService.ts +73 -32
- package/src/internal/nodes/interface.ts +16 -2
- package/src/internal/nodes/nodesAccess.ts +17 -0
- package/src/internal/nodes/nodesManagement.test.ts +21 -10
- package/src/internal/nodes/nodesManagement.ts +20 -24
- package/src/internal/photos/upload.ts +3 -3
- package/src/internal/sharingPublic/index.ts +7 -3
- package/src/internal/sharingPublic/nodes.ts +43 -2
- package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
- package/src/internal/sharingPublic/unauthApiService.ts +32 -0
- package/src/internal/upload/apiService.ts +4 -3
- package/src/internal/upload/cryptoService.ts +73 -12
- package/src/internal/upload/fileUploader.test.ts +1 -1
- package/src/internal/upload/interface.ts +24 -13
- package/src/internal/upload/manager.test.ts +5 -4
- package/src/internal/upload/manager.ts +7 -4
- package/src/internal/upload/streamUploader.test.ts +8 -5
- package/src/internal/upload/streamUploader.ts +10 -4
- package/src/protonDriveClient.ts +7 -2
- package/src/protonDrivePublicLinkClient.ts +8 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getUnauthEndpoint } from './unauthApiService';
|
|
2
|
+
|
|
3
|
+
describe('getUnauthEndpoint', () => {
|
|
4
|
+
it('should not change urls endpoints', () => {
|
|
5
|
+
expect(getUnauthEndpoint('drive/urls/anything')).toBe('drive/urls/anything');
|
|
6
|
+
expect(getUnauthEndpoint('drive/urls/drive/anything')).toBe('drive/urls/drive/anything');
|
|
7
|
+
expect(getUnauthEndpoint('drive/urls/drive/v2/anything')).toBe('drive/urls/drive/v2/anything');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should not change v2/urls endpoints', () => {
|
|
11
|
+
expect(getUnauthEndpoint('drive/v2/urls/anything')).toBe('drive/v2/urls/anything');
|
|
12
|
+
expect(getUnauthEndpoint('drive/v2/urls/drive/anything')).toBe('drive/v2/urls/drive/anything');
|
|
13
|
+
expect(getUnauthEndpoint('drive/v2/urls/drive/v2/anything')).toBe('drive/v2/urls/drive/v2/anything');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should put unauth prefix for v2 endpoints', () => {
|
|
17
|
+
expect(getUnauthEndpoint('drive/v2/anything')).toBe('drive/unauth/v2/anything');
|
|
18
|
+
expect(getUnauthEndpoint('drive/v2/drive/anything')).toBe('drive/unauth/v2/drive/anything');
|
|
19
|
+
expect(getUnauthEndpoint('drive/v2/drive/v2/anything')).toBe('drive/unauth/v2/drive/v2/anything');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should put unauth prefix for non-v2 endpoints', () => {
|
|
23
|
+
expect(getUnauthEndpoint('drive/anything')).toBe('drive/unauth/anything');
|
|
24
|
+
expect(getUnauthEndpoint('drive/anything/v2/anything')).toBe('drive/unauth/anything/v2/anything');
|
|
25
|
+
expect(getUnauthEndpoint('drive/anything/drive/anything')).toBe('drive/unauth/anything/drive/anything');
|
|
26
|
+
expect(getUnauthEndpoint('drive/anything/drive/v2/anything')).toBe('drive/unauth/anything/drive/v2/anything');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DriveAPIService } from '../apiService';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drive API Service for public links.
|
|
5
|
+
*
|
|
6
|
+
* This service is used to make requests to the Drive API without
|
|
7
|
+
* authentication. The unauth context uses the same endpoint, but
|
|
8
|
+
* with an `unauth` prefix. The goal is to avoid the need to use
|
|
9
|
+
* different path and use the exact endpoint for both contexts.
|
|
10
|
+
* However, API has global logic for handling expired sessions that
|
|
11
|
+
* is not compatible with the unauth context. For this reason, this
|
|
12
|
+
* service is used to make requests to the Drive API for public
|
|
13
|
+
* link context in the mean time.
|
|
14
|
+
*/
|
|
15
|
+
export class UnauthDriveAPIService extends DriveAPIService {
|
|
16
|
+
protected async makeRequest<RequestPayload, ResponsePayload>(
|
|
17
|
+
url: string,
|
|
18
|
+
method = 'GET',
|
|
19
|
+
data?: RequestPayload,
|
|
20
|
+
signal?: AbortSignal,
|
|
21
|
+
): Promise<ResponsePayload> {
|
|
22
|
+
const unauthUrl = getUnauthEndpoint(url);
|
|
23
|
+
return super.makeRequest(unauthUrl, method, data, signal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getUnauthEndpoint(url: string): string {
|
|
28
|
+
if (url.startsWith('drive/urls/') || url.startsWith('drive/v2/urls/')) {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
return url.replace(/^drive\//, 'drive/unauth/');
|
|
32
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
3
|
import { base64StringToUint8Array, uint8ArrayToBase64String } from '../../crypto';
|
|
4
|
+
import { AnonymousUser } from '../../interface';
|
|
4
5
|
import { APICodeError, DriveAPIService, drivePaths, isCodeOk } from '../apiService';
|
|
5
6
|
import { splitNodeUid, makeNodeUid, splitNodeRevisionUid, makeNodeRevisionUid } from '../uids';
|
|
6
7
|
import { UploadTokens } from './interface';
|
|
@@ -65,7 +66,7 @@ export class UploadAPIService {
|
|
|
65
66
|
armoredNodePassphraseSignature: string;
|
|
66
67
|
base64ContentKeyPacket: string;
|
|
67
68
|
armoredContentKeyPacketSignature: string;
|
|
68
|
-
signatureEmail: string;
|
|
69
|
+
signatureEmail: string | AnonymousUser;
|
|
69
70
|
},
|
|
70
71
|
): Promise<{
|
|
71
72
|
nodeUid: string;
|
|
@@ -150,7 +151,7 @@ export class UploadAPIService {
|
|
|
150
151
|
|
|
151
152
|
async requestBlockUpload(
|
|
152
153
|
draftNodeRevisionUid: string,
|
|
153
|
-
addressId: string,
|
|
154
|
+
addressId: string | AnonymousUser,
|
|
154
155
|
blocks: {
|
|
155
156
|
contentBlocks: {
|
|
156
157
|
index: number;
|
|
@@ -211,7 +212,7 @@ export class UploadAPIService {
|
|
|
211
212
|
draftNodeRevisionUid: string,
|
|
212
213
|
options: {
|
|
213
214
|
armoredManifestSignature: string;
|
|
214
|
-
signatureEmail: string;
|
|
215
|
+
signatureEmail: string | AnonymousUser;
|
|
215
216
|
armoredExtendedAttributes?: string;
|
|
216
217
|
},
|
|
217
218
|
): Promise<void> {
|
|
@@ -2,8 +2,15 @@ import { c } from 'ttag';
|
|
|
2
2
|
|
|
3
3
|
import { DriveCrypto, PrivateKey, SessionKey } from '../../crypto';
|
|
4
4
|
import { IntegrityError } from '../../errors';
|
|
5
|
-
import { Thumbnail } from '../../interface';
|
|
6
|
-
import {
|
|
5
|
+
import { Thumbnail, AnonymousUser } from '../../interface';
|
|
6
|
+
import {
|
|
7
|
+
EncryptedBlock,
|
|
8
|
+
EncryptedThumbnail,
|
|
9
|
+
NodeCrypto,
|
|
10
|
+
NodeCryptoSigningKeys,
|
|
11
|
+
NodeRevisionDraftKeys,
|
|
12
|
+
NodesService,
|
|
13
|
+
} from './interface';
|
|
7
14
|
|
|
8
15
|
export class UploadCryptoService {
|
|
9
16
|
constructor(
|
|
@@ -19,11 +26,15 @@ export class UploadCryptoService {
|
|
|
19
26
|
parentKeys: { key: PrivateKey; hashKey: Uint8Array },
|
|
20
27
|
name: string,
|
|
21
28
|
): Promise<NodeCrypto> {
|
|
22
|
-
const
|
|
29
|
+
const signingKeys = await this.getSigningKeys({ parentNodeUid: parentUid });
|
|
30
|
+
|
|
31
|
+
if (!signingKeys.nameAndPassphraseSigningKey) {
|
|
32
|
+
throw new Error('Cannot create new node without a name and passphrase signing key');
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
const [nodeKeys, { armoredNodeName }, hash] = await Promise.all([
|
|
25
|
-
this.driveCrypto.generateKey([parentKeys.key],
|
|
26
|
-
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key,
|
|
36
|
+
this.driveCrypto.generateKey([parentKeys.key], signingKeys.nameAndPassphraseSigningKey),
|
|
37
|
+
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, signingKeys.nameAndPassphraseSigningKey),
|
|
27
38
|
this.driveCrypto.generateLookupHash(name, parentKeys.hashKey),
|
|
28
39
|
]);
|
|
29
40
|
|
|
@@ -36,7 +47,57 @@ export class UploadCryptoService {
|
|
|
36
47
|
encryptedName: armoredNodeName,
|
|
37
48
|
hash,
|
|
38
49
|
},
|
|
39
|
-
|
|
50
|
+
signingKeys: {
|
|
51
|
+
email: signingKeys.email,
|
|
52
|
+
addressId: signingKeys.addressId,
|
|
53
|
+
nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
|
|
54
|
+
contentSigningKey: signingKeys.contentSigningKey || nodeKeys.decrypted.key,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getSigningKeysForExistingNode(uids: {
|
|
60
|
+
nodeUid: string;
|
|
61
|
+
parentNodeUid?: string;
|
|
62
|
+
}): Promise<NodeCryptoSigningKeys> {
|
|
63
|
+
const signingKeys = await this.getSigningKeys(uids);
|
|
64
|
+
|
|
65
|
+
if (!signingKeys.nameAndPassphraseSigningKey) {
|
|
66
|
+
throw new Error('Cannot get name and passphrase signing key for existing node');
|
|
67
|
+
}
|
|
68
|
+
if (!signingKeys.contentSigningKey) {
|
|
69
|
+
throw new Error('Cannot get content signing key for existing node');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
email: signingKeys.email,
|
|
74
|
+
addressId: signingKeys.addressId,
|
|
75
|
+
nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
|
|
76
|
+
contentSigningKey: signingKeys.contentSigningKey,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async getSigningKeys(
|
|
81
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
82
|
+
): Promise<
|
|
83
|
+
Omit<NodeCryptoSigningKeys, 'nameAndPassphraseSigningKey' | 'contentSigningKey'> & {
|
|
84
|
+
nameAndPassphraseSigningKey?: PrivateKey;
|
|
85
|
+
contentSigningKey?: PrivateKey;
|
|
86
|
+
}
|
|
87
|
+
> {
|
|
88
|
+
const signingKeys = await this.nodesService.getNodeSigningKeys(uids);
|
|
89
|
+
|
|
90
|
+
const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
|
|
91
|
+
const addressId = signingKeys.type === 'userAddress' ? signingKeys.addressId : null;
|
|
92
|
+
const nameAndPassphraseSigningKey =
|
|
93
|
+
signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.parentNodeKey;
|
|
94
|
+
const contentSigningKey = signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.nodeKey;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
email,
|
|
98
|
+
addressId,
|
|
99
|
+
nameAndPassphraseSigningKey,
|
|
100
|
+
contentSigningKey,
|
|
40
101
|
};
|
|
41
102
|
}
|
|
42
103
|
|
|
@@ -47,7 +108,7 @@ export class UploadCryptoService {
|
|
|
47
108
|
const { encryptedData } = await this.driveCrypto.encryptThumbnailBlock(
|
|
48
109
|
thumbnail.thumbnail,
|
|
49
110
|
nodeRevisionDraftKeys.contentKeyPacketSessionKey,
|
|
50
|
-
nodeRevisionDraftKeys.
|
|
111
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
51
112
|
);
|
|
52
113
|
|
|
53
114
|
const digest = await crypto.subtle.digest('SHA-256', encryptedData);
|
|
@@ -71,7 +132,7 @@ export class UploadCryptoService {
|
|
|
71
132
|
block,
|
|
72
133
|
nodeRevisionDraftKeys.key,
|
|
73
134
|
nodeRevisionDraftKeys.contentKeyPacketSessionKey,
|
|
74
|
-
nodeRevisionDraftKeys.
|
|
135
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
75
136
|
);
|
|
76
137
|
|
|
77
138
|
const digest = await crypto.subtle.digest('SHA-256', encryptedData);
|
|
@@ -94,25 +155,25 @@ export class UploadCryptoService {
|
|
|
94
155
|
extendedAttributes?: string,
|
|
95
156
|
): Promise<{
|
|
96
157
|
armoredManifestSignature: string;
|
|
97
|
-
signatureEmail: string;
|
|
158
|
+
signatureEmail: string | AnonymousUser;
|
|
98
159
|
armoredExtendedAttributes?: string;
|
|
99
160
|
}> {
|
|
100
161
|
const { armoredManifestSignature } = await this.driveCrypto.signManifest(
|
|
101
162
|
manifest,
|
|
102
|
-
nodeRevisionDraftKeys.
|
|
163
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
103
164
|
);
|
|
104
165
|
|
|
105
166
|
const { armoredExtendedAttributes } = extendedAttributes
|
|
106
167
|
? await this.driveCrypto.encryptExtendedAttributes(
|
|
107
168
|
extendedAttributes,
|
|
108
169
|
nodeRevisionDraftKeys.key,
|
|
109
|
-
nodeRevisionDraftKeys.
|
|
170
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
110
171
|
)
|
|
111
172
|
: { armoredExtendedAttributes: undefined };
|
|
112
173
|
|
|
113
174
|
return {
|
|
114
175
|
armoredManifestSignature,
|
|
115
|
-
signatureEmail: nodeRevisionDraftKeys.
|
|
176
|
+
signatureEmail: nodeRevisionDraftKeys.signingKeys.email,
|
|
116
177
|
armoredExtendedAttributes,
|
|
117
178
|
};
|
|
118
179
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PrivateKey, SessionKey } from '../../crypto';
|
|
2
2
|
|
|
3
|
-
import { MetricVolumeType, ThumbnailType, Result, Revision } from '../../interface';
|
|
3
|
+
import { MetricVolumeType, ThumbnailType, Result, Revision, AnonymousUser } from '../../interface';
|
|
4
4
|
import { DecryptedNode } from '../nodes';
|
|
5
5
|
|
|
6
6
|
export type NodeRevisionDraft = {
|
|
@@ -22,7 +22,7 @@ export type NodeRevisionDraft = {
|
|
|
22
22
|
export type NodeRevisionDraftKeys = {
|
|
23
23
|
key: PrivateKey;
|
|
24
24
|
contentKeyPacketSessionKey: SessionKey;
|
|
25
|
-
|
|
25
|
+
signingKeys: NodeCryptoSigningKeys;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export type NodeCrypto = {
|
|
@@ -51,13 +51,14 @@ export type NodeCrypto = {
|
|
|
51
51
|
encryptedName: string;
|
|
52
52
|
hash: string;
|
|
53
53
|
};
|
|
54
|
-
|
|
54
|
+
signingKeys: NodeCryptoSigningKeys;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
export type
|
|
58
|
-
email: string;
|
|
59
|
-
addressId: string;
|
|
60
|
-
|
|
57
|
+
export type NodeCryptoSigningKeys = {
|
|
58
|
+
email: string | AnonymousUser;
|
|
59
|
+
addressId: string | AnonymousUser;
|
|
60
|
+
nameAndPassphraseSigningKey: PrivateKey;
|
|
61
|
+
contentSigningKey: PrivateKey;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
export type EncryptedBlockMetadata = {
|
|
@@ -102,12 +103,9 @@ export interface NodesService {
|
|
|
102
103
|
contentKeyPacketSessionKey?: SessionKey;
|
|
103
104
|
hashKey?: Uint8Array;
|
|
104
105
|
}>;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
addressKey: PrivateKey;
|
|
109
|
-
addressKeyId: string;
|
|
110
|
-
}>;
|
|
106
|
+
getNodeSigningKeys(
|
|
107
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
108
|
+
): Promise<NodeSigningKeys>;
|
|
111
109
|
notifyChildCreated(nodeUid: string): Promise<void>;
|
|
112
110
|
notifyNodeChanged(nodeUid: string): Promise<void>;
|
|
113
111
|
}
|
|
@@ -126,6 +124,19 @@ export interface NodesServiceNode {
|
|
|
126
124
|
activeRevision?: Result<Revision, Error>;
|
|
127
125
|
}
|
|
128
126
|
|
|
127
|
+
export type NodeSigningKeys =
|
|
128
|
+
| {
|
|
129
|
+
type: 'userAddress';
|
|
130
|
+
email: string;
|
|
131
|
+
addressId: string;
|
|
132
|
+
key: PrivateKey;
|
|
133
|
+
}
|
|
134
|
+
| {
|
|
135
|
+
type: 'nodeKey';
|
|
136
|
+
nodeKey?: PrivateKey;
|
|
137
|
+
parentNodeKey?: PrivateKey;
|
|
138
|
+
};
|
|
139
|
+
|
|
129
140
|
/**
|
|
130
141
|
* Interface describing the dependencies to the shares module.
|
|
131
142
|
*/
|
|
@@ -50,7 +50,7 @@ describe('UploadManager', () => {
|
|
|
50
50
|
encryptedName: 'newNode:encryptedName',
|
|
51
51
|
hash: 'newNode:hash',
|
|
52
52
|
},
|
|
53
|
-
|
|
53
|
+
signingKeys: {
|
|
54
54
|
email: 'signatureEmail',
|
|
55
55
|
},
|
|
56
56
|
}),
|
|
@@ -69,7 +69,8 @@ describe('UploadManager', () => {
|
|
|
69
69
|
hashKey: 'parentNode:hashKey',
|
|
70
70
|
key: 'parentNode:nodekey',
|
|
71
71
|
}),
|
|
72
|
-
|
|
72
|
+
getNodeSigningKeys: jest.fn().mockResolvedValue({
|
|
73
|
+
type: 'userAddress',
|
|
73
74
|
email: 'signatureEmail',
|
|
74
75
|
addressId: 'addressId',
|
|
75
76
|
}),
|
|
@@ -100,7 +101,7 @@ describe('UploadManager', () => {
|
|
|
100
101
|
nodeKeys: {
|
|
101
102
|
key: 'newNode:key',
|
|
102
103
|
contentKeyPacketSessionKey: 'newNode:ContentKeyPacketSessionKey',
|
|
103
|
-
|
|
104
|
+
signingKeys: {
|
|
104
105
|
email: 'signatureEmail',
|
|
105
106
|
},
|
|
106
107
|
},
|
|
@@ -153,7 +154,7 @@ describe('UploadManager', () => {
|
|
|
153
154
|
nodeKeys: {
|
|
154
155
|
key: 'newNode:key',
|
|
155
156
|
contentKeyPacketSessionKey: 'newNode:ContentKeyPacketSessionKey',
|
|
156
|
-
|
|
157
|
+
signingKeys: {
|
|
157
158
|
email: 'signatureEmail',
|
|
158
159
|
},
|
|
159
160
|
},
|
|
@@ -57,7 +57,7 @@ export class UploadManager {
|
|
|
57
57
|
nodeKeys: {
|
|
58
58
|
key: generatedNodeCrypto.nodeKeys.decrypted.key,
|
|
59
59
|
contentKeyPacketSessionKey: generatedNodeCrypto.contentKey.decrypted.contentKeyPacketSessionKey,
|
|
60
|
-
|
|
60
|
+
signingKeys: generatedNodeCrypto.signingKeys,
|
|
61
61
|
},
|
|
62
62
|
parentNodeKeys: {
|
|
63
63
|
hashKey: parentKeys.hashKey,
|
|
@@ -93,7 +93,7 @@ export class UploadManager {
|
|
|
93
93
|
base64ContentKeyPacket: generatedNodeCrypto.contentKey.encrypted.base64ContentKeyPacket,
|
|
94
94
|
armoredContentKeyPacketSignature:
|
|
95
95
|
generatedNodeCrypto.contentKey.encrypted.armoredContentKeyPacketSignature,
|
|
96
|
-
signatureEmail: generatedNodeCrypto.
|
|
96
|
+
signatureEmail: generatedNodeCrypto.signingKeys.email,
|
|
97
97
|
});
|
|
98
98
|
return result;
|
|
99
99
|
} catch (error: unknown) {
|
|
@@ -192,7 +192,10 @@ export class UploadManager {
|
|
|
192
192
|
throw new ValidationError(c('Error').t`Creating revisions in non-files is not allowed`);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
const
|
|
195
|
+
const signingKeys = await this.cryptoService.getSigningKeysForExistingNode({
|
|
196
|
+
nodeUid,
|
|
197
|
+
parentNodeUid: node.parentUid,
|
|
198
|
+
});
|
|
196
199
|
|
|
197
200
|
const { nodeRevisionUid } = await this.apiService.createDraftRevision(nodeUid, {
|
|
198
201
|
currentRevisionUid: node.activeRevision.value.uid,
|
|
@@ -205,7 +208,7 @@ export class UploadManager {
|
|
|
205
208
|
nodeKeys: {
|
|
206
209
|
key: nodeKeys.key,
|
|
207
210
|
contentKeyPacketSessionKey: nodeKeys.contentKeyPacketSessionKey,
|
|
208
|
-
|
|
211
|
+
signingKeys,
|
|
209
212
|
},
|
|
210
213
|
};
|
|
211
214
|
}
|
|
@@ -110,7 +110,9 @@ describe('StreamUploader', () => {
|
|
|
110
110
|
nodeRevisionUid: 'revisionUid',
|
|
111
111
|
nodeUid: 'nodeUid',
|
|
112
112
|
nodeKeys: {
|
|
113
|
-
|
|
113
|
+
signingKeys: {
|
|
114
|
+
addressId: 'addressId',
|
|
115
|
+
},
|
|
114
116
|
},
|
|
115
117
|
} as NodeRevisionDraft;
|
|
116
118
|
|
|
@@ -312,8 +314,9 @@ describe('StreamUploader', () => {
|
|
|
312
314
|
throw new Error('Failed to encrypt block');
|
|
313
315
|
});
|
|
314
316
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
+
// Thumbnail are uploaded with the first content block. If the
|
|
318
|
+
// content block fails to encrypt, nothing is uploaded.
|
|
319
|
+
await verifyFailure('Failed to encrypt block', 0);
|
|
317
320
|
// 1 block + 1 retry, others are skipped
|
|
318
321
|
expect(cryptoService.encryptBlock).toHaveBeenCalledTimes(2);
|
|
319
322
|
});
|
|
@@ -415,7 +418,7 @@ describe('StreamUploader', () => {
|
|
|
415
418
|
expect(apiService.requestBlockUpload).toHaveBeenCalledTimes(2);
|
|
416
419
|
expect(apiService.requestBlockUpload).toHaveBeenCalledWith(
|
|
417
420
|
revisionDraft.nodeRevisionUid,
|
|
418
|
-
revisionDraft.nodeKeys.
|
|
421
|
+
revisionDraft.nodeKeys.signingKeys.addressId,
|
|
419
422
|
{
|
|
420
423
|
contentBlocks: [
|
|
421
424
|
{
|
|
@@ -436,7 +439,7 @@ describe('StreamUploader', () => {
|
|
|
436
439
|
describe('verifyIntegrity', () => {
|
|
437
440
|
it('should report block verification error', async () => {
|
|
438
441
|
blockVerifier.verifyBlock = jest.fn().mockRejectedValue(new IntegrityError('Block verification error'));
|
|
439
|
-
await verifyFailure('Block verification error',
|
|
442
|
+
await verifyFailure('Block verification error', 0);
|
|
440
443
|
expect(telemetry.logBlockVerificationError).toHaveBeenCalledWith(false);
|
|
441
444
|
});
|
|
442
445
|
|
|
@@ -319,7 +319,7 @@ export class StreamUploader {
|
|
|
319
319
|
this.logger.info(`Requesting upload tokens for ${this.encryptedBlocks.size} blocks`);
|
|
320
320
|
const uploadTokens = await this.apiService.requestBlockUpload(
|
|
321
321
|
this.revisionDraft.nodeRevisionUid,
|
|
322
|
-
this.revisionDraft.nodeKeys.
|
|
322
|
+
this.revisionDraft.nodeKeys.signingKeys.addressId,
|
|
323
323
|
{
|
|
324
324
|
contentBlocks: Array.from(
|
|
325
325
|
this.encryptedBlocks.values().map((block) => ({
|
|
@@ -340,6 +340,12 @@ export class StreamUploader {
|
|
|
340
340
|
},
|
|
341
341
|
);
|
|
342
342
|
|
|
343
|
+
// If the upload was aborted while requesting next upload tokens,
|
|
344
|
+
// do not schedule any next upload.
|
|
345
|
+
if (this.isUploadAborted) {
|
|
346
|
+
throw this.error || new AbortError();
|
|
347
|
+
}
|
|
348
|
+
|
|
343
349
|
for (const thumbnailToken of uploadTokens.thumbnailTokens) {
|
|
344
350
|
let encryptedThumbnail = this.encryptedThumbnails.get(thumbnailToken.type);
|
|
345
351
|
if (!encryptedThumbnail) {
|
|
@@ -418,7 +424,7 @@ export class StreamUploader {
|
|
|
418
424
|
break;
|
|
419
425
|
} catch (error: unknown) {
|
|
420
426
|
// Do not retry or report anything if the upload was aborted.
|
|
421
|
-
if (error instanceof AbortError) {
|
|
427
|
+
if (error instanceof AbortError || this.isUploadAborted) {
|
|
422
428
|
throw error;
|
|
423
429
|
}
|
|
424
430
|
|
|
@@ -485,7 +491,7 @@ export class StreamUploader {
|
|
|
485
491
|
break;
|
|
486
492
|
} catch (error: unknown) {
|
|
487
493
|
// Do not retry or report anything if the upload was aborted.
|
|
488
|
-
if (error instanceof AbortError) {
|
|
494
|
+
if (error instanceof AbortError || this.isUploadAborted) {
|
|
489
495
|
throw error;
|
|
490
496
|
}
|
|
491
497
|
|
|
@@ -501,7 +507,7 @@ export class StreamUploader {
|
|
|
501
507
|
logger.warn(`Token expired, fetching new token and retrying`);
|
|
502
508
|
const uploadTokens = await this.apiService.requestBlockUpload(
|
|
503
509
|
this.revisionDraft.nodeRevisionUid,
|
|
504
|
-
this.revisionDraft.nodeKeys.
|
|
510
|
+
this.revisionDraft.nodeKeys.signingKeys.addressId,
|
|
505
511
|
{
|
|
506
512
|
contentBlocks: [
|
|
507
513
|
{
|
package/src/protonDriveClient.ts
CHANGED
|
@@ -106,7 +106,11 @@ export class ProtonDriveClient {
|
|
|
106
106
|
* Experimental feature to authenticate a public link and
|
|
107
107
|
* return the client for the public link to access it.
|
|
108
108
|
*/
|
|
109
|
-
authPublicLink: (
|
|
109
|
+
authPublicLink: (
|
|
110
|
+
url: string,
|
|
111
|
+
customPassword?: string,
|
|
112
|
+
isAnonymousContext?: boolean,
|
|
113
|
+
) => Promise<ProtonDrivePublicLinkClient>;
|
|
110
114
|
};
|
|
111
115
|
|
|
112
116
|
constructor({
|
|
@@ -222,7 +226,7 @@ export class ProtonDriveClient {
|
|
|
222
226
|
this.logger.info(`Getting info for public link ${url}`);
|
|
223
227
|
return this.publicSessionManager.getInfo(url);
|
|
224
228
|
},
|
|
225
|
-
authPublicLink: async (url: string, customPassword?: string) => {
|
|
229
|
+
authPublicLink: async (url: string, customPassword?: string, isAnonymousContext: boolean = false) => {
|
|
226
230
|
this.logger.info(`Authenticating public link ${url}`);
|
|
227
231
|
const { httpClient, token, shareKey, rootUid } = await this.publicSessionManager.auth(
|
|
228
232
|
url,
|
|
@@ -239,6 +243,7 @@ export class ProtonDriveClient {
|
|
|
239
243
|
token,
|
|
240
244
|
publicShareKey: shareKey,
|
|
241
245
|
publicRootNodeUid: rootUid,
|
|
246
|
+
isAnonymousContext,
|
|
242
247
|
});
|
|
243
248
|
},
|
|
244
249
|
};
|
|
@@ -27,10 +27,9 @@ import {
|
|
|
27
27
|
convertInternalMissingNodeIterator,
|
|
28
28
|
getUids,
|
|
29
29
|
} from './transformers';
|
|
30
|
-
import { DriveAPIService } from './internal/apiService';
|
|
31
30
|
import { initDownloadModule } from './internal/download';
|
|
32
31
|
import { SDKEvents } from './internal/sdkEvents';
|
|
33
|
-
import { initSharingPublicModule } from './internal/sharingPublic';
|
|
32
|
+
import { initSharingPublicModule, UnauthDriveAPIService } from './internal/sharingPublic';
|
|
34
33
|
import { initUploadModule } from './internal/upload';
|
|
35
34
|
|
|
36
35
|
/**
|
|
@@ -81,6 +80,7 @@ export class ProtonDrivePublicLinkClient {
|
|
|
81
80
|
token,
|
|
82
81
|
publicShareKey,
|
|
83
82
|
publicRootNodeUid,
|
|
83
|
+
isAnonymousContext,
|
|
84
84
|
}: {
|
|
85
85
|
httpClient: ProtonDriveHTTPClient;
|
|
86
86
|
account: ProtonDriveAccount;
|
|
@@ -92,6 +92,7 @@ export class ProtonDrivePublicLinkClient {
|
|
|
92
92
|
token: string;
|
|
93
93
|
publicShareKey: PrivateKey;
|
|
94
94
|
publicRootNodeUid: string;
|
|
95
|
+
isAnonymousContext: boolean;
|
|
95
96
|
}) {
|
|
96
97
|
if (!telemetry) {
|
|
97
98
|
telemetry = new Telemetry();
|
|
@@ -105,7 +106,7 @@ export class ProtonDrivePublicLinkClient {
|
|
|
105
106
|
const fullConfig = getConfig(config);
|
|
106
107
|
this.sdkEvents = new SDKEvents(telemetry);
|
|
107
108
|
|
|
108
|
-
const apiService = new
|
|
109
|
+
const apiService = new UnauthDriveAPIService(
|
|
109
110
|
telemetry,
|
|
110
111
|
this.sdkEvents,
|
|
111
112
|
httpClient,
|
|
@@ -124,6 +125,7 @@ export class ProtonDrivePublicLinkClient {
|
|
|
124
125
|
token,
|
|
125
126
|
publicShareKey,
|
|
126
127
|
publicRootNodeUid,
|
|
128
|
+
isAnonymousContext,
|
|
127
129
|
);
|
|
128
130
|
this.download = initDownloadModule(
|
|
129
131
|
telemetry,
|
|
@@ -133,6 +135,9 @@ export class ProtonDrivePublicLinkClient {
|
|
|
133
135
|
this.sharingPublic.shares,
|
|
134
136
|
this.sharingPublic.nodes.access,
|
|
135
137
|
this.sharingPublic.nodes.revisions,
|
|
138
|
+
// Ignore manifest integrity verifications for public links.
|
|
139
|
+
// Anonymous user on public page cannot load public keys of other users (yet).
|
|
140
|
+
true,
|
|
136
141
|
);
|
|
137
142
|
this.upload = initUploadModule(
|
|
138
143
|
telemetry,
|