cojson 0.9.19 → 0.10.0
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 +19 -0
- package/dist/native/coValueState.js +6 -4
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/coValues/group.js +26 -23
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/crypto/WasmCrypto.js +1 -16
- package/dist/native/crypto/WasmCrypto.js.map +1 -1
- package/dist/native/exports.js +2 -0
- package/dist/native/exports.js.map +1 -1
- package/dist/native/permissions.js +10 -5
- package/dist/native/permissions.js.map +1 -1
- package/dist/web/coValueState.js +6 -4
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/coValues/group.js +26 -23
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/crypto/WasmCrypto.js +1 -16
- package/dist/web/crypto/WasmCrypto.js.map +1 -1
- package/dist/web/exports.js +2 -0
- package/dist/web/exports.js.map +1 -1
- package/dist/web/permissions.js +10 -5
- package/dist/web/permissions.js.map +1 -1
- package/package.json +3 -4
- package/src/coValueState.ts +6 -4
- package/src/coValues/group.ts +45 -31
- package/src/crypto/WasmCrypto.ts +3 -17
- package/src/exports.ts +2 -0
- package/src/permissions.ts +15 -3
- package/src/tests/SyncStateManager.test.ts +41 -91
- package/src/tests/coValueState.test.ts +44 -22
- package/src/tests/group.test.ts +230 -27
- package/src/tests/permissions.test.ts +59 -1
- package/src/tests/sync.test.ts +257 -325
- package/src/tests/testUtils.ts +112 -19
package/src/tests/group.test.ts
CHANGED
|
@@ -467,33 +467,6 @@ describe("writeOnly", () => {
|
|
|
467
467
|
expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
|
|
468
468
|
});
|
|
469
469
|
|
|
470
|
-
test("inherited writer roles should work correctly", async () => {
|
|
471
|
-
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
472
|
-
|
|
473
|
-
const group = node1.node.createGroup();
|
|
474
|
-
group.addMember(
|
|
475
|
-
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
476
|
-
"writer",
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
const childGroup = node1.node.createGroup();
|
|
480
|
-
childGroup.extend(group);
|
|
481
|
-
childGroup.addMember(
|
|
482
|
-
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
483
|
-
"writeOnly",
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
const map = childGroup.createMap();
|
|
487
|
-
map.set("test", "Written from the admin");
|
|
488
|
-
|
|
489
|
-
await map.core.waitForSync();
|
|
490
|
-
|
|
491
|
-
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
492
|
-
|
|
493
|
-
// The writer role should be able to see the edits from the admin
|
|
494
|
-
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
495
|
-
});
|
|
496
|
-
|
|
497
470
|
test("upgrade to writer roles should work correctly", async () => {
|
|
498
471
|
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
499
472
|
|
|
@@ -528,6 +501,35 @@ describe("writeOnly", () => {
|
|
|
528
501
|
// The writer role should be able to see the edits from the admin
|
|
529
502
|
expect(mapOnNode2.get("test")).toEqual("Written from the writeOnly member");
|
|
530
503
|
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe("extend", () => {
|
|
507
|
+
test("inherited writer roles should work correctly", async () => {
|
|
508
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
509
|
+
|
|
510
|
+
const group = node1.node.createGroup();
|
|
511
|
+
group.addMember(
|
|
512
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
513
|
+
"writer",
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
const childGroup = node1.node.createGroup();
|
|
517
|
+
childGroup.extend(group);
|
|
518
|
+
childGroup.addMember(
|
|
519
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
520
|
+
"writeOnly",
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
const map = childGroup.createMap();
|
|
524
|
+
map.set("test", "Written from the admin");
|
|
525
|
+
|
|
526
|
+
await map.core.waitForSync();
|
|
527
|
+
|
|
528
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
529
|
+
|
|
530
|
+
// The writer role should be able to see the edits from the admin
|
|
531
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
532
|
+
});
|
|
531
533
|
|
|
532
534
|
test("a user should be able to extend a group when his role on the parent group is writer", async () => {
|
|
533
535
|
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
@@ -638,4 +640,205 @@ describe("writeOnly", () => {
|
|
|
638
640
|
|
|
639
641
|
expect(map.get("test")).toEqual("Hello!");
|
|
640
642
|
});
|
|
643
|
+
|
|
644
|
+
test("a writerInvite role should not be inherited", async () => {
|
|
645
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
646
|
+
|
|
647
|
+
const group = node1.node.createGroup();
|
|
648
|
+
group.addMember(
|
|
649
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
650
|
+
"writerInvite",
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const childGroup = node1.node.createGroup();
|
|
654
|
+
childGroup.extend(group);
|
|
655
|
+
|
|
656
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
describe("extend with role mapping", () => {
|
|
661
|
+
test("mapping to writer should add the ability to write", async () => {
|
|
662
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
663
|
+
|
|
664
|
+
const group = node1.node.createGroup();
|
|
665
|
+
group.addMember(
|
|
666
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
667
|
+
"reader",
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
const childGroup = node1.node.createGroup();
|
|
671
|
+
childGroup.extend(group, "writer");
|
|
672
|
+
|
|
673
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
|
|
674
|
+
|
|
675
|
+
const map = childGroup.createMap();
|
|
676
|
+
map.set("test", "Written from the admin");
|
|
677
|
+
|
|
678
|
+
await map.core.waitForSync();
|
|
679
|
+
|
|
680
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
681
|
+
|
|
682
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
683
|
+
|
|
684
|
+
mapOnNode2.set("test", "Written from the inherited role");
|
|
685
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");
|
|
686
|
+
|
|
687
|
+
await mapOnNode2.core.waitForSync();
|
|
688
|
+
|
|
689
|
+
expect(map.get("test")).toEqual("Written from the inherited role");
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("mapping to reader should remove the ability to write", async () => {
|
|
693
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
694
|
+
|
|
695
|
+
const group = node1.node.createGroup();
|
|
696
|
+
group.addMember(
|
|
697
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
698
|
+
"writer",
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const childGroup = node1.node.createGroup();
|
|
702
|
+
childGroup.extend(group, "reader");
|
|
703
|
+
|
|
704
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("reader");
|
|
705
|
+
|
|
706
|
+
const map = childGroup.createMap();
|
|
707
|
+
map.set("test", "Written from the admin");
|
|
708
|
+
|
|
709
|
+
await map.core.waitForSync();
|
|
710
|
+
|
|
711
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
712
|
+
|
|
713
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
714
|
+
|
|
715
|
+
mapOnNode2.set("test", "Should not be visible");
|
|
716
|
+
|
|
717
|
+
await mapOnNode2.core.waitForSync();
|
|
718
|
+
|
|
719
|
+
expect(map.get("test")).toEqual("Written from the admin");
|
|
720
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test("mapping to admin should add the ability to add members", async () => {
|
|
724
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
725
|
+
"server",
|
|
726
|
+
"server",
|
|
727
|
+
"server",
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
const group = node1.node.createGroup();
|
|
731
|
+
group.addMember(
|
|
732
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
733
|
+
"reader",
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const childGroup = node1.node.createGroup();
|
|
737
|
+
childGroup.extend(group, "admin");
|
|
738
|
+
|
|
739
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("admin");
|
|
740
|
+
|
|
741
|
+
await childGroup.core.waitForSync();
|
|
742
|
+
|
|
743
|
+
const childGroupOnNode2 = await loadCoValueOrFail(
|
|
744
|
+
node2.node,
|
|
745
|
+
childGroup.id,
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
childGroupOnNode2.addMember(
|
|
749
|
+
await loadCoValueOrFail(node2.node, node3.accountID),
|
|
750
|
+
"reader",
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
expect(childGroupOnNode2.roleOf(node3.accountID)).toEqual("reader");
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
test("mapping to reader should remove the ability to add members", async () => {
|
|
757
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
758
|
+
"server",
|
|
759
|
+
"server",
|
|
760
|
+
"server",
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
const group = node1.node.createGroup();
|
|
764
|
+
group.addMember(
|
|
765
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
766
|
+
"admin",
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
const childGroup = node1.node.createGroup();
|
|
770
|
+
childGroup.extend(group, "reader");
|
|
771
|
+
|
|
772
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual("reader");
|
|
773
|
+
|
|
774
|
+
await childGroup.core.waitForSync();
|
|
775
|
+
|
|
776
|
+
const childGroupOnNode2 = await loadCoValueOrFail(
|
|
777
|
+
node2.node,
|
|
778
|
+
childGroup.id,
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
const accountToAdd = await loadCoValueOrFail(node2.node, node3.accountID);
|
|
782
|
+
|
|
783
|
+
expect(() => {
|
|
784
|
+
childGroupOnNode2.addMember(accountToAdd, "reader");
|
|
785
|
+
}).toThrow();
|
|
786
|
+
|
|
787
|
+
expect(childGroupOnNode2.roleOf(node3.accountID)).toEqual(undefined);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("non-inheritable roles should not give access to the child group when role mapping is used", async () => {
|
|
791
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
792
|
+
|
|
793
|
+
const group = node1.node.createGroup();
|
|
794
|
+
group.addMember(
|
|
795
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
796
|
+
"writeOnly",
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
const childGroup = node1.node.createGroup();
|
|
800
|
+
childGroup.extend(group, "reader");
|
|
801
|
+
|
|
802
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
|
|
803
|
+
|
|
804
|
+
const map = childGroup.createMap();
|
|
805
|
+
map.set("test", "Written from the admin");
|
|
806
|
+
|
|
807
|
+
await map.core.waitForSync();
|
|
808
|
+
|
|
809
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
810
|
+
|
|
811
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("invite roles should not give write access to the child group when role mapping is used", async () => {
|
|
815
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
816
|
+
|
|
817
|
+
const group = node1.node.createGroup();
|
|
818
|
+
group.addMember(
|
|
819
|
+
await loadCoValueOrFail(node1.node, node2.accountID),
|
|
820
|
+
"writerInvite",
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
const childGroup = node1.node.createGroup();
|
|
824
|
+
childGroup.extend(group, "writer");
|
|
825
|
+
|
|
826
|
+
expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
|
|
827
|
+
|
|
828
|
+
const map = childGroup.createMap();
|
|
829
|
+
map.set("test", "Written from the admin");
|
|
830
|
+
|
|
831
|
+
await map.core.waitForSync();
|
|
832
|
+
|
|
833
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
834
|
+
|
|
835
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin"); // The invite roles have access to the readKey hence can read the values on inherited groups
|
|
836
|
+
|
|
837
|
+
mapOnNode2.set("test", "Should not be visible");
|
|
838
|
+
|
|
839
|
+
await mapOnNode2.core.waitForSync();
|
|
840
|
+
|
|
841
|
+
expect(map.get("test")).toEqual("Written from the admin");
|
|
842
|
+
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
|
|
843
|
+
});
|
|
641
844
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
1
|
+
import { expect, test, vi } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
3
|
import { ControlledAgent } from "../coValues/account.js";
|
|
4
4
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
@@ -2908,3 +2908,61 @@ test("extend cycles should not break the keys rotation", () => {
|
|
|
2908
2908
|
|
|
2909
2909
|
expect(map.get("test")).toEqual("Hello!");
|
|
2910
2910
|
});
|
|
2911
|
+
|
|
2912
|
+
test("Admin can remove themselves from a group", async () => {
|
|
2913
|
+
const warnSpy = vi.spyOn(console, "warn");
|
|
2914
|
+
const { group, admin } = newGroupHighLevel();
|
|
2915
|
+
|
|
2916
|
+
// Admin removes themselves
|
|
2917
|
+
await group.removeMember(admin);
|
|
2918
|
+
|
|
2919
|
+
expect(group.myRole()).toBeUndefined();
|
|
2920
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
2921
|
+
});
|
|
2922
|
+
|
|
2923
|
+
test("Can revoke read permission from 'everyone'", async () => {
|
|
2924
|
+
const { group } = newGroupHighLevel();
|
|
2925
|
+
const childObject = group.createMap();
|
|
2926
|
+
|
|
2927
|
+
// Give everyone read access
|
|
2928
|
+
group.addMember("everyone", "reader");
|
|
2929
|
+
|
|
2930
|
+
childObject.set("foo", "bar", "private");
|
|
2931
|
+
expect(childObject.get("foo")).toEqual("bar");
|
|
2932
|
+
|
|
2933
|
+
// Create a new account to verify access
|
|
2934
|
+
const newAccount = new ControlledAgent(Crypto.newRandomAgentSecret(), Crypto);
|
|
2935
|
+
const childContent = expectMap(
|
|
2936
|
+
childObject.core
|
|
2937
|
+
.testWithDifferentAccount(
|
|
2938
|
+
newAccount,
|
|
2939
|
+
Crypto.newRandomSessionID(newAccount.currentAgentID()._unsafeUnwrap()),
|
|
2940
|
+
)
|
|
2941
|
+
.getCurrentContent(),
|
|
2942
|
+
);
|
|
2943
|
+
|
|
2944
|
+
// Verify the new account can read
|
|
2945
|
+
expect(childContent.get("foo")).toEqual("bar");
|
|
2946
|
+
|
|
2947
|
+
// Revoke everyone's access
|
|
2948
|
+
await group.removeMember("everyone");
|
|
2949
|
+
|
|
2950
|
+
childObject.set("foo", "updated after revoke", "private");
|
|
2951
|
+
|
|
2952
|
+
// Create another new account to verify access is revoked
|
|
2953
|
+
const newAccount2 = new ControlledAgent(
|
|
2954
|
+
Crypto.newRandomAgentSecret(),
|
|
2955
|
+
Crypto,
|
|
2956
|
+
);
|
|
2957
|
+
const childContent2 = expectMap(
|
|
2958
|
+
childObject.core
|
|
2959
|
+
.testWithDifferentAccount(
|
|
2960
|
+
newAccount2,
|
|
2961
|
+
Crypto.newRandomSessionID(newAccount2.currentAgentID()._unsafeUnwrap()),
|
|
2962
|
+
)
|
|
2963
|
+
.getCurrentContent(),
|
|
2964
|
+
);
|
|
2965
|
+
|
|
2966
|
+
// Verify the new account cannot read after revocation
|
|
2967
|
+
expect(childContent2.get("foo")).toEqual("bar");
|
|
2968
|
+
});
|