cojson 0.18.23 → 0.18.25

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.
@@ -3,11 +3,13 @@ import type { CoID, RawGroup } from "../exports";
3
3
  import { NewContentMessage } from "../sync";
4
4
  import {
5
5
  SyncMessagesLog,
6
+ createNConnectedNodes,
6
7
  createThreeConnectedNodes,
7
8
  createTwoConnectedNodes,
8
9
  loadCoValueOrFail,
9
10
  setupTestNode,
10
11
  } from "./testUtils";
12
+ import { expectMap } from "../coValue.js";
11
13
 
12
14
  let jazzCloud: ReturnType<typeof setupTestNode>;
13
15
 
@@ -21,17 +23,12 @@ describe("extend", () => {
21
23
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
22
24
 
23
25
  const group = node1.node.createGroup();
24
- group.addMember(
25
- await loadCoValueOrFail(node1.node, node2.accountID),
26
- "writer",
27
- );
26
+ const node2OnNode1 = await loadCoValueOrFail(node1.node, node2.accountID);
27
+ group.addMember(node2OnNode1, "writer");
28
28
 
29
29
  const childGroup = node1.node.createGroup();
30
30
  childGroup.extend(group);
31
- childGroup.addMember(
32
- await loadCoValueOrFail(node1.node, node2.accountID),
33
- "writeOnly",
34
- );
31
+ childGroup.addMember(node2OnNode1, "writeOnly");
35
32
 
36
33
  const map = childGroup.createMap();
37
34
  map.set("test", "Written from the admin");
@@ -124,6 +121,115 @@ describe("extend", () => {
124
121
  expect(mapOnNode2.get("hello")).toEqual("from node 2");
125
122
  });
126
123
 
124
+ test("existing parent groups have access to new writeOnly keys in the child group", async () => {
125
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
126
+ "server",
127
+ "server",
128
+ "server",
129
+ );
130
+
131
+ const parentGroup = node1.node.createGroup();
132
+ const account2OnNode1 = await loadCoValueOrFail(
133
+ node1.node,
134
+ node2.accountID,
135
+ );
136
+ parentGroup.addMember(account2OnNode1, "admin");
137
+
138
+ const childGroup = node1.node.createGroup();
139
+ const account3OnNode1 = await loadCoValueOrFail(
140
+ node1.node,
141
+ node3.accountID,
142
+ );
143
+ childGroup.extend(parentGroup);
144
+ // The existing parent group should have access to content written by
145
+ // an account with writeOnly permission
146
+ childGroup.addMember(account3OnNode1, "writeOnly");
147
+
148
+ const map = childGroup.createMap();
149
+
150
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
151
+ mapOnNode3.set("test", "Written by writeOnly member");
152
+
153
+ expect(mapOnNode3.get("test")).toEqual("Written by writeOnly member");
154
+ await mapOnNode3.core.waitForSync();
155
+
156
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
157
+ expect(mapOnNode2.get("test")).toEqual("Written by writeOnly member");
158
+ });
159
+
160
+ test("new parent groups have access to existing writeOnly keys in the child group", async () => {
161
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
162
+ "server",
163
+ "server",
164
+ "server",
165
+ );
166
+
167
+ const parentGroup = node1.node.createGroup();
168
+ const account2OnNode1 = await loadCoValueOrFail(
169
+ node1.node,
170
+ node2.accountID,
171
+ );
172
+ parentGroup.addMember(account2OnNode1, "admin");
173
+
174
+ const childGroup = node1.node.createGroup();
175
+ const account3OnNode1 = await loadCoValueOrFail(
176
+ node1.node,
177
+ node3.accountID,
178
+ );
179
+ childGroup.addMember(account3OnNode1, "writeOnly");
180
+ // The new parent group should have access to content written by
181
+ // an account with writeOnly permission
182
+ childGroup.extend(parentGroup);
183
+
184
+ const map = childGroup.createMap();
185
+
186
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
187
+ mapOnNode3.set("test", "Written by writeOnly member");
188
+
189
+ expect(mapOnNode3.get("test")).toEqual("Written by writeOnly member");
190
+ await mapOnNode3.core.waitForSync();
191
+
192
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
193
+ expect(mapOnNode2.get("test")).toEqual("Written by writeOnly member");
194
+ });
195
+
196
+ test("writeOnly keys are rotated for parent groups", async () => {
197
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
198
+ "server",
199
+ "server",
200
+ "server",
201
+ );
202
+
203
+ const parentGroup = node1.node.createGroup();
204
+ const account2OnNode1 = await loadCoValueOrFail(
205
+ node1.node,
206
+ node2.accountID,
207
+ );
208
+ parentGroup.addMember(account2OnNode1, "admin");
209
+
210
+ const childGroup = node1.node.createGroup();
211
+ const account3OnNode1 = await loadCoValueOrFail(
212
+ node1.node,
213
+ node3.accountID,
214
+ );
215
+ childGroup.addMember(account3OnNode1, "writeOnly");
216
+ childGroup.extend(parentGroup);
217
+
218
+ childGroup.rotateReadKey();
219
+
220
+ const childGroupOnNode3 = await loadCoValueOrFail(
221
+ node3.node,
222
+ childGroup.id,
223
+ );
224
+ const map = childGroupOnNode3.createMap();
225
+ map.set("test", "Written by writeOnly member");
226
+
227
+ await map.core.waitForSync();
228
+
229
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
230
+ expect(mapOnNode2.get("test")).toEqual("Written by writeOnly member");
231
+ });
232
+
127
233
  test("a user should be able to extend a group when his role on the parent group is writeOnly", async () => {
128
234
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
129
235
 
@@ -344,6 +450,104 @@ describe("extend", () => {
344
450
  expect(childGroup.roleOf(alice.id)).toBe("writer");
345
451
  });
346
452
 
453
+ test("group inheritance should work for groups extended without having membership in the parent group", async () => {
454
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
455
+ "server",
456
+ "server",
457
+ "server",
458
+ );
459
+
460
+ const parentGroup = node1.node.createGroup();
461
+ const account2OnNode1 = await loadCoValueOrFail(
462
+ node1.node,
463
+ node2.accountID,
464
+ );
465
+ parentGroup.addMember(account2OnNode1, "admin");
466
+
467
+ const childGroup = node1.node.createGroup();
468
+ childGroup.extend(parentGroup, "admin");
469
+
470
+ const sharedGroup = node3.node.createGroup();
471
+
472
+ // Account3 does not have permissions over the childGroup being extended
473
+ const childGroupOnNode3 = await loadCoValueOrFail(
474
+ node3.node,
475
+ childGroup.id,
476
+ );
477
+ sharedGroup.extend(childGroupOnNode3, "admin");
478
+
479
+ expect(sharedGroup.roleOf(node1.accountID)).toEqual("admin");
480
+ expect(sharedGroup.roleOf(node2.accountID)).toEqual("admin");
481
+ expect(sharedGroup.roleOf(node3.accountID)).toEqual("admin");
482
+
483
+ // Create a map owned by sharedGroup
484
+ const testMap = sharedGroup.createMap();
485
+ testMap.set("name", "Test");
486
+
487
+ // node1 should be able to access the map because it is admin of the childGroup
488
+ const testMapOnNode1 = expectMap(
489
+ await loadCoValueOrFail(node1.node, testMap.id),
490
+ );
491
+ expect(testMapOnNode1.get("name")).toEqual("Test");
492
+
493
+ // node2 should also be able to access the map because it is admin of parentGroup
494
+ const testMapOnNode2 = expectMap(
495
+ await loadCoValueOrFail(node2.node, testMap.id),
496
+ );
497
+ expect(testMapOnNode2.get("name")).toEqual("Test");
498
+ });
499
+
500
+ test("adding new group members should work for groups extended without having membership in the parent group", async () => {
501
+ const nodes = await createNConnectedNodes(
502
+ "server",
503
+ "server",
504
+ "server",
505
+ "server",
506
+ );
507
+ const node1 = nodes[0]!;
508
+ const node2 = nodes[1]!;
509
+ const node3 = nodes[2]!;
510
+ const node4 = nodes[3]!;
511
+
512
+ const parentGroup = node1.node.createGroup();
513
+ const account2OnNode1 = await loadCoValueOrFail(
514
+ node1.node,
515
+ node2.accountID,
516
+ );
517
+ parentGroup.addMember(account2OnNode1, "admin");
518
+
519
+ const childGroup = node1.node.createGroup();
520
+ childGroup.extend(parentGroup, "admin");
521
+
522
+ const sharedGroup = node3.node.createGroup();
523
+
524
+ // Account3 does not have permissions over the childGroup being extended
525
+ const childGroupOnNode3 = await loadCoValueOrFail(
526
+ node3.node,
527
+ childGroup.id,
528
+ );
529
+ sharedGroup.extend(childGroupOnNode3, "admin");
530
+
531
+ // Add a new parent group to the previously extended child group
532
+ const newParentGroup = node1.node.createGroup();
533
+ const account4OnNode1 = await loadCoValueOrFail(
534
+ node1.node,
535
+ node4.accountID,
536
+ );
537
+ newParentGroup.addMember(account4OnNode1, "admin");
538
+ childGroup.extend(newParentGroup);
539
+
540
+ // Create a map owned by sharedGroup
541
+ const testMap = sharedGroup.createMap();
542
+ testMap.set("name", "Test");
543
+
544
+ // Account4 should be able to access the map because it is admin of newParentGroup
545
+ const testMapOnNode4 = expectMap(
546
+ await loadCoValueOrFail(node4.node, testMap.id),
547
+ );
548
+ expect(testMapOnNode4.get("name")).toEqual("Test");
549
+ });
550
+
347
551
  test("should be possible to extend a group after getting revoked from the parent group", async () => {
348
552
  const { node1, node2, node3 } = await createThreeConnectedNodes(
349
553
  "server",
@@ -30,7 +30,7 @@ describe("LocalNode auth sync", () => {
30
30
  creationProps: {
31
31
  name: "new-account",
32
32
  },
33
- peersToLoadFrom: [peer],
33
+ peers: [peer],
34
34
  crypto: Crypto,
35
35
  });
36
36
 
@@ -74,7 +74,7 @@ describe("LocalNode auth sync", () => {
74
74
  creationProps: {
75
75
  name: "new-account",
76
76
  },
77
- peersToLoadFrom: [peer],
77
+ peers: [peer],
78
78
  crypto: Crypto,
79
79
  async migration(account) {
80
80
  const root = account.createMap();
@@ -143,7 +143,7 @@ describe("LocalNode auth sync", () => {
143
143
  creationProps: {
144
144
  name: "new-account",
145
145
  },
146
- peersToLoadFrom: [newAccountPeer],
146
+ peers: [newAccountPeer],
147
147
  crypto: Crypto,
148
148
  });
149
149
 
@@ -155,7 +155,7 @@ describe("LocalNode auth sync", () => {
155
155
  const node = await LocalNode.withLoadedAccount({
156
156
  accountID,
157
157
  accountSecret,
158
- peersToLoadFrom: [existingAccountPeer],
158
+ peers: [existingAccountPeer],
159
159
  sessionID: undefined,
160
160
  crypto: Crypto,
161
161
  });
@@ -210,7 +210,7 @@ describe("LocalNode auth sync", () => {
210
210
  creationProps: {
211
211
  name: "new-account",
212
212
  },
213
- peersToLoadFrom: [newAccountPeer],
213
+ peers: [newAccountPeer],
214
214
  crypto: Crypto,
215
215
  });
216
216
 
@@ -225,7 +225,7 @@ describe("LocalNode auth sync", () => {
225
225
  const node = await LocalNode.withLoadedAccount({
226
226
  accountID,
227
227
  accountSecret,
228
- peersToLoadFrom: [existingAccountPeer],
228
+ peers: [existingAccountPeer],
229
229
  sessionID: undefined,
230
230
  crypto: Crypto,
231
231
  });
@@ -70,33 +70,11 @@ export async function createTwoConnectedNodes(
70
70
  node1Role: Peer["role"],
71
71
  node2Role: Peer["role"],
72
72
  ) {
73
- // Connect nodes initially
74
- const [node1ToNode2Peer, node2ToNode1Peer] = connectedPeers(
75
- "node1ToNode2",
76
- "node2ToNode1",
77
- {
78
- peer1role: node2Role,
79
- peer2role: node1Role,
80
- },
81
- );
82
-
83
- const node1 = await LocalNode.withNewlyCreatedAccount({
84
- peersToLoadFrom: [node1ToNode2Peer],
85
- crypto: Crypto,
86
- creationProps: { name: "Client" },
87
- });
88
-
89
- const node2 = await LocalNode.withNewlyCreatedAccount({
90
- peersToLoadFrom: [node2ToNode1Peer],
91
- crypto: Crypto,
92
- creationProps: { name: "Server" },
93
- });
73
+ const [node1, node2] = await createNConnectedNodes(node1Role, node2Role);
94
74
 
95
75
  return {
96
- node1,
97
- node2,
98
- node1ToNode2Peer,
99
- node2ToNode1Peer,
76
+ node1: node1!,
77
+ node2: node2!,
100
78
  };
101
79
  }
102
80
 
@@ -105,64 +83,42 @@ export async function createThreeConnectedNodes(
105
83
  node2Role: Peer["role"],
106
84
  node3Role: Peer["role"],
107
85
  ) {
108
- const [node1ToNode2Peer, node2ToNode1Peer] = connectedPeers(
109
- "node1ToNode2",
110
- "node2ToNode1",
111
- {
112
- peer1role: node2Role,
113
- peer2role: node1Role,
114
- },
115
- );
116
-
117
- const [node1ToNode3Peer, node3ToNode1Peer] = connectedPeers(
118
- "node1ToNode3",
119
- "node3ToNode1",
120
- {
121
- peer1role: node3Role,
122
- peer2role: node1Role,
123
- },
124
- );
125
-
126
- const [node2ToNode3Peer, node3ToNode2Peer] = connectedPeers(
127
- "node2ToNode3",
128
- "node3ToNode2",
129
- {
130
- peer1role: node3Role,
131
- peer2role: node2Role,
132
- },
86
+ const [node1, node2, node3] = await createNConnectedNodes(
87
+ node1Role,
88
+ node2Role,
89
+ node3Role,
133
90
  );
134
91
 
135
- const node1 = await LocalNode.withNewlyCreatedAccount({
136
- peersToLoadFrom: [node1ToNode2Peer, node1ToNode3Peer],
137
- crypto: Crypto,
138
- creationProps: { name: "Node 1" },
139
- });
140
-
141
- const node2 = await LocalNode.withNewlyCreatedAccount({
142
- peersToLoadFrom: [node2ToNode1Peer, node2ToNode3Peer],
143
- crypto: Crypto,
144
- creationProps: { name: "Node 2" },
145
- });
146
-
147
- const node3 = await LocalNode.withNewlyCreatedAccount({
148
- peersToLoadFrom: [node3ToNode1Peer, node3ToNode2Peer],
149
- crypto: Crypto,
150
- creationProps: { name: "Node 3" },
151
- });
152
-
153
92
  return {
154
- node1,
155
- node2,
156
- node3,
157
- node1ToNode2Peer,
158
- node2ToNode1Peer,
159
- node1ToNode3Peer,
160
- node3ToNode1Peer,
161
- node2ToNode3Peer,
162
- node3ToNode2Peer,
93
+ node1: node1!,
94
+ node2: node2!,
95
+ node3: node3!,
163
96
  };
164
97
  }
165
98
 
99
+ export async function createNConnectedNodes(...nodeRoles: Peer["role"][]) {
100
+ const nodes = await Promise.all(
101
+ Array.from({ length: nodeRoles.length }, async (_, i) => {
102
+ return LocalNode.withNewlyCreatedAccount({
103
+ peers: [],
104
+ crypto: Crypto,
105
+ creationProps: { name: `Node ${i + 1}` },
106
+ });
107
+ }),
108
+ );
109
+ for (let i = 0; i < nodes.length; i++) {
110
+ for (let j = i + 1; j < nodes.length; j++) {
111
+ connectTwoPeers(
112
+ nodes[i]!.node,
113
+ nodes[j]!.node,
114
+ nodeRoles[i]!,
115
+ nodeRoles[j]!,
116
+ );
117
+ }
118
+ }
119
+ return nodes;
120
+ }
121
+
166
122
  export function connectTwoPeers(
167
123
  a: LocalNode,
168
124
  b: LocalNode,
@@ -595,7 +551,7 @@ export async function setupTestAccount(
595
551
  } = {},
596
552
  ) {
597
553
  const ctx = await LocalNode.withNewlyCreatedAccount({
598
- peersToLoadFrom: [],
554
+ peers: [],
599
555
  crypto: Crypto,
600
556
  creationProps: { name: "Client" },
601
557
  storage: opts.storage,