cojson 0.18.31 → 0.18.33
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +17 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +1 -0
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +17 -2
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +14 -9
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +62 -47
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +2 -2
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +86 -75
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +24 -4
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +2 -10
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/knownState.d.ts +1 -1
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +1 -1
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +1 -2
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +16 -1
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/storage/knownState.js +2 -2
- package/dist/storage/knownState.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js +17 -3
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts +6 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +1 -3
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqlite/types.d.ts +2 -0
- package/dist/storage/sqlite/types.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/index.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/index.js +17 -3
- package/dist/storage/sqliteAsync/index.js.map +1 -1
- package/dist/storage/sqliteAsync/types.d.ts +2 -0
- package/dist/storage/sqliteAsync/types.d.ts.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +8 -2
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.js +1 -1
- package/dist/tests/PureJSCrypto.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +11 -11
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +3 -3
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +1 -1
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +1 -1
- package/dist/tests/coStream.test.js +12 -12
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.d.ts +2 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js +421 -0
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js.map +1 -0
- package/dist/tests/coValueCore.isStreaming.test.d.ts +2 -0
- package/dist/tests/coValueCore.isStreaming.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.isStreaming.test.js +181 -0
- package/dist/tests/coValueCore.isStreaming.test.js.map +1 -0
- package/dist/tests/coValueCore.newContentSince.test.d.ts +2 -0
- package/dist/tests/coValueCore.newContentSince.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.newContentSince.test.js +808 -0
- package/dist/tests/coValueCore.newContentSince.test.js.map +1 -0
- package/dist/tests/coreWasm.test.js +2 -2
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/group.childKeyRotation.test.d.ts +2 -0
- package/dist/tests/group.childKeyRotation.test.d.ts.map +1 -0
- package/dist/tests/group.childKeyRotation.test.js +261 -0
- package/dist/tests/group.childKeyRotation.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +1 -114
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.test.js +11 -11
- package/dist/tests/knownState.test.js.map +1 -1
- package/dist/tests/sync.auth.test.js +6 -6
- package/dist/tests/sync.load.test.js +68 -5
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +11 -17
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +1 -1
- package/dist/tests/sync.storage.test.js +7 -7
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +4 -4
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +96 -40
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +2 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +22 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/SyncStateManager.ts +2 -5
- package/src/coValueCore/SessionMap.ts +26 -1
- package/src/coValueCore/coValueCore.ts +77 -55
- package/src/coValueCore/verifiedState.ts +123 -108
- package/src/coValues/group.ts +27 -4
- package/src/crypto/PureJSCrypto.ts +5 -21
- package/src/knownState.ts +1 -1
- package/src/localNode.ts +1 -2
- package/src/queue/LocalTransactionsSyncQueue.ts +25 -0
- package/src/storage/knownState.ts +2 -2
- package/src/storage/sqlite/index.ts +16 -3
- package/src/storage/sqlite/sqliteMigrations.ts +7 -4
- package/src/storage/sqlite/types.ts +2 -0
- package/src/storage/sqliteAsync/index.ts +19 -6
- package/src/storage/sqliteAsync/types.ts +2 -0
- package/src/sync.ts +7 -2
- package/src/tests/PureJSCrypto.test.ts +1 -2
- package/src/tests/StorageApiAsync.test.ts +11 -11
- package/src/tests/StorageApiSync.test.ts +3 -3
- package/src/tests/WasmCrypto.test.ts +1 -2
- package/src/tests/coPlainText.test.ts +1 -1
- package/src/tests/coStream.test.ts +12 -12
- package/src/tests/coValueCore.isCompletelyDownloaded.test.ts +589 -0
- package/src/tests/coValueCore.isStreaming.test.ts +271 -0
- package/src/tests/coValueCore.newContentSince.test.ts +966 -0
- package/src/tests/coreWasm.test.ts +2 -2
- package/src/tests/group.childKeyRotation.test.ts +431 -0
- package/src/tests/group.removeMember.test.ts +1 -184
- package/src/tests/knownState.test.ts +11 -11
- package/src/tests/sync.auth.test.ts +6 -6
- package/src/tests/sync.load.test.ts +80 -5
- package/src/tests/sync.mesh.test.ts +11 -17
- package/src/tests/sync.peerReconciliation.test.ts +1 -1
- package/src/tests/sync.storage.test.ts +7 -7
- package/src/tests/sync.storageAsync.test.ts +4 -4
- package/src/tests/sync.upload.test.ts +106 -40
- package/src/tests/testUtils.ts +24 -2
|
@@ -42,7 +42,7 @@ describe("SessionLog WASM", () => {
|
|
|
42
42
|
|
|
43
43
|
const group = node.createGroup();
|
|
44
44
|
const sessionContent =
|
|
45
|
-
group.core.
|
|
45
|
+
group.core.newContentSince(undefined)?.[0]?.new[session];
|
|
46
46
|
assert(sessionContent);
|
|
47
47
|
|
|
48
48
|
let log = wasmCrypto.createSessionLog(
|
|
@@ -63,7 +63,7 @@ describe("SessionLog WASM", () => {
|
|
|
63
63
|
|
|
64
64
|
const group = node.createGroup();
|
|
65
65
|
const sessionContent =
|
|
66
|
-
group.core.
|
|
66
|
+
group.core.newContentSince(undefined)?.[0]?.new[session];
|
|
67
67
|
assert(sessionContent);
|
|
68
68
|
|
|
69
69
|
let log = wasmCrypto.createSessionLog(
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { setCoValueLoadingRetryDelay } from "../config.js";
|
|
3
|
+
import {
|
|
4
|
+
SyncMessagesLog,
|
|
5
|
+
TEST_NODE_CONFIG,
|
|
6
|
+
loadCoValueOrFail,
|
|
7
|
+
setupTestAccount,
|
|
8
|
+
setupTestNode,
|
|
9
|
+
waitFor,
|
|
10
|
+
} from "./testUtils.js";
|
|
11
|
+
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
12
|
+
import { RawAccount } from "../exports.js";
|
|
13
|
+
|
|
14
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
15
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
16
|
+
|
|
17
|
+
setCoValueLoadingRetryDelay(10);
|
|
18
|
+
|
|
19
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
SyncMessagesLog.clear();
|
|
23
|
+
jazzCloud = setupTestNode({ isSyncServer: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("Group.childKeyRotation", () => {
|
|
27
|
+
let admin: Awaited<ReturnType<typeof setupTestAccount>>;
|
|
28
|
+
let bob: Awaited<ReturnType<typeof setupTestAccount>>;
|
|
29
|
+
let alice: Awaited<ReturnType<typeof setupTestAccount>>;
|
|
30
|
+
let charlie: Awaited<ReturnType<typeof setupTestAccount>>;
|
|
31
|
+
let aliceOnAdminNode: RawAccount;
|
|
32
|
+
let bobOnAdminNode: RawAccount;
|
|
33
|
+
let charlieOnAdminNode: RawAccount;
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
admin = await setupTestAccount({
|
|
37
|
+
connected: true,
|
|
38
|
+
});
|
|
39
|
+
alice = await setupTestAccount({
|
|
40
|
+
connected: true,
|
|
41
|
+
});
|
|
42
|
+
bob = await setupTestAccount({
|
|
43
|
+
connected: true,
|
|
44
|
+
});
|
|
45
|
+
charlie = await setupTestAccount({
|
|
46
|
+
connected: true,
|
|
47
|
+
});
|
|
48
|
+
aliceOnAdminNode = await loadCoValueOrFail(admin.node, alice.accountID);
|
|
49
|
+
bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
50
|
+
charlieOnAdminNode = await loadCoValueOrFail(admin.node, charlie.accountID);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("removing a member should rotate the readKey on available child groups", async () => {
|
|
54
|
+
const group = admin.node.createGroup();
|
|
55
|
+
const childGroup = admin.node.createGroup();
|
|
56
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
57
|
+
|
|
58
|
+
childGroup.extend(group);
|
|
59
|
+
|
|
60
|
+
group.removeMember(aliceOnAdminNode);
|
|
61
|
+
|
|
62
|
+
// ReadKey rotated, now alice can't read the map
|
|
63
|
+
const map = childGroup.createMap();
|
|
64
|
+
map.set("test", "Not readable by alice");
|
|
65
|
+
|
|
66
|
+
await map.core.waitForSync();
|
|
67
|
+
|
|
68
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
69
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("removing a member should rotate the readKey on unloaded child groups", async () => {
|
|
73
|
+
const group = admin.node.createGroup();
|
|
74
|
+
|
|
75
|
+
let childGroup = bob.node.createGroup();
|
|
76
|
+
group.addMember(bobOnAdminNode, "reader");
|
|
77
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
78
|
+
|
|
79
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
80
|
+
|
|
81
|
+
childGroup.extend(groupOnBobNode);
|
|
82
|
+
|
|
83
|
+
await childGroup.core.waitForSync();
|
|
84
|
+
|
|
85
|
+
group.removeMember(aliceOnAdminNode);
|
|
86
|
+
|
|
87
|
+
// Spinning a new session for bob, to be sure to trigger the group migration
|
|
88
|
+
// that handles the key rotation
|
|
89
|
+
const newBobSession = await bob.spawnNewSession();
|
|
90
|
+
|
|
91
|
+
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
92
|
+
newBobSession,
|
|
93
|
+
childGroup.id,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// The key should be rotated at this point, so alice can't read the map
|
|
97
|
+
const map = childGroupOnNewBobNode.createMap();
|
|
98
|
+
map.set("test", "Not readable by alice");
|
|
99
|
+
|
|
100
|
+
await map.core.waitForSync();
|
|
101
|
+
|
|
102
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
103
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("removing a member on a large group should rotate the readKey on unloaded child group", async () => {
|
|
107
|
+
const group = admin.node.createGroup();
|
|
108
|
+
|
|
109
|
+
const childGroup = bob.node.createGroup();
|
|
110
|
+
group.addMember(bobOnAdminNode, "reader");
|
|
111
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
112
|
+
|
|
113
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
114
|
+
|
|
115
|
+
childGroup.extend(groupOnBobNode);
|
|
116
|
+
|
|
117
|
+
await childGroup.core.waitForSync();
|
|
118
|
+
|
|
119
|
+
// Disconnect the admin node to sync the content manually and simulate the delay of the last chunk
|
|
120
|
+
admin.disconnect();
|
|
121
|
+
|
|
122
|
+
// Make the group to become large enough to require multiple messages to be synced
|
|
123
|
+
for (let i = 0; i < 100; i++) {
|
|
124
|
+
// @ts-expect-error - test property is not part of the group shape
|
|
125
|
+
group.set("test", "1".repeat(1024));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
expect(group.core.verified.newContentSince(undefined)?.length).toBe(2);
|
|
129
|
+
|
|
130
|
+
group.removeMember(aliceOnAdminNode);
|
|
131
|
+
|
|
132
|
+
const content = group.core.verified.newContentSince(undefined);
|
|
133
|
+
assert(content);
|
|
134
|
+
const lastChunk = content.pop();
|
|
135
|
+
assert(lastChunk);
|
|
136
|
+
|
|
137
|
+
// Spinning a new session for bob, to be sure to trigger the group migration
|
|
138
|
+
const newBobSession = await bob.spawnNewSession();
|
|
139
|
+
|
|
140
|
+
for (const chunk of content) {
|
|
141
|
+
newBobSession.syncManager.handleNewContent(chunk, "import");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
145
|
+
newBobSession,
|
|
146
|
+
childGroup.id,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
newBobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
150
|
+
|
|
151
|
+
// The migration waits for the group to be completely downloaded
|
|
152
|
+
await childGroupOnNewBobNode.core.waitForAsync((core) =>
|
|
153
|
+
core.isCompletelyDownloaded(),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// The key should be rotated at this point, so alice can't read the map
|
|
157
|
+
const map = childGroupOnNewBobNode.createMap();
|
|
158
|
+
map.set("test", "Not readable by alice");
|
|
159
|
+
|
|
160
|
+
await map.core.waitForSync();
|
|
161
|
+
|
|
162
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
163
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("removing a member on a large parent group should rotate the readKey on unloaded grandChild group", async () => {
|
|
167
|
+
const parentGroup = admin.node.createGroup();
|
|
168
|
+
|
|
169
|
+
const group = bob.node.createGroup();
|
|
170
|
+
const childGroup = bob.node.createGroup();
|
|
171
|
+
parentGroup.addMember(bobOnAdminNode, "reader");
|
|
172
|
+
parentGroup.addMember(aliceOnAdminNode, "reader");
|
|
173
|
+
|
|
174
|
+
const parentGroupOnBobNode = await loadCoValueOrFail(
|
|
175
|
+
bob.node,
|
|
176
|
+
parentGroup.id,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
group.extend(parentGroupOnBobNode);
|
|
180
|
+
childGroup.extend(group);
|
|
181
|
+
|
|
182
|
+
await childGroup.core.waitForSync();
|
|
183
|
+
|
|
184
|
+
// Disconnect the admin node to sync the content manually and simulate the delay of the last chunk
|
|
185
|
+
admin.disconnect();
|
|
186
|
+
|
|
187
|
+
// Make the parent group to become large enough to require multiple messages to be synced
|
|
188
|
+
for (let i = 0; i < 200; i++) {
|
|
189
|
+
// @ts-expect-error - test property is not part of the group shape
|
|
190
|
+
parentGroup.set("test", "1".repeat(1024));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
expect(parentGroup.core.verified.newContentSince(undefined)?.length).toBe(
|
|
194
|
+
3,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
parentGroup.removeMember(aliceOnAdminNode);
|
|
198
|
+
|
|
199
|
+
const content = parentGroup.core.verified.newContentSince(undefined);
|
|
200
|
+
assert(content);
|
|
201
|
+
const lastChunk = content.pop();
|
|
202
|
+
assert(lastChunk);
|
|
203
|
+
|
|
204
|
+
// Spinning a new session for bob, to be sure to trigger the group migration
|
|
205
|
+
const newBobSession = await bob.spawnNewSession();
|
|
206
|
+
|
|
207
|
+
for (const chunk of content) {
|
|
208
|
+
newBobSession.syncManager.handleNewContent(chunk, "import");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
212
|
+
newBobSession,
|
|
213
|
+
childGroup.id,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
newBobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
217
|
+
|
|
218
|
+
// The migration waits for the group to be completely downloaded, this includes full streaming of the parent group
|
|
219
|
+
await childGroupOnNewBobNode.core.waitForAsync((core) =>
|
|
220
|
+
core.isCompletelyDownloaded(),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// The key should be rotated at this point, so alice can't read the map
|
|
224
|
+
const map = childGroupOnNewBobNode.createMap();
|
|
225
|
+
map.set("test", "Not readable by alice");
|
|
226
|
+
|
|
227
|
+
await map.core.waitForSync();
|
|
228
|
+
|
|
229
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
230
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("non-admin accounts can't trigger the unloaded child group key rotation", async () => {
|
|
234
|
+
const group = admin.node.createGroup();
|
|
235
|
+
const childGroup = bob.node.createGroup();
|
|
236
|
+
|
|
237
|
+
group.addMember(bobOnAdminNode, "writer");
|
|
238
|
+
group.addMember(aliceOnAdminNode, "writer");
|
|
239
|
+
group.addMember(charlieOnAdminNode, "writer");
|
|
240
|
+
|
|
241
|
+
await group.core.waitForSync();
|
|
242
|
+
|
|
243
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
244
|
+
|
|
245
|
+
childGroup.extend(groupOnBobNode);
|
|
246
|
+
|
|
247
|
+
await childGroup.core.waitForSync();
|
|
248
|
+
await groupOnBobNode.core.waitForSync();
|
|
249
|
+
|
|
250
|
+
group.removeMember(charlieOnAdminNode);
|
|
251
|
+
|
|
252
|
+
await group.core.waitForSync();
|
|
253
|
+
|
|
254
|
+
// Alice only have writer access to the child group, so she can't rotate the readKey
|
|
255
|
+
const childGroupOnAliceNode = await loadCoValueOrFail(
|
|
256
|
+
alice.node,
|
|
257
|
+
childGroup.id,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const map = childGroupOnAliceNode.createMap();
|
|
261
|
+
map.set("test", "Readable by charlie");
|
|
262
|
+
|
|
263
|
+
await map.core.waitForSync();
|
|
264
|
+
|
|
265
|
+
// This means that Charlie can read what Alice wrote
|
|
266
|
+
const mapOnCharlieNode = await loadCoValueOrFail(charlie.node, map.id);
|
|
267
|
+
expect(mapOnCharlieNode.get("test")).toBe("Readable by charlie");
|
|
268
|
+
|
|
269
|
+
// Instead Bob is an admin, so when loading the child group he can rotate the readKey
|
|
270
|
+
const newBobSession = await bob.spawnNewSession();
|
|
271
|
+
const mapOnNewBobNode = await loadCoValueOrFail(newBobSession, map.id);
|
|
272
|
+
|
|
273
|
+
mapOnNewBobNode.set("test", "Not readable by charlie");
|
|
274
|
+
|
|
275
|
+
await mapOnNewBobNode.core.waitForSync();
|
|
276
|
+
|
|
277
|
+
const updatedMapOnCharlieNode = await loadCoValueOrFail(
|
|
278
|
+
charlie.node,
|
|
279
|
+
map.id,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Ensure that the map is fully synced
|
|
283
|
+
await waitFor(async () => {
|
|
284
|
+
expect(updatedMapOnCharlieNode.core.knownState()).toEqual(
|
|
285
|
+
mapOnNewBobNode.core.knownState(),
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Charlie should not be able to read what Bob wrote
|
|
290
|
+
expect(updatedMapOnCharlieNode.get("test")).toBe("Readable by charlie");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("direct manager account can trigger the unloaded child group key rotation", async () => {
|
|
294
|
+
const group = admin.node.createGroup();
|
|
295
|
+
const childGroup = bob.node.createGroup();
|
|
296
|
+
|
|
297
|
+
group.addMember(bobOnAdminNode, "writer");
|
|
298
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
299
|
+
|
|
300
|
+
await group.core.waitForSync();
|
|
301
|
+
|
|
302
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
303
|
+
|
|
304
|
+
childGroup.extend(groupOnBobNode);
|
|
305
|
+
|
|
306
|
+
const charlieOnBobNode = await loadCoValueOrFail(
|
|
307
|
+
bob.node,
|
|
308
|
+
charlie.accountID,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
childGroup.addMember(charlieOnBobNode, "manager");
|
|
312
|
+
|
|
313
|
+
await childGroup.core.waitForSync();
|
|
314
|
+
await groupOnBobNode.core.waitForSync();
|
|
315
|
+
|
|
316
|
+
group.removeMember(aliceOnAdminNode);
|
|
317
|
+
|
|
318
|
+
await group.core.waitForSync();
|
|
319
|
+
|
|
320
|
+
const childGroupOnCharlieNode = await loadCoValueOrFail(
|
|
321
|
+
charlie.node,
|
|
322
|
+
childGroup.id,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const map = childGroupOnCharlieNode.createMap();
|
|
326
|
+
map.set("test", "Not readable by alice");
|
|
327
|
+
|
|
328
|
+
await map.core.waitForSync();
|
|
329
|
+
|
|
330
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
331
|
+
|
|
332
|
+
// Ensure that the map is fully synced
|
|
333
|
+
await waitFor(async () => {
|
|
334
|
+
expect(mapOnAliceNode.core.knownState()).toEqual(map.core.knownState());
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Charlie should not be able to read what Bob wrote because key was rotated
|
|
338
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("inherited admin account triggers the unloaded child group key rotation", async () => {
|
|
342
|
+
const group = admin.node.createGroup();
|
|
343
|
+
const childGroup = bob.node.createGroup();
|
|
344
|
+
|
|
345
|
+
// Alice is admin on parent group (will inherit to child), but not directly on child
|
|
346
|
+
group.addMember(aliceOnAdminNode, "admin");
|
|
347
|
+
group.addMember(bobOnAdminNode, "writer");
|
|
348
|
+
group.addMember(charlieOnAdminNode, "reader");
|
|
349
|
+
|
|
350
|
+
await group.core.waitForSync();
|
|
351
|
+
|
|
352
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
353
|
+
|
|
354
|
+
childGroup.extend(groupOnBobNode);
|
|
355
|
+
|
|
356
|
+
await childGroup.core.waitForSync();
|
|
357
|
+
await groupOnBobNode.core.waitForSync();
|
|
358
|
+
|
|
359
|
+
group.removeMember(charlieOnAdminNode);
|
|
360
|
+
|
|
361
|
+
await group.core.waitForSync();
|
|
362
|
+
|
|
363
|
+
// Alice has inherited admin access but is not a direct admin on the child group
|
|
364
|
+
// So she can't trigger key rotation
|
|
365
|
+
const childGroupOnNewAliceNode = await loadCoValueOrFail(
|
|
366
|
+
alice.node,
|
|
367
|
+
childGroup.id,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const map = childGroupOnNewAliceNode.createMap();
|
|
371
|
+
map.set("test", "Readable by charlie");
|
|
372
|
+
|
|
373
|
+
await map.core.waitForSync();
|
|
374
|
+
|
|
375
|
+
// Charlie should be able to read what Alice wrote because key wasn't rotated
|
|
376
|
+
const mapOnCharlieNode = await loadCoValueOrFail(charlie.node, map.id);
|
|
377
|
+
expect(mapOnCharlieNode.get("test")).toBe(undefined);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// TODO: In this case the child can't detect the parent group rotation, because it doesn't have access to the parent group readKey
|
|
381
|
+
// We need to replace the writeOnlyKey with an asymmetric key sealing mechanism to cover this case
|
|
382
|
+
test.skip("removing a member should rotate the writeOnlyKey on child group", async () => {
|
|
383
|
+
const admin = await setupTestAccount({
|
|
384
|
+
connected: true,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const bob = await setupTestAccount({
|
|
388
|
+
connected: true,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const alice = await setupTestAccount({
|
|
392
|
+
connected: true,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
396
|
+
admin.node,
|
|
397
|
+
alice.accountID,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const group = admin.node.createGroup();
|
|
401
|
+
const childGroup = bob.node.createGroup();
|
|
402
|
+
|
|
403
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
404
|
+
|
|
405
|
+
await group.core.waitForSync();
|
|
406
|
+
|
|
407
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
408
|
+
|
|
409
|
+
childGroup.extend(groupOnBobNode);
|
|
410
|
+
|
|
411
|
+
await childGroup.core.waitForSync();
|
|
412
|
+
|
|
413
|
+
group.removeMember(aliceOnAdminNode);
|
|
414
|
+
|
|
415
|
+
await group.core.waitForSync();
|
|
416
|
+
|
|
417
|
+
const newBobSession = await bob.spawnNewSession();
|
|
418
|
+
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
419
|
+
newBobSession,
|
|
420
|
+
childGroup.id,
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const map = childGroupOnNewBobNode.createMap();
|
|
424
|
+
map.set("test", "Not readable by alice");
|
|
425
|
+
|
|
426
|
+
await map.core.waitForSync();
|
|
427
|
+
|
|
428
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
429
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, test } from "vitest";
|
|
|
2
2
|
import { setCoValueLoadingRetryDelay } from "../config.js";
|
|
3
3
|
import {
|
|
4
4
|
SyncMessagesLog,
|
|
5
|
+
TEST_NODE_CONFIG,
|
|
5
6
|
blockMessageTypeOnOutgoingPeer,
|
|
6
7
|
loadCoValueOrFail,
|
|
7
8
|
setupTestAccount,
|
|
@@ -347,188 +348,4 @@ describe("Group.removeMember", () => {
|
|
|
347
348
|
undefined,
|
|
348
349
|
);
|
|
349
350
|
});
|
|
350
|
-
|
|
351
|
-
test("removing a member should rotate the readKey on available child groups", async () => {
|
|
352
|
-
const admin = await setupTestAccount({
|
|
353
|
-
connected: true,
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const alice = await setupTestAccount({
|
|
357
|
-
connected: true,
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
361
|
-
admin.node,
|
|
362
|
-
alice.accountID,
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
const group = admin.node.createGroup();
|
|
366
|
-
const childGroup = admin.node.createGroup();
|
|
367
|
-
group.addMember(aliceOnAdminNode, "reader");
|
|
368
|
-
|
|
369
|
-
childGroup.extend(group);
|
|
370
|
-
|
|
371
|
-
group.removeMember(aliceOnAdminNode);
|
|
372
|
-
|
|
373
|
-
const map = childGroup.createMap();
|
|
374
|
-
map.set("test", "Not readable by alice");
|
|
375
|
-
|
|
376
|
-
await map.core.waitForSync();
|
|
377
|
-
|
|
378
|
-
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
379
|
-
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test.skip("removing a member should rotate the readKey on unloaded child groups", async () => {
|
|
383
|
-
const admin = await setupTestAccount({
|
|
384
|
-
connected: true,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const bob = await setupTestAccount({
|
|
388
|
-
connected: true,
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
const alice = await setupTestAccount({
|
|
392
|
-
connected: true,
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
396
|
-
|
|
397
|
-
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
398
|
-
admin.node,
|
|
399
|
-
alice.accountID,
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
const group = admin.node.createGroup();
|
|
403
|
-
|
|
404
|
-
let childGroup = bob.node.createGroup();
|
|
405
|
-
group.addMember(bobOnAdminNode, "reader");
|
|
406
|
-
group.addMember(aliceOnAdminNode, "reader");
|
|
407
|
-
|
|
408
|
-
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
409
|
-
|
|
410
|
-
childGroup.extend(groupOnBobNode);
|
|
411
|
-
|
|
412
|
-
await childGroup.core.waitForSync();
|
|
413
|
-
|
|
414
|
-
group.removeMember(aliceOnAdminNode);
|
|
415
|
-
|
|
416
|
-
// Rotating the child group keys is async when the child group is not loaded
|
|
417
|
-
await admin.node.syncManager.waitForAllCoValuesSync();
|
|
418
|
-
|
|
419
|
-
// Reload the group the trigger the loading checks
|
|
420
|
-
childGroup.core.resetParsedTransactions();
|
|
421
|
-
childGroup = expectGroup(childGroup.core.getCurrentContent());
|
|
422
|
-
|
|
423
|
-
const map = childGroup.createMap();
|
|
424
|
-
map.set("test", "Not readable by alice");
|
|
425
|
-
|
|
426
|
-
await map.core.waitForSync();
|
|
427
|
-
|
|
428
|
-
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
429
|
-
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
test("removing a member should work even if there are partially available child groups", async () => {
|
|
433
|
-
const admin = await setupTestAccount({
|
|
434
|
-
connected: true,
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
const bob = await setupTestAccount();
|
|
438
|
-
const { peer } = bob.connectToSyncServer();
|
|
439
|
-
|
|
440
|
-
const alice = await setupTestAccount({
|
|
441
|
-
connected: true,
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
445
|
-
|
|
446
|
-
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
447
|
-
admin.node,
|
|
448
|
-
alice.accountID,
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
const group = admin.node.createGroup();
|
|
452
|
-
const childGroup = bob.node.createGroup();
|
|
453
|
-
|
|
454
|
-
group.addMember(bobOnAdminNode, "reader");
|
|
455
|
-
group.addMember(aliceOnAdminNode, "reader");
|
|
456
|
-
|
|
457
|
-
await group.core.waitForSync();
|
|
458
|
-
|
|
459
|
-
blockMessageTypeOnOutgoingPeer(peer, "content", {
|
|
460
|
-
id: childGroup.id,
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
464
|
-
|
|
465
|
-
childGroup.extend(groupOnBobNode);
|
|
466
|
-
|
|
467
|
-
await groupOnBobNode.core.waitForSync();
|
|
468
|
-
|
|
469
|
-
group.removeMember(aliceOnAdminNode);
|
|
470
|
-
|
|
471
|
-
await admin.node.syncManager.waitForAllCoValuesSync();
|
|
472
|
-
|
|
473
|
-
const map = group.createMap();
|
|
474
|
-
map.set("test", "Not readable by alice");
|
|
475
|
-
|
|
476
|
-
await map.core.waitForSync();
|
|
477
|
-
|
|
478
|
-
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
479
|
-
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test("removing a member should work even if there are unavailable child groups", async () => {
|
|
483
|
-
const admin = await setupTestAccount({
|
|
484
|
-
connected: true,
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
const { peerOnServer } = admin.connectToSyncServer();
|
|
488
|
-
|
|
489
|
-
const bob = await setupTestAccount({
|
|
490
|
-
connected: true,
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
const alice = await setupTestAccount({
|
|
494
|
-
connected: true,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
498
|
-
|
|
499
|
-
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
500
|
-
admin.node,
|
|
501
|
-
alice.accountID,
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
const group = admin.node.createGroup();
|
|
505
|
-
const childGroup = bob.node.createGroup();
|
|
506
|
-
|
|
507
|
-
blockMessageTypeOnOutgoingPeer(peerOnServer, "content", {
|
|
508
|
-
id: childGroup.id,
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
group.addMember(bobOnAdminNode, "reader");
|
|
512
|
-
group.addMember(aliceOnAdminNode, "reader");
|
|
513
|
-
|
|
514
|
-
await group.core.waitForSync();
|
|
515
|
-
|
|
516
|
-
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
517
|
-
|
|
518
|
-
childGroup.extend(groupOnBobNode);
|
|
519
|
-
|
|
520
|
-
await groupOnBobNode.core.waitForSync();
|
|
521
|
-
|
|
522
|
-
group.removeMember(aliceOnAdminNode);
|
|
523
|
-
|
|
524
|
-
await group.core.waitForSync();
|
|
525
|
-
|
|
526
|
-
const map = group.createMap();
|
|
527
|
-
map.set("test", "Not readable by alice");
|
|
528
|
-
|
|
529
|
-
await map.core.waitForSync();
|
|
530
|
-
|
|
531
|
-
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
532
|
-
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
533
|
-
});
|
|
534
351
|
});
|