@protontech/drive-sdk 0.6.1 → 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/featureFlags.d.ts +7 -0
- package/dist/featureFlags.js +14 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/interface/featureFlags.d.ts +7 -0
- package/dist/interface/featureFlags.js +3 -0
- package/dist/interface/featureFlags.js.map +1 -0
- package/dist/interface/index.d.ts +3 -0
- package/dist/interface/index.js.map +1 -1
- 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 +1 -1
- 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.d.ts +5 -2
- package/dist/internal/upload/fileUploader.js +7 -4
- package/dist/internal/upload/fileUploader.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 +2 -2
- package/dist/protonDriveClient.js +7 -2
- 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/featureFlags.ts +11 -0
- package/src/index.ts +1 -0
- package/src/interface/featureFlags.ts +7 -0
- package/src/interface/index.ts +3 -0
- 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/fileUploader.ts +18 -11
- 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 +12 -2
- package/src/protonDrivePublicLinkClient.ts +8 -3
|
@@ -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
|
}
|
|
@@ -15,7 +15,7 @@ import { UploadTelemetry } from './telemetry';
|
|
|
15
15
|
* This class is not meant to be used directly, but rather to be extended
|
|
16
16
|
* by `FileUploader` and `FileRevisionUploader`.
|
|
17
17
|
*/
|
|
18
|
-
class Uploader {
|
|
18
|
+
abstract class Uploader {
|
|
19
19
|
protected controller: UploadController;
|
|
20
20
|
protected abortController: AbortController;
|
|
21
21
|
|
|
@@ -83,7 +83,7 @@ class Uploader {
|
|
|
83
83
|
stream: ReadableStream,
|
|
84
84
|
thumbnails: Thumbnail[],
|
|
85
85
|
onProgress?: (uploadedBytes: number) => void,
|
|
86
|
-
): Promise<{ nodeRevisionUid: string
|
|
86
|
+
): Promise<{ nodeRevisionUid: string; nodeUid: string }> {
|
|
87
87
|
const uploader = await this.initStreamUploader();
|
|
88
88
|
return uploader.start(stream, thumbnails, onProgress);
|
|
89
89
|
}
|
|
@@ -94,15 +94,11 @@ class Uploader {
|
|
|
94
94
|
const onFinish = async (failure: boolean) => {
|
|
95
95
|
this.onFinish();
|
|
96
96
|
if (failure) {
|
|
97
|
-
await this.
|
|
97
|
+
await this.deleteRevisionDraft(revisionDraft);
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
return this.newStreamUploader(
|
|
102
|
-
blockVerifier,
|
|
103
|
-
revisionDraft,
|
|
104
|
-
onFinish,
|
|
105
|
-
);
|
|
101
|
+
return this.newStreamUploader(blockVerifier, revisionDraft, onFinish);
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
protected async newStreamUploader(
|
|
@@ -124,9 +120,12 @@ class Uploader {
|
|
|
124
120
|
);
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
protected
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
protected abstract createRevisionDraft(): Promise<{
|
|
124
|
+
revisionDraft: NodeRevisionDraft;
|
|
125
|
+
blockVerifier: BlockVerifier;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
protected abstract deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void>;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
/**
|
|
@@ -176,6 +175,10 @@ export class FileUploader extends Uploader {
|
|
|
176
175
|
blockVerifier,
|
|
177
176
|
};
|
|
178
177
|
}
|
|
178
|
+
|
|
179
|
+
protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
|
|
180
|
+
await this.manager.deleteDraftNode(revisionDraft.nodeUid);
|
|
181
|
+
}
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
/**
|
|
@@ -223,4 +226,8 @@ export class FileRevisionUploader extends Uploader {
|
|
|
223
226
|
blockVerifier,
|
|
224
227
|
};
|
|
225
228
|
}
|
|
229
|
+
|
|
230
|
+
protected async deleteRevisionDraft(revisionDraft: NodeRevisionDraft): Promise<void> {
|
|
231
|
+
await this.manager.deleteDraftRevision(revisionDraft.nodeRevisionUid);
|
|
232
|
+
}
|
|
226
233
|
}
|
|
@@ -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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getConfig } from './config';
|
|
2
2
|
import { DriveCrypto, SessionKey } from './crypto';
|
|
3
|
+
import { NullFeatureFlagProvider } from './featureFlags';
|
|
3
4
|
import {
|
|
4
5
|
Logger,
|
|
5
6
|
ProtonDriveClientContructorParameters,
|
|
@@ -105,7 +106,11 @@ export class ProtonDriveClient {
|
|
|
105
106
|
* Experimental feature to authenticate a public link and
|
|
106
107
|
* return the client for the public link to access it.
|
|
107
108
|
*/
|
|
108
|
-
authPublicLink: (
|
|
109
|
+
authPublicLink: (
|
|
110
|
+
url: string,
|
|
111
|
+
customPassword?: string,
|
|
112
|
+
isAnonymousContext?: boolean,
|
|
113
|
+
) => Promise<ProtonDrivePublicLinkClient>;
|
|
109
114
|
};
|
|
110
115
|
|
|
111
116
|
constructor({
|
|
@@ -117,11 +122,15 @@ export class ProtonDriveClient {
|
|
|
117
122
|
srpModule,
|
|
118
123
|
config,
|
|
119
124
|
telemetry,
|
|
125
|
+
featureFlagProvider,
|
|
120
126
|
latestEventIdProvider,
|
|
121
127
|
}: ProtonDriveClientContructorParameters) {
|
|
122
128
|
if (!telemetry) {
|
|
123
129
|
telemetry = new Telemetry();
|
|
124
130
|
}
|
|
131
|
+
if (!featureFlagProvider) {
|
|
132
|
+
featureFlagProvider = new NullFeatureFlagProvider();
|
|
133
|
+
}
|
|
125
134
|
this.logger = telemetry.getLogger('interface');
|
|
126
135
|
|
|
127
136
|
const fullConfig = getConfig(config);
|
|
@@ -217,7 +226,7 @@ export class ProtonDriveClient {
|
|
|
217
226
|
this.logger.info(`Getting info for public link ${url}`);
|
|
218
227
|
return this.publicSessionManager.getInfo(url);
|
|
219
228
|
},
|
|
220
|
-
authPublicLink: async (url: string, customPassword?: string) => {
|
|
229
|
+
authPublicLink: async (url: string, customPassword?: string, isAnonymousContext: boolean = false) => {
|
|
221
230
|
this.logger.info(`Authenticating public link ${url}`);
|
|
222
231
|
const { httpClient, token, shareKey, rootUid } = await this.publicSessionManager.auth(
|
|
223
232
|
url,
|
|
@@ -234,6 +243,7 @@ export class ProtonDriveClient {
|
|
|
234
243
|
token,
|
|
235
244
|
publicShareKey: shareKey,
|
|
236
245
|
publicRootNodeUid: rootUid,
|
|
246
|
+
isAnonymousContext,
|
|
237
247
|
});
|
|
238
248
|
},
|
|
239
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,
|