@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.
- package/dist/lib/client/SecrecyCloudClient.js +61 -74
- package/dist/lib/client/convert/node.js +2 -1
- package/dist/lib/crypto/helpers.js +6 -3
- package/dist/types/client/SecrecyCloudClient.d.ts +2 -2
- package/dist/types/client/types/node.d.ts +6 -1
- package/dist/types/client.d.ts +534 -148
- package/dist/types/crypto/helpers.d.ts +6 -1
- package/package.json +2 -2
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
88
|
+
async leaveNodeSharing({ nodeId, sharedByPubKey, }) {
|
|
92
89
|
const { isDeleted } = await this.#client.apiClient.cloud.leaveSharing.mutate({
|
|
93
90
|
nodeId,
|
|
94
|
-
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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:
|
|
140
|
+
name: encName,
|
|
138
141
|
parentId: parentFolderId ?? null,
|
|
139
|
-
|
|
140
|
-
fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
|
|
142
|
+
encryptedAccesses,
|
|
141
143
|
});
|
|
142
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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:
|
|
550
|
-
|
|
551
|
-
fromIdentityPubKey: this.#client.currentIdentity.identityPubKey,
|
|
549
|
+
fileName: encName,
|
|
550
|
+
encryptedAccesses,
|
|
552
551
|
});
|
|
553
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
17
|
-
const encryptedNameKey =
|
|
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,
|
|
35
|
+
leaveNodeSharing({ nodeId, sharedByPubKey, }: {
|
|
36
36
|
nodeId: string;
|
|
37
|
-
|
|
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
|
-
|
|
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;
|