@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
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
MetricVolumeType,
|
|
11
11
|
Revision,
|
|
12
12
|
RevisionState,
|
|
13
|
+
AnonymousUser,
|
|
13
14
|
} from '../../interface';
|
|
14
15
|
|
|
15
16
|
export type FilterOptions = {
|
|
@@ -57,8 +58,8 @@ export interface EncryptedNode extends BaseNode {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export interface EncryptedNodeCrypto {
|
|
60
|
-
signatureEmail?: string;
|
|
61
|
-
nameSignatureEmail?: string;
|
|
61
|
+
signatureEmail?: string | AnonymousUser;
|
|
62
|
+
nameSignatureEmail?: string | AnonymousUser;
|
|
62
63
|
armoredKey: string;
|
|
63
64
|
armoredNodePassphrase: string;
|
|
64
65
|
armoredNodePassphraseSignature?: string;
|
|
@@ -88,6 +89,19 @@ export interface EncryptedNodeFolderCrypto extends EncryptedNodeCrypto {
|
|
|
88
89
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
89
90
|
export interface EncryptedNodeAlbumCrypto extends EncryptedNodeCrypto {}
|
|
90
91
|
|
|
92
|
+
export type NodeSigningKeys =
|
|
93
|
+
| {
|
|
94
|
+
type: 'userAddress';
|
|
95
|
+
email: string;
|
|
96
|
+
addressId: string;
|
|
97
|
+
key: PrivateKey;
|
|
98
|
+
}
|
|
99
|
+
| {
|
|
100
|
+
type: 'nodeKey';
|
|
101
|
+
nodeKey?: PrivateKey;
|
|
102
|
+
parentNodeKey?: PrivateKey;
|
|
103
|
+
};
|
|
104
|
+
|
|
91
105
|
/**
|
|
92
106
|
* Interface used only internally in the nodes module.
|
|
93
107
|
*
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
DecryptedNode,
|
|
30
30
|
DecryptedNodeKeys,
|
|
31
31
|
FilterOptions,
|
|
32
|
+
NodeSigningKeys,
|
|
32
33
|
} from './interface';
|
|
33
34
|
import { validateNodeName } from './validations';
|
|
34
35
|
import { isProtonDocument, isProtonSheet } from './mediaTypes';
|
|
@@ -425,6 +426,22 @@ export class NodesAccess {
|
|
|
425
426
|
};
|
|
426
427
|
}
|
|
427
428
|
|
|
429
|
+
async getNodeSigningKeys(
|
|
430
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
431
|
+
): Promise<NodeSigningKeys> {
|
|
432
|
+
const contextNodeUid = uids.nodeUid || uids.parentNodeUid;
|
|
433
|
+
if (!contextNodeUid) {
|
|
434
|
+
throw new Error('Context node UID is required for signing keys');
|
|
435
|
+
}
|
|
436
|
+
const address = await this.getRootNodeEmailKey(contextNodeUid);
|
|
437
|
+
return {
|
|
438
|
+
type: 'userAddress',
|
|
439
|
+
email: address.email,
|
|
440
|
+
addressId: address.addressId,
|
|
441
|
+
key: address.addressKey,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
428
445
|
async getRootNodeEmailKey(nodeUid: string): Promise<{
|
|
429
446
|
email: string;
|
|
430
447
|
addressId: string;
|
|
@@ -57,7 +57,7 @@ describe('NodesManagement', () => {
|
|
|
57
57
|
restoreNodes: jest.fn(async function* (uids) {
|
|
58
58
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
59
59
|
}),
|
|
60
|
-
|
|
60
|
+
deleteTrashedNodes: jest.fn(async function* (uids) {
|
|
61
61
|
yield* uids.map((uid) => ({ ok: true, uid }) as NodeResult);
|
|
62
62
|
}),
|
|
63
63
|
createFolder: jest.fn(),
|
|
@@ -117,7 +117,12 @@ describe('NodesManagement', () => {
|
|
|
117
117
|
nameSessionKey: `${uid}-nameSessionKey`,
|
|
118
118
|
}),
|
|
119
119
|
),
|
|
120
|
-
|
|
120
|
+
getNodeSigningKeys: jest.fn().mockResolvedValue({
|
|
121
|
+
type: 'userAddress',
|
|
122
|
+
email: 'root-email',
|
|
123
|
+
addressId: 'root-addressId',
|
|
124
|
+
key: 'root-key',
|
|
125
|
+
}),
|
|
121
126
|
notifyNodeChanged: jest.fn(),
|
|
122
127
|
notifyNodeDeleted: jest.fn(),
|
|
123
128
|
notifyChildCreated: jest.fn(),
|
|
@@ -136,11 +141,11 @@ describe('NodesManagement', () => {
|
|
|
136
141
|
nameAuthor: { ok: true, value: 'newSignatureEmail' },
|
|
137
142
|
hash: 'newHash',
|
|
138
143
|
});
|
|
139
|
-
expect(nodesAccess.
|
|
144
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({ nodeUid: 'nodeUid', parentNodeUid: 'parentUid' });
|
|
140
145
|
expect(cryptoService.encryptNewName).toHaveBeenCalledWith(
|
|
141
146
|
{ key: 'parentUid-key', hashKey: 'parentUid-hashKey' },
|
|
142
147
|
'nodeUid-nameSessionKey',
|
|
143
|
-
{ email: 'root-email',
|
|
148
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
144
149
|
'new name',
|
|
145
150
|
);
|
|
146
151
|
expect(apiService.renameNode).toHaveBeenCalledWith(
|
|
@@ -181,7 +186,10 @@ describe('NodesManagement', () => {
|
|
|
181
186
|
keyAuthor: { ok: true, value: 'movedSignatureEmail' },
|
|
182
187
|
nameAuthor: { ok: true, value: 'movedNameSignatureEmail' },
|
|
183
188
|
});
|
|
184
|
-
expect(nodesAccess.
|
|
189
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({
|
|
190
|
+
nodeUid: 'nodeUid',
|
|
191
|
+
parentNodeUid: 'newParentNodeUid',
|
|
192
|
+
});
|
|
185
193
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
186
194
|
nodes.nodeUid,
|
|
187
195
|
expect.objectContaining({
|
|
@@ -192,7 +200,7 @@ describe('NodesManagement', () => {
|
|
|
192
200
|
nameSessionKey: 'nodeUid-nameSessionKey',
|
|
193
201
|
}),
|
|
194
202
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
195
|
-
{ email: 'root-email',
|
|
203
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
196
204
|
);
|
|
197
205
|
expect(apiService.moveNode).toHaveBeenCalledWith(
|
|
198
206
|
'nodeUid',
|
|
@@ -232,7 +240,7 @@ describe('NodesManagement', () => {
|
|
|
232
240
|
nameSessionKey: 'anonymousNodeUid-nameSessionKey',
|
|
233
241
|
}),
|
|
234
242
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
235
|
-
{ email: 'root-email',
|
|
243
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
236
244
|
);
|
|
237
245
|
expect(newNode).toEqual({
|
|
238
246
|
...nodes.anonymousNodeUid,
|
|
@@ -276,7 +284,10 @@ describe('NodesManagement', () => {
|
|
|
276
284
|
keyAuthor: { ok: true, value: 'copiedSignatureEmail' },
|
|
277
285
|
nameAuthor: { ok: true, value: 'copiedNameSignatureEmail' },
|
|
278
286
|
});
|
|
279
|
-
expect(nodesAccess.
|
|
287
|
+
expect(nodesAccess.getNodeSigningKeys).toHaveBeenCalledWith({
|
|
288
|
+
nodeUid: 'nodeUid',
|
|
289
|
+
parentNodeUid: 'newParentNodeUid',
|
|
290
|
+
});
|
|
280
291
|
expect(cryptoService.encryptNodeWithNewParent).toHaveBeenCalledWith(
|
|
281
292
|
nodes.nodeUid,
|
|
282
293
|
expect.objectContaining({
|
|
@@ -287,7 +298,7 @@ describe('NodesManagement', () => {
|
|
|
287
298
|
nameSessionKey: 'nodeUid-nameSessionKey',
|
|
288
299
|
}),
|
|
289
300
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
290
|
-
{ email: 'root-email',
|
|
301
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
291
302
|
);
|
|
292
303
|
expect(apiService.copyNode).toHaveBeenCalledWith('nodeUid', {
|
|
293
304
|
parentUid: 'newParentNodeUid',
|
|
@@ -322,7 +333,7 @@ describe('NodesManagement', () => {
|
|
|
322
333
|
nameSessionKey: 'anonymousNodeUid-nameSessionKey',
|
|
323
334
|
}),
|
|
324
335
|
expect.objectContaining({ key: 'newParentNodeUid-key', hashKey: 'newParentNodeUid-hashKey' }),
|
|
325
|
-
{ email: 'root-email',
|
|
336
|
+
{ type: 'userAddress', email: 'root-email', addressId: 'root-addressId', key: 'root-key' },
|
|
326
337
|
);
|
|
327
338
|
expect(newNode).toEqual({
|
|
328
339
|
...nodes.anonymousNodeUid,
|
|
@@ -28,10 +28,10 @@ const AVAILABLE_NAME_LIMIT = 1000;
|
|
|
28
28
|
*/
|
|
29
29
|
export class NodesManagement {
|
|
30
30
|
constructor(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
protected apiService: NodeAPIService,
|
|
32
|
+
protected cryptoCache: NodesCryptoCache,
|
|
33
|
+
protected cryptoService: NodesCryptoService,
|
|
34
|
+
protected nodesAccess: NodesAccess,
|
|
35
35
|
) {
|
|
36
36
|
this.apiService = apiService;
|
|
37
37
|
this.cryptoCache = cryptoCache;
|
|
@@ -49,7 +49,7 @@ export class NodesManagement {
|
|
|
49
49
|
const node = await this.nodesAccess.getNode(nodeUid);
|
|
50
50
|
const { nameSessionKey: nodeNameSessionKey } = await this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid);
|
|
51
51
|
const parentKeys = await this.nodesAccess.getParentKeys(node);
|
|
52
|
-
const
|
|
52
|
+
const signingKeys = await this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: node.parentUid });
|
|
53
53
|
|
|
54
54
|
if (!options.allowRenameRootNode && (!node.hash || !parentKeys.hashKey)) {
|
|
55
55
|
throw new ValidationError(c('Error').t`Renaming root item is not allowed`);
|
|
@@ -58,7 +58,7 @@ export class NodesManagement {
|
|
|
58
58
|
const { signatureEmail, armoredNodeName, hash } = await this.cryptoService.encryptNewName(
|
|
59
59
|
parentKeys,
|
|
60
60
|
nodeNameSessionKey,
|
|
61
|
-
|
|
61
|
+
signingKeys,
|
|
62
62
|
newName,
|
|
63
63
|
);
|
|
64
64
|
|
|
@@ -95,7 +95,7 @@ export class NodesManagement {
|
|
|
95
95
|
...node,
|
|
96
96
|
name: resultOk(newName),
|
|
97
97
|
encryptedName: armoredNodeName,
|
|
98
|
-
nameAuthor: resultOk(signatureEmail),
|
|
98
|
+
nameAuthor: resultOk(signatureEmail || null),
|
|
99
99
|
hash,
|
|
100
100
|
};
|
|
101
101
|
return newNode;
|
|
@@ -124,14 +124,12 @@ export class NodesManagement {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
async moveNode(nodeUid: string, newParentUid: string): Promise<DecryptedNode> {
|
|
127
|
-
const
|
|
128
|
-
this.nodesAccess.getNode(nodeUid),
|
|
129
|
-
this.nodesAccess.getRootNodeEmailKey(newParentUid),
|
|
130
|
-
]);
|
|
127
|
+
const node = await this.nodesAccess.getNode(nodeUid);
|
|
131
128
|
|
|
132
|
-
const [keys, newParentKeys] = await Promise.all([
|
|
129
|
+
const [keys, newParentKeys, signingKeys] = await Promise.all([
|
|
133
130
|
this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid),
|
|
134
131
|
this.nodesAccess.getNodeKeys(newParentUid),
|
|
132
|
+
this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: newParentUid }),
|
|
135
133
|
]);
|
|
136
134
|
|
|
137
135
|
if (!node.hash) {
|
|
@@ -145,7 +143,7 @@ export class NodesManagement {
|
|
|
145
143
|
node,
|
|
146
144
|
keys,
|
|
147
145
|
{ key: newParentKeys.key, hashKey: newParentKeys.hashKey },
|
|
148
|
-
|
|
146
|
+
signingKeys,
|
|
149
147
|
);
|
|
150
148
|
|
|
151
149
|
// Node could be uploaded or renamed by anonymous user and thus have
|
|
@@ -214,14 +212,12 @@ export class NodesManagement {
|
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
async copyNode(nodeUid: string, newParentUid: string): Promise<DecryptedNode> {
|
|
217
|
-
const
|
|
218
|
-
this.nodesAccess.getNode(nodeUid),
|
|
219
|
-
this.nodesAccess.getRootNodeEmailKey(newParentUid),
|
|
220
|
-
]);
|
|
215
|
+
const node = await this.nodesAccess.getNode(nodeUid);
|
|
221
216
|
|
|
222
|
-
const [keys, newParentKeys] = await Promise.all([
|
|
217
|
+
const [keys, newParentKeys, signingKeys] = await Promise.all([
|
|
223
218
|
this.nodesAccess.getNodePrivateAndSessionKeys(nodeUid),
|
|
224
219
|
this.nodesAccess.getNodeKeys(newParentUid),
|
|
220
|
+
this.nodesAccess.getNodeSigningKeys({ nodeUid, parentNodeUid: newParentUid }),
|
|
225
221
|
]);
|
|
226
222
|
|
|
227
223
|
if (!newParentKeys.hashKey) {
|
|
@@ -232,7 +228,7 @@ export class NodesManagement {
|
|
|
232
228
|
node,
|
|
233
229
|
keys,
|
|
234
230
|
{ key: newParentKeys.key, hashKey: newParentKeys.hashKey },
|
|
235
|
-
|
|
231
|
+
signingKeys,
|
|
236
232
|
);
|
|
237
233
|
|
|
238
234
|
// Node could be uploaded or renamed by anonymous user and thus have
|
|
@@ -286,7 +282,7 @@ export class NodesManagement {
|
|
|
286
282
|
}
|
|
287
283
|
|
|
288
284
|
async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
289
|
-
for await (const result of this.apiService.
|
|
285
|
+
for await (const result of this.apiService.deleteTrashedNodes(nodeUids, signal)) {
|
|
290
286
|
if (result.ok) {
|
|
291
287
|
await this.nodesAccess.notifyNodeDeleted(result.uid);
|
|
292
288
|
}
|
|
@@ -303,12 +299,12 @@ export class NodesManagement {
|
|
|
303
299
|
throw new ValidationError(c('Error').t`Creating folders in non-folders is not allowed`);
|
|
304
300
|
}
|
|
305
301
|
|
|
306
|
-
const
|
|
302
|
+
const signingKeys = await this.nodesAccess.getNodeSigningKeys({ parentNodeUid });
|
|
307
303
|
const extendedAttributes = generateFolderExtendedAttributes(modificationTime);
|
|
308
304
|
|
|
309
305
|
const { encryptedCrypto, keys } = await this.cryptoService.createFolder(
|
|
310
306
|
{ key: parentKeys.key, hashKey: parentKeys.hashKey },
|
|
311
|
-
|
|
307
|
+
signingKeys,
|
|
312
308
|
folderName,
|
|
313
309
|
extendedAttributes,
|
|
314
310
|
);
|
|
@@ -344,8 +340,8 @@ export class NodesManagement {
|
|
|
344
340
|
|
|
345
341
|
// Decrypted metadata
|
|
346
342
|
isStale: false,
|
|
347
|
-
keyAuthor: resultOk(encryptedCrypto.signatureEmail),
|
|
348
|
-
nameAuthor: resultOk(encryptedCrypto.signatureEmail),
|
|
343
|
+
keyAuthor: resultOk(encryptedCrypto.signatureEmail || null),
|
|
344
|
+
nameAuthor: resultOk(encryptedCrypto.signatureEmail || null),
|
|
349
345
|
name: resultOk(folderName),
|
|
350
346
|
treeEventScopeId: splitNodeUid(nodeUid).volumeId,
|
|
351
347
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DriveCrypto } from '../../crypto';
|
|
2
|
-
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail } from '../../interface';
|
|
2
|
+
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail, AnonymousUser } from '../../interface';
|
|
3
3
|
import { DriveAPIService, drivePaths } from '../apiService';
|
|
4
4
|
import { generateFileExtendedAttributes } from '../nodes';
|
|
5
5
|
import { splitNodeRevisionUid } from '../uids';
|
|
@@ -198,7 +198,7 @@ export class PhotoUploadAPIService extends UploadAPIService {
|
|
|
198
198
|
draftNodeRevisionUid: string,
|
|
199
199
|
options: {
|
|
200
200
|
armoredManifestSignature: string;
|
|
201
|
-
signatureEmail: string;
|
|
201
|
+
signatureEmail: string | AnonymousUser;
|
|
202
202
|
armoredExtendedAttributes?: string;
|
|
203
203
|
},
|
|
204
204
|
photo: {
|
|
@@ -220,7 +220,7 @@ export class PhotoUploadAPIService extends UploadAPIService {
|
|
|
220
220
|
XAttr: options.armoredExtendedAttributes || null,
|
|
221
221
|
Photo: {
|
|
222
222
|
ContentHash: photo.contentHash,
|
|
223
|
-
CaptureTime: photo.captureTime?.getTime()
|
|
223
|
+
CaptureTime: photo.captureTime ? Math.floor(photo.captureTime?.getTime() / 1000) : 0,
|
|
224
224
|
MainPhotoLinkID: photo.mainPhotoLinkID || null,
|
|
225
225
|
Tags: photo.tags || [],
|
|
226
226
|
Exif: null, // Deprecated field, not used.
|
|
@@ -10,13 +10,13 @@ import { NodeAPIService } from '../nodes/apiService';
|
|
|
10
10
|
import { NodesCache } from '../nodes/cache';
|
|
11
11
|
import { NodesCryptoCache } from '../nodes/cryptoCache';
|
|
12
12
|
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
13
|
-
import { NodesManagement } from '../nodes/nodesManagement';
|
|
14
13
|
import { NodesRevisons } from '../nodes/nodesRevisions';
|
|
15
14
|
import { SharingPublicCryptoReporter } from './cryptoReporter';
|
|
16
|
-
import { SharingPublicNodesAccess } from './nodes';
|
|
15
|
+
import { SharingPublicNodesAccess, SharingPublicNodesManagement } from './nodes';
|
|
17
16
|
import { SharingPublicSharesManager } from './shares';
|
|
18
17
|
|
|
19
18
|
export { SharingPublicSessionManager } from './session/manager';
|
|
19
|
+
export { UnauthDriveAPIService } from './unauthApiService';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Provides facade for the whole sharing public module.
|
|
@@ -38,6 +38,7 @@ export function initSharingPublicModule(
|
|
|
38
38
|
token: string,
|
|
39
39
|
publicShareKey: PrivateKey,
|
|
40
40
|
publicRootNodeUid: string,
|
|
41
|
+
isAnonymousContext: boolean,
|
|
41
42
|
) {
|
|
42
43
|
const shares = new SharingPublicSharesManager(account, publicShareKey, publicRootNodeUid);
|
|
43
44
|
const nodes = initSharingPublicNodesModule(
|
|
@@ -52,6 +53,7 @@ export function initSharingPublicModule(
|
|
|
52
53
|
token,
|
|
53
54
|
publicShareKey,
|
|
54
55
|
publicRootNodeUid,
|
|
56
|
+
isAnonymousContext,
|
|
55
57
|
);
|
|
56
58
|
|
|
57
59
|
return {
|
|
@@ -78,6 +80,7 @@ export function initSharingPublicNodesModule(
|
|
|
78
80
|
token: string,
|
|
79
81
|
publicShareKey: PrivateKey,
|
|
80
82
|
publicRootNodeUid: string,
|
|
83
|
+
isAnonymousContext: boolean,
|
|
81
84
|
) {
|
|
82
85
|
const clientUid = undefined; // No client UID for public context yet.
|
|
83
86
|
const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService, clientUid);
|
|
@@ -96,8 +99,9 @@ export function initSharingPublicNodesModule(
|
|
|
96
99
|
token,
|
|
97
100
|
publicShareKey,
|
|
98
101
|
publicRootNodeUid,
|
|
102
|
+
isAnonymousContext,
|
|
99
103
|
);
|
|
100
|
-
const nodesManagement = new
|
|
104
|
+
const nodesManagement = new SharingPublicNodesManagement(api, cryptoCache, cryptoService, nodesAccess);
|
|
101
105
|
const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
|
|
102
106
|
|
|
103
107
|
return {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { ProtonDriveTelemetry } from '../../interface';
|
|
1
|
+
import { NodeResult, ProtonDriveTelemetry } from '../../interface';
|
|
2
2
|
import { NodeAPIService } from '../nodes/apiService';
|
|
3
3
|
import { NodesCache } from '../nodes/cache';
|
|
4
4
|
import { NodesCryptoCache } from '../nodes/cryptoCache';
|
|
5
5
|
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
6
6
|
import { NodesAccess } from '../nodes/nodesAccess';
|
|
7
|
+
import { NodesManagement } from '../nodes/nodesManagement';
|
|
7
8
|
import { isProtonDocument, isProtonSheet } from '../nodes/mediaTypes';
|
|
8
9
|
import { splitNodeUid } from '../uids';
|
|
9
10
|
import { SharingPublicSharesManager } from './shares';
|
|
10
|
-
import { DecryptedNode, DecryptedNodeKeys } from '../nodes/interface';
|
|
11
|
+
import { DecryptedNode, DecryptedNodeKeys, NodeSigningKeys } from '../nodes/interface';
|
|
11
12
|
import { PrivateKey } from '../../crypto';
|
|
12
13
|
|
|
13
14
|
export class SharingPublicNodesAccess extends NodesAccess {
|
|
@@ -22,11 +23,13 @@ export class SharingPublicNodesAccess extends NodesAccess {
|
|
|
22
23
|
private token: string,
|
|
23
24
|
private publicShareKey: PrivateKey,
|
|
24
25
|
private publicRootNodeUid: string,
|
|
26
|
+
private isAnonymousContext: boolean,
|
|
25
27
|
) {
|
|
26
28
|
super(telemetry, apiService, cache, cryptoCache, cryptoService, sharesService);
|
|
27
29
|
this.token = token;
|
|
28
30
|
this.publicShareKey = publicShareKey;
|
|
29
31
|
this.publicRootNodeUid = publicRootNodeUid;
|
|
32
|
+
this.isAnonymousContext = isAnonymousContext;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
async getParentKeys(
|
|
@@ -56,4 +59,42 @@ export class SharingPublicNodesAccess extends NodesAccess {
|
|
|
56
59
|
// Public link doesn't support specific node URLs.
|
|
57
60
|
return this.url;
|
|
58
61
|
}
|
|
62
|
+
|
|
63
|
+
async getNodeSigningKeys(
|
|
64
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
65
|
+
): Promise<NodeSigningKeys> {
|
|
66
|
+
if (this.isAnonymousContext) {
|
|
67
|
+
const nodeKeys = uids.nodeUid ? await this.getNodeKeys(uids.nodeUid) : { key: undefined };
|
|
68
|
+
const parentNodeKeys = uids.parentNodeUid ? await this.getNodeKeys(uids.parentNodeUid) : { key: undefined };
|
|
69
|
+
return {
|
|
70
|
+
type: 'nodeKey',
|
|
71
|
+
nodeKey: nodeKeys.key,
|
|
72
|
+
parentNodeKey: parentNodeKeys.key,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return super.getNodeSigningKeys(uids);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class SharingPublicNodesManagement extends NodesManagement {
|
|
81
|
+
constructor(
|
|
82
|
+
apiService: NodeAPIService,
|
|
83
|
+
cryptoCache: NodesCryptoCache,
|
|
84
|
+
cryptoService: NodesCryptoService,
|
|
85
|
+
nodesAccess: SharingPublicNodesAccess,
|
|
86
|
+
) {
|
|
87
|
+
super(apiService, cryptoCache, cryptoService, nodesAccess);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
91
|
+
// Public link does not support trashing and deleting trashed nodes.
|
|
92
|
+
// Instead, if user is owner, API allows directly deleting existing nodes.
|
|
93
|
+
for await (const result of this.apiService.deleteExistingNodes(nodeUids, signal)) {
|
|
94
|
+
if (result.ok) {
|
|
95
|
+
await this.nodesAccess.notifyNodeDeleted(result.uid);
|
|
96
|
+
}
|
|
97
|
+
yield result;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
59
100
|
}
|
|
@@ -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> {
|