@secrecy/lib 1.85.1 → 1.85.2-feat-folder-perms.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.
@@ -9,38 +9,32 @@ import { apiNodeForEncryptionToInternal, apiNodeFullToInternalFull, apiNodeToExt
9
9
  import { buildBytesFromApiData } from '../crypto/domain.js';
10
10
  import { chunkByTotalItems } from '../utils/object.js';
11
11
  import { uploadData } from './upload.js';
12
- import { encryptName, generateAndEncryptNameAndKey } from '../crypto/helpers.js';
12
+ import { encryptName, encryptNameKey } from '../crypto/helpers.js';
13
13
  import { downloadDataFromLink, } from './data-link.js';
14
+ import { secretStreamKeygen } from '../crypto/data.js';
14
15
  export class SecrecyCloudClient {
15
16
  #client;
16
17
  constructor(client) {
17
18
  this.#client = client;
18
19
  }
19
20
  async addDataToHistory({ dataId, nodeId, }) {
21
+ const currentNode = await apiNodeFullToInternalFull(await this.#client.apiClient.cloud.nodeFullById.query({ id: nodeId }), this.#client.keyPairs);
22
+ const currentData = currentNode.history.find((d) => d.id === dataId);
23
+ if (currentData === undefined) {
24
+ throw new Error(`Data ${dataId} is not in node ${nodeId}`);
25
+ }
26
+ const encryptedAccesses = Object.keys(currentNode.identities).map((pubKey) => ({
27
+ pubKey,
28
+ key: currentData.key
29
+ ? sodium.to_hex(encryptCryptoBox(sodium.from_hex(currentData.key), pubKey, this.#client.currentIdentityPrivateKey))
30
+ : null,
31
+ }));
20
32
  const addDataToHistory = await this.#client.apiClient.cloud.addDataToHistory.mutate({
21
33
  dataId,
22
34
  nodeId,
23
- fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
35
+ encryptedAccesses,
24
36
  });
25
37
  const node = await apiNodeFullToInternalFull(addDataToHistory, this.#client.keyPairs);
26
- const data = node.history.find((d) => d.id === dataId);
27
- if (data !== undefined) {
28
- const othersIdentities = Object.entries(node.identities).filter(([id]) => id !== this.#client.currentIdentity.identityPubKey);
29
- const shares = othersIdentities.map(([publicKey]) => {
30
- return {
31
- pubKey: publicKey,
32
- key: data.key
33
- ? sodium.to_hex(encryptCryptoBox(sodium.from_hex(data.key), publicKey, this.#client.currentIdentityPrivateKey))
34
- : null,
35
- };
36
- });
37
- await this.#client.apiClient.cloud.shareDataInHistory.mutate({
38
- dataId: data.id,
39
- nodeId,
40
- users: shares,
41
- fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
42
- });
43
- }
44
38
  return internalNodeFullToNodeFull(node);
45
39
  }
46
40
  async uploadData(opts) {
@@ -81,17 +75,20 @@ export class SecrecyCloudClient {
81
75
  return await Promise.all(nodesSharedWithMe.map(async (node) => await apiNodeToExternal(node, this.#client.keyPairs)));
82
76
  }
83
77
  async deleteNodeSharing({ nodeId, destPubKey, }) {
78
+ const node = await this.node({ id: nodeId });
79
+ const rootAccessSharedByPubKey = node.accesses.find((access) => access.identityPubKey === destPubKey && access.type === 'ROOT')?.sharedByPubKey;
84
80
  const { isDeleted } = await this.#client.apiClient.cloud.deleteNodeSharing.mutate({
85
81
  nodeId,
86
82
  destPubKey,
83
+ rootAccessSharedByPubKey,
87
84
  fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
88
85
  });
89
86
  return isDeleted;
90
87
  }
91
- async leaveNodeSharing({ nodeId, fromPubKey, }) {
88
+ async leaveNodeSharing({ nodeId, sharedByPubKey, }) {
92
89
  const { isDeleted } = await this.#client.apiClient.cloud.leaveSharing.mutate({
93
90
  nodeId,
94
- fromPubKey,
91
+ sharedByPubKey,
95
92
  fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
96
93
  });
97
94
  return isDeleted;
@@ -110,7 +107,7 @@ export class SecrecyCloudClient {
110
107
  }
111
108
  name =
112
109
  typeof name === 'string' && node.accesses[0].nameKey !== null
113
- ? await encryptName(name, node.accesses[0].nameKey)
110
+ ? await encryptName(name, sodium.from_hex(node.accesses[0].nameKey))
114
111
  : null;
115
112
  const { isDuplicated } = await this.#client.apiClient.cloud.duplicateNode.mutate({
116
113
  nodeId,
@@ -128,34 +125,23 @@ export class SecrecyCloudClient {
128
125
  return isDeleted;
129
126
  }
130
127
  async createFolder({ name, parentFolderId, }) {
131
- const { encryptedNameKey, encryptedName } = await generateAndEncryptNameAndKey({
132
- name,
133
- privateKey: this.#client.currentIdentityPrivateKey,
134
- publicKey: this.#client.currentIdentity.identityPubKey,
135
- });
128
+ const nameKey = secretStreamKeygen();
129
+ const encName = await encryptName(name, nameKey);
130
+ const parentFolder = await this.node({ id: parentFolderId });
131
+ const encryptedAccesses = await Promise.all(Object.keys(parentFolder.identities).map(async (pubKey) => ({
132
+ pubKey,
133
+ nameKey: await encryptNameKey({
134
+ nameKey,
135
+ privateKey: this.#client.currentIdentityPrivateKey,
136
+ publicKey: pubKey,
137
+ }),
138
+ })));
136
139
  const createdFolder = await this.#client.apiClient.cloud.createFolder.mutate({
137
- name: encryptedName,
140
+ name: encName,
138
141
  parentId: parentFolderId ?? null,
139
- nameKey: encryptedNameKey,
140
- fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
142
+ encryptedAccesses,
141
143
  });
142
- const folder = await apiNodeToExternalNodeFull(createdFolder, this.#client.keyPairs);
143
- if (folder.parent) {
144
- const othersIdentities = Object.entries(folder.parent.identities).filter(([id]) => id !== this.#client.currentIdentity.identityPubKey);
145
- // TODO: ??
146
- if (othersIdentities.length > 0) {
147
- await this.shareNode(othersIdentities.map(([identityPubKey, permissions]) => ({
148
- pubKey: identityPubKey,
149
- nodeId: folder.id,
150
- rights: permissions.rights,
151
- addAccess: permissions.addAccess,
152
- delAccess: permissions.delAccess,
153
- sharingAddAccess: permissions.sharingAddAccess,
154
- sharingDelAccess: permissions.sharingDelAccess,
155
- })));
156
- }
157
- }
158
- return folder;
144
+ return apiNodeToExternalNodeFull(createdFolder, this.#client.keyPairs);
159
145
  }
160
146
  async node({ id, deleted, } = {}) {
161
147
  const node = await this.#client.apiClient.cloud.nodeById.query({
@@ -286,6 +272,7 @@ export class SecrecyCloudClient {
286
272
  if (!item) {
287
273
  throw new Error('Unable to retrieve user mapped nodes!');
288
274
  }
275
+ console.dir({ node, item });
289
276
  return {
290
277
  id: node.id,
291
278
  data: node.data,
@@ -373,7 +360,7 @@ export class SecrecyCloudClient {
373
360
  }
374
361
  name =
375
362
  typeof name === 'string' && node.accesses[0].nameKey !== null
376
- ? await encryptName(name, node.accesses[0].nameKey)
363
+ ? await encryptName(name, sodium.from_hex(node.accesses[0].nameKey))
377
364
  : null;
378
365
  const updateNode = await this.#client.apiClient.cloud.updateNode.mutate({
379
366
  deletedAt: deletedAt ?? null,
@@ -534,35 +521,35 @@ export class SecrecyCloudClient {
534
521
  else {
535
522
  key = data.key;
536
523
  }
537
- key = key
538
- ? sodium.to_hex(encryptCryptoBox(sodium.from_hex(key), this.#client.currentIdentity.identityPubKey, this.#client.currentIdentityPrivateKey))
539
- : null;
540
- const { encryptedNameKey, encryptedName } = await generateAndEncryptNameAndKey({
541
- name,
542
- privateKey: this.#client.currentIdentityPrivateKey,
543
- publicKey: this.#client.currentIdentity.identityPubKey,
544
- });
524
+ const rawDataKey = key ? sodium.from_hex(key) : null;
525
+ const nameKey = secretStreamKeygen();
526
+ const encName = await encryptName(name, nameKey);
527
+ const parentFolder = nodeId ? nodesCache.get(nodeId) : await this.node();
528
+ if (!parentFolder) {
529
+ throw new Error('Unable to retrieve parent');
530
+ }
531
+ const encryptedAccesses = await Promise.all(Object.keys(parentFolder.identities).map(async (pubKey) => {
532
+ const encryptedNameKey = await encryptNameKey({
533
+ nameKey,
534
+ privateKey: this.#client.currentIdentityPrivateKey,
535
+ publicKey: pubKey,
536
+ });
537
+ const dataKey = rawDataKey
538
+ ? sodium.to_hex(encryptCryptoBox(rawDataKey, pubKey, this.#client.currentIdentityPrivateKey))
539
+ : null;
540
+ return {
541
+ pubKey,
542
+ nameKey: encryptedNameKey,
543
+ key: dataKey,
544
+ };
545
+ }));
545
546
  const saveInCloud = await this.#client.apiClient.cloud.saveInCloud.mutate({
546
547
  dataId,
547
- key,
548
548
  nodeId: nodeId ?? null,
549
- fileName: encryptedName,
550
- nameKey: encryptedNameKey,
551
- fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
549
+ fileName: encName,
550
+ encryptedAccesses,
552
551
  });
553
- const node = await apiNodeToExternalNodeFull(saveInCloud, this.#client.keyPairs);
554
- if (node.parent) {
555
- const othersIdentities = Object.entries(node.parent.identities).filter(([id]) => id !== this.#client.currentIdentity.identityPubKey);
556
- // TODO: ??
557
- if (othersIdentities.length > 0) {
558
- await this.shareNode(othersIdentities.map(([identityPubKey, permissions]) => ({
559
- pubKey: identityPubKey,
560
- nodeId: node.id,
561
- ...permissions,
562
- })));
563
- }
564
- }
565
- return node;
552
+ return apiNodeToExternalNodeFull(saveInCloud, this.#client.keyPairs);
566
553
  }
567
554
  encryptNodesForIdentities = async (identityNodes) => {
568
555
  const nodeIds = Object.values(identityNodes).flatMap((nodeIds) => nodeIds);
@@ -84,9 +84,10 @@ function internalNodeToNode(internal) {
84
84
  name: b.name,
85
85
  })),
86
86
  accesses: internal.accesses.map((a) => ({
87
- isRoot: a.isRoot,
87
+ type: a.type,
88
88
  identityPubKey: a.identityPubKey,
89
89
  sharedByPubKey: a.sharedByPubKey,
90
+ rootAccess: a.rootAccess,
90
91
  rights: a.rights,
91
92
  addAccess: a.addAccess,
92
93
  delAccess: a.delAccess,
@@ -8,16 +8,19 @@ export function derivePassword(password, salt) {
8
8
  return sodium.crypto_pwhash(sodium.crypto_secretbox_KEYBYTES, password, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13, 'hex');
9
9
  }
10
10
  export async function encryptName(name, nameKey) {
11
- const { data } = await encryptSecretStream(sodium.from_hex(nameKey), sodium.from_string(name));
11
+ const { data } = await encryptSecretStream(nameKey, sodium.from_string(name));
12
12
  return sodium.to_hex(data);
13
13
  }
14
14
  export async function generateAndEncryptNameAndKey(args) {
15
15
  const nameKey = secretStreamKeygen();
16
- const encryptedName = await encryptName(args.name, sodium.to_hex(nameKey));
17
- const encryptedNameKey = sodium.to_hex(encryptCryptoBox(nameKey, args.publicKey, args.privateKey));
16
+ const encryptedName = await encryptName(args.name, nameKey);
17
+ const encryptedNameKey = await encryptNameKey({ ...args, nameKey });
18
18
  return {
19
19
  nameKey,
20
20
  encryptedName,
21
21
  encryptedNameKey,
22
22
  };
23
23
  }
24
+ export async function encryptNameKey(args) {
25
+ return sodium.to_hex(encryptCryptoBox(args.nameKey, args.publicKey, args.privateKey));
26
+ }
@@ -32,9 +32,9 @@ export declare class SecrecyCloudClient {
32
32
  nodeId: string;
33
33
  destPubKey: string;
34
34
  }): Promise<boolean>;
35
- leaveNodeSharing({ nodeId, fromPubKey, }: {
35
+ leaveNodeSharing({ nodeId, sharedByPubKey, }: {
36
36
  nodeId: string;
37
- fromPubKey: string;
37
+ sharedByPubKey: string;
38
38
  }): Promise<boolean>;
39
39
  duplicateNode({ nodeId, folderId, name, }: {
40
40
  nodeId: string;
@@ -2,10 +2,15 @@ import { type RouterOutputs } from '../../client.js';
2
2
  import type { DataMetadata, InternalData, UserAppOrg } from './index.js';
3
3
  export type Permissions = ApiNode['identities'][number];
4
4
  export type Rights = Permissions['rights'];
5
+ export type NodeAccessType = 'ROOT' | 'INHERITED';
5
6
  export type NodeAccess<T extends Record<string, unknown> = Record<string, unknown>> = T & Permissions & {
6
- isRoot: boolean;
7
+ type: NodeAccessType;
7
8
  sharedByPubKey: string;
8
9
  identityPubKey: string;
10
+ rootAccess: {
11
+ type: NodeAccessType;
12
+ sharedByPubKey: string;
13
+ } | null;
9
14
  };
10
15
  export interface NodeBreadcrumbItem {
11
16
  id: string;