@secrecy/lib 1.62.0-feat-node-sharing.1 → 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.
@@ -72,12 +72,17 @@ export class SecrecyAppClient {
72
72
  publicKeysCache.get(`userPublicKey:${userId}-${appId}`),
73
73
  ])
74
74
  .filter(([_, key]) => !!key));
75
- const missingKeys = userIds.filter((userId) => publicKeys[userId] === undefined);
75
+ const missingKeys = [
76
+ ...new Set(userIds.filter((userId) => publicKeys[userId] === undefined)),
77
+ ];
76
78
  if (missingKeys.length > 0) {
77
79
  const userKeysMap = await this.#apiClient.application.userPublicKey.query(userIds);
78
80
  if ('publicKey' in userKeysMap) {
79
81
  throw Error('Should not happen!');
80
82
  }
83
+ if (Object.keys(userKeysMap).length !== missingKeys.length) {
84
+ throw new Error("Unable to load some user's public keys!");
85
+ }
81
86
  for (const userId in userKeysMap) {
82
87
  publicKeys[userId] = userKeysMap[userId];
83
88
  publicKeysCache.set(`userPublicKey:${userId}-${appId}`, userKeysMap[userId]);
@@ -296,15 +296,10 @@ export class SecrecyCloudClient {
296
296
  const users = folder.parent?.users?.filter(([u]) => u.id !== this.#client.app.userId) ??
297
297
  [];
298
298
  if (users.length > 0) {
299
- await Promise.all(users.map(async ([u, rights]) => await this.shareNode({
300
- rights,
301
- nodes: [
302
- {
303
- userId: u.id,
304
- nodeId: folder.id,
305
- },
306
- ],
307
- })));
299
+ await this.shareNode({
300
+ nodeId: folder.id,
301
+ users: users.map(([user, rights]) => ({ id: user.id, rights })),
302
+ });
308
303
  }
309
304
  return folder;
310
305
  }
@@ -322,23 +317,33 @@ export class SecrecyCloudClient {
322
317
  return apiDataToExternal(data, this.#keys);
323
318
  }
324
319
  async shareNode(input) {
325
- const userIds = Array.isArray(input.nodes)
326
- ? input.nodes.map(({ userId }) => userId)
327
- : 'userIds' in input.nodes
328
- ? input.nodes.userIds
329
- : [input.nodes.userId];
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
+ };
330
333
  const [publicKeysMap, nodesIdsMap] = await Promise.all([
331
334
  this.#client.app.userPublicKey(userIds),
332
- this.#apiClient.cloud.shareNode.mutate(input.nodes),
335
+ this.#apiClient.cloud.shareNode.mutate(nodesToShare),
333
336
  ]);
334
- const nodesInfos = await this.perNodes(nodesIdsMap, publicKeysMap);
335
- const sharingState = await this.#apiClient.cloud.shareNodeFinish.mutate(Object.entries(nodesInfos).map(([userId, nodes]) => {
336
- return {
337
- userId,
338
- nodes,
339
- rights: input.rights,
340
- };
341
- }));
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
+ }));
342
347
  return sharingState;
343
348
  }
344
349
  async updateNode({ nodeId, name, isFavorite, deletedAt, }) {
@@ -524,19 +529,16 @@ export class SecrecyCloudClient {
524
529
  if (me !== undefined && ['admin', 'write'].includes(me[1])) {
525
530
  const others = node.parent?.users.filter(([u]) => u.id !== this.#client.app.userId) ??
526
531
  [];
527
- await Promise.all(others.map(async ([u, rights]) => await this.shareNode({
528
- rights,
529
- nodes: [
530
- {
531
- nodeId: node.id,
532
- userId: u.id,
533
- },
534
- ],
535
- })));
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
+ }
536
538
  }
537
539
  return node;
538
540
  }
539
- perNodes = async (userNodes, // { [userId]: nodeIds[] }
541
+ encryptNodesForUsers = async (userNodes, // { [userId]: nodeIds[] }
540
542
  userPublicKeys) => {
541
543
  const userIds = Object.keys(userNodes).map((userId) => userId);
542
544
  const nodeIds = Object.values(userNodes).flatMap((nodeIds) => nodeIds);
@@ -3,15 +3,6 @@ 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
- async function readName({ encryptedName, encryptedNameKey, sharedByPubKey, privateKey, }) {
7
- const nameKeyBuffer = decryptCryptoBox(sodium.from_hex(encryptedNameKey), sharedByPubKey, privateKey);
8
- const nameKey = sodium.to_hex(nameKeyBuffer);
9
- const name = sodium.to_string(await decryptSecretStream(nameKeyBuffer, sodium.from_hex(encryptedName)));
10
- return {
11
- name,
12
- nameKey,
13
- };
14
- }
15
6
  async function apiNodeToInternal(apiNode, keyPair) {
16
7
  const internal = {
17
8
  id: apiNode.id,
@@ -34,6 +25,7 @@ async function apiNodeToInternal(apiNode, keyPair) {
34
25
  parentId: apiNode.parentId ?? null,
35
26
  currentDataId: apiNode.currentDataId ?? null,
36
27
  };
28
+ internal.access = { ...apiNode.access };
37
29
  if (apiNode.access.nameKey !== null) {
38
30
  const key = decryptCryptoBox(sodium.from_hex(apiNode.access.nameKey), apiNode.access.sharedByPubKey, keyPair.privateKey);
39
31
  internal.name = sodium.to_string(await decryptSecretStream(key, sodium.from_hex(internal.name)));
@@ -56,7 +56,25 @@ export declare class SecrecyCloudClient {
56
56
  }): Promise<DataMetadata>;
57
57
  shareNode(input: {
58
58
  rights: Rights;
59
- nodes: RouterInputs['cloud']['shareNode'];
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
+ }[];
60
78
  }): Promise<RouterOutputs['cloud']['shareNodeFinish']>;
61
79
  updateNode({ nodeId, name, isFavorite, deletedAt, }: {
62
80
  nodeId: string;
@@ -99,7 +117,7 @@ export declare class SecrecyCloudClient {
99
117
  name: string;
100
118
  nodeId?: string;
101
119
  }): Promise<NodeFull>;
102
- private readonly perNodes;
120
+ private readonly encryptNodesForUsers;
103
121
  reportData({ id, reasons, }: Omit<RouterInputs['cloud']['reportData'], 'encryptedDataKey'>): Promise<RouterOutputs['cloud']['reportData']>;
104
122
  updateDataStorageType(input: RouterInputs['cloud']['moveToStorageType']): Promise<{
105
123
  isMoved: boolean;
@@ -61,4 +61,12 @@ export type ApiNode = RouterOutputs['cloud']['nodeById'];
61
61
  export type ApiNodeFull = RouterOutputs['cloud']['nodeFullById'];
62
62
  export type ApiNodeParent = NonNullable<RouterOutputs['cloud']['nodeFullById']['parent']>;
63
63
  export type NodeType = ApiNode['type'];
64
+ export type EncryptedNodeInfos = {
65
+ id: string;
66
+ nameKey: string | null;
67
+ data: {
68
+ id: string;
69
+ key: string | null;
70
+ }[];
71
+ };
64
72
  export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@secrecy/lib",
3
3
  "author": "Anonymize <anonymize@gmail.com>",
4
4
  "description": "Anonymize Secrecy Library",
5
- "version": "1.62.0-feat-node-sharing.1",
5
+ "version": "1.62.0-feat-node-sharing.2",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/anonymize-org/lib.git"