cojson 0.8.44 → 0.8.48
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 +3 -3
- package/CHANGELOG.md +14 -0
- package/dist/native/coValueCore.js +4 -4
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValues/account.js +3 -0
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/coValues/group.js +8 -2
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/exports.js +3 -2
- package/dist/native/exports.js.map +1 -1
- package/dist/native/permissions.js +5 -2
- package/dist/native/permissions.js.map +1 -1
- package/dist/web/coValueCore.js +4 -4
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValues/account.js +3 -0
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/coValues/group.js +8 -2
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/exports.js +3 -2
- package/dist/web/exports.js.map +1 -1
- package/dist/web/permissions.js +5 -2
- package/dist/web/permissions.js.map +1 -1
- package/package.json +1 -1
- package/src/coValueCore.ts +6 -2
- package/src/coValues/account.ts +5 -0
- package/src/coValues/group.ts +13 -2
- package/src/exports.ts +9 -2
- package/src/permissions.ts +9 -2
- package/src/tests/account.test.ts +14 -3
- package/src/tests/crypto.test.ts +149 -140
- package/src/tests/group.test.ts +117 -0
- package/src/tests/permissions.test.ts +18 -9
package/src/tests/crypto.test.ts
CHANGED
|
@@ -3,178 +3,187 @@ import { x25519 } from "@noble/curves/ed25519";
|
|
|
3
3
|
import { blake3 } from "@noble/hashes/blake3";
|
|
4
4
|
import { base58, base64url } from "@scure/base";
|
|
5
5
|
import { expect, test } from "vitest";
|
|
6
|
+
import { PureJSCrypto } from "../crypto/PureJSCrypto.js";
|
|
6
7
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
7
8
|
import { SessionID } from "../ids.js";
|
|
8
9
|
import { stableStringify } from "../jsonStringify.js";
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const signer2 = Crypto.newRandomSigner();
|
|
31
|
-
const wrongSignature = Crypto.sign(signer2, data);
|
|
11
|
+
const wasmCrypto = await WasmCrypto.create();
|
|
12
|
+
const pureJSCrypto = await PureJSCrypto.create();
|
|
13
|
+
|
|
14
|
+
[wasmCrypto, pureJSCrypto].forEach((crypto) => {
|
|
15
|
+
const name = crypto.constructor.name;
|
|
16
|
+
|
|
17
|
+
test(`Signatures round-trip and use stable stringify [${name}]`, () => {
|
|
18
|
+
const data = { b: "world", a: "hello" };
|
|
19
|
+
const signer = crypto.newRandomSigner();
|
|
20
|
+
const signature = crypto.sign(signer, data);
|
|
21
|
+
|
|
22
|
+
expect(signature).toMatch(/^signature_z/);
|
|
23
|
+
expect(
|
|
24
|
+
crypto.verify(
|
|
25
|
+
signature,
|
|
26
|
+
{ a: "hello", b: "world" },
|
|
27
|
+
crypto.getSignerID(signer),
|
|
28
|
+
),
|
|
29
|
+
).toBe(true);
|
|
30
|
+
});
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
test(`Invalid signatures don't verify [${name}]`, () => {
|
|
33
|
+
const data = { b: "world", a: "hello" };
|
|
34
|
+
const signer = crypto.newRandomSigner();
|
|
35
|
+
const signer2 = crypto.newRandomSigner();
|
|
36
|
+
const wrongSignature = crypto.sign(signer2, data);
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const sealer = Crypto.newRandomSealer();
|
|
42
|
-
const wrongSealer = Crypto.newRandomSealer();
|
|
43
|
-
|
|
44
|
-
const nOnceMaterial = {
|
|
45
|
-
in: "co_zTEST",
|
|
46
|
-
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
47
|
-
} as const;
|
|
48
|
-
|
|
49
|
-
const sealed = Crypto.seal({
|
|
50
|
-
message: data,
|
|
51
|
-
from: sender,
|
|
52
|
-
to: Crypto.getSealerID(sealer),
|
|
53
|
-
nOnceMaterial,
|
|
38
|
+
expect(
|
|
39
|
+
crypto.verify(wrongSignature, data, crypto.getSignerID(signer)),
|
|
40
|
+
).toBe(false);
|
|
54
41
|
});
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
test(`encrypting round-trips, but invalid receiver can't unseal [${name}]`, () => {
|
|
44
|
+
const data = { b: "world", a: "hello" };
|
|
45
|
+
const sender = crypto.newRandomSealer();
|
|
46
|
+
const sealer = crypto.newRandomSealer();
|
|
47
|
+
const wrongSealer = crypto.newRandomSealer();
|
|
48
|
+
|
|
49
|
+
const nOnceMaterial = {
|
|
50
|
+
in: "co_zTEST",
|
|
51
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
const sealed = crypto.seal({
|
|
55
|
+
message: data,
|
|
56
|
+
from: sender,
|
|
57
|
+
to: crypto.getSealerID(sealer),
|
|
64
58
|
nOnceMaterial,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(
|
|
62
|
+
crypto.unseal(sealed, sealer, crypto.getSealerID(sender), nOnceMaterial),
|
|
63
|
+
).toEqual(data);
|
|
64
|
+
expect(() =>
|
|
65
|
+
crypto.unseal(
|
|
66
|
+
sealed,
|
|
67
|
+
wrongSealer,
|
|
68
|
+
crypto.getSealerID(sender),
|
|
69
|
+
nOnceMaterial,
|
|
70
|
+
),
|
|
71
|
+
).toThrow(/Wrong tag/);
|
|
72
|
+
|
|
73
|
+
// trying with wrong sealer secret, by hand
|
|
74
|
+
const nOnce = blake3(
|
|
75
|
+
new TextEncoder().encode(stableStringify(nOnceMaterial)),
|
|
76
|
+
).slice(0, 24);
|
|
77
|
+
const sealer3priv = base58.decode(
|
|
78
|
+
wrongSealer.substring("sealerSecret_z".length),
|
|
79
|
+
);
|
|
80
|
+
const senderPub = base58.decode(
|
|
81
|
+
crypto.getSealerID(sender).substring("sealer_z".length),
|
|
82
|
+
);
|
|
83
|
+
const sealedBytes = base64url.decode(sealed.substring("sealed_U".length));
|
|
84
|
+
const sharedSecret = x25519.getSharedSecret(sealer3priv, senderPub);
|
|
85
|
+
|
|
86
|
+
expect(() => {
|
|
87
|
+
const _ = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(sealedBytes);
|
|
88
|
+
}).toThrow("Wrong tag");
|
|
89
|
+
});
|
|
85
90
|
|
|
86
|
-
test(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
test(`Hashing is deterministic [${name}]`, () => {
|
|
92
|
+
expect(crypto.secureHash({ b: "world", a: "hello" })).toEqual(
|
|
93
|
+
crypto.secureHash({ a: "hello", b: "world" }),
|
|
94
|
+
);
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
96
|
+
expect(crypto.shortHash({ b: "world", a: "hello" })).toEqual(
|
|
97
|
+
crypto.shortHash({ a: "hello", b: "world" }),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
95
100
|
|
|
96
|
-
test(
|
|
97
|
-
|
|
101
|
+
test(`Encryption for transactions round-trips [${name}]`, () => {
|
|
102
|
+
const { secret } = crypto.newRandomKeySecret();
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
const encrypted1 = crypto.encryptForTransaction({ a: "hello" }, secret, {
|
|
105
|
+
in: "co_zTEST",
|
|
106
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
107
|
+
});
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
const encrypted2 = crypto.encryptForTransaction({ b: "world" }, secret, {
|
|
110
|
+
in: "co_zTEST",
|
|
111
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
112
|
+
});
|
|
108
113
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
const decrypted1 = crypto.decryptForTransaction(encrypted1, secret, {
|
|
115
|
+
in: "co_zTEST",
|
|
116
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
117
|
+
});
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
const decrypted2 = crypto.decryptForTransaction(encrypted2, secret, {
|
|
120
|
+
in: "co_zTEST",
|
|
121
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
|
|
117
125
|
});
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
});
|
|
127
|
+
test(`Encryption for transactions doesn't decrypt with a wrong key [${name}]`, () => {
|
|
128
|
+
const { secret } = crypto.newRandomKeySecret();
|
|
129
|
+
const { secret: secret2 } = crypto.newRandomKeySecret();
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
const encrypted1 = crypto.encryptForTransaction({ a: "hello" }, secret, {
|
|
132
|
+
in: "co_zTEST",
|
|
133
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
134
|
+
});
|
|
125
135
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
const encrypted2 = crypto.encryptForTransaction({ b: "world" }, secret, {
|
|
137
|
+
in: "co_zTEST",
|
|
138
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
139
|
+
});
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
const decrypted1 = crypto.decryptForTransaction(encrypted1, secret2, {
|
|
142
|
+
in: "co_zTEST",
|
|
143
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
144
|
+
});
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
const decrypted2 = crypto.decryptForTransaction(encrypted2, secret2, {
|
|
147
|
+
in: "co_zTEST",
|
|
148
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
149
|
+
});
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
in: "co_zTEST",
|
|
143
|
-
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
151
|
+
expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
|
|
144
152
|
});
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
test("Encryption of keySecrets round-trips", () => {
|
|
150
|
-
const toEncrypt = Crypto.newRandomKeySecret();
|
|
151
|
-
const encrypting = Crypto.newRandomKeySecret();
|
|
154
|
+
test(`Encryption of keySecrets round-trips [${name}]`, () => {
|
|
155
|
+
const toEncrypt = crypto.newRandomKeySecret();
|
|
156
|
+
const encrypting = crypto.newRandomKeySecret();
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
const keys = {
|
|
159
|
+
toEncrypt,
|
|
160
|
+
encrypting,
|
|
161
|
+
};
|
|
157
162
|
|
|
158
|
-
|
|
163
|
+
const encrypted = crypto.encryptKeySecret(keys);
|
|
159
164
|
|
|
160
|
-
|
|
165
|
+
const decrypted = crypto.decryptKeySecret(encrypted, encrypting.secret);
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
});
|
|
167
|
+
expect(decrypted).toEqual(toEncrypt.secret);
|
|
168
|
+
});
|
|
164
169
|
|
|
165
|
-
test(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
test(`Encryption of keySecrets doesn't decrypt with a wrong key [${name}]`, () => {
|
|
171
|
+
const toEncrypt = crypto.newRandomKeySecret();
|
|
172
|
+
const encrypting = crypto.newRandomKeySecret();
|
|
173
|
+
const encryptingWrong = crypto.newRandomKeySecret();
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
const keys = {
|
|
176
|
+
toEncrypt,
|
|
177
|
+
encrypting,
|
|
178
|
+
};
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
const encrypted = crypto.encryptKeySecret(keys);
|
|
176
181
|
|
|
177
|
-
|
|
182
|
+
const decrypted = crypto.decryptKeySecret(
|
|
183
|
+
encrypted,
|
|
184
|
+
encryptingWrong.secret,
|
|
185
|
+
);
|
|
178
186
|
|
|
179
|
-
|
|
187
|
+
expect(decrypted).toBeUndefined();
|
|
188
|
+
});
|
|
180
189
|
});
|
package/src/tests/group.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { CoValueState } from "../coValueState.js";
|
|
2
3
|
import { RawCoList } from "../coValues/coList.js";
|
|
3
4
|
import { RawCoMap } from "../coValues/coMap.js";
|
|
4
5
|
import { RawCoStream } from "../coValues/coStream.js";
|
|
@@ -492,4 +493,120 @@ describe("writeOnly", () => {
|
|
|
492
493
|
// The writer role should be able to see the edits from the admin
|
|
493
494
|
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
494
495
|
});
|
|
496
|
+
|
|
497
|
+
test("upgrade to writer roles should work correctly", async () => {
|
|
498
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
499
|
+
|
|
500
|
+
const group = node1.node.createGroup();
|
|
501
|
+
group.addMember(
|
|
502
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
503
|
+
"writeOnly",
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
await group.core.waitForSync();
|
|
507
|
+
|
|
508
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
509
|
+
const map = groupOnNode2.createMap();
|
|
510
|
+
map.set("test", "Written from the writeOnly member");
|
|
511
|
+
|
|
512
|
+
await map.core.waitForSync();
|
|
513
|
+
|
|
514
|
+
group.addMember(
|
|
515
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
516
|
+
"writer",
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
group.core.waitForSync();
|
|
520
|
+
|
|
521
|
+
node2.node.coValuesStore.coValues.delete(map.id);
|
|
522
|
+
expect(node2.node.coValuesStore.get(map.id)).toEqual(
|
|
523
|
+
CoValueState.Unknown(map.id),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
527
|
+
|
|
528
|
+
// The writer role should be able to see the edits from the admin
|
|
529
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the writeOnly member");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("a user should be able to extend a group when his role on the parent group is writer", async () => {
|
|
533
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
534
|
+
|
|
535
|
+
const group = node1.node.createGroup();
|
|
536
|
+
group.addMember(
|
|
537
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
538
|
+
"writer",
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
await group.core.waitForSync();
|
|
542
|
+
|
|
543
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
544
|
+
|
|
545
|
+
const childGroup = node2.node.createGroup();
|
|
546
|
+
childGroup.extend(groupOnNode2);
|
|
547
|
+
|
|
548
|
+
const map = childGroup.createMap();
|
|
549
|
+
map.set("test", "Written from node2");
|
|
550
|
+
|
|
551
|
+
await map.core.waitForSync();
|
|
552
|
+
await childGroup.core.waitForSync();
|
|
553
|
+
|
|
554
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
555
|
+
|
|
556
|
+
expect(mapOnNode2.get("test")).toEqual("Written from node2");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("a user should be able to extend a group when his role on the parent group is reader", async () => {
|
|
560
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
561
|
+
|
|
562
|
+
const group = node1.node.createGroup();
|
|
563
|
+
group.addMember(
|
|
564
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
565
|
+
"reader",
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
await group.core.waitForSync();
|
|
569
|
+
|
|
570
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
571
|
+
|
|
572
|
+
const childGroup = node2.node.createGroup();
|
|
573
|
+
childGroup.extend(groupOnNode2);
|
|
574
|
+
|
|
575
|
+
const map = childGroup.createMap();
|
|
576
|
+
map.set("test", "Written from node2");
|
|
577
|
+
|
|
578
|
+
await map.core.waitForSync();
|
|
579
|
+
await childGroup.core.waitForSync();
|
|
580
|
+
|
|
581
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
582
|
+
|
|
583
|
+
expect(mapOnNode2.get("test")).toEqual("Written from node2");
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("a user should be able to extend a group when his role on the parent group is writeOnly", async () => {
|
|
587
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
588
|
+
|
|
589
|
+
const group = node1.node.createGroup();
|
|
590
|
+
group.addMember(
|
|
591
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
592
|
+
"writeOnly",
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
await group.core.waitForSync();
|
|
596
|
+
|
|
597
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
598
|
+
|
|
599
|
+
const childGroup = node2.node.createGroup();
|
|
600
|
+
childGroup.extend(groupOnNode2);
|
|
601
|
+
|
|
602
|
+
const map = childGroup.createMap();
|
|
603
|
+
map.set("test", "Written from node2");
|
|
604
|
+
|
|
605
|
+
await map.core.waitForSync();
|
|
606
|
+
await childGroup.core.waitForSync();
|
|
607
|
+
|
|
608
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
609
|
+
|
|
610
|
+
expect(mapOnNode2.get("test")).toEqual("Written from node2");
|
|
611
|
+
});
|
|
495
612
|
});
|
|
@@ -2157,21 +2157,17 @@ test("Admins can set child extensions when the admin role is inherited", async (
|
|
|
2157
2157
|
);
|
|
2158
2158
|
});
|
|
2159
2159
|
|
|
2160
|
-
test("Writers, readers and
|
|
2160
|
+
test("Writers, readers and writeOnly can set child extensions", () => {
|
|
2161
2161
|
const { group, node } = newGroupHighLevel();
|
|
2162
2162
|
const childGroup = node.createGroup();
|
|
2163
2163
|
|
|
2164
2164
|
const writer = node.createAccount();
|
|
2165
2165
|
const reader = node.createAccount();
|
|
2166
|
-
const
|
|
2167
|
-
const writerInvite = node.createAccount();
|
|
2168
|
-
const readerInvite = node.createAccount();
|
|
2166
|
+
const writeOnly = node.createAccount();
|
|
2169
2167
|
|
|
2170
2168
|
group.addMember(writer, "writer");
|
|
2171
2169
|
group.addMember(reader, "reader");
|
|
2172
|
-
group.addMember(
|
|
2173
|
-
group.addMember(writerInvite, "writerInvite");
|
|
2174
|
-
group.addMember(readerInvite, "readerInvite");
|
|
2170
|
+
group.addMember(writeOnly, "writeOnly");
|
|
2175
2171
|
|
|
2176
2172
|
const groupAsWriter = expectGroup(
|
|
2177
2173
|
group.core
|
|
@@ -2180,7 +2176,7 @@ test("Writers, readers and invitees can not set child extensions", () => {
|
|
|
2180
2176
|
);
|
|
2181
2177
|
|
|
2182
2178
|
groupAsWriter.set(`child_${childGroup.id}`, "extend", "trusting");
|
|
2183
|
-
expect(groupAsWriter.get(`child_${childGroup.id}`)).
|
|
2179
|
+
expect(groupAsWriter.get(`child_${childGroup.id}`)).toEqual("extend");
|
|
2184
2180
|
|
|
2185
2181
|
const groupAsReader = expectGroup(
|
|
2186
2182
|
group.core
|
|
@@ -2189,7 +2185,20 @@ test("Writers, readers and invitees can not set child extensions", () => {
|
|
|
2189
2185
|
);
|
|
2190
2186
|
|
|
2191
2187
|
groupAsReader.set(`child_${childGroup.id}`, "extend", "trusting");
|
|
2192
|
-
expect(groupAsReader.get(`child_${childGroup.id}`)).
|
|
2188
|
+
expect(groupAsReader.get(`child_${childGroup.id}`)).toEqual("extend");
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
test("Invitees can not set child extensions", () => {
|
|
2192
|
+
const { group, node } = newGroupHighLevel();
|
|
2193
|
+
const childGroup = node.createGroup();
|
|
2194
|
+
|
|
2195
|
+
const adminInvite = node.createAccount();
|
|
2196
|
+
const writerInvite = node.createAccount();
|
|
2197
|
+
const readerInvite = node.createAccount();
|
|
2198
|
+
|
|
2199
|
+
group.addMember(adminInvite, "adminInvite");
|
|
2200
|
+
group.addMember(writerInvite, "writerInvite");
|
|
2201
|
+
group.addMember(readerInvite, "readerInvite");
|
|
2193
2202
|
|
|
2194
2203
|
const groupAsAdminInvite = expectGroup(
|
|
2195
2204
|
group.core
|