@secrecy/lib 1.74.0-feat-groups-identity.4 → 1.74.0-feat-transfer-adaptations.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/lib/base-client.js +4 -27
- package/dist/lib/client/SecrecyAppClient.js +17 -13
- package/dist/lib/client/SecrecyCloudClient.js +156 -368
- package/dist/lib/client/SecrecyDbClient.js +7 -3
- package/dist/lib/client/SecrecyMailClient.js +48 -38
- package/dist/lib/client/SecrecyOrganizationClient.js +12 -10
- package/dist/lib/client/SecrecyPayClient.js +5 -1
- package/dist/lib/client/SecrecyPseudonymClient.js +8 -4
- package/dist/lib/client/SecrecyUserClient.js +11 -11
- package/dist/lib/client/SecrecyWalletClient.js +2 -0
- package/dist/lib/client/convert/data.js +4 -4
- package/dist/lib/client/convert/mail.js +6 -5
- package/dist/lib/client/convert/node.js +34 -46
- package/dist/lib/client/data-link.js +77 -0
- package/dist/lib/client/download.js +84 -0
- package/dist/lib/client/helpers.js +11 -18
- package/dist/lib/client/index.js +12 -48
- package/dist/lib/client/storage.js +2 -3
- package/dist/lib/client/types/index.js +7 -3
- package/dist/lib/client/upload.js +252 -0
- package/dist/lib/client.js +7 -0
- package/dist/lib/crypto/data.js +3 -0
- package/dist/lib/crypto/domain.js +123 -12
- package/dist/lib/crypto/helpers.js +23 -0
- package/dist/lib/index.js +0 -1
- package/dist/types/base-client.d.ts +1 -2
- package/dist/types/client/SecrecyAppClient.d.ts +3 -2
- package/dist/types/client/SecrecyCloudClient.d.ts +28 -20
- package/dist/types/client/SecrecyDbClient.d.ts +3 -1
- package/dist/types/client/SecrecyMailClient.d.ts +3 -2
- package/dist/types/client/SecrecyOrganizationClient.d.ts +3 -2
- package/dist/types/client/SecrecyPayClient.d.ts +3 -1
- package/dist/types/client/SecrecyPseudonymClient.d.ts +3 -2
- package/dist/types/client/SecrecyUserClient.d.ts +3 -2
- package/dist/types/client/convert/data.d.ts +3 -3
- package/dist/types/client/convert/mail.d.ts +5 -3
- package/dist/types/client/convert/node.d.ts +5 -5
- package/dist/types/client/data-link.d.ts +37 -0
- package/dist/types/client/download.d.ts +2 -0
- package/dist/types/client/index.d.ts +3 -11
- package/dist/types/client/storage.d.ts +2 -3
- package/dist/types/client/types/index.d.ts +9 -13
- package/dist/types/client/types/mail.d.ts +1 -2
- package/dist/types/client/types/node.d.ts +9 -12
- package/dist/types/client/types/user.d.ts +0 -15
- package/dist/types/client/upload.d.ts +38 -0
- package/dist/types/client.d.ts +6174 -681
- package/dist/types/crypto/domain.d.ts +36 -8
- package/dist/types/crypto/helpers.d.ts +12 -0
- package/dist/types/crypto/index.d.ts +3 -3
- package/dist/types/index.d.ts +1 -2
- package/package.json +4 -2
- package/dist/lib/client/types/identity.js +0 -18
- package/dist/types/client/types/identity.d.ts +0 -29
|
@@ -1,43 +1,48 @@
|
|
|
1
|
-
import ky from 'ky';
|
|
2
1
|
import { nodesCache, dataCache, dataContentCache, nodesEncryptionCache, } from '../cache.js';
|
|
3
|
-
import { secretStreamKeygen } from '../crypto/data.js';
|
|
4
2
|
import { decryptCryptoBox, encryptCryptoBox } from '../crypto/index.js';
|
|
5
|
-
import {
|
|
3
|
+
import { decompress } from '../minify/index.js';
|
|
6
4
|
import { sodium } from '../sodium.js';
|
|
7
|
-
import { enumerate, chunks, concatenate } from '../utils/array.js';
|
|
8
5
|
import { md5 } from '../worker/md5.js';
|
|
9
|
-
import { decrypt
|
|
6
|
+
import { decrypt } from '../worker/sodium.js';
|
|
10
7
|
import { apiDataToExternal, apiDataToInternal } from './convert/data.js';
|
|
11
8
|
import { apiNodeForEncryptionToInternal, apiNodeFullToInternalFull, apiNodeToExternal, apiNodeToExternalNodeFull, internalNodeFullToNodeFull, } from './convert/node.js';
|
|
12
|
-
import {
|
|
13
|
-
import { kiloToBytes } from '../utils.js';
|
|
14
|
-
import { fileTypeFromBuffer } from 'file-type';
|
|
15
|
-
import { encryptName, generateAndEncryptNameAndKey } from '../crypto/domain.js';
|
|
9
|
+
import { buildBytesFromApiData } from '../crypto/domain.js';
|
|
16
10
|
import { chunkByTotalItems } from '../utils/object.js';
|
|
17
|
-
import
|
|
11
|
+
import { createPublicDataLink, uploadData } from './upload.js';
|
|
12
|
+
import { encryptName, generateAndEncryptNameAndKey } from '../crypto/helpers.js';
|
|
13
|
+
import { downloadDataFromLink, } from './data-link.js';
|
|
18
14
|
export class SecrecyCloudClient {
|
|
19
15
|
#client;
|
|
20
|
-
|
|
16
|
+
#keys;
|
|
17
|
+
#apiClient;
|
|
18
|
+
constructor(client, keys, apiClient) {
|
|
21
19
|
this.#client = client;
|
|
20
|
+
this.#keys = keys;
|
|
21
|
+
this.#apiClient = apiClient;
|
|
22
22
|
}
|
|
23
23
|
async addDataToHistory({ dataId, nodeId, }) {
|
|
24
|
-
const addDataToHistory = await this.#
|
|
25
|
-
|
|
26
|
-
nodeId,
|
|
27
|
-
});
|
|
28
|
-
const node = await apiNodeFullToInternalFull(addDataToHistory, this.#client.keyPairs);
|
|
24
|
+
const addDataToHistory = await this.#apiClient.cloud.addDataToHistory.mutate({ dataId, nodeId });
|
|
25
|
+
const node = await apiNodeFullToInternalFull(addDataToHistory, this.#keys);
|
|
29
26
|
const data = node.history.find((d) => d.id === dataId);
|
|
30
27
|
if (data !== undefined) {
|
|
31
|
-
|
|
32
|
-
const
|
|
28
|
+
// TODO sad to have to make this extra request
|
|
29
|
+
const me = await this.#client.me();
|
|
30
|
+
const users = node.users.filter(([u]) => u.id !== me.id);
|
|
31
|
+
const userIds = users.map(([user]) => user.id);
|
|
32
|
+
const userKeys = await this.#client.app.userPublicKey(userIds);
|
|
33
|
+
const shares = users.map(([user]) => {
|
|
34
|
+
const publicKey = userKeys[user.id];
|
|
35
|
+
if (!publicKey) {
|
|
36
|
+
throw new Error('Unable to retrieve share by public key!');
|
|
37
|
+
}
|
|
33
38
|
return {
|
|
34
|
-
|
|
39
|
+
id: user.id,
|
|
35
40
|
key: data.key
|
|
36
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), publicKey, this.#
|
|
41
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), publicKey, this.#keys.privateKey))
|
|
37
42
|
: null,
|
|
38
43
|
};
|
|
39
44
|
});
|
|
40
|
-
await this.#
|
|
45
|
+
await this.#apiClient.cloud.shareDataInHistory.mutate({
|
|
41
46
|
dataId: data.id,
|
|
42
47
|
nodeId,
|
|
43
48
|
users: shares,
|
|
@@ -45,180 +50,12 @@ export class SecrecyCloudClient {
|
|
|
45
50
|
}
|
|
46
51
|
return internalNodeFullToNodeFull(node);
|
|
47
52
|
}
|
|
48
|
-
async uploadData(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
else if (typeof meta !== 'undefined') {
|
|
55
|
-
filetype = meta;
|
|
56
|
-
}
|
|
57
|
-
else if (!encrypted) {
|
|
58
|
-
filetype = await fileTypeFromBuffer(dataBuffer);
|
|
59
|
-
}
|
|
60
|
-
if (storageType === 'lite' && dataBuffer.byteLength > kiloToBytes(1024)) {
|
|
61
|
-
throw new Error('The data is too big for lite upload!');
|
|
62
|
-
}
|
|
63
|
-
const compressed = encrypted ? compress(dataBuffer) : dataBuffer;
|
|
64
|
-
const dataKey = encrypted ? secretStreamKeygen() : null;
|
|
65
|
-
const { data: encryptedData, md5: md5Data, md5Encrypted, } = dataKey
|
|
66
|
-
? await encrypt(dataKey, compressed, encryptProgress, signal)
|
|
67
|
-
: { data: compressed, md5: await md5(compressed) };
|
|
68
|
-
const encryptedDataKey = dataKey
|
|
69
|
-
? encryptCryptoBox(dataKey, this.#client.uaIdentity.identityPubKey, this.#client.uaPrivateKey)
|
|
70
|
-
: null;
|
|
71
|
-
await uploadProgress?.({
|
|
72
|
-
total: encryptedData.byteLength,
|
|
73
|
-
current: 0,
|
|
74
|
-
percent: 0,
|
|
53
|
+
async uploadData(opts) {
|
|
54
|
+
return uploadData({
|
|
55
|
+
...opts,
|
|
56
|
+
keyPair: this.#keys,
|
|
57
|
+
apiClient: this.#apiClient,
|
|
75
58
|
});
|
|
76
|
-
if (storageType === 'lite') {
|
|
77
|
-
const uploadDataArgs = encryptedDataKey && md5Encrypted
|
|
78
|
-
? {
|
|
79
|
-
type: 'encrypted',
|
|
80
|
-
content: Buffer.from(encryptedData),
|
|
81
|
-
sizeEncrypted: BigInt(encryptedData.byteLength),
|
|
82
|
-
size: BigInt(dataBuffer.byteLength),
|
|
83
|
-
key: sodium.to_hex(encryptedDataKey),
|
|
84
|
-
md5Encrypted,
|
|
85
|
-
md5: md5Data,
|
|
86
|
-
...filetype,
|
|
87
|
-
}
|
|
88
|
-
: {
|
|
89
|
-
type: 'unencrypted',
|
|
90
|
-
content: Buffer.from(encryptedData),
|
|
91
|
-
md5: md5Data,
|
|
92
|
-
sizeEncrypted: undefined,
|
|
93
|
-
size: BigInt(dataBuffer.byteLength),
|
|
94
|
-
...filetype,
|
|
95
|
-
};
|
|
96
|
-
const uploadData = await this.#client.apiClient.cloud.uploadLiteData.mutate(uploadDataArgs, { signal });
|
|
97
|
-
await uploadProgress?.({
|
|
98
|
-
total: encryptedData.byteLength,
|
|
99
|
-
current: encryptedData.byteLength,
|
|
100
|
-
percent: 1,
|
|
101
|
-
});
|
|
102
|
-
const localData = {
|
|
103
|
-
id: uploadData.id,
|
|
104
|
-
storageType: 'lite',
|
|
105
|
-
size: uploadDataArgs.size,
|
|
106
|
-
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
107
|
-
data: dataBuffer,
|
|
108
|
-
...filetype,
|
|
109
|
-
};
|
|
110
|
-
dataContentCache.set(uploadData.id, localData);
|
|
111
|
-
return localData;
|
|
112
|
-
}
|
|
113
|
-
if (storageType === 's3' || storageType === 'cold') {
|
|
114
|
-
const uploadDataArgs = encryptedDataKey && md5Encrypted
|
|
115
|
-
? {
|
|
116
|
-
type: 'encrypted',
|
|
117
|
-
sizeEncrypted: BigInt(encryptedData.byteLength),
|
|
118
|
-
size: BigInt(dataBuffer.byteLength),
|
|
119
|
-
key: sodium.to_hex(encryptedDataKey),
|
|
120
|
-
md5Encrypted,
|
|
121
|
-
md5: md5Data,
|
|
122
|
-
...filetype,
|
|
123
|
-
}
|
|
124
|
-
: {
|
|
125
|
-
type: 'unencrypted',
|
|
126
|
-
md5: md5Data,
|
|
127
|
-
size: BigInt(dataBuffer.byteLength),
|
|
128
|
-
sizeEncrypted: undefined,
|
|
129
|
-
...filetype,
|
|
130
|
-
};
|
|
131
|
-
const uploadDataCaller = storageType === 's3'
|
|
132
|
-
? this.#client.apiClient.cloud.uploadData
|
|
133
|
-
: this.#client.apiClient.cloud.uploadColdData;
|
|
134
|
-
const uploadData = await uploadDataCaller.mutate(uploadDataArgs, {
|
|
135
|
-
signal,
|
|
136
|
-
});
|
|
137
|
-
if (uploadData.parts.length === 0) {
|
|
138
|
-
if (uploadData.type === 'authed' &&
|
|
139
|
-
uploadData.keyPair.pub !== this.#client.uaIdentity.identityPubKey) {
|
|
140
|
-
throw new Error('The public key does not match with cached key!');
|
|
141
|
-
}
|
|
142
|
-
await uploadProgress?.({
|
|
143
|
-
total: encryptedData.byteLength,
|
|
144
|
-
current: encryptedData.byteLength,
|
|
145
|
-
percent: 1,
|
|
146
|
-
});
|
|
147
|
-
const localData = {
|
|
148
|
-
id: uploadData.id,
|
|
149
|
-
storageType: storageType,
|
|
150
|
-
size: uploadDataArgs.size,
|
|
151
|
-
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
152
|
-
data: dataBuffer,
|
|
153
|
-
...filetype,
|
|
154
|
-
};
|
|
155
|
-
dataContentCache.set(uploadData.id, localData);
|
|
156
|
-
return localData;
|
|
157
|
-
}
|
|
158
|
-
const uploadDataPartEnd = async (md5, order) => {
|
|
159
|
-
return this.#client.apiClient.cloud.uploadDataPartEnd.mutate({ dataId: uploadData.id, md5, order }, { signal });
|
|
160
|
-
};
|
|
161
|
-
const chunkParts = new Array();
|
|
162
|
-
for (const [index, chunk] of enumerate(chunks(encryptedData, Number(uploadData.partSize)))) {
|
|
163
|
-
chunkParts.push({
|
|
164
|
-
order: index + 1,
|
|
165
|
-
data: chunk,
|
|
166
|
-
md5: await md5(chunk),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
const progressParts = {};
|
|
170
|
-
const onProgress = (part, progressEvent) => {
|
|
171
|
-
progressParts[part] = progressEvent;
|
|
172
|
-
const current = Object.values(progressParts).reduce((prv, cur) => prv + cur.transferredBytes, 0);
|
|
173
|
-
void uploadProgress?.({
|
|
174
|
-
percent: current / encryptedData.byteLength,
|
|
175
|
-
total: encryptedData.byteLength,
|
|
176
|
-
current,
|
|
177
|
-
});
|
|
178
|
-
};
|
|
179
|
-
const byPart = async (part) => {
|
|
180
|
-
const formData = new FormData();
|
|
181
|
-
const chunk = chunkParts.find((p) => p.order === part.order);
|
|
182
|
-
if (chunk === undefined) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
for (const [key, value] of Object.entries(part.fields)) {
|
|
186
|
-
formData.append(key, value);
|
|
187
|
-
}
|
|
188
|
-
formData.append('file', new Blob([chunk.data], { type: filetype?.mime }), `${uploadData.id}-${chunk.order}`);
|
|
189
|
-
// await ky.post(part.url, {
|
|
190
|
-
// signal,
|
|
191
|
-
// body: formData,
|
|
192
|
-
// onUploadProgress: (progressEvent) => {
|
|
193
|
-
// onProgress(part.order, progressEvent)
|
|
194
|
-
// },
|
|
195
|
-
// })
|
|
196
|
-
await axios.post(part.url, formData, {
|
|
197
|
-
onUploadProgress: (progressEvent) => {
|
|
198
|
-
onProgress(part.order, {
|
|
199
|
-
percent: progressEvent.progress ?? 0,
|
|
200
|
-
totalBytes: progressEvent.total ?? 0,
|
|
201
|
-
transferredBytes: progressEvent.loaded,
|
|
202
|
-
});
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
return uploadDataPartEnd(chunk.md5, chunk.order);
|
|
206
|
-
};
|
|
207
|
-
await promiseAllLimit(3, uploadData.parts.map((p) => async () => {
|
|
208
|
-
await byPart(p);
|
|
209
|
-
}));
|
|
210
|
-
const localData = {
|
|
211
|
-
id: uploadData.id,
|
|
212
|
-
storageType: storageType,
|
|
213
|
-
size: uploadDataArgs.size,
|
|
214
|
-
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
215
|
-
data: dataBuffer,
|
|
216
|
-
...filetype,
|
|
217
|
-
};
|
|
218
|
-
dataContentCache.set(uploadData.id, localData);
|
|
219
|
-
return localData;
|
|
220
|
-
}
|
|
221
|
-
throw new Error(`The "${storageType}" is not implemented yet!`);
|
|
222
59
|
}
|
|
223
60
|
async uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress, storageType = 's3', signal, }) {
|
|
224
61
|
const uploadedData = await this.uploadData({
|
|
@@ -231,21 +68,21 @@ export class SecrecyCloudClient {
|
|
|
231
68
|
return await this.saveInCloud({ dataId: uploadedData.id, name, nodeId });
|
|
232
69
|
}
|
|
233
70
|
async deletedNodes() {
|
|
234
|
-
const deletedNodes = await this.#
|
|
235
|
-
return await Promise.all(deletedNodes.map(async (node) => await apiNodeToExternal(node, this.#
|
|
71
|
+
const deletedNodes = await this.#apiClient.cloud.nodesDeleted.query({});
|
|
72
|
+
return await Promise.all(deletedNodes.map(async (node) => await apiNodeToExternal(node, this.#keys)));
|
|
236
73
|
}
|
|
237
74
|
async sharedNodes() {
|
|
238
|
-
const nodesShared = await this.#
|
|
239
|
-
return await Promise.all(nodesShared.map(async (node) => await apiNodeToExternal(node, this.#
|
|
75
|
+
const nodesShared = await this.#apiClient.cloud.nodesShared.query();
|
|
76
|
+
return await Promise.all(nodesShared.map(async (node) => await apiNodeToExternal(node, this.#keys)));
|
|
240
77
|
}
|
|
241
78
|
async nodesSharedWithMe(type = 'FOLDER') {
|
|
242
|
-
const nodesSharedWithMe = await this.#
|
|
243
|
-
return await Promise.all(nodesSharedWithMe.map(async (node) => await apiNodeToExternal(node, this.#
|
|
79
|
+
const nodesSharedWithMe = await this.#apiClient.cloud.nodesSharedWithMe.query({ type });
|
|
80
|
+
return await Promise.all(nodesSharedWithMe.map(async (node) => await apiNodeToExternal(node, this.#keys)));
|
|
244
81
|
}
|
|
245
|
-
async deleteNodeSharing({ nodeId,
|
|
246
|
-
const { isDeleted } = await this.#
|
|
82
|
+
async deleteNodeSharing({ nodeId, userId, }) {
|
|
83
|
+
const { isDeleted } = await this.#apiClient.cloud.deleteNodeSharing.mutate({
|
|
247
84
|
nodeId,
|
|
248
|
-
|
|
85
|
+
userId,
|
|
249
86
|
});
|
|
250
87
|
return isDeleted;
|
|
251
88
|
}
|
|
@@ -258,14 +95,14 @@ export class SecrecyCloudClient {
|
|
|
258
95
|
throw new Error(`Node (${nodeId}) does not exists`);
|
|
259
96
|
}
|
|
260
97
|
}
|
|
261
|
-
if (
|
|
262
|
-
throw new Error(`
|
|
98
|
+
if (node.access?.nameKey === undefined) {
|
|
99
|
+
throw new Error(`Can't have access to node ${nodeId}`);
|
|
263
100
|
}
|
|
264
101
|
name =
|
|
265
|
-
typeof name === 'string' && node.
|
|
266
|
-
? await encryptName(name, node.
|
|
102
|
+
typeof name === 'string' && node.access.nameKey !== null
|
|
103
|
+
? await encryptName(name, node.access.nameKey)
|
|
267
104
|
: null;
|
|
268
|
-
const { isDuplicated } = await this.#
|
|
105
|
+
const { isDuplicated } = await this.#apiClient.cloud.duplicateNode.mutate({
|
|
269
106
|
nodeId,
|
|
270
107
|
folderId: folderId ?? null,
|
|
271
108
|
name,
|
|
@@ -273,54 +110,50 @@ export class SecrecyCloudClient {
|
|
|
273
110
|
return isDuplicated;
|
|
274
111
|
}
|
|
275
112
|
async deleteNodeCloudTrash({ ids }) {
|
|
276
|
-
const { isDeleted } = await this.#
|
|
113
|
+
const { isDeleted } = await this.#apiClient.cloud.deleteNodeCloudTrash.mutate({ ids });
|
|
277
114
|
return isDeleted;
|
|
278
115
|
}
|
|
279
116
|
async createFolder({ name, parentFolderId, }) {
|
|
280
117
|
const { encryptedNameKey, encryptedName } = await generateAndEncryptNameAndKey({
|
|
281
118
|
name,
|
|
282
|
-
privateKey: this.#
|
|
283
|
-
publicKey: this.#
|
|
119
|
+
privateKey: this.#keys.privateKey,
|
|
120
|
+
publicKey: this.#keys.publicKey,
|
|
284
121
|
});
|
|
285
|
-
const createdFolder = await this.#
|
|
122
|
+
const createdFolder = await this.#apiClient.cloud.createFolder.mutate({
|
|
286
123
|
name: encryptedName,
|
|
287
124
|
parentId: parentFolderId ?? null,
|
|
288
125
|
nameKey: encryptedNameKey,
|
|
289
126
|
});
|
|
290
|
-
const folder = await apiNodeToExternalNodeFull(createdFolder, this.#
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
sharingDelAccess: permissions.sharingDelAccess,
|
|
303
|
-
})));
|
|
304
|
-
}
|
|
127
|
+
const folder = await apiNodeToExternalNodeFull(createdFolder, this.#keys);
|
|
128
|
+
const users = folder.parent?.users?.filter(([u]) => u.id !== folder.owner.id) ?? [];
|
|
129
|
+
if (users.length > 0) {
|
|
130
|
+
await this.shareNode(users.map(([user, permissions]) => ({
|
|
131
|
+
userId: user.id,
|
|
132
|
+
nodeId: folder.id,
|
|
133
|
+
rights: permissions.rights,
|
|
134
|
+
addAccess: permissions.addAccess,
|
|
135
|
+
delAccess: permissions.delAccess,
|
|
136
|
+
sharingAddAccess: permissions.sharingAddAccess,
|
|
137
|
+
sharingDelAccess: permissions.sharingDelAccess,
|
|
138
|
+
})));
|
|
305
139
|
}
|
|
306
140
|
return folder;
|
|
307
141
|
}
|
|
308
142
|
async node({ id, deleted, } = {}) {
|
|
309
|
-
const node = await this.#
|
|
310
|
-
|
|
311
|
-
deleted,
|
|
312
|
-
});
|
|
313
|
-
return await apiNodeToExternalNodeFull(node, this.#client.keyPairs);
|
|
143
|
+
const node = await this.#apiClient.cloud.nodeFullById.query({ id, deleted });
|
|
144
|
+
return await apiNodeToExternalNodeFull(node, this.#keys);
|
|
314
145
|
}
|
|
315
146
|
async dataMetadata({ id }) {
|
|
316
|
-
const data = await this.#
|
|
317
|
-
return apiDataToExternal(data, this.#
|
|
147
|
+
const data = await this.#apiClient.cloud.dataById.query({ id });
|
|
148
|
+
return apiDataToExternal(data, this.#keys);
|
|
318
149
|
}
|
|
319
|
-
async shareNode(
|
|
150
|
+
async shareNode(input, progress) {
|
|
320
151
|
// TODO: Validate input
|
|
321
|
-
const nodesMap = await this.#
|
|
322
|
-
|
|
323
|
-
|
|
152
|
+
const nodesMap = await this.#apiClient.cloud.shareNode.mutate(input);
|
|
153
|
+
const neededUserKey = Object.entries(nodesMap)
|
|
154
|
+
.filter(([, nodes]) => nodes.some((node) => node.includeKeys))
|
|
155
|
+
.map(([userId]) => userId);
|
|
156
|
+
const publicKeysMap = await this.#client.app.userPublicKey(neededUserKey);
|
|
324
157
|
const maxNodesBatchSize = 1000;
|
|
325
158
|
const totalNodesToShare = Object.values(nodesMap).reduce((size, ids) => size + ids.length, 0);
|
|
326
159
|
progress?.({ total: totalNodesToShare, current: 0, percent: 0 });
|
|
@@ -337,9 +170,9 @@ export class SecrecyCloudClient {
|
|
|
337
170
|
return filtered.length > 0 ? [userId, filtered] : null;
|
|
338
171
|
})
|
|
339
172
|
.filter((entry) => entry !== null));
|
|
340
|
-
const infos = await this.
|
|
341
|
-
const nodesToUpdateRights = Object.fromEntries(Object.entries(nodesMap).map(([
|
|
342
|
-
|
|
173
|
+
const infos = await this.encryptNodesForUsers(nodesToEncrypt, publicKeysMap);
|
|
174
|
+
const nodesToUpdateRights = Object.fromEntries(Object.entries(nodesMap).map(([userId, nodes]) => [
|
|
175
|
+
userId,
|
|
343
176
|
nodes
|
|
344
177
|
.filter((node) => !node.includeKeys)
|
|
345
178
|
.map((node) => {
|
|
@@ -355,13 +188,13 @@ export class SecrecyCloudClient {
|
|
|
355
188
|
};
|
|
356
189
|
}),
|
|
357
190
|
]));
|
|
358
|
-
const withKeys = Object.fromEntries(Object.entries(infos).map(([
|
|
191
|
+
const withKeys = Object.fromEntries(Object.entries(infos).map(([userId, nodes]) => {
|
|
359
192
|
return [
|
|
360
|
-
|
|
193
|
+
userId,
|
|
361
194
|
{
|
|
362
|
-
|
|
195
|
+
userId,
|
|
363
196
|
nodes: nodes.map((node) => {
|
|
364
|
-
const map = nodesMap[
|
|
197
|
+
const map = nodesMap[userId];
|
|
365
198
|
if (!map) {
|
|
366
199
|
throw new Error('Unable to retrieve mapped nodes!');
|
|
367
200
|
}
|
|
@@ -384,22 +217,20 @@ export class SecrecyCloudClient {
|
|
|
384
217
|
];
|
|
385
218
|
}));
|
|
386
219
|
const finishInput = [];
|
|
387
|
-
for (const [
|
|
220
|
+
for (const [userId, nodes] of Object.entries(withKeys)) {
|
|
388
221
|
if (nodes.nodes.length === 0) {
|
|
389
222
|
continue;
|
|
390
223
|
}
|
|
391
|
-
finishInput.push({
|
|
224
|
+
finishInput.push({ userId, nodes: nodes.nodes });
|
|
392
225
|
}
|
|
393
|
-
for (const [
|
|
226
|
+
for (const [userId, nodes] of Object.entries(nodesToUpdateRights)) {
|
|
394
227
|
if (nodes.length === 0) {
|
|
395
228
|
continue;
|
|
396
229
|
}
|
|
397
|
-
finishInput.push({
|
|
230
|
+
finishInput.push({ userId, nodes });
|
|
398
231
|
}
|
|
399
232
|
const subState = finishInput.length > 0
|
|
400
|
-
? await this.#
|
|
401
|
-
accesses: finishInput,
|
|
402
|
-
})
|
|
233
|
+
? await this.#apiClient.cloud.shareNodeFinish.mutate(finishInput)
|
|
403
234
|
: { isFinished: true, details: {} };
|
|
404
235
|
const currentProgress = Math.min((index + 1) * maxNodesBatchSize, totalNodesToShare);
|
|
405
236
|
progress?.({
|
|
@@ -446,31 +277,27 @@ export class SecrecyCloudClient {
|
|
|
446
277
|
throw `Can't find Node ${nodeId}`;
|
|
447
278
|
}
|
|
448
279
|
}
|
|
449
|
-
if (node.
|
|
450
|
-
|
|
451
|
-
throw new Error(`No access to update node ${nodeId}`);
|
|
452
|
-
}
|
|
453
|
-
if (!node.accesses[0]) {
|
|
454
|
-
throw new Error(`No access found for node ${nodeId}`);
|
|
280
|
+
if (node.access?.nameKey === undefined) {
|
|
281
|
+
throw new Error(`Can't have access to node ${nodeId}`);
|
|
455
282
|
}
|
|
456
283
|
name =
|
|
457
|
-
typeof name === 'string' && node.
|
|
458
|
-
? await encryptName(name, node.
|
|
284
|
+
typeof name === 'string' && node.access.nameKey !== null
|
|
285
|
+
? await encryptName(name, node.access.nameKey)
|
|
459
286
|
: null;
|
|
460
|
-
const updateNode = await this.#
|
|
287
|
+
const updateNode = await this.#apiClient.cloud.updateNode.mutate({
|
|
461
288
|
deletedAt: deletedAt ?? null,
|
|
462
289
|
id: nodeId,
|
|
463
290
|
isFavorite: isFavorite ?? null,
|
|
464
291
|
name,
|
|
465
292
|
});
|
|
466
|
-
return await apiNodeToExternalNodeFull(updateNode, this.#
|
|
293
|
+
return await apiNodeToExternalNodeFull(updateNode, this.#keys);
|
|
467
294
|
}
|
|
468
295
|
async dataContent({ dataId, onDownloadProgress, progressDecrypt, signal, }) {
|
|
469
296
|
const cached = dataContentCache.get(dataId);
|
|
470
297
|
if (cached !== undefined) {
|
|
471
298
|
return cached;
|
|
472
299
|
}
|
|
473
|
-
const dataContent = await this.#
|
|
300
|
+
const dataContent = await this.#apiClient.cloud.dataContentById.query({
|
|
474
301
|
id: dataId,
|
|
475
302
|
});
|
|
476
303
|
const totalBytes = Number(dataContent.sizeEncrypted ?? dataContent.size);
|
|
@@ -491,7 +318,7 @@ export class SecrecyCloudClient {
|
|
|
491
318
|
if (cachedData.length === dataIds.length) {
|
|
492
319
|
return cachedData;
|
|
493
320
|
}
|
|
494
|
-
const missingContents = await this.#
|
|
321
|
+
const missingContents = await this.#apiClient.cloud.dataContentByIds.query({
|
|
495
322
|
ids: dataIds.filter((dataId) => !cachedData.some((datum) => datum.id === dataId)),
|
|
496
323
|
});
|
|
497
324
|
const allDataContents = [
|
|
@@ -522,33 +349,33 @@ export class SecrecyCloudClient {
|
|
|
522
349
|
}));
|
|
523
350
|
}
|
|
524
351
|
async deleteNodes({ nodeIds, }) {
|
|
525
|
-
return this.#
|
|
352
|
+
return this.#apiClient.cloud.deleteNodes.mutate({ ids: nodeIds });
|
|
526
353
|
}
|
|
527
354
|
async deleteData({ dataId, nodeId, }) {
|
|
528
|
-
const { isDeleted } = await this.#
|
|
355
|
+
const { isDeleted } = await this.#apiClient.cloud.deleteData.mutate({
|
|
529
356
|
dataId,
|
|
530
357
|
nodeId,
|
|
531
358
|
});
|
|
532
359
|
return isDeleted;
|
|
533
360
|
}
|
|
534
361
|
async deleteNode({ nodeId }) {
|
|
535
|
-
const { isDeleted } = await this.#
|
|
362
|
+
const { isDeleted } = await this.#apiClient.cloud.deleteNode.mutate({
|
|
536
363
|
id: nodeId,
|
|
537
364
|
});
|
|
538
365
|
return isDeleted;
|
|
539
366
|
}
|
|
540
367
|
async emptyTrash() {
|
|
541
|
-
const { isCleaned } = await this.#
|
|
368
|
+
const { isCleaned } = await this.#apiClient.cloud.emptyNodeCloudTrash.mutate({});
|
|
542
369
|
return isCleaned;
|
|
543
370
|
}
|
|
544
371
|
async recoverNode(id) {
|
|
545
|
-
const { isRecovered } = await this.#
|
|
372
|
+
const { isRecovered } = await this.#apiClient.cloud.recoverNode.mutate({
|
|
546
373
|
id,
|
|
547
374
|
});
|
|
548
375
|
return isRecovered;
|
|
549
376
|
}
|
|
550
377
|
async moveNodes({ nodeIds, parentNodeId, }) {
|
|
551
|
-
const { isMoved } = await this.#
|
|
378
|
+
const { isMoved } = await this.#apiClient.cloud.moveNodes.mutate({
|
|
552
379
|
ids: nodeIds,
|
|
553
380
|
parentId: parentNodeId ?? null,
|
|
554
381
|
});
|
|
@@ -593,7 +420,7 @@ export class SecrecyCloudClient {
|
|
|
593
420
|
}
|
|
594
421
|
const senderPubKey = await this.#client.app.userPublicKey(mail.sender.id);
|
|
595
422
|
const dataKey = attachment.key
|
|
596
|
-
? decryptCryptoBox(sodium.from_hex(attachment.key), senderPubKey, this.#
|
|
423
|
+
? decryptCryptoBox(sodium.from_hex(attachment.key), senderPubKey, this.#keys.privateKey)
|
|
597
424
|
: null;
|
|
598
425
|
key = dataKey !== null ? sodium.to_hex(dataKey) : null;
|
|
599
426
|
}
|
|
@@ -605,27 +432,28 @@ export class SecrecyCloudClient {
|
|
|
605
432
|
key = data.key;
|
|
606
433
|
}
|
|
607
434
|
key = key
|
|
608
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), this.#
|
|
435
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), this.#keys.publicKey, this.#keys.privateKey))
|
|
609
436
|
: null;
|
|
610
437
|
const { encryptedNameKey, encryptedName } = await generateAndEncryptNameAndKey({
|
|
611
438
|
name,
|
|
612
|
-
privateKey: this.#
|
|
613
|
-
publicKey: this.#
|
|
439
|
+
privateKey: this.#keys.privateKey,
|
|
440
|
+
publicKey: this.#keys.publicKey,
|
|
614
441
|
});
|
|
615
|
-
const saveInCloud = await this.#
|
|
442
|
+
const saveInCloud = await this.#apiClient.cloud.saveInCloud.mutate({
|
|
616
443
|
dataId,
|
|
617
444
|
key,
|
|
618
445
|
nodeId: nodeId ?? null,
|
|
619
446
|
fileName: encryptedName,
|
|
620
447
|
nameKey: encryptedNameKey,
|
|
621
448
|
});
|
|
622
|
-
const node = await apiNodeToExternalNodeFull(saveInCloud, this.#
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
449
|
+
const node = await apiNodeToExternalNodeFull(saveInCloud, this.#keys);
|
|
450
|
+
const me = node.parent?.users.find(([u]) => u.id === node.owner.id);
|
|
451
|
+
// TODO: ??
|
|
452
|
+
if (me !== undefined && ['delete', 'write'].includes(me[1].rights)) {
|
|
453
|
+
const others = node.parent?.users.filter(([u]) => u.id !== node.owner.id) ?? [];
|
|
454
|
+
if (others.length > 0) {
|
|
455
|
+
await this.shareNode(others.map(([user, permissions]) => ({
|
|
456
|
+
userId: user.id,
|
|
629
457
|
nodeId: node.id,
|
|
630
458
|
...permissions,
|
|
631
459
|
})));
|
|
@@ -633,13 +461,20 @@ export class SecrecyCloudClient {
|
|
|
633
461
|
}
|
|
634
462
|
return node;
|
|
635
463
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
464
|
+
encryptNodesForUsers = async (userNodes, // { [userId]: nodeIds[] }
|
|
465
|
+
userPublicKeys) => {
|
|
466
|
+
const userIds = Object.keys(userNodes).map((userId) => userId);
|
|
467
|
+
const nodeIds = Object.values(userNodes).flatMap((nodeIds) => nodeIds);
|
|
639
468
|
if (nodeIds.length === 0) {
|
|
640
469
|
return {};
|
|
641
470
|
}
|
|
642
471
|
const nodes = [];
|
|
472
|
+
// Pre check to ensure we get all public keys for users!
|
|
473
|
+
for (const userId of userIds) {
|
|
474
|
+
if (userPublicKeys[userId] === undefined) {
|
|
475
|
+
throw new Error(`Unable to retrieve some user public keys!`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
643
478
|
// Retrieve and format nodes.
|
|
644
479
|
for (const nodeId of nodeIds) {
|
|
645
480
|
let node = nodesEncryptionCache.get(nodeId) ?? nodesCache.get(nodeId);
|
|
@@ -664,7 +499,7 @@ export class SecrecyCloudClient {
|
|
|
664
499
|
// Retrieve all missing nodes from cache to api.
|
|
665
500
|
const missingNodeIds = nodeIds.filter((nodeId) => !nodes.some((node) => node.id === nodeId));
|
|
666
501
|
const fetchedNodes = missingNodeIds.length > 0
|
|
667
|
-
? await this.#
|
|
502
|
+
? await this.#apiClient.cloud.nodesForEncryption.query({
|
|
668
503
|
ids: missingNodeIds,
|
|
669
504
|
})
|
|
670
505
|
: [];
|
|
@@ -673,13 +508,13 @@ export class SecrecyCloudClient {
|
|
|
673
508
|
throw new Error(`Unable to fetch some node infos (${diff.join(', ')})`);
|
|
674
509
|
}
|
|
675
510
|
const finalNodes = await Promise.all(fetchedNodes.map((node) => {
|
|
676
|
-
return apiNodeForEncryptionToInternal(node, this.#
|
|
511
|
+
return apiNodeForEncryptionToInternal(node, this.#keys);
|
|
677
512
|
}));
|
|
678
513
|
nodes.push(...finalNodes);
|
|
679
514
|
const nodesMappedUsers = {};
|
|
680
|
-
for (const
|
|
681
|
-
nodesMappedUsers[
|
|
682
|
-
for (const nodeId of
|
|
515
|
+
for (const userId in userNodes) {
|
|
516
|
+
nodesMappedUsers[userId] ??= [];
|
|
517
|
+
for (const nodeId of userNodes[userId]) {
|
|
683
518
|
const node = nodes.find((node) => node.id === nodeId);
|
|
684
519
|
if (!node) {
|
|
685
520
|
throw new Error('Unable to retrieve node from ram');
|
|
@@ -689,16 +524,17 @@ export class SecrecyCloudClient {
|
|
|
689
524
|
throw new Error(`Can't share a node without data (${node.id})!`);
|
|
690
525
|
}
|
|
691
526
|
const nameKey = node.access?.nameKey;
|
|
692
|
-
|
|
527
|
+
const publicKey = userPublicKeys[userId];
|
|
528
|
+
nodesMappedUsers[userId].push({
|
|
693
529
|
id: node.id,
|
|
694
530
|
nameKey: nameKey !== null
|
|
695
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(nameKey),
|
|
531
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(nameKey), publicKey, this.#keys.privateKey))
|
|
696
532
|
: null,
|
|
697
533
|
data: 'history' in node
|
|
698
534
|
? node.history.map((f) => ({
|
|
699
535
|
id: f.id,
|
|
700
536
|
key: f.key
|
|
701
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(f.key),
|
|
537
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(f.key), publicKey, this.#keys.privateKey))
|
|
702
538
|
: null,
|
|
703
539
|
}))
|
|
704
540
|
: [],
|
|
@@ -709,15 +545,15 @@ export class SecrecyCloudClient {
|
|
|
709
545
|
};
|
|
710
546
|
async reportData({ id, reasons, }) {
|
|
711
547
|
const [rawData, secrecy] = await Promise.all([
|
|
712
|
-
this.#
|
|
713
|
-
this.#
|
|
548
|
+
this.#apiClient.cloud.dataById.query({ id }),
|
|
549
|
+
this.#apiClient.misc.publicKey.query(),
|
|
714
550
|
]);
|
|
715
|
-
const data = apiDataToInternal(rawData, this.#
|
|
716
|
-
return this.#
|
|
551
|
+
const data = apiDataToInternal(rawData, this.#keys);
|
|
552
|
+
return this.#apiClient.cloud.reportData.mutate({
|
|
717
553
|
id: id,
|
|
718
554
|
reasons: reasons,
|
|
719
555
|
encryptedDataKey: data.key
|
|
720
|
-
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), secrecy.publicKey, this.#
|
|
556
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), secrecy.publicKey, this.#keys.privateKey))
|
|
721
557
|
: null,
|
|
722
558
|
});
|
|
723
559
|
}
|
|
@@ -731,95 +567,47 @@ export class SecrecyCloudClient {
|
|
|
731
567
|
throw new Error("It's not possible to transfer a cold stored data to a lite storage!");
|
|
732
568
|
}
|
|
733
569
|
}
|
|
734
|
-
return this.#
|
|
570
|
+
return this.#apiClient.cloud.moveToStorageType.mutate(input);
|
|
735
571
|
}
|
|
736
572
|
getPublicDataLink(input) {
|
|
737
|
-
return this.#
|
|
573
|
+
return this.#apiClient.cloud.dataLink.query(input);
|
|
738
574
|
}
|
|
739
575
|
getPublicDataLinks(input) {
|
|
740
|
-
return this.#
|
|
576
|
+
return this.#apiClient.cloud.dataLinks.query(input);
|
|
741
577
|
}
|
|
742
578
|
checkAccesses(input) {
|
|
743
|
-
return this.#
|
|
579
|
+
return this.#apiClient.cloud.checkAccesses.query(input);
|
|
744
580
|
}
|
|
745
581
|
createPublicDataLink(input) {
|
|
746
|
-
|
|
747
|
-
throw new Error('Unable to create public link using a past expireAt date!');
|
|
748
|
-
}
|
|
749
|
-
return this.#client.apiClient.cloud.createDataLink.mutate(input);
|
|
582
|
+
return createPublicDataLink({ ...input, apiClient: this.#apiClient });
|
|
750
583
|
}
|
|
751
584
|
updatePublicDataLink(input) {
|
|
752
|
-
return this.#
|
|
585
|
+
return this.#apiClient.cloud.updateDataLink.mutate(input);
|
|
753
586
|
}
|
|
754
587
|
deletePublicDataLink(input) {
|
|
755
|
-
return this.#
|
|
588
|
+
return this.#apiClient.cloud.deleteDataLink.mutate(input);
|
|
589
|
+
}
|
|
590
|
+
downloadDataFromLink(input) {
|
|
591
|
+
return downloadDataFromLink({
|
|
592
|
+
...input,
|
|
593
|
+
dataUrl: this.#client.secrecyUrls.data,
|
|
594
|
+
});
|
|
756
595
|
}
|
|
757
596
|
async _handleDataContent({ dataContent, totalBytes, progressParts, onDownloadProgress, progressDecrypt, signal, }) {
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
};
|
|
767
|
-
const encryptedContentFromParts = async (arg) => {
|
|
768
|
-
const parts = {};
|
|
769
|
-
const byPart = async (part) => {
|
|
770
|
-
const buf = new Uint8Array(await ky
|
|
771
|
-
.get(part.contentUrl, {
|
|
772
|
-
timeout: false,
|
|
773
|
-
onDownloadProgress: (pr) => {
|
|
774
|
-
onProgress(`${arg.dataId}-${part.order}`, pr);
|
|
775
|
-
},
|
|
776
|
-
signal,
|
|
777
|
-
})
|
|
778
|
-
.arrayBuffer());
|
|
779
|
-
const md5Part = await md5(buf);
|
|
780
|
-
if (md5Part !== part.md5) {
|
|
781
|
-
throw new Error(`Invalid md5 for part ${part.order} of data ${arg.dataId}`);
|
|
782
|
-
}
|
|
783
|
-
if (typeof parts[arg.dataId] === 'undefined') {
|
|
784
|
-
parts[arg.dataId] = [{ data: buf, order: part.order }];
|
|
785
|
-
}
|
|
786
|
-
else {
|
|
787
|
-
parts[arg.dataId].push({ data: buf, order: part.order });
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
await promiseAllLimit(3, arg.dataParts.map((p) => async () => byPart(p)));
|
|
791
|
-
return concatenate(...parts[arg.dataId]
|
|
792
|
-
.sort((a, b) => a.order - b.order)
|
|
793
|
-
.map((p) => p.data));
|
|
794
|
-
};
|
|
795
|
-
const encryptedContent = dataContent.type === 'lite'
|
|
796
|
-
? new Uint8Array(dataContent.content)
|
|
797
|
-
: dataContent.type === 'cloud'
|
|
798
|
-
? await encryptedContentFromParts({
|
|
799
|
-
dataId: dataContent.id,
|
|
800
|
-
dataParts: dataContent.parts,
|
|
801
|
-
})
|
|
802
|
-
: dataContent.maybeContent !== null
|
|
803
|
-
? new Uint8Array(dataContent.maybeContent)
|
|
804
|
-
: dataContent.maybeParts !== null
|
|
805
|
-
? await encryptedContentFromParts({
|
|
806
|
-
dataId: dataContent.id,
|
|
807
|
-
dataParts: dataContent.maybeParts,
|
|
808
|
-
})
|
|
809
|
-
: null;
|
|
810
|
-
if (encryptedContent === null) {
|
|
811
|
-
throw `Can't find content for data ${dataContent.id}`;
|
|
812
|
-
}
|
|
813
|
-
const md5Encrypted = await md5(encryptedContent);
|
|
814
|
-
if (md5Encrypted !== dataContent.md5Encrypted) {
|
|
815
|
-
throw new Error(`Encrypted content does not match`);
|
|
816
|
-
}
|
|
597
|
+
const { encryptedContent } = await buildBytesFromApiData({
|
|
598
|
+
dataContent,
|
|
599
|
+
totalBytes,
|
|
600
|
+
progressParts,
|
|
601
|
+
onDownloadProgress,
|
|
602
|
+
progressDecrypt,
|
|
603
|
+
signal,
|
|
604
|
+
});
|
|
817
605
|
const key = dataContent.key
|
|
818
606
|
? decryptCryptoBox(sodium.from_hex(dataContent.key), dataContent.type === 'received_mail'
|
|
819
607
|
? dataContent.senderPublicKey
|
|
820
608
|
: dataContent.type === 'cloud' || dataContent.type === 'lite'
|
|
821
609
|
? dataContent.publicKey
|
|
822
|
-
: this.#
|
|
610
|
+
: this.#keys.publicKey, this.#keys.privateKey)
|
|
823
611
|
: null;
|
|
824
612
|
const src = key
|
|
825
613
|
? await decrypt(key, encryptedContent, progressDecrypt, signal)
|