@secrecy/lib 1.62.0-feat-storage-providers.4 → 1.62.0-feat-node-sharing.2
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/cache.js +1 -0
- package/dist/lib/client/SecrecyAppClient.js +25 -10
- package/dist/lib/client/SecrecyCloudClient.js +155 -106
- package/dist/lib/client/convert/node.js +2 -2
- package/dist/types/cache.d.ts +2 -1
- package/dist/types/client/SecrecyAppClient.d.ts +1 -0
- package/dist/types/client/SecrecyCloudClient.d.ts +52 -17
- package/dist/types/client/convert/node.d.ts +1 -3
- package/dist/types/client/types/data.d.ts +3 -18
- package/dist/types/client/types/node.d.ts +20 -0
- package/dist/types/client.d.ts +616 -1628
- package/package.json +2 -2
package/dist/lib/cache.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LRUCache } from 'lru-cache';
|
|
|
2
2
|
import { gigaToBytes } from './utils.js';
|
|
3
3
|
export const dataCache = new Map();
|
|
4
4
|
export const nodesCache = new Map();
|
|
5
|
+
export const nodesEncryptionCache = new Map();
|
|
5
6
|
export const usersCache = new Map();
|
|
6
7
|
export const publicKeysCache = new Map();
|
|
7
8
|
export const dataContentCache = new LRUCache({
|
|
@@ -63,17 +63,32 @@ export class SecrecyAppClient {
|
|
|
63
63
|
async notifications() {
|
|
64
64
|
return await this.#apiClient.application.notification.query();
|
|
65
65
|
}
|
|
66
|
-
async userPublicKey(
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
const { publicKey } = await this.#apiClient.application.userPublicKey.query({
|
|
66
|
+
async userPublicKey(input) {
|
|
67
|
+
const appId = this.jwtDecoded.aud?.toString();
|
|
68
|
+
const userIds = Array.isArray(input) ? input : [input];
|
|
69
|
+
const publicKeys = Object.fromEntries(userIds
|
|
70
|
+
.map((userId) => [
|
|
73
71
|
userId,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
publicKeysCache.get(`userPublicKey:${userId}-${appId}`),
|
|
73
|
+
])
|
|
74
|
+
.filter(([_, key]) => !!key));
|
|
75
|
+
const missingKeys = [
|
|
76
|
+
...new Set(userIds.filter((userId) => publicKeys[userId] === undefined)),
|
|
77
|
+
];
|
|
78
|
+
if (missingKeys.length > 0) {
|
|
79
|
+
const userKeysMap = await this.#apiClient.application.userPublicKey.query(userIds);
|
|
80
|
+
if ('publicKey' in userKeysMap) {
|
|
81
|
+
throw Error('Should not happen!');
|
|
82
|
+
}
|
|
83
|
+
if (Object.keys(userKeysMap).length !== missingKeys.length) {
|
|
84
|
+
throw new Error("Unable to load some user's public keys!");
|
|
85
|
+
}
|
|
86
|
+
for (const userId in userKeysMap) {
|
|
87
|
+
publicKeys[userId] = userKeysMap[userId];
|
|
88
|
+
publicKeysCache.set(`userPublicKey:${userId}-${appId}`, userKeysMap[userId]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Array.isArray(input) ? publicKeys : publicKeys[input];
|
|
77
92
|
}
|
|
78
93
|
async settings() {
|
|
79
94
|
const settings = await this.#apiClient.application.settings.query({});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import ky from 'ky';
|
|
3
3
|
import { encryptName } from '../index.js';
|
|
4
|
-
import { nodesCache, dataCache, dataContentCache } from '../cache.js';
|
|
4
|
+
import { nodesCache, dataCache, dataContentCache, nodesEncryptionCache, } from '../cache.js';
|
|
5
5
|
import { secretStreamKeygen } from '../crypto/data.js';
|
|
6
6
|
import { decryptCryptoBox, encryptCryptoBox } from '../crypto/index.js';
|
|
7
7
|
import { compress, decompress } from '../minify/index.js';
|
|
@@ -13,8 +13,6 @@ import { apiDataToExternal, apiDataToInternal } from './convert/data.js';
|
|
|
13
13
|
import { apiNodeFullToInternalFull, apiNodeToExternal, apiNodeToExternalNodeFull, internalNodeFullToNodeFull, } from './convert/node.js';
|
|
14
14
|
import { promiseAllLimit } from '../utils/promise.js';
|
|
15
15
|
import { kiloToBytes } from '../utils.js';
|
|
16
|
-
// import { md5 } from "../worker/index.js";
|
|
17
|
-
// import { firstValueFrom, of } from "rxjs";
|
|
18
16
|
import { fileTypeFromBuffer } from 'file-type';
|
|
19
17
|
export class SecrecyCloudClient {
|
|
20
18
|
#client;
|
|
@@ -50,7 +48,7 @@ export class SecrecyCloudClient {
|
|
|
50
48
|
}
|
|
51
49
|
return internalNodeFullToNodeFull(node);
|
|
52
50
|
}
|
|
53
|
-
async uploadData({
|
|
51
|
+
async uploadData({ storageType, data, encrypted = true, encryptProgress, uploadProgress, signal, meta, }) {
|
|
54
52
|
const dataBuffer = data instanceof File ? new Uint8Array(await data.arrayBuffer()) : data;
|
|
55
53
|
let filetype;
|
|
56
54
|
if (meta === true) {
|
|
@@ -62,8 +60,7 @@ export class SecrecyCloudClient {
|
|
|
62
60
|
else if (!encrypted) {
|
|
63
61
|
filetype = await fileTypeFromBuffer(dataBuffer);
|
|
64
62
|
}
|
|
65
|
-
if (
|
|
66
|
-
dataBuffer.byteLength > kiloToBytes(1024)) {
|
|
63
|
+
if (storageType === 'lite' && dataBuffer.byteLength > kiloToBytes(1024)) {
|
|
67
64
|
throw new Error('The data is too big for lite upload!');
|
|
68
65
|
}
|
|
69
66
|
const compressed = encrypted ? compress(dataBuffer) : dataBuffer;
|
|
@@ -82,11 +79,11 @@ export class SecrecyCloudClient {
|
|
|
82
79
|
current: 0,
|
|
83
80
|
percent: 0,
|
|
84
81
|
});
|
|
85
|
-
if (
|
|
82
|
+
if (storageType === 'lite') {
|
|
86
83
|
const uploadDataArgs = encryptedDataKey && md5Encrypted
|
|
87
84
|
? {
|
|
88
85
|
type: 'encrypted',
|
|
89
|
-
|
|
86
|
+
content: Buffer.from(encryptedData),
|
|
90
87
|
sizeEncrypted: BigInt(encryptedData.byteLength),
|
|
91
88
|
size: BigInt(dataBuffer.byteLength),
|
|
92
89
|
key: sodium.to_hex(encryptedDataKey),
|
|
@@ -96,13 +93,13 @@ export class SecrecyCloudClient {
|
|
|
96
93
|
}
|
|
97
94
|
: {
|
|
98
95
|
type: 'unencrypted',
|
|
99
|
-
|
|
96
|
+
content: Buffer.from(encryptedData),
|
|
100
97
|
md5: md5Data,
|
|
101
98
|
sizeEncrypted: undefined,
|
|
102
99
|
size: BigInt(dataBuffer.byteLength),
|
|
103
100
|
...filetype,
|
|
104
101
|
};
|
|
105
|
-
const uploadData = await this.#apiClient.cloud.
|
|
102
|
+
const uploadData = await this.#apiClient.cloud.uploadLiteData.mutate(uploadDataArgs, { signal });
|
|
106
103
|
await uploadProgress?.({
|
|
107
104
|
total: encryptedData.byteLength,
|
|
108
105
|
current: encryptedData.byteLength,
|
|
@@ -110,7 +107,7 @@ export class SecrecyCloudClient {
|
|
|
110
107
|
});
|
|
111
108
|
const localData = {
|
|
112
109
|
id: uploadData.id,
|
|
113
|
-
|
|
110
|
+
storageType: 'lite',
|
|
114
111
|
size: uploadDataArgs.size,
|
|
115
112
|
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
116
113
|
data: dataBuffer,
|
|
@@ -119,7 +116,7 @@ export class SecrecyCloudClient {
|
|
|
119
116
|
dataContentCache.set(uploadData.id, localData);
|
|
120
117
|
return localData;
|
|
121
118
|
}
|
|
122
|
-
if (
|
|
119
|
+
if (storageType === 's3' || storageType === 'cold') {
|
|
123
120
|
const uploadDataArgs = encryptedDataKey && md5Encrypted
|
|
124
121
|
? {
|
|
125
122
|
type: 'encrypted',
|
|
@@ -137,7 +134,7 @@ export class SecrecyCloudClient {
|
|
|
137
134
|
sizeEncrypted: undefined,
|
|
138
135
|
...filetype,
|
|
139
136
|
};
|
|
140
|
-
const uploadDataCaller =
|
|
137
|
+
const uploadDataCaller = storageType === 's3'
|
|
141
138
|
? this.#apiClient.cloud.uploadData
|
|
142
139
|
: this.#apiClient.cloud.uploadColdData;
|
|
143
140
|
const uploadData = await uploadDataCaller.mutate(uploadDataArgs, {
|
|
@@ -154,7 +151,7 @@ export class SecrecyCloudClient {
|
|
|
154
151
|
});
|
|
155
152
|
const localData = {
|
|
156
153
|
id: uploadData.id,
|
|
157
|
-
|
|
154
|
+
storageType: storageType,
|
|
158
155
|
size: uploadDataArgs.size,
|
|
159
156
|
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
160
157
|
data: dataBuffer,
|
|
@@ -211,7 +208,7 @@ export class SecrecyCloudClient {
|
|
|
211
208
|
}));
|
|
212
209
|
const localData = {
|
|
213
210
|
id: uploadData.id,
|
|
214
|
-
|
|
211
|
+
storageType: storageType,
|
|
215
212
|
size: uploadDataArgs.size,
|
|
216
213
|
sizeEncrypted: uploadDataArgs.sizeEncrypted ?? null,
|
|
217
214
|
data: dataBuffer,
|
|
@@ -220,11 +217,11 @@ export class SecrecyCloudClient {
|
|
|
220
217
|
dataContentCache.set(uploadData.id, localData);
|
|
221
218
|
return localData;
|
|
222
219
|
}
|
|
223
|
-
throw new Error(`
|
|
220
|
+
throw new Error(`The "${storageType}" is not implemented yet!`);
|
|
224
221
|
}
|
|
225
|
-
async uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress,
|
|
222
|
+
async uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress, storageType = 's3', signal, }) {
|
|
226
223
|
const uploadedData = await this.uploadData({
|
|
227
|
-
|
|
224
|
+
storageType,
|
|
228
225
|
data,
|
|
229
226
|
encryptProgress,
|
|
230
227
|
uploadProgress,
|
|
@@ -299,11 +296,10 @@ export class SecrecyCloudClient {
|
|
|
299
296
|
const users = folder.parent?.users?.filter(([u]) => u.id !== this.#client.app.userId) ??
|
|
300
297
|
[];
|
|
301
298
|
if (users.length > 0) {
|
|
302
|
-
await
|
|
299
|
+
await this.shareNode({
|
|
303
300
|
nodeId: folder.id,
|
|
304
|
-
rights,
|
|
305
|
-
|
|
306
|
-
})));
|
|
301
|
+
users: users.map(([user, rights]) => ({ id: user.id, rights })),
|
|
302
|
+
});
|
|
307
303
|
}
|
|
308
304
|
return folder;
|
|
309
305
|
}
|
|
@@ -320,25 +316,35 @@ export class SecrecyCloudClient {
|
|
|
320
316
|
});
|
|
321
317
|
return apiDataToExternal(data, this.#keys);
|
|
322
318
|
}
|
|
323
|
-
async shareNode(
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
319
|
+
async shareNode(input) {
|
|
320
|
+
const userIds = 'rights' in input
|
|
321
|
+
? Array.isArray(input.nodes)
|
|
322
|
+
? input.nodes.map(({ userId }) => userId)
|
|
323
|
+
: 'userIds' in input.nodes
|
|
324
|
+
? input.nodes.userIds
|
|
325
|
+
: [input.nodes.userId]
|
|
326
|
+
: input.users.map(({ id }) => id);
|
|
327
|
+
const nodesToShare = 'nodes' in input
|
|
328
|
+
? input.nodes
|
|
329
|
+
: {
|
|
330
|
+
nodeId: input.nodeId,
|
|
331
|
+
userIds: input.users.map(({ id }) => id),
|
|
332
|
+
};
|
|
333
|
+
const [publicKeysMap, nodesIdsMap] = await Promise.all([
|
|
334
|
+
this.#client.app.userPublicKey(userIds),
|
|
335
|
+
this.#apiClient.cloud.shareNode.mutate(nodesToShare),
|
|
336
|
+
]);
|
|
337
|
+
const nodesInfos = await this.encryptNodesForUsers(nodesIdsMap, publicKeysMap);
|
|
338
|
+
const sharingState = await this.#apiClient.cloud.shareNodeFinish.mutate(Object.entries(nodesInfos).map('rights' in input
|
|
339
|
+
? ([userId, nodes]) => ({ userId, nodes, rights: input.rights })
|
|
340
|
+
: ([userId, nodes]) => {
|
|
341
|
+
const user = input.users.find((user) => user.id === userId);
|
|
342
|
+
if (!user) {
|
|
343
|
+
throw new Error(`Unable to find rights for user: ${userId}`);
|
|
344
|
+
}
|
|
345
|
+
return { userId, nodes, rights: user.rights };
|
|
346
|
+
}));
|
|
347
|
+
return sharingState;
|
|
342
348
|
}
|
|
343
349
|
async updateNode({ nodeId, name, isFavorite, deletedAt, }) {
|
|
344
350
|
let node = nodesCache.get(nodeId);
|
|
@@ -398,13 +404,13 @@ export class SecrecyCloudClient {
|
|
|
398
404
|
id: data.id,
|
|
399
405
|
size: data.size,
|
|
400
406
|
sizeEncrypted: data.sizeEncrypted,
|
|
401
|
-
|
|
407
|
+
storageType: data.storageType,
|
|
402
408
|
})),
|
|
403
409
|
...cachedData.map((data) => ({
|
|
404
410
|
id: data.id,
|
|
405
411
|
size: data.size,
|
|
406
412
|
sizeEncrypted: data.sizeEncrypted,
|
|
407
|
-
|
|
413
|
+
storageType: data.storageType,
|
|
408
414
|
})),
|
|
409
415
|
];
|
|
410
416
|
const allDataContentsBytes = Number(allDataContents.reduce((curr, next) => curr + (next.sizeEncrypted ?? next.size), 0n));
|
|
@@ -523,42 +529,102 @@ export class SecrecyCloudClient {
|
|
|
523
529
|
if (me !== undefined && ['admin', 'write'].includes(me[1])) {
|
|
524
530
|
const others = node.parent?.users.filter(([u]) => u.id !== this.#client.app.userId) ??
|
|
525
531
|
[];
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
532
|
+
if (others.length > 0) {
|
|
533
|
+
await this.shareNode({
|
|
534
|
+
nodeId: node.id,
|
|
535
|
+
users: others.map(([user, rights]) => ({ id: user.id, rights })),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
531
538
|
}
|
|
532
539
|
return node;
|
|
533
540
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
+
encryptNodesForUsers = async (userNodes, // { [userId]: nodeIds[] }
|
|
542
|
+
userPublicKeys) => {
|
|
543
|
+
const userIds = Object.keys(userNodes).map((userId) => userId);
|
|
544
|
+
const nodeIds = Object.values(userNodes).flatMap((nodeIds) => nodeIds);
|
|
545
|
+
const nodes = [];
|
|
546
|
+
// Pre check to ensure we get all public keys for users!
|
|
547
|
+
for (const userId of userIds) {
|
|
548
|
+
if (userPublicKeys[userId] === undefined) {
|
|
549
|
+
throw new Error(`Unable to retreive some user public keys!`);
|
|
541
550
|
}
|
|
542
551
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
552
|
+
// Retreive and format nodes.
|
|
553
|
+
for (const nodeId of nodeIds) {
|
|
554
|
+
let node;
|
|
555
|
+
node ??= nodesEncryptionCache.get(nodeId);
|
|
556
|
+
node ??= nodesCache.get(nodeId);
|
|
557
|
+
if (node &&
|
|
558
|
+
'history' in node &&
|
|
559
|
+
node.history.length > 0 &&
|
|
560
|
+
node.access.nameKey) {
|
|
561
|
+
nodes.push({
|
|
562
|
+
id: nodeId,
|
|
563
|
+
name: node.name,
|
|
564
|
+
type: node.type,
|
|
565
|
+
access: { nameKey: node.access.nameKey },
|
|
566
|
+
history: node.history.map((data) => {
|
|
567
|
+
if (!data.key) {
|
|
568
|
+
throw new Error('Unable to retreive data key!');
|
|
569
|
+
}
|
|
570
|
+
return { id: data.id, key: data.key };
|
|
571
|
+
}),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
546
574
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
575
|
+
// Retreive all missing nodes from cache to api.
|
|
576
|
+
const missingNodeIds = nodeIds.filter((nodeId) => !nodes.some((node) => node.id === nodeId));
|
|
577
|
+
const fetchedNodes = await this.#apiClient.cloud.nodesForEncryption.query({
|
|
578
|
+
ids: missingNodeIds,
|
|
579
|
+
});
|
|
580
|
+
if (fetchedNodes.length !== missingNodeIds.length) {
|
|
581
|
+
const diff = missingNodeIds.filter((id) => !fetchedNodes.some((node) => node.id === id));
|
|
582
|
+
throw new Error(`Unable to fetch some node infos (${diff.join(', ')})`);
|
|
583
|
+
}
|
|
584
|
+
nodes.push(...fetchedNodes.map((node) => {
|
|
585
|
+
return {
|
|
586
|
+
id: node.id,
|
|
587
|
+
type: node.type,
|
|
588
|
+
name: node.name,
|
|
589
|
+
access: { nameKey: node.access.nameKey },
|
|
590
|
+
history: node.history.map((data) => ({
|
|
591
|
+
id: data.id,
|
|
592
|
+
key: data.access.key,
|
|
593
|
+
})),
|
|
594
|
+
};
|
|
595
|
+
}));
|
|
596
|
+
const nodesMappedUsers = {};
|
|
597
|
+
for (const userId in userNodes) {
|
|
598
|
+
nodesMappedUsers[userId] ??= [];
|
|
599
|
+
for (const nodeId of userNodes[userId]) {
|
|
600
|
+
const node = nodes.find((node) => node.id === nodeId);
|
|
601
|
+
if (!node) {
|
|
602
|
+
throw new Error('Unable to retreive node from ram');
|
|
603
|
+
}
|
|
604
|
+
if (node.type === 'FILE' &&
|
|
605
|
+
(!('history' in node) || node.history.length === 0)) {
|
|
606
|
+
throw new Error(`Can't share a node without data (${node.id})!`);
|
|
607
|
+
}
|
|
608
|
+
const nameKey = node.access?.nameKey;
|
|
609
|
+
const publicKey = userPublicKeys[userId];
|
|
610
|
+
nodesEncryptionCache.set(node.id, node);
|
|
611
|
+
nodesMappedUsers[userId].push({
|
|
612
|
+
id: node.id,
|
|
613
|
+
nameKey: nameKey !== null
|
|
614
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(nameKey), publicKey, this.#keys.privateKey))
|
|
558
615
|
: null,
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
616
|
+
data: 'history' in node
|
|
617
|
+
? node.history.map((f) => ({
|
|
618
|
+
id: f.id,
|
|
619
|
+
key: f.key
|
|
620
|
+
? sodium.to_hex(encryptCryptoBox(sodium.from_hex(f.key), publicKey, this.#keys.privateKey))
|
|
621
|
+
: null,
|
|
622
|
+
}))
|
|
623
|
+
: [],
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return nodesMappedUsers;
|
|
562
628
|
};
|
|
563
629
|
async reportData({ id, reasons, }) {
|
|
564
630
|
const [rawData, secrecy] = await Promise.all([
|
|
@@ -574,18 +640,13 @@ export class SecrecyCloudClient {
|
|
|
574
640
|
: null,
|
|
575
641
|
});
|
|
576
642
|
}
|
|
577
|
-
async
|
|
643
|
+
async updateDataStorageType(input) {
|
|
578
644
|
const data = dataContentCache.get(input.dataId);
|
|
579
645
|
if (data) {
|
|
580
|
-
if (data.
|
|
581
|
-
data
|
|
582
|
-
data.storage.mode === input.mode &&
|
|
583
|
-
data.storage.region === input.region) {
|
|
584
|
-
throw new Error(`The data is already on "${data.storage}`);
|
|
646
|
+
if (data.storageType === input.storageType) {
|
|
647
|
+
throw new Error(`The data is already on "${data.storageType}`);
|
|
585
648
|
}
|
|
586
|
-
if (data.
|
|
587
|
-
data.storage.mode === 'glacier' &&
|
|
588
|
-
input.protocol === 'mongo') {
|
|
649
|
+
if (data.storageType === 'cold' && input.storageType === 'lite') {
|
|
589
650
|
throw new Error("It's not possible to transfer a cold stored data to a lite storage!");
|
|
590
651
|
}
|
|
591
652
|
}
|
|
@@ -597,6 +658,9 @@ export class SecrecyCloudClient {
|
|
|
597
658
|
getPublicDataLinks(input) {
|
|
598
659
|
return this.#apiClient.cloud.dataLinks.query(input);
|
|
599
660
|
}
|
|
661
|
+
checkAccesses(input) {
|
|
662
|
+
return this.#apiClient.cloud.checkAccesses.query(input);
|
|
663
|
+
}
|
|
600
664
|
createPublicDataLink(input) {
|
|
601
665
|
if (input.expireAt && input.expireAt <= new Date()) {
|
|
602
666
|
throw new Error('Unable to create public link using a past expireAt date!');
|
|
@@ -647,25 +711,19 @@ export class SecrecyCloudClient {
|
|
|
647
711
|
.sort((a, b) => a.order - b.order)
|
|
648
712
|
.map((p) => p.data));
|
|
649
713
|
};
|
|
650
|
-
const encryptedContent = dataContent.type === '
|
|
651
|
-
? new Uint8Array(dataContent.
|
|
652
|
-
: dataContent.type === '
|
|
714
|
+
const encryptedContent = dataContent.type === 'lite'
|
|
715
|
+
? new Uint8Array(dataContent.content)
|
|
716
|
+
: dataContent.type === 'cloud'
|
|
653
717
|
? await encryptedContentFromParts({
|
|
654
718
|
dataId: dataContent.id,
|
|
655
|
-
dataParts: dataContent.parts
|
|
656
|
-
...part,
|
|
657
|
-
contentUrl: url,
|
|
658
|
-
})),
|
|
719
|
+
dataParts: dataContent.parts,
|
|
659
720
|
})
|
|
660
|
-
: dataContent.
|
|
661
|
-
? new Uint8Array(dataContent.
|
|
721
|
+
: dataContent.maybeContent !== null
|
|
722
|
+
? new Uint8Array(dataContent.maybeContent)
|
|
662
723
|
: dataContent.maybeParts !== null
|
|
663
724
|
? await encryptedContentFromParts({
|
|
664
725
|
dataId: dataContent.id,
|
|
665
|
-
dataParts: dataContent.maybeParts
|
|
666
|
-
...part,
|
|
667
|
-
contentUrl: url,
|
|
668
|
-
})),
|
|
726
|
+
dataParts: dataContent.maybeParts,
|
|
669
727
|
})
|
|
670
728
|
: null;
|
|
671
729
|
if (encryptedContent === null) {
|
|
@@ -678,7 +736,7 @@ export class SecrecyCloudClient {
|
|
|
678
736
|
const key = dataContent.key
|
|
679
737
|
? decryptCryptoBox(sodium.from_hex(dataContent.key), dataContent.type === 'received_mail'
|
|
680
738
|
? dataContent.senderPublicKey
|
|
681
|
-
: dataContent.type === '
|
|
739
|
+
: dataContent.type === 'cloud' || dataContent.type === 'lite'
|
|
682
740
|
? dataContent.publicKey
|
|
683
741
|
: this.#keys.publicKey, this.#keys.privateKey)
|
|
684
742
|
: null;
|
|
@@ -690,20 +748,11 @@ export class SecrecyCloudClient {
|
|
|
690
748
|
throw new Error(`Content does not match`);
|
|
691
749
|
}
|
|
692
750
|
const decompressed = decompress(src);
|
|
693
|
-
const storageOptions = dataContent.storage.protocol === 'mongo'
|
|
694
|
-
? dataContent.storage.mongoStorageOptions
|
|
695
|
-
: dataContent.storage.s3StorageOptions;
|
|
696
|
-
const storage = {
|
|
697
|
-
protocol: dataContent.storage.protocol,
|
|
698
|
-
provider: dataContent.storage.provider,
|
|
699
|
-
mode: storageOptions.mode,
|
|
700
|
-
region: storageOptions.region,
|
|
701
|
-
};
|
|
702
751
|
dataContentCache.set(dataContent.id, {
|
|
703
752
|
id: dataContent.id,
|
|
704
753
|
size: dataContent.size,
|
|
705
754
|
sizeEncrypted: dataContent.sizeEncrypted,
|
|
706
|
-
|
|
755
|
+
storageType: dataContent.storageType,
|
|
707
756
|
data: decompressed,
|
|
708
757
|
mime: dataContent.mime ?? undefined,
|
|
709
758
|
ext: dataContent.ext ?? undefined,
|
|
@@ -712,7 +761,7 @@ export class SecrecyCloudClient {
|
|
|
712
761
|
id: dataContent.id,
|
|
713
762
|
size: dataContent.size,
|
|
714
763
|
sizeEncrypted: dataContent.sizeEncrypted,
|
|
715
|
-
|
|
764
|
+
storageType: dataContent.storageType,
|
|
716
765
|
data: decompressed,
|
|
717
766
|
mime: dataContent.mime ?? undefined,
|
|
718
767
|
ext: dataContent.ext ?? undefined,
|
|
@@ -3,7 +3,7 @@ import { decryptCryptoBox } from '../../crypto/index.js';
|
|
|
3
3
|
import { nodesCache } from '../../cache.js';
|
|
4
4
|
import { decryptSecretStream } from '../../crypto/data.js';
|
|
5
5
|
import { apiDataToInternal, internalDataToExternalData } from './data.js';
|
|
6
|
-
|
|
6
|
+
async function apiNodeToInternal(apiNode, keyPair) {
|
|
7
7
|
const internal = {
|
|
8
8
|
id: apiNode.id,
|
|
9
9
|
type: apiNode.type,
|
|
@@ -58,7 +58,7 @@ export async function apiNodeFullToInternalFull(apiNodeFull, keyPair) {
|
|
|
58
58
|
nodesCache.set(f.id, nodeFull);
|
|
59
59
|
return nodeFull;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
function internalNodeToNode(internal) {
|
|
62
62
|
const node = {
|
|
63
63
|
...internal,
|
|
64
64
|
breadcrumb: internal.breadcrumb.map((b) => ({
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { InternalNode, InternalData, InternalNodeFull, LocalData } from './client/types/index.js';
|
|
1
|
+
import type { InternalNode, InternalData, InternalNodeFull, LocalData, InternalMinimalNodeForEncryption } from './client/types/index.js';
|
|
2
2
|
import { LRUCache } from 'lru-cache';
|
|
3
3
|
export declare const dataCache: Map<string, InternalData>;
|
|
4
4
|
export declare const nodesCache: Map<string, InternalNode | InternalNodeFull>;
|
|
5
|
+
export declare const nodesEncryptionCache: Map<string, InternalMinimalNodeForEncryption>;
|
|
5
6
|
export declare const usersCache: Map<string, {
|
|
6
7
|
id: string;
|
|
7
8
|
lastname: string;
|
|
@@ -16,6 +16,7 @@ export declare class SecrecyAppClient {
|
|
|
16
16
|
updateNotifications(notifications: Partial<UserAppNotifications>): Promise<UserAppNotifications>;
|
|
17
17
|
notifications(): Promise<UserAppNotifications>;
|
|
18
18
|
userPublicKey(userId: string): Promise<string>;
|
|
19
|
+
userPublicKey(userId: string[]): Promise<Record<string, string>>;
|
|
19
20
|
settings(): Promise<UserAppSettings>;
|
|
20
21
|
updateSettings(settings: Partial<UserAppSettings>): Promise<UserAppSettings>;
|
|
21
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProgressCallback, SecrecyClient } from '../index.js';
|
|
2
|
-
import type { DataMetadata,
|
|
2
|
+
import type { DataMetadata, DataStorageType, KeyPair, LocalData, Node, NodeFull, NodeType, Rights } from './types/index.js';
|
|
3
3
|
import { type RouterInputs, type ApiClient, type RouterOutputs } from '../client.js';
|
|
4
4
|
import { type DownloadProgress } from '../types.js';
|
|
5
5
|
import { FileTypeResult } from 'file-type';
|
|
@@ -10,8 +10,8 @@ export declare class SecrecyCloudClient {
|
|
|
10
10
|
dataId: string;
|
|
11
11
|
nodeId: string;
|
|
12
12
|
}): Promise<NodeFull>;
|
|
13
|
-
uploadData({
|
|
14
|
-
|
|
13
|
+
uploadData({ storageType, data, encrypted, encryptProgress, uploadProgress, signal, meta, }: {
|
|
14
|
+
storageType: DataStorageType;
|
|
15
15
|
data: globalThis.File | Uint8Array;
|
|
16
16
|
encrypted?: boolean;
|
|
17
17
|
encryptProgress?: ProgressCallback;
|
|
@@ -19,14 +19,14 @@ export declare class SecrecyCloudClient {
|
|
|
19
19
|
signal?: AbortSignal;
|
|
20
20
|
meta?: FileTypeResult | true;
|
|
21
21
|
}): Promise<LocalData>;
|
|
22
|
-
uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress,
|
|
22
|
+
uploadDataInCloud({ data, name, nodeId, encryptProgress, uploadProgress, storageType, signal, }: {
|
|
23
23
|
data: globalThis.File | Uint8Array;
|
|
24
24
|
name: string;
|
|
25
25
|
nodeId?: string;
|
|
26
26
|
encryptProgress?: ProgressCallback;
|
|
27
27
|
uploadProgress?: ProgressCallback;
|
|
28
28
|
signal?: AbortSignal;
|
|
29
|
-
|
|
29
|
+
storageType?: DataStorageType;
|
|
30
30
|
}): Promise<NodeFull>;
|
|
31
31
|
deletedNodes(): Promise<Node[]>;
|
|
32
32
|
sharedNodes(): Promise<Node[]>;
|
|
@@ -54,11 +54,28 @@ export declare class SecrecyCloudClient {
|
|
|
54
54
|
dataMetadata({ id }: {
|
|
55
55
|
id: string;
|
|
56
56
|
}): Promise<DataMetadata>;
|
|
57
|
-
shareNode(
|
|
58
|
-
nodeId: string;
|
|
59
|
-
userId: string;
|
|
57
|
+
shareNode(input: {
|
|
60
58
|
rights: Rights;
|
|
61
|
-
|
|
59
|
+
nodes: {
|
|
60
|
+
nodeId: string;
|
|
61
|
+
userId: string;
|
|
62
|
+
}[] | {
|
|
63
|
+
nodeId: string;
|
|
64
|
+
userIds: string[];
|
|
65
|
+
} | {
|
|
66
|
+
nodeIds: string[];
|
|
67
|
+
userId: string;
|
|
68
|
+
} | {
|
|
69
|
+
nodeIds: string[];
|
|
70
|
+
userIds: string[];
|
|
71
|
+
};
|
|
72
|
+
} | {
|
|
73
|
+
nodeId: string;
|
|
74
|
+
users: {
|
|
75
|
+
id: string;
|
|
76
|
+
rights: Rights;
|
|
77
|
+
}[];
|
|
78
|
+
}): Promise<RouterOutputs['cloud']['shareNodeFinish']>;
|
|
62
79
|
updateNode({ nodeId, name, isFavorite, deletedAt, }: {
|
|
63
80
|
nodeId: string;
|
|
64
81
|
name?: string | null | undefined;
|
|
@@ -100,16 +117,12 @@ export declare class SecrecyCloudClient {
|
|
|
100
117
|
name: string;
|
|
101
118
|
nodeId?: string;
|
|
102
119
|
}): Promise<NodeFull>;
|
|
103
|
-
private readonly
|
|
120
|
+
private readonly encryptNodesForUsers;
|
|
104
121
|
reportData({ id, reasons, }: Omit<RouterInputs['cloud']['reportData'], 'encryptedDataKey'>): Promise<RouterOutputs['cloud']['reportData']>;
|
|
105
|
-
|
|
122
|
+
updateDataStorageType(input: RouterInputs['cloud']['moveToStorageType']): Promise<{
|
|
106
123
|
isMoved: boolean;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
fromRegion?: string | undefined;
|
|
110
|
-
toProtocol?: "s3" | "mongo" | undefined;
|
|
111
|
-
toMode?: "standard" | "glacier" | undefined;
|
|
112
|
-
toRegion?: string | undefined;
|
|
124
|
+
fromType: "s3" | "cold" | "lite";
|
|
125
|
+
toType: "s3" | "cold" | "lite";
|
|
113
126
|
}>;
|
|
114
127
|
getPublicDataLink(input: RouterInputs['cloud']['dataLink']): Promise<{
|
|
115
128
|
name: string;
|
|
@@ -125,6 +138,28 @@ export declare class SecrecyCloudClient {
|
|
|
125
138
|
expireAt: Date | null;
|
|
126
139
|
slug: string;
|
|
127
140
|
}[]>;
|
|
141
|
+
checkAccesses(input: RouterInputs['cloud']['checkAccesses']): Promise<{
|
|
142
|
+
isMatching: true;
|
|
143
|
+
} | {
|
|
144
|
+
details: {
|
|
145
|
+
missingNodeAccesses: {
|
|
146
|
+
userId: string;
|
|
147
|
+
nodeId: string;
|
|
148
|
+
}[];
|
|
149
|
+
missingDataAccesses: {
|
|
150
|
+
userId: string;
|
|
151
|
+
dataId: string;
|
|
152
|
+
nodeId: string;
|
|
153
|
+
}[];
|
|
154
|
+
invalidRightsAccesses: {
|
|
155
|
+
userId: string;
|
|
156
|
+
current: "admin" | "write" | "read";
|
|
157
|
+
nodeId: string;
|
|
158
|
+
expect: "admin" | "write" | "read";
|
|
159
|
+
}[];
|
|
160
|
+
};
|
|
161
|
+
isMatching: false;
|
|
162
|
+
}>;
|
|
128
163
|
createPublicDataLink(input: RouterInputs['cloud']['createDataLink']): Promise<{
|
|
129
164
|
name: string;
|
|
130
165
|
id: string;
|