cojson 0.17.10 → 0.17.11
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 +11 -0
- package/dist/coValueCore/SessionMap.d.ts +3 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +9 -4
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +16 -8
- 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 +1 -1
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +6 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts +2 -2
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +3 -0
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +1 -1
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +1 -1
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/permissions.d.ts +17 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +55 -49
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.js +15 -1
- package/dist/tests/PureJSCrypto.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/coValueCore.test.js +2 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/group.addMember.test.js +6 -11
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +40 -1
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.test.js +1 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/package.json +2 -2
- package/src/coValueCore/SessionMap.ts +4 -5
- package/src/coValueCore/coValueCore.ts +42 -32
- package/src/coValueCore/verifiedState.ts +1 -3
- package/src/coValues/group.ts +10 -2
- package/src/crypto/PureJSCrypto.ts +6 -2
- package/src/crypto/WasmCrypto.ts +1 -1
- package/src/crypto/crypto.ts +1 -1
- package/src/permissions.ts +17 -1
- package/src/sync.ts +63 -59
- package/src/tests/PureJSCrypto.test.ts +25 -2
- package/src/tests/WasmCrypto.test.ts +0 -2
- package/src/tests/coValueCore.test.ts +0 -4
- package/src/tests/group.addMember.test.ts +69 -63
- package/src/tests/sync.load.test.ts +52 -0
- package/src/tests/sync.test.ts +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
2
|
-
import { Result, err } from "neverthrow";
|
|
2
|
+
import { Result, err, ok } from "neverthrow";
|
|
3
3
|
import type { PeerState } from "../PeerState.js";
|
|
4
4
|
import type { RawCoValue } from "../coValue.js";
|
|
5
5
|
import type { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
@@ -431,43 +431,46 @@ export class CoValueCore {
|
|
|
431
431
|
tryAddTransactions(
|
|
432
432
|
sessionID: SessionID,
|
|
433
433
|
newTransactions: Transaction[],
|
|
434
|
-
givenExpectedNewHash: Hash | undefined,
|
|
435
434
|
newSignature: Signature,
|
|
436
|
-
notifyMode: "immediate" | "deferred",
|
|
437
435
|
skipVerify: boolean = false,
|
|
438
|
-
givenNewStreamingHash?: StreamingHash,
|
|
439
436
|
): Result<true, TryAddTransactionsError> {
|
|
440
|
-
|
|
441
|
-
.resolveAccountAgent(
|
|
442
|
-
accountOrAgentIDfromSessionID(sessionID),
|
|
443
|
-
"Expected to know signer of transaction",
|
|
444
|
-
)
|
|
445
|
-
.andThen((agent) => {
|
|
446
|
-
if (!this.verified) {
|
|
447
|
-
return err({
|
|
448
|
-
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
449
|
-
id: this.id,
|
|
450
|
-
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
451
|
-
}
|
|
437
|
+
let result: Result<SignerID | undefined, TryAddTransactionsError>;
|
|
452
438
|
|
|
453
|
-
|
|
439
|
+
if (skipVerify) {
|
|
440
|
+
result = ok(undefined);
|
|
441
|
+
} else {
|
|
442
|
+
result = this.node
|
|
443
|
+
.resolveAccountAgent(
|
|
444
|
+
accountOrAgentIDfromSessionID(sessionID),
|
|
445
|
+
"Expected to know signer of transaction",
|
|
446
|
+
)
|
|
447
|
+
.andThen((agent) => {
|
|
448
|
+
return ok(this.crypto.getAgentSignerID(agent));
|
|
449
|
+
});
|
|
450
|
+
}
|
|
454
451
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
givenNewStreamingHash,
|
|
463
|
-
);
|
|
452
|
+
return result.andThen((signerID) => {
|
|
453
|
+
if (!this.verified) {
|
|
454
|
+
return err({
|
|
455
|
+
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
456
|
+
id: this.id,
|
|
457
|
+
} satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
|
|
458
|
+
}
|
|
464
459
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
460
|
+
const result = this.verified.tryAddTransactions(
|
|
461
|
+
sessionID,
|
|
462
|
+
signerID,
|
|
463
|
+
newTransactions,
|
|
464
|
+
newSignature,
|
|
465
|
+
skipVerify,
|
|
466
|
+
);
|
|
468
467
|
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
if (result.isOk()) {
|
|
469
|
+
this.updateContentAndNotifyUpdate("immediate");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return result;
|
|
473
|
+
});
|
|
471
474
|
}
|
|
472
475
|
|
|
473
476
|
deferredUpdates = 0;
|
|
@@ -973,7 +976,7 @@ export type InvalidSignatureError = {
|
|
|
973
976
|
id: RawCoID;
|
|
974
977
|
newSignature: Signature;
|
|
975
978
|
sessionID: SessionID;
|
|
976
|
-
signerID: SignerID;
|
|
979
|
+
signerID: SignerID | undefined;
|
|
977
980
|
};
|
|
978
981
|
|
|
979
982
|
export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
@@ -981,8 +984,15 @@ export type TriedToAddTransactionsWithoutVerifiedStateErrpr = {
|
|
|
981
984
|
id: RawCoID;
|
|
982
985
|
};
|
|
983
986
|
|
|
987
|
+
export type TriedToAddTransactionsWithoutSignerIDError = {
|
|
988
|
+
type: "TriedToAddTransactionsWithoutSignerID";
|
|
989
|
+
id: RawCoID;
|
|
990
|
+
sessionID: SessionID;
|
|
991
|
+
};
|
|
992
|
+
|
|
984
993
|
export type TryAddTransactionsError =
|
|
985
994
|
| TriedToAddTransactionsWithoutVerifiedStateErrpr
|
|
995
|
+
| TriedToAddTransactionsWithoutSignerIDError
|
|
986
996
|
| ResolveAccountAgentError
|
|
987
997
|
| InvalidHashError
|
|
988
998
|
| InvalidSignatureError;
|
|
@@ -89,12 +89,10 @@ export class VerifiedState {
|
|
|
89
89
|
|
|
90
90
|
tryAddTransactions(
|
|
91
91
|
sessionID: SessionID,
|
|
92
|
-
signerID: SignerID,
|
|
92
|
+
signerID: SignerID | undefined,
|
|
93
93
|
newTransactions: Transaction[],
|
|
94
|
-
givenExpectedNewHash: Hash | undefined,
|
|
95
94
|
newSignature: Signature,
|
|
96
95
|
skipVerify: boolean = false,
|
|
97
|
-
givenNewStreamingHash?: StreamingHash,
|
|
98
96
|
): Result<true, TryAddTransactionsError> {
|
|
99
97
|
const result = this.sessions.addTransaction(
|
|
100
98
|
sessionID,
|
package/src/coValues/group.ts
CHANGED
|
@@ -370,16 +370,24 @@ export class RawGroup<
|
|
|
370
370
|
if (role === "writeOnly" || role === "writeOnlyInvite") {
|
|
371
371
|
const previousRole = this.get(memberKey);
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
if (
|
|
374
|
+
previousRole === "admin" &&
|
|
375
|
+
memberKey !== this.core.node.getCurrentAgent().id
|
|
376
|
+
) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
"Administrators cannot demote other administrators in a group",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
374
381
|
|
|
375
382
|
if (
|
|
376
383
|
previousRole === "reader" ||
|
|
377
384
|
previousRole === "writer" ||
|
|
378
385
|
previousRole === "admin"
|
|
379
386
|
) {
|
|
380
|
-
this.rotateReadKey();
|
|
387
|
+
this.rotateReadKey(memberKey);
|
|
381
388
|
}
|
|
382
389
|
|
|
390
|
+
this.set(memberKey, role, "trusting");
|
|
383
391
|
this.internalCreateWriteOnlyKeyForMember(memberKey, agent);
|
|
384
392
|
} else {
|
|
385
393
|
const currentReadKey = this.getCurrentReadKey();
|
|
@@ -212,7 +212,7 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
212
212
|
createSessionLog(
|
|
213
213
|
coID: RawCoID,
|
|
214
214
|
sessionID: SessionID,
|
|
215
|
-
signerID
|
|
215
|
+
signerID?: SignerID,
|
|
216
216
|
): SessionLogImpl {
|
|
217
217
|
return new PureJSSessionLog(coID, sessionID, signerID, this);
|
|
218
218
|
}
|
|
@@ -226,7 +226,7 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
226
226
|
constructor(
|
|
227
227
|
private readonly coID: RawCoID,
|
|
228
228
|
private readonly sessionID: SessionID,
|
|
229
|
-
private readonly signerID: SignerID,
|
|
229
|
+
private readonly signerID: SignerID | undefined,
|
|
230
230
|
private readonly crypto: PureJSCrypto,
|
|
231
231
|
) {
|
|
232
232
|
this.streamingHash = this.crypto.emptyBlake3State();
|
|
@@ -263,6 +263,10 @@ export class PureJSSessionLog implements SessionLogImpl {
|
|
|
263
263
|
skipVerify: boolean,
|
|
264
264
|
) {
|
|
265
265
|
if (!skipVerify) {
|
|
266
|
+
if (!this.signerID) {
|
|
267
|
+
throw new Error("Tried to add transactions without signer ID");
|
|
268
|
+
}
|
|
269
|
+
|
|
266
270
|
const checkHasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
267
271
|
|
|
268
272
|
for (const tx of transactions) {
|
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -205,7 +205,7 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID
|
|
208
|
+
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID?: SignerID) {
|
|
209
209
|
return new SessionLogAdapter(new SessionLog(coID, sessionID, signerID));
|
|
210
210
|
}
|
|
211
211
|
}
|
package/src/crypto/crypto.ts
CHANGED
package/src/permissions.ts
CHANGED
|
@@ -31,7 +31,23 @@ export type PermissionsDef =
|
|
|
31
31
|
| { type: "ownedByGroup"; group: RawCoID }
|
|
32
32
|
| { type: "unsafeAllowAll" };
|
|
33
33
|
|
|
34
|
-
export type AccountRole =
|
|
34
|
+
export type AccountRole =
|
|
35
|
+
/**
|
|
36
|
+
* Can read the group's CoValues
|
|
37
|
+
*/
|
|
38
|
+
| "reader"
|
|
39
|
+
/**
|
|
40
|
+
* Can read and write to the group's CoValues
|
|
41
|
+
*/
|
|
42
|
+
| "writer"
|
|
43
|
+
/**
|
|
44
|
+
* Can read and write to the group, and change group member roles
|
|
45
|
+
*/
|
|
46
|
+
| "admin"
|
|
47
|
+
/**
|
|
48
|
+
* Can only write to the group's CoValues and read their own changes
|
|
49
|
+
*/
|
|
50
|
+
| "writeOnly";
|
|
35
51
|
|
|
36
52
|
export type Role =
|
|
37
53
|
| AccountRole
|
package/src/sync.ts
CHANGED
|
@@ -511,28 +511,31 @@ export class SyncManager {
|
|
|
511
511
|
(content) => content.newTransactions,
|
|
512
512
|
);
|
|
513
513
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
514
|
+
// If we'll be performing transaction verification, ensure all the dependencies available.
|
|
515
|
+
if (!this.skipVerify) {
|
|
516
|
+
for (const dependency of getDependedOnCoValuesFromRawData(
|
|
517
|
+
msg.id,
|
|
518
|
+
msg.header,
|
|
519
|
+
sessionIDs,
|
|
520
|
+
transactions,
|
|
521
|
+
)) {
|
|
522
|
+
const dependencyCoValue = this.local.getCoValue(dependency);
|
|
523
|
+
|
|
524
|
+
if (!dependencyCoValue.hasVerifiedContent()) {
|
|
525
|
+
coValue.markMissingDependency(dependency);
|
|
521
526
|
|
|
522
|
-
|
|
523
|
-
coValue.markMissingDependency(dependency);
|
|
527
|
+
const peers = this.getServerPeers();
|
|
524
528
|
|
|
525
|
-
|
|
529
|
+
// if the peer that sent the content is a client, we add it to the list of peers
|
|
530
|
+
// to also ask them for the dependency
|
|
531
|
+
if (peer?.role === "client") {
|
|
532
|
+
peers.push(peer);
|
|
533
|
+
}
|
|
526
534
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
peers.push(peer);
|
|
535
|
+
dependencyCoValue.load(peers);
|
|
536
|
+
} else if (!dependencyCoValue.isAvailable()) {
|
|
537
|
+
coValue.markMissingDependency(dependency);
|
|
531
538
|
}
|
|
532
|
-
|
|
533
|
-
dependencyCoValue.load(peers);
|
|
534
|
-
} else if (!dependencyCoValue.isAvailable()) {
|
|
535
|
-
coValue.markMissingDependency(dependency);
|
|
536
539
|
}
|
|
537
540
|
}
|
|
538
541
|
|
|
@@ -592,60 +595,61 @@ export class SyncManager {
|
|
|
592
595
|
continue;
|
|
593
596
|
}
|
|
594
597
|
|
|
595
|
-
|
|
598
|
+
// If we'll be performing transaction verification, ensure the account is available.
|
|
599
|
+
if (!this.skipVerify) {
|
|
600
|
+
const accountId = accountOrAgentIDfromSessionID(sessionID);
|
|
596
601
|
|
|
597
|
-
|
|
598
|
-
|
|
602
|
+
if (isAccountID(accountId)) {
|
|
603
|
+
const account = this.local.getCoValue(accountId);
|
|
599
604
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
605
|
+
// We can't verify the transaction without the account, so we delay the session content handling until the account is available
|
|
606
|
+
if (!account.isAvailable()) {
|
|
607
|
+
// This covers the case where we are getting a new session on an already loaded coValue
|
|
608
|
+
// where we need to load the account to get their public key
|
|
609
|
+
if (!coValue.missingDependencies.has(accountId)) {
|
|
610
|
+
const peers = this.getServerPeers();
|
|
606
611
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
if (peer?.role === "client") {
|
|
613
|
+
// if the peer that sent the content is a client, we add it to the list of peers
|
|
614
|
+
// to also ask them for the dependency
|
|
615
|
+
peers.push(peer);
|
|
616
|
+
}
|
|
612
617
|
|
|
613
|
-
|
|
614
|
-
|
|
618
|
+
account.load(peers);
|
|
619
|
+
}
|
|
615
620
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
621
|
+
// We need to wait for the account to be available before we can verify the transaction
|
|
622
|
+
// Currently doing this by delaying the handleNewContent for the session to when we have the account
|
|
623
|
+
//
|
|
624
|
+
// This is not the best solution, because the knownState is not updated and the ACK response will be given
|
|
625
|
+
// by excluding the session.
|
|
626
|
+
// This is good enough implementation for now because the only case for the account to be missing are out-of-order
|
|
627
|
+
// dependencies push, so the gap should be short lived.
|
|
628
|
+
//
|
|
629
|
+
// When we are going to have sharded-peers we should revisit this, and store unverified sessions that are considered as part of the
|
|
630
|
+
// knwonState, but not actively used until they can be verified.
|
|
631
|
+
void account.waitForAvailable().then(() => {
|
|
632
|
+
this.handleNewContent(
|
|
633
|
+
{
|
|
634
|
+
action: "content",
|
|
635
|
+
id: coValue.id,
|
|
636
|
+
new: {
|
|
637
|
+
[sessionID]: newContentForSession,
|
|
638
|
+
},
|
|
639
|
+
priority: msg.priority,
|
|
633
640
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
continue;
|
|
641
|
+
from,
|
|
642
|
+
);
|
|
643
|
+
});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
640
646
|
}
|
|
641
647
|
}
|
|
642
648
|
|
|
643
649
|
const result = coValue.tryAddTransactions(
|
|
644
650
|
sessionID,
|
|
645
651
|
newTransactions,
|
|
646
|
-
undefined,
|
|
647
652
|
newContentForSession.lastSignature,
|
|
648
|
-
"immediate",
|
|
649
653
|
this.skipVerify,
|
|
650
654
|
);
|
|
651
655
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
setCurrentTestCryptoProvider,
|
|
5
5
|
setupTestNode,
|
|
6
6
|
setupTestAccount,
|
|
7
|
+
randomAgentAndSessionID,
|
|
7
8
|
} from "./testUtils";
|
|
8
9
|
import { PureJSCrypto } from "../crypto/PureJSCrypto";
|
|
9
10
|
import { stableStringify } from "../jsonStringify";
|
|
@@ -113,9 +114,7 @@ describe("PureJSCrypto", () => {
|
|
|
113
114
|
madeAt: Date.now(),
|
|
114
115
|
},
|
|
115
116
|
],
|
|
116
|
-
"hash_z12345678",
|
|
117
117
|
"signature_z12345678",
|
|
118
|
-
"immediate",
|
|
119
118
|
true,
|
|
120
119
|
);
|
|
121
120
|
|
|
@@ -128,3 +127,27 @@ describe("PureJSCrypto", () => {
|
|
|
128
127
|
expect(map.get("count")).toEqual(0);
|
|
129
128
|
});
|
|
130
129
|
});
|
|
130
|
+
|
|
131
|
+
describe("PureJSSessionLog", () => {
|
|
132
|
+
it("fails to verify signatures without a signer ID", async () => {
|
|
133
|
+
const agentSecret = jsCrypto.newRandomAgentSecret();
|
|
134
|
+
const sessionID = jsCrypto.newRandomSessionID(
|
|
135
|
+
jsCrypto.getAgentID(agentSecret),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const sessionLog = jsCrypto.createSessionLog("co_z12345678", sessionID);
|
|
139
|
+
expect(() =>
|
|
140
|
+
sessionLog.tryAdd(
|
|
141
|
+
[
|
|
142
|
+
{
|
|
143
|
+
privacy: "trusting",
|
|
144
|
+
changes: stableStringify([{ op: "set", key: "count", value: 1 }]),
|
|
145
|
+
madeAt: Date.now(),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
"signature_z12345678",
|
|
149
|
+
false,
|
|
150
|
+
),
|
|
151
|
+
).toThrow("Tried to add transactions without signer ID");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -76,9 +76,7 @@ test("transactions with wrong signature are rejected", () => {
|
|
|
76
76
|
const result = newEntry.tryAddTransactions(
|
|
77
77
|
node.currentSessionID,
|
|
78
78
|
[transaction],
|
|
79
|
-
undefined,
|
|
80
79
|
signature,
|
|
81
|
-
"immediate",
|
|
82
80
|
);
|
|
83
81
|
|
|
84
82
|
expect(result.isErr()).toBe(true);
|
|
@@ -301,9 +299,7 @@ test("getValidTransactions should skip private transactions with invalid JSON",
|
|
|
301
299
|
.tryAddTransactions(
|
|
302
300
|
fixtures.session,
|
|
303
301
|
[fixtures.transaction],
|
|
304
|
-
undefined,
|
|
305
302
|
fixtures.signature,
|
|
306
|
-
"immediate",
|
|
307
303
|
)
|
|
308
304
|
._unsafeUnwrap();
|
|
309
305
|
|
|
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, test } from "vitest";
|
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
3
|
import {
|
|
4
4
|
SyncMessagesLog,
|
|
5
|
-
TEST_NODE_CONFIG,
|
|
6
5
|
loadCoValueOrFail,
|
|
7
6
|
setupTestAccount,
|
|
8
7
|
setupTestNode,
|
|
@@ -220,68 +219,75 @@ describe("Group.addMember", () => {
|
|
|
220
219
|
expect(personOnReaderNode.get("name")).toEqual(undefined);
|
|
221
220
|
});
|
|
222
221
|
|
|
223
|
-
test("
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
222
|
+
test.each(["writer", "reader", "writeOnly"] as const)(
|
|
223
|
+
"an admin should not be able to downgrade an admin to %s",
|
|
224
|
+
async (targetRole) => {
|
|
225
|
+
const admin = await setupTestAccount({
|
|
226
|
+
connected: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const otherAdmin = await setupTestAccount({
|
|
230
|
+
connected: true,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const group = admin.node.createGroup();
|
|
234
|
+
const person = group.createMap({
|
|
235
|
+
name: "John Doe",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const otherAdminOnAdminNode = await loadCoValueOrFail(
|
|
239
|
+
admin.node,
|
|
240
|
+
otherAdmin.accountID,
|
|
241
|
+
);
|
|
242
|
+
group.addMember(otherAdminOnAdminNode, "admin");
|
|
243
|
+
|
|
244
|
+
// Try to downgrade other admin
|
|
245
|
+
expect(() => group.addMember(otherAdminOnAdminNode, targetRole)).toThrow(
|
|
246
|
+
"Administrators cannot demote other administrators in a group",
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expect(group.roleOf(otherAdmin.accountID)).toEqual("admin");
|
|
250
|
+
|
|
251
|
+
// Verify other admin still has admin access by adding a new member
|
|
252
|
+
const reader = await setupTestAccount({
|
|
253
|
+
connected: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const readerOnOtherAdminNode = await loadCoValueOrFail(
|
|
257
|
+
otherAdmin.node,
|
|
258
|
+
reader.accountID,
|
|
259
|
+
);
|
|
260
|
+
group.addMember(readerOnOtherAdminNode, "reader");
|
|
261
|
+
|
|
262
|
+
const personOnReaderNode = await loadCoValueOrFail(
|
|
263
|
+
reader.node,
|
|
264
|
+
person.id,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
expect(
|
|
269
|
+
expectMap(personOnReaderNode.core.getCurrentContent()).get("name"),
|
|
270
|
+
).toEqual("John Doe");
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
test.each(["writer", "reader", "writeOnly"] as const)(
|
|
276
|
+
"an admin should be able downgrade themselves to %s",
|
|
277
|
+
async (targetRole) => {
|
|
278
|
+
const admin = await setupTestAccount({
|
|
279
|
+
connected: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const group = admin.node.createGroup();
|
|
283
|
+
|
|
284
|
+
const account = await loadCoValueOrFail(admin.node, admin.accountID);
|
|
285
|
+
|
|
286
|
+
// Downgrade self to target role
|
|
287
|
+
group.addMember(account, targetRole);
|
|
288
|
+
expect(group.roleOf(admin.accountID)).toEqual(targetRole);
|
|
289
|
+
},
|
|
290
|
+
);
|
|
285
291
|
|
|
286
292
|
test("an admin should be able downgrade a writeOnly to reader", async () => {
|
|
287
293
|
const admin = await setupTestAccount({
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
SyncMessagesLog,
|
|
10
10
|
TEST_NODE_CONFIG,
|
|
11
11
|
blockMessageTypeOnOutgoingPeer,
|
|
12
|
+
getSyncServerConnectedPeer,
|
|
12
13
|
loadCoValueOrFail,
|
|
13
14
|
setupTestAccount,
|
|
14
15
|
setupTestNode,
|
|
@@ -1034,4 +1035,55 @@ describe("loading coValues from server", () => {
|
|
|
1034
1035
|
|
|
1035
1036
|
vi.useRealTimers();
|
|
1036
1037
|
});
|
|
1038
|
+
|
|
1039
|
+
test("should not request dependencies if transaction verification is disabled", async () => {
|
|
1040
|
+
// Create a disconnected client
|
|
1041
|
+
const { node: client, accountID } = await setupTestAccount({
|
|
1042
|
+
connected: false,
|
|
1043
|
+
});
|
|
1044
|
+
const account = client.expectCurrentAccount(accountID);
|
|
1045
|
+
|
|
1046
|
+
// Prepare a group -- this will be a non-account dependency of a forthcoming map.
|
|
1047
|
+
const group = client.createGroup();
|
|
1048
|
+
group.addMember("everyone", "writer");
|
|
1049
|
+
|
|
1050
|
+
// Create a sync server and disable transaction verification
|
|
1051
|
+
const syncServer = await setupTestAccount({ isSyncServer: true });
|
|
1052
|
+
syncServer.node.syncManager.disableTransactionVerification();
|
|
1053
|
+
|
|
1054
|
+
// Connect the client, but don't setup syncing just yet...
|
|
1055
|
+
const { peer } = getSyncServerConnectedPeer({
|
|
1056
|
+
peerId: client.getCurrentAgent().id,
|
|
1057
|
+
syncServer: syncServer.node,
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
// Disable reconciliation while we setup syncing because we don't want the
|
|
1061
|
+
// server to know about our forthcoming map's dependencies (group + account).
|
|
1062
|
+
const blocker = blockMessageTypeOnOutgoingPeer(peer, "load", {});
|
|
1063
|
+
client.syncManager.addPeer(peer);
|
|
1064
|
+
blocker.unblock();
|
|
1065
|
+
|
|
1066
|
+
// Create a map and set a value on it.
|
|
1067
|
+
// If transaction verification were enabled, this would trigger LOAD messages
|
|
1068
|
+
// from the server to the client asking for the group and account. However, we
|
|
1069
|
+
// don't expect to see those messages since we disabled transaction verification.
|
|
1070
|
+
const map = group.createMap();
|
|
1071
|
+
map.set("hello", "world");
|
|
1072
|
+
await map.core.waitForSync();
|
|
1073
|
+
|
|
1074
|
+
const syncMessages = SyncMessagesLog.getMessages({
|
|
1075
|
+
Account: account.core,
|
|
1076
|
+
Group: group.core,
|
|
1077
|
+
Map: map.core,
|
|
1078
|
+
});
|
|
1079
|
+
expect(
|
|
1080
|
+
syncMessages.some(
|
|
1081
|
+
(msg) => msg.includes("LOAD Account") || msg.includes("LOAD Group"),
|
|
1082
|
+
),
|
|
1083
|
+
).toBe(false);
|
|
1084
|
+
|
|
1085
|
+
// Verify the map is available on the server (transaction was accepted)
|
|
1086
|
+
const mapOnServerCore = await syncServer.node.loadCoValueCore(map.core.id);
|
|
1087
|
+
expect(mapOnServerCore.isAvailable()).toBe(true);
|
|
1088
|
+
});
|
|
1037
1089
|
});
|