Merge pull request #2 from gardencmp/anselm/gar-77-high-level-nodeteam-api
Initial high-level node/team API
This commit is contained in:
@@ -153,9 +153,10 @@ export class CoMap<
|
||||
return json;
|
||||
}
|
||||
|
||||
edit(changer: (editable: WriteableCoMap<M, Meta>) => void): void {
|
||||
edit(changer: (editable: WriteableCoMap<M, Meta>) => void): CoMap<M, Meta> {
|
||||
const editable = new WriteableCoMap<M, Meta>(this.multiLog);
|
||||
changer(editable);
|
||||
return new CoMap(this.multiLog);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,50 +106,54 @@ test("Hashing is deterministic", () => {
|
||||
test("Encryption for transactions round-trips", () => {
|
||||
const { secret } = newRandomKeySecret();
|
||||
|
||||
const encryptedChunks = [
|
||||
encryptForTransaction({ a: "hello" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
}),
|
||||
encryptForTransaction({ b: "world" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
}),
|
||||
];
|
||||
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
});
|
||||
|
||||
const decryptedChunks = encryptedChunks.map((chunk, i) =>
|
||||
decryptForTransaction(chunk, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: i },
|
||||
})
|
||||
);
|
||||
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
});
|
||||
|
||||
expect(decryptedChunks).toEqual([{ a: "hello" }, { b: "world" }]);
|
||||
const decrypted1 = decryptForTransaction(encrypted1, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
});
|
||||
|
||||
const decrypted2 = decryptForTransaction(encrypted2, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
});
|
||||
|
||||
expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
|
||||
});
|
||||
|
||||
test("Encryption for transactions doesn't decrypt with a wrong key", () => {
|
||||
const { secret } = newRandomKeySecret();
|
||||
const { secret: secret2 } = newRandomKeySecret();
|
||||
|
||||
const encryptedChunks = [
|
||||
encryptForTransaction({ a: "hello" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
}),
|
||||
encryptForTransaction({ b: "world" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
}),
|
||||
];
|
||||
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
});
|
||||
|
||||
const decryptedChunks = encryptedChunks.map((chunk, i) =>
|
||||
decryptForTransaction(chunk, secret2, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: i },
|
||||
})
|
||||
);
|
||||
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
});
|
||||
|
||||
expect(decryptedChunks).toEqual([undefined, undefined]);
|
||||
const decrypted1 = decryptForTransaction(encrypted1, secret2, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 },
|
||||
});
|
||||
|
||||
const decrypted2 = decryptForTransaction(encrypted2, secret2, {
|
||||
in: "coval_zTEST",
|
||||
tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 },
|
||||
});
|
||||
|
||||
expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
|
||||
});
|
||||
|
||||
test("Encryption of keySecrets round-trips", () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ export type Signature = `signature_z${string}`;
|
||||
|
||||
export type RecipientSecret = `recipientSecret_z${string}`;
|
||||
export type RecipientID = `recipient_z${string}`;
|
||||
export type Sealed = `sealed_U${string}`;
|
||||
export type Sealed<T> = `sealed_U${string}` & { __type: T };
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
@@ -64,16 +64,16 @@ export function getRecipientID(secret: RecipientSecret): RecipientID {
|
||||
)}`;
|
||||
}
|
||||
|
||||
export type SealedSet = {
|
||||
[recipient: RecipientID]: Sealed;
|
||||
export type SealedSet<T> = {
|
||||
[recipient: RecipientID]: Sealed<T>;
|
||||
};
|
||||
|
||||
export function seal(
|
||||
message: JsonValue,
|
||||
export function seal<T extends JsonValue>(
|
||||
message: T,
|
||||
from: RecipientSecret,
|
||||
to: Set<RecipientID>,
|
||||
nOnceMaterial: { in: MultiLogID; tx: TransactionID }
|
||||
): SealedSet {
|
||||
): SealedSet<T> {
|
||||
const nOnce = blake3(
|
||||
textEncoder.encode(stableStringify(nOnceMaterial))
|
||||
).slice(0, 24);
|
||||
@@ -88,7 +88,7 @@ export function seal(
|
||||
|
||||
const plaintext = textEncoder.encode(stableStringify(message));
|
||||
|
||||
const sealedSet: SealedSet = {};
|
||||
const sealedSet: SealedSet<T> = {};
|
||||
|
||||
for (let i = 0; i < recipientsSorted.length; i++) {
|
||||
const recipient = recipientsSorted[i];
|
||||
@@ -101,18 +101,20 @@ export function seal(
|
||||
plaintext
|
||||
);
|
||||
|
||||
sealedSet[recipient] = `sealed_U${base64url.encode(sealedBytes)}`;
|
||||
sealedSet[recipient] = `sealed_U${base64url.encode(
|
||||
sealedBytes
|
||||
)}` as Sealed<T>;
|
||||
}
|
||||
|
||||
return sealedSet;
|
||||
}
|
||||
|
||||
export function openAs(
|
||||
sealedSet: SealedSet,
|
||||
export function openAs<T extends JsonValue>(
|
||||
sealedSet: SealedSet<T>,
|
||||
recipient: RecipientSecret,
|
||||
from: RecipientID,
|
||||
nOnceMaterial: { in: MultiLogID; tx: TransactionID }
|
||||
): JsonValue | undefined {
|
||||
): T | undefined {
|
||||
const nOnce = blake3(
|
||||
textEncoder.encode(stableStringify(nOnceMaterial))
|
||||
).slice(0, 24);
|
||||
@@ -140,6 +142,7 @@ export function openAs(
|
||||
try {
|
||||
return JSON.parse(textDecoder.decode(plaintext));
|
||||
} catch (e) {
|
||||
console.error("Failed to decrypt/parse sealed message", e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +184,10 @@ export function shortHash(value: JsonValue): ShortHash {
|
||||
)}`;
|
||||
}
|
||||
|
||||
export type Encrypted<T extends JsonValue> = `encrypted_U${string}`;
|
||||
export type Encrypted<
|
||||
T extends JsonValue,
|
||||
N extends JsonValue
|
||||
> = `encrypted_U${string}` & { __type: T; __nOnceMaterial: N };
|
||||
|
||||
export type KeySecret = `keySecret_z${string}`;
|
||||
export type KeyID = `key_z${string}`;
|
||||
@@ -197,7 +203,7 @@ function encrypt<T extends JsonValue, N extends JsonValue>(
|
||||
value: T,
|
||||
keySecret: KeySecret,
|
||||
nOnceMaterial: N
|
||||
): Encrypted<T> {
|
||||
): Encrypted<T, N> {
|
||||
const keySecretBytes = base58.decode(
|
||||
keySecret.substring("keySecret_z".length)
|
||||
);
|
||||
@@ -207,21 +213,25 @@ function encrypt<T extends JsonValue, N extends JsonValue>(
|
||||
|
||||
const plaintext = textEncoder.encode(stableStringify(value));
|
||||
const ciphertext = xsalsa20(keySecretBytes, nOnce, plaintext);
|
||||
return `encrypted_U${base64url.encode(ciphertext)}`;
|
||||
return `encrypted_U${base64url.encode(ciphertext)}` as Encrypted<T, N>;
|
||||
}
|
||||
|
||||
export function encryptForTransaction<T extends JsonValue>(
|
||||
value: T,
|
||||
keySecret: KeySecret,
|
||||
nOnceMaterial: { in: MultiLogID; tx: TransactionID }
|
||||
): Encrypted<T> {
|
||||
): Encrypted<T, { in: MultiLogID; tx: TransactionID }> {
|
||||
return encrypt(value, keySecret, nOnceMaterial);
|
||||
}
|
||||
|
||||
export function sealKeySecret(keys: {
|
||||
toSeal: { id: KeyID; secret: KeySecret };
|
||||
sealing: { id: KeyID; secret: KeySecret };
|
||||
}): { sealed: KeyID; sealing: KeyID; encrypted: Encrypted<KeySecret> } {
|
||||
}): {
|
||||
sealed: KeyID;
|
||||
sealing: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { sealed: KeyID; sealing: KeyID }>;
|
||||
} {
|
||||
const nOnceMaterial = {
|
||||
sealed: keys.toSeal.id,
|
||||
sealing: keys.sealing.id,
|
||||
@@ -239,7 +249,7 @@ export function sealKeySecret(keys: {
|
||||
}
|
||||
|
||||
function decrypt<T extends JsonValue, N extends JsonValue>(
|
||||
encrypted: Encrypted<T>,
|
||||
encrypted: Encrypted<T, N>,
|
||||
keySecret: KeySecret,
|
||||
nOnceMaterial: N
|
||||
): T | undefined {
|
||||
@@ -263,7 +273,7 @@ function decrypt<T extends JsonValue, N extends JsonValue>(
|
||||
}
|
||||
|
||||
export function decryptForTransaction<T extends JsonValue>(
|
||||
encrypted: Encrypted<T>,
|
||||
encrypted: Encrypted<T, { in: MultiLogID; tx: TransactionID }>,
|
||||
keySecret: KeySecret,
|
||||
nOnceMaterial: { in: MultiLogID; tx: TransactionID }
|
||||
): T | undefined {
|
||||
@@ -271,10 +281,17 @@ export function decryptForTransaction<T extends JsonValue>(
|
||||
}
|
||||
|
||||
export function unsealKeySecret(
|
||||
sealedInfo: { sealed: KeyID; sealing: KeyID; encrypted: Encrypted<KeySecret> },
|
||||
sealedInfo: {
|
||||
sealed: KeyID;
|
||||
sealing: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { sealed: KeyID; sealing: KeyID }>;
|
||||
},
|
||||
sealingSecret: KeySecret
|
||||
): KeySecret | undefined {
|
||||
const nOnceMaterial = { sealed: sealedInfo.sealed, sealing: sealedInfo.sealing };
|
||||
const nOnceMaterial = {
|
||||
sealed: sealedInfo.sealed,
|
||||
sealing: sealedInfo.sealing,
|
||||
};
|
||||
|
||||
return decrypt(sealedInfo.encrypted, sealingSecret, nOnceMaterial);
|
||||
}
|
||||
|
||||
107
src/multilog.ts
107
src/multilog.ts
@@ -28,7 +28,7 @@ import { base58 } from "@scure/base";
|
||||
import {
|
||||
PermissionsDef as RulesetDef,
|
||||
determineValidTransactions,
|
||||
expectTeam,
|
||||
expectTeamContent,
|
||||
} from "./permissions";
|
||||
|
||||
export type MultiLogID = `coval_${string}`;
|
||||
@@ -65,7 +65,7 @@ export type PrivateTransaction = {
|
||||
privacy: "private";
|
||||
madeAt: number;
|
||||
keyUsed: KeyID;
|
||||
encryptedChanges: Encrypted<JsonValue[]>;
|
||||
encryptedChanges: Encrypted<JsonValue[], {in: MultiLogID, tx: TransactionID}>;
|
||||
};
|
||||
|
||||
export type TrustingTransaction = {
|
||||
@@ -243,7 +243,7 @@ export class MultiLog {
|
||||
let transaction: Transaction;
|
||||
|
||||
if (privacy === "private") {
|
||||
const { keySecret, keyID } = this.getCurrentReadKey();
|
||||
const { secret: keySecret, id: keyID } = this.getCurrentReadKey();
|
||||
|
||||
transaction = {
|
||||
privacy: "private",
|
||||
@@ -336,46 +336,21 @@ export class MultiLog {
|
||||
return allTransactions;
|
||||
}
|
||||
|
||||
getCurrentReadKey(): { keySecret: KeySecret; keyID: KeyID } {
|
||||
getCurrentReadKey(): { secret: KeySecret; id: KeyID } {
|
||||
if (this.header.ruleset.type === "team") {
|
||||
const content = expectTeam(this.getCurrentContent());
|
||||
const content = expectTeamContent(this.getCurrentContent());
|
||||
|
||||
const currentRevelation = content.get("readKey");
|
||||
const currentKeyId = content.get("readKey")?.keyID;
|
||||
|
||||
if (!currentRevelation) {
|
||||
throw new Error("No readKey");
|
||||
if (!currentKeyId) {
|
||||
throw new Error("No readKey set");
|
||||
}
|
||||
|
||||
const revelationTxID = content.getLastTxID("readKey");
|
||||
|
||||
if (!revelationTxID) {
|
||||
throw new Error("No readKey transaction ID");
|
||||
}
|
||||
|
||||
const revealer = agentIDfromSessionID(revelationTxID.sessionID);
|
||||
const revealerAgent = this.knownAgents[revealer];
|
||||
|
||||
if (!revealerAgent) {
|
||||
throw new Error("Unknown revealer");
|
||||
}
|
||||
|
||||
const secret = openAs(
|
||||
currentRevelation.revelation,
|
||||
this.agentCredential.recipientSecret,
|
||||
revealerAgent.recipientID,
|
||||
{
|
||||
in: this.id,
|
||||
tx: revelationTxID,
|
||||
}
|
||||
);
|
||||
|
||||
if (!secret) {
|
||||
throw new Error("Couldn't decrypt readKey");
|
||||
}
|
||||
const secret = this.getReadKey(currentKeyId);
|
||||
|
||||
return {
|
||||
keySecret: secret as KeySecret,
|
||||
keyID: currentRevelation.keyID,
|
||||
secret: secret,
|
||||
id: currentKeyId,
|
||||
};
|
||||
} else if (this.header.ruleset.type === "ownedByTeam") {
|
||||
return this.requiredMultiLogs[
|
||||
@@ -390,36 +365,36 @@ export class MultiLog {
|
||||
|
||||
getReadKey(keyID: KeyID): KeySecret {
|
||||
if (this.header.ruleset.type === "team") {
|
||||
const content = expectTeam(this.getCurrentContent());
|
||||
const content = expectTeamContent(this.getCurrentContent());
|
||||
|
||||
const readKeyHistory = content.getHistory("readKey");
|
||||
|
||||
const matchingEntry = readKeyHistory.find(
|
||||
(entry) => entry.value?.keyID === keyID
|
||||
);
|
||||
// Try to find direct relevation of key for us
|
||||
|
||||
if (!matchingEntry || !matchingEntry.value) {
|
||||
throw new Error("No matching readKey");
|
||||
}
|
||||
for (const entry of readKeyHistory) {
|
||||
if (entry.value?.keyID === keyID) {
|
||||
const revealer = agentIDfromSessionID(entry.txID.sessionID);
|
||||
const revealerAgent = this.knownAgents[revealer];
|
||||
|
||||
const revealer = agentIDfromSessionID(matchingEntry.txID.sessionID);
|
||||
const revealerAgent = this.knownAgents[revealer];
|
||||
if (!revealerAgent) {
|
||||
throw new Error("Unknown revealer");
|
||||
}
|
||||
|
||||
if (!revealerAgent) {
|
||||
throw new Error("Unknown revealer");
|
||||
}
|
||||
const secret = openAs(
|
||||
entry.value.revelation,
|
||||
this.agentCredential.recipientSecret,
|
||||
revealerAgent.recipientID,
|
||||
{
|
||||
in: this.id,
|
||||
tx: entry.txID,
|
||||
}
|
||||
);
|
||||
|
||||
const secret = openAs(
|
||||
matchingEntry.value.revelation,
|
||||
this.agentCredential.recipientSecret,
|
||||
revealerAgent.recipientID,
|
||||
{
|
||||
in: this.id,
|
||||
tx: matchingEntry.txID,
|
||||
if (secret) return secret as KeySecret;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (secret) return secret as KeySecret;
|
||||
// Try to find indirect revelation through previousKeys
|
||||
|
||||
for (const entry of readKeyHistory) {
|
||||
if (entry.value?.previousKeys?.[keyID]) {
|
||||
@@ -430,15 +405,29 @@ export class MultiLog {
|
||||
continue;
|
||||
}
|
||||
|
||||
const secret = unsealKeySecret({ sealed: keyID, sealing: sealingKeyID, encrypted: entry.value.previousKeys[keyID] }, sealingKeySecret);
|
||||
const secret = unsealKeySecret(
|
||||
{
|
||||
sealed: keyID,
|
||||
sealing: sealingKeyID,
|
||||
encrypted: entry.value.previousKeys[keyID],
|
||||
},
|
||||
sealingKeySecret
|
||||
);
|
||||
|
||||
if (secret) {
|
||||
return secret;
|
||||
} else {
|
||||
console.error(`Sealing ${sealingKeyID} key didn't unseal ${keyID}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("readKey " + keyID + " not revealed for " + getAgentID(getAgent(this.agentCredential)));
|
||||
throw new Error(
|
||||
"readKey " +
|
||||
keyID +
|
||||
" not revealed for " +
|
||||
getAgentID(getAgent(this.agentCredential))
|
||||
);
|
||||
} else if (this.header.ruleset.type === "ownedByTeam") {
|
||||
return this.requiredMultiLogs[this.header.ruleset.team].getReadKey(
|
||||
keyID
|
||||
|
||||
52
src/node.ts
52
src/node.ts
@@ -1,3 +1,5 @@
|
||||
import { CoMap } from "./coValue";
|
||||
import { newRandomKeySecret, seal } from "./crypto";
|
||||
import {
|
||||
MultiLogID,
|
||||
MultiLog,
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
getAgentMultilogHeader,
|
||||
MultiLogHeader,
|
||||
} from "./multilog";
|
||||
import { Team, expectTeamContent } from "./permissions";
|
||||
|
||||
export class LocalNode {
|
||||
multilogs: { [key: MultiLogID]: Promise<MultiLog> | MultiLog } = {};
|
||||
@@ -38,9 +41,14 @@ export class LocalNode {
|
||||
}
|
||||
|
||||
createMultiLog(header: MultiLogHeader): MultiLog {
|
||||
const requiredMultiLogs = header.ruleset.type === "ownedByTeam" ? {
|
||||
[header.ruleset.team]: this.expectMultiLogLoaded(header.ruleset.team)
|
||||
} : {};
|
||||
const requiredMultiLogs =
|
||||
header.ruleset.type === "ownedByTeam"
|
||||
? {
|
||||
[header.ruleset.team]: this.expectMultiLogLoaded(
|
||||
header.ruleset.team
|
||||
),
|
||||
}
|
||||
: {};
|
||||
|
||||
const multilog = new MultiLog(
|
||||
header,
|
||||
@@ -63,6 +71,44 @@ export class LocalNode {
|
||||
}
|
||||
return multilog;
|
||||
}
|
||||
|
||||
addKnownAgent(agent: Agent) {
|
||||
const agentID = getAgentID(agent);
|
||||
this.knownAgents[agentID] = agent;
|
||||
}
|
||||
|
||||
createTeam(): Team {
|
||||
const teamMultilog = this.createMultiLog({
|
||||
type: "comap",
|
||||
ruleset: { type: "team", initialAdmin: this.agentID },
|
||||
meta: null,
|
||||
});
|
||||
|
||||
let teamContent = expectTeamContent(teamMultilog.getCurrentContent());
|
||||
|
||||
teamContent = teamContent.edit((editable) => {
|
||||
editable.set(this.agentID, "admin", "trusting");
|
||||
|
||||
const readKey = newRandomKeySecret();
|
||||
const revelation = seal(
|
||||
readKey.secret,
|
||||
this.agentCredential.recipientSecret,
|
||||
new Set([getAgent(this.agentCredential).recipientID]),
|
||||
{
|
||||
in: teamMultilog.id,
|
||||
tx: teamMultilog.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKey.id, revelation },
|
||||
"trusting"
|
||||
);
|
||||
});
|
||||
|
||||
return new Team(teamContent, this);
|
||||
}
|
||||
}
|
||||
|
||||
// type Hostname = string;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "./multilog";
|
||||
import { LocalNode } from "./node";
|
||||
import { expectMap } from "./coValue";
|
||||
import { expectTeam } from "./permissions";
|
||||
import { expectTeamContent } from "./permissions";
|
||||
import {
|
||||
getRecipientID,
|
||||
newRandomKeySecret,
|
||||
@@ -21,14 +21,14 @@ function teamWithTwoAdmins() {
|
||||
const otherAdmin = newRandomAgentCredential();
|
||||
const otherAdminID = getAgentID(getAgent(otherAdmin));
|
||||
|
||||
let content = expectTeam(team.getCurrentContent());
|
||||
let content = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
content.edit((editable) => {
|
||||
editable.set(otherAdminID, "admin", "trusting");
|
||||
expect(editable.get(otherAdminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
content = expectTeam(team.getCurrentContent());
|
||||
content = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
if (content.type !== "comap") {
|
||||
throw new Error("Expected map");
|
||||
@@ -50,7 +50,7 @@ function newTeam() {
|
||||
meta: null,
|
||||
});
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(adminID, "admin", "trusting");
|
||||
@@ -64,6 +64,34 @@ test("Initial admin can add another admin to a team", () => {
|
||||
teamWithTwoAdmins();
|
||||
});
|
||||
|
||||
function newTeamHighLevel() {
|
||||
const admin = newRandomAgentCredential();
|
||||
const adminID = getAgentID(getAgent(admin));
|
||||
|
||||
const node = new LocalNode(admin, newRandomSessionID(adminID));
|
||||
|
||||
const team = node.createTeam();
|
||||
|
||||
return { admin, adminID, node, team };
|
||||
}
|
||||
|
||||
function teamWithTwoAdminsHighLevel() {
|
||||
const { admin, adminID, node, team } = newTeamHighLevel();
|
||||
|
||||
const otherAdmin = newRandomAgentCredential();
|
||||
const otherAdminID = getAgentID(getAgent(otherAdmin));
|
||||
|
||||
node.addKnownAgent(getAgent(otherAdmin));
|
||||
|
||||
team.addMember(otherAdminID, "admin");
|
||||
|
||||
return { admin, adminID, node, team, otherAdmin, otherAdminID };
|
||||
}
|
||||
|
||||
test("Initial admin can add another admin to a team (high level)", () => {
|
||||
teamWithTwoAdminsHighLevel();
|
||||
});
|
||||
|
||||
test("Added admin can add a third admin to a team", () => {
|
||||
const { team, otherAdmin, otherAdminID } = teamWithTwoAdmins();
|
||||
|
||||
@@ -72,7 +100,7 @@ test("Added admin can add a third admin to a team", () => {
|
||||
newRandomSessionID(otherAdminID)
|
||||
);
|
||||
|
||||
let otherContent = expectTeam(teamAsOtherAdmin.getCurrentContent());
|
||||
let otherContent = expectTeamContent(teamAsOtherAdmin.getCurrentContent());
|
||||
|
||||
expect(otherContent.get(otherAdminID)).toEqual("admin");
|
||||
|
||||
@@ -84,22 +112,41 @@ test("Added admin can add a third admin to a team", () => {
|
||||
expect(editable.get(thirdAdminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
otherContent = expectTeam(teamAsOtherAdmin.getCurrentContent());
|
||||
otherContent = expectTeamContent(teamAsOtherAdmin.getCurrentContent());
|
||||
|
||||
expect(otherContent.get(thirdAdminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
test("Added adming can add a third admin to a team (high level)", () => {
|
||||
const { team, otherAdmin, otherAdminID, node } =
|
||||
teamWithTwoAdminsHighLevel();
|
||||
|
||||
const teamAsOtherAdmin = team.testWithDifferentCredentials(
|
||||
otherAdmin,
|
||||
newRandomSessionID(otherAdminID)
|
||||
);
|
||||
|
||||
const thirdAdmin = newRandomAgentCredential();
|
||||
const thirdAdminID = getAgentID(getAgent(thirdAdmin));
|
||||
|
||||
node.addKnownAgent(getAgent(thirdAdmin));
|
||||
|
||||
teamAsOtherAdmin.addMember(thirdAdminID, "admin");
|
||||
|
||||
expect(teamAsOtherAdmin.teamMap.get(thirdAdminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
test("Admins can't demote other admins in a team", () => {
|
||||
const { team, adminID, otherAdmin, otherAdminID } = teamWithTwoAdmins();
|
||||
|
||||
let teamContent = expectTeam(team.getCurrentContent());
|
||||
let teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(otherAdminID, "writer", "trusting");
|
||||
expect(editable.get(otherAdminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
teamContent = expectTeam(team.getCurrentContent());
|
||||
teamContent = expectTeamContent(team.getCurrentContent());
|
||||
expect(teamContent.get(otherAdminID)).toEqual("admin");
|
||||
|
||||
const teamAsOtherAdmin = team.testWithDifferentCredentials(
|
||||
@@ -107,7 +154,7 @@ test("Admins can't demote other admins in a team", () => {
|
||||
newRandomSessionID(otherAdminID)
|
||||
);
|
||||
|
||||
let teamContentAsOtherAdmin = expectTeam(
|
||||
let teamContentAsOtherAdmin = expectTeamContent(
|
||||
teamAsOtherAdmin.getCurrentContent()
|
||||
);
|
||||
|
||||
@@ -116,24 +163,42 @@ test("Admins can't demote other admins in a team", () => {
|
||||
expect(editable.get(adminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
teamContentAsOtherAdmin = expectTeam(teamAsOtherAdmin.getCurrentContent());
|
||||
teamContentAsOtherAdmin = expectTeamContent(
|
||||
teamAsOtherAdmin.getCurrentContent()
|
||||
);
|
||||
|
||||
expect(teamContentAsOtherAdmin.get(adminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
test("Admins can't demote other admins in a team (high level)", () => {
|
||||
const { team, adminID, otherAdmin, otherAdminID } =
|
||||
teamWithTwoAdminsHighLevel();
|
||||
|
||||
const teamAsOtherAdmin = team.testWithDifferentCredentials(
|
||||
otherAdmin,
|
||||
newRandomSessionID(otherAdminID)
|
||||
);
|
||||
|
||||
expect(() => teamAsOtherAdmin.addMember(adminID, "writer")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
|
||||
expect(teamAsOtherAdmin.teamMap.get(adminID)).toEqual("admin");
|
||||
});
|
||||
|
||||
test("Admins an add writers to a team, who can't add admins, writers, or readers", () => {
|
||||
const { team } = newTeam();
|
||||
const writer = newRandomAgentCredential();
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
|
||||
let teamContent = expectTeam(team.getCurrentContent());
|
||||
let teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(writerID, "writer", "trusting");
|
||||
expect(editable.get(writerID)).toEqual("writer");
|
||||
});
|
||||
|
||||
teamContent = expectTeam(team.getCurrentContent());
|
||||
teamContent = expectTeamContent(team.getCurrentContent());
|
||||
expect(teamContent.get(writerID)).toEqual("writer");
|
||||
|
||||
const teamAsWriter = team.testWithDifferentCredentials(
|
||||
@@ -141,7 +206,9 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers
|
||||
newRandomSessionID(writerID)
|
||||
);
|
||||
|
||||
let teamContentAsWriter = expectTeam(teamAsWriter.getCurrentContent());
|
||||
let teamContentAsWriter = expectTeamContent(
|
||||
teamAsWriter.getCurrentContent()
|
||||
);
|
||||
|
||||
expect(teamContentAsWriter.get(writerID)).toEqual("writer");
|
||||
|
||||
@@ -159,24 +226,60 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers
|
||||
expect(editable.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
teamContentAsWriter = expectTeam(teamAsWriter.getCurrentContent());
|
||||
teamContentAsWriter = expectTeamContent(teamAsWriter.getCurrentContent());
|
||||
|
||||
expect(teamContentAsWriter.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Admins an add writers to a team, who can't add admins, writers, or readers (high level)", () => {
|
||||
const { team, node } = newTeamHighLevel();
|
||||
|
||||
const writer = newRandomAgentCredential();
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
|
||||
node.addKnownAgent(getAgent(writer));
|
||||
|
||||
team.addMember(writerID, "writer");
|
||||
expect(team.teamMap.get(writerID)).toEqual("writer");
|
||||
|
||||
const teamAsWriter = team.testWithDifferentCredentials(
|
||||
writer,
|
||||
newRandomSessionID(writerID)
|
||||
);
|
||||
|
||||
expect(teamAsWriter.teamMap.get(writerID)).toEqual("writer");
|
||||
|
||||
const otherAgent = newRandomAgentCredential();
|
||||
const otherAgentID = getAgentID(getAgent(otherAgent));
|
||||
|
||||
node.addKnownAgent(getAgent(otherAgent));
|
||||
|
||||
expect(() => teamAsWriter.addMember(otherAgentID, "admin")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => teamAsWriter.addMember(otherAgentID, "writer")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => teamAsWriter.addMember(otherAgentID, "reader")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
|
||||
expect(teamAsWriter.teamMap.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Admins can add readers to a team, who can't add admins, writers, or readers", () => {
|
||||
const { team } = newTeam();
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
let teamContent = expectTeam(team.getCurrentContent());
|
||||
let teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(readerID, "reader", "trusting");
|
||||
expect(editable.get(readerID)).toEqual("reader");
|
||||
});
|
||||
|
||||
teamContent = expectTeam(team.getCurrentContent());
|
||||
teamContent = expectTeamContent(team.getCurrentContent());
|
||||
expect(teamContent.get(readerID)).toEqual("reader");
|
||||
|
||||
const teamAsReader = team.testWithDifferentCredentials(
|
||||
@@ -184,7 +287,9 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
let teamContentAsReader = expectTeam(teamAsReader.getCurrentContent());
|
||||
let teamContentAsReader = expectTeamContent(
|
||||
teamAsReader.getCurrentContent()
|
||||
);
|
||||
|
||||
expect(teamContentAsReader.get(readerID)).toEqual("reader");
|
||||
|
||||
@@ -202,11 +307,47 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader
|
||||
expect(editable.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
teamContentAsReader = expectTeam(teamAsReader.getCurrentContent());
|
||||
teamContentAsReader = expectTeamContent(teamAsReader.getCurrentContent());
|
||||
|
||||
expect(teamContentAsReader.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Admins can add readers to a team, who can't add admins, writers, or readers (high level)", () => {
|
||||
const { team, node } = newTeamHighLevel();
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
node.addKnownAgent(getAgent(reader));
|
||||
|
||||
team.addMember(readerID, "reader");
|
||||
expect(team.teamMap.get(readerID)).toEqual("reader");
|
||||
|
||||
const teamAsReader = team.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
expect(teamAsReader.teamMap.get(readerID)).toEqual("reader");
|
||||
|
||||
const otherAgent = newRandomAgentCredential();
|
||||
const otherAgentID = getAgentID(getAgent(otherAgent));
|
||||
|
||||
node.addKnownAgent(getAgent(otherAgent));
|
||||
|
||||
expect(() => teamAsReader.addMember(otherAgentID, "admin")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => teamAsReader.addMember(otherAgentID, "writer")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
expect(() => teamAsReader.addMember(otherAgentID, "reader")).toThrow(
|
||||
"Failed to set role"
|
||||
);
|
||||
|
||||
expect(teamAsReader.teamMap.get(otherAgentID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Admins can write to an object that is owned by their team", () => {
|
||||
const { node, team } = newTeam();
|
||||
|
||||
@@ -228,13 +369,26 @@ test("Admins can write to an object that is owned by their team", () => {
|
||||
expect(childContent.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can write to an object that is owned by their team (high level)", () => {
|
||||
const { node, team } = newTeamHighLevel();
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "trusting");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Writers can write to an object that is owned by their team", () => {
|
||||
const { node, team } = newTeam();
|
||||
|
||||
const writer = newRandomAgentCredential();
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
|
||||
expectTeam(team.getCurrentContent()).edit((editable) => {
|
||||
expectTeamContent(team.getCurrentContent()).edit((editable) => {
|
||||
editable.set(writerID, "writer", "trusting");
|
||||
expect(editable.get(writerID)).toEqual("writer");
|
||||
});
|
||||
@@ -264,13 +418,39 @@ test("Writers can write to an object that is owned by their team", () => {
|
||||
expect(childContentAsWriter.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Writers can write to an object that is owned by their team (high level)", () => {
|
||||
const { node, team } = newTeamHighLevel();
|
||||
|
||||
const writer = newRandomAgentCredential();
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
|
||||
node.addKnownAgent(getAgent(writer));
|
||||
|
||||
team.addMember(writerID, "writer");
|
||||
|
||||
const childObject = team.createMap();
|
||||
|
||||
let childObjectAsWriter = expectMap(
|
||||
childObject.multiLog
|
||||
.testWithDifferentCredentials(writer, newRandomSessionID(writerID))
|
||||
.getCurrentContent()
|
||||
);
|
||||
|
||||
childObjectAsWriter = childObjectAsWriter.edit((editable) => {
|
||||
editable.set("foo", "bar", "trusting");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObjectAsWriter.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Readers can not write to an object that is owned by their team", () => {
|
||||
const { node, team } = newTeam();
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
expectTeam(team.getCurrentContent()).edit((editable) => {
|
||||
expectTeamContent(team.getCurrentContent()).edit((editable) => {
|
||||
editable.set(readerID, "reader", "trusting");
|
||||
expect(editable.get(readerID)).toEqual("reader");
|
||||
});
|
||||
@@ -300,10 +480,36 @@ test("Readers can not write to an object that is owned by their team", () => {
|
||||
expect(childContentAsReader.get("foo")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Readers can not write to an object that is owned by their team (high level)", () => {
|
||||
const { node, team } = newTeamHighLevel();
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
node.addKnownAgent(getAgent(reader));
|
||||
|
||||
team.addMember(readerID, "reader");
|
||||
|
||||
const childObject = team.createMap();
|
||||
|
||||
let childObjectAsReader = expectMap(
|
||||
childObject.multiLog
|
||||
.testWithDifferentCredentials(reader, newRandomSessionID(readerID))
|
||||
.getCurrentContent()
|
||||
);
|
||||
|
||||
childObjectAsReader = childObjectAsReader.edit((editable) => {
|
||||
editable.set("foo", "bar", "trusting");
|
||||
expect(editable.get("foo")).toBeUndefined();
|
||||
});
|
||||
|
||||
expect(childObjectAsReader.get("foo")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then use it to create and read private transactions in owned objects", () => {
|
||||
const { node, team, admin, adminID } = newTeam();
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
@@ -321,7 +527,7 @@ test("Admins can set team read key and then use it to create and read private tr
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().keySecret).toEqual(readKey);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
const childObject = node.createMultiLog({
|
||||
@@ -341,6 +547,19 @@ test("Admins can set team read key and then use it to create and read private tr
|
||||
expect(childContent.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then use it to create and read private transactions in owned objects (high level)", () => {
|
||||
const { node, team, admin } = newTeamHighLevel();
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects", () => {
|
||||
const { node, team, admin } = newTeam();
|
||||
|
||||
@@ -348,7 +567,7 @@ test("Admins can set team read key and then writers can use it to create and rea
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(writerID, "writer", "trusting");
|
||||
@@ -380,7 +599,7 @@ test("Admins can set team read key and then writers can use it to create and rea
|
||||
newRandomSessionID(writerID)
|
||||
);
|
||||
|
||||
expect(childObject.getCurrentReadKey().keySecret).toEqual(readKey);
|
||||
expect(childObject.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
let childContentAsWriter = expectMap(
|
||||
childObjectAsWriter.getCurrentContent()
|
||||
@@ -396,6 +615,32 @@ test("Admins can set team read key and then writers can use it to create and rea
|
||||
expect(childContentAsWriter.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects (high level)", () => {
|
||||
const { node, team, admin } = newTeamHighLevel();
|
||||
|
||||
const writer = newRandomAgentCredential();
|
||||
const writerID = getAgentID(getAgent(writer));
|
||||
|
||||
node.addKnownAgent(getAgent(writer));
|
||||
|
||||
team.addMember(writerID, "writer");
|
||||
|
||||
const childObject = team.createMap();
|
||||
|
||||
let childObjectAsWriter = expectMap(
|
||||
childObject.multiLog
|
||||
.testWithDifferentCredentials(writer, newRandomSessionID(writerID))
|
||||
.getCurrentContent()
|
||||
);
|
||||
|
||||
childObjectAsWriter = childObjectAsWriter.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObjectAsWriter.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read", () => {
|
||||
const { node, team, admin } = newTeam();
|
||||
|
||||
@@ -403,7 +648,7 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(readerID, "reader", "trusting");
|
||||
@@ -440,7 +685,7 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
expect(childObjectAsReader.getCurrentReadKey().keySecret).toEqual(readKey);
|
||||
expect(childObjectAsReader.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
const childContentAsReader = expectMap(
|
||||
childObjectAsReader.getCurrentContent()
|
||||
@@ -449,10 +694,161 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
expect(childContentAsReader.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read (high level)", () => {
|
||||
const { node, team, admin } = newTeamHighLevel();
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
node.addKnownAgent(getAgent(reader));
|
||||
|
||||
team.addMember(readerID, "reader");
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const childContentAsReader = expectMap(childObject.multiLog.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
).getCurrentContent());
|
||||
|
||||
expect(childContentAsReader.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
|
||||
test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key", () => {
|
||||
const { node, team, admin } = newTeam();
|
||||
|
||||
const reader1 = newRandomAgentCredential();
|
||||
const reader1ID = getAgentID(getAgent(reader1));
|
||||
const reader2 = newRandomAgentCredential();
|
||||
const reader2ID = getAgentID(getAgent(reader2));
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
editable.set(reader1ID, "reader", "trusting");
|
||||
expect(editable.get(reader1ID)).toEqual("reader");
|
||||
|
||||
const revelation1 = seal(
|
||||
readKey,
|
||||
admin.recipientSecret,
|
||||
new Set([
|
||||
getRecipientID(admin.recipientSecret),
|
||||
getRecipientID(reader1.recipientSecret),
|
||||
]),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKeyID, revelation: revelation1 },
|
||||
"trusting"
|
||||
);
|
||||
|
||||
const revelation2 = seal(
|
||||
readKey,
|
||||
admin.recipientSecret,
|
||||
new Set([getRecipientID(reader2.recipientSecret)]),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKeyID, revelation: revelation2 },
|
||||
"trusting"
|
||||
);
|
||||
});
|
||||
|
||||
const childObject = node.createMultiLog({
|
||||
type: "comap",
|
||||
ruleset: { type: "ownedByTeam", team: team.id },
|
||||
meta: null,
|
||||
});
|
||||
|
||||
expectMap(childObject.getCurrentContent()).edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const childObjectAsReader1 = childObject.testWithDifferentCredentials(
|
||||
reader1,
|
||||
newRandomSessionID(reader1ID)
|
||||
);
|
||||
|
||||
expect(childObjectAsReader1.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
const childContentAsReader1 = expectMap(
|
||||
childObjectAsReader1.getCurrentContent()
|
||||
);
|
||||
|
||||
expect(childContentAsReader1.get("foo")).toEqual("bar");
|
||||
|
||||
const childObjectAsReader2 = childObject.testWithDifferentCredentials(
|
||||
reader2,
|
||||
newRandomSessionID(reader2ID)
|
||||
);
|
||||
|
||||
expect(childObjectAsReader2.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
const childContentAsReader2 = expectMap(
|
||||
childObjectAsReader2.getCurrentContent()
|
||||
);
|
||||
|
||||
expect(childContentAsReader2.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key (high level)", () => {
|
||||
const { node, team, admin } = newTeamHighLevel();
|
||||
|
||||
const reader1 = newRandomAgentCredential();
|
||||
const reader1ID = getAgentID(getAgent(reader1));
|
||||
const reader2 = newRandomAgentCredential();
|
||||
const reader2ID = getAgentID(getAgent(reader2));
|
||||
|
||||
node.addKnownAgent(getAgent(reader1));
|
||||
node.addKnownAgent(getAgent(reader2));
|
||||
|
||||
team.addMember(reader1ID, "reader");
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
const childContentAsReader1 = expectMap(childObject.multiLog.testWithDifferentCredentials(
|
||||
reader1,
|
||||
newRandomSessionID(reader1ID)
|
||||
).getCurrentContent());
|
||||
|
||||
expect(childContentAsReader1.get("foo")).toEqual("bar");
|
||||
|
||||
team.addMember(reader2ID, "reader");
|
||||
|
||||
const childContentAsReader2 = expectMap(childObject.multiLog.testWithDifferentCredentials(
|
||||
reader2,
|
||||
newRandomSessionID(reader2ID)
|
||||
).getCurrentContent());
|
||||
|
||||
expect(childContentAsReader2.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
|
||||
test("Admins can set team read key, make a private transaction in an owned object, rotate the read key, make another private transaction, and both can be read by the admin", () => {
|
||||
const { node, team, admin, adminID } = newTeam();
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
@@ -470,7 +866,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().keySecret).toEqual(readKey);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
const childObject = node.createMultiLog({
|
||||
@@ -507,7 +903,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().keySecret).toEqual(readKey2);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
});
|
||||
|
||||
childContent = expectMap(childObject.getCurrentContent());
|
||||
@@ -522,6 +918,33 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
expect(childContent.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
test("Admins can set team read key, make a private transaction in an owned object, rotate the read key, make another private transaction, and both can be read by the admin (high level)", () => {
|
||||
const { node, team, admin, adminID } = newTeamHighLevel();
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
const firstReadKey = childObject.multiLog.getCurrentReadKey();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
|
||||
team.rotateReadKey();
|
||||
|
||||
expect(childObject.multiLog.getCurrentReadKey()).not.toEqual(firstReadKey);
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
expect(editable.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
expect(childObject.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
test("Admins can set team read key, make a private transaction in an owned object, rotate the read key, add a reader, make another private transaction in the owned object, and both can be read by the reader", () => {
|
||||
const { node, team, admin, adminID } = newTeam();
|
||||
|
||||
@@ -531,7 +954,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
meta: null,
|
||||
});
|
||||
|
||||
const teamContent = expectTeam(team.getCurrentContent());
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
@@ -549,7 +972,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().keySecret).toEqual(readKey);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
let childContent = expectMap(childObject.getCurrentContent());
|
||||
@@ -598,7 +1021,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().keySecret).toEqual(readKey2);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
editable.set(readerID, "reader", "trusting");
|
||||
expect(editable.get(readerID)).toEqual("reader");
|
||||
@@ -614,9 +1037,7 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
expect(childObjectAsReader.getCurrentReadKey().keySecret).toEqual(readKey2);
|
||||
|
||||
console.log(readKeyID2);
|
||||
expect(childObjectAsReader.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
const childContentAsReader = expectMap(
|
||||
childObjectAsReader.getCurrentContent()
|
||||
@@ -625,3 +1046,229 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
expect(childContentAsReader.get("foo")).toEqual("bar");
|
||||
expect(childContentAsReader.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
test("Admins can set team read key, make a private transaction in an owned object, rotate the read key, add a reader, make another private transaction in the owned object, and both can be read by the reader (high level)", () => {
|
||||
const { node, team, admin, adminID } = newTeamHighLevel();
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
const firstReadKey = childObject.multiLog.getCurrentReadKey();
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
|
||||
team.rotateReadKey();
|
||||
|
||||
expect(childObject.multiLog.getCurrentReadKey()).not.toEqual(firstReadKey);
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
|
||||
node.addKnownAgent(getAgent(reader));
|
||||
|
||||
team.addMember(readerID, "reader");
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
expect(editable.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
const childContentAsReader = expectMap(childObject.multiLog.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
).getCurrentContent());
|
||||
|
||||
expect(childContentAsReader.get("foo")).toEqual("bar");
|
||||
expect(childContentAsReader.get("foo2")).toEqual("bar2");
|
||||
})
|
||||
|
||||
|
||||
test("Admins can set team read rey, make a private transaction in an owned object, rotate the read key, add two readers, rotate the read key again to kick out one reader, make another private transaction in the owned object, and only the remaining reader can read both transactions", () => {
|
||||
const { node, team, admin, adminID } = newTeam();
|
||||
|
||||
const childObject = node.createMultiLog({
|
||||
type: "comap",
|
||||
ruleset: { type: "ownedByTeam", team: team.id },
|
||||
meta: null,
|
||||
});
|
||||
|
||||
const teamContent = expectTeamContent(team.getCurrentContent());
|
||||
const { secret: readKey, id: readKeyID } = newRandomKeySecret();
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
const reader2 = newRandomAgentCredential();
|
||||
const reader2ID = getAgentID(getAgent(reader));
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation = seal(
|
||||
readKey,
|
||||
admin.recipientSecret,
|
||||
new Set([
|
||||
getRecipientID(admin.recipientSecret),
|
||||
getRecipientID(reader.recipientSecret),
|
||||
getRecipientID(reader2.recipientSecret),
|
||||
]),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
editable.set(readerID, "reader", "trusting");
|
||||
expect(editable.get(readerID)).toEqual("reader");
|
||||
editable.set(reader2ID, "reader", "trusting");
|
||||
expect(editable.get(reader2ID)).toEqual("reader");
|
||||
});
|
||||
|
||||
let childContent = expectMap(childObject.getCurrentContent());
|
||||
|
||||
childContent.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
childContent = expectMap(childObject.getCurrentContent());
|
||||
expect(childContent.get("foo")).toEqual("bar");
|
||||
|
||||
let childObjectAsReader = childObject.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
expect(
|
||||
expectMap(childObjectAsReader.getCurrentContent()).get("foo")
|
||||
).toEqual("bar");
|
||||
|
||||
let childObjectAsReader2 = childObject.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
|
||||
expect(
|
||||
expectMap(childObjectAsReader2.getCurrentContent()).get("foo")
|
||||
).toEqual("bar");
|
||||
|
||||
const { secret: readKey2, id: readKeyID2 } = newRandomKeySecret();
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation = seal(
|
||||
readKey2,
|
||||
admin.recipientSecret,
|
||||
new Set([
|
||||
getRecipientID(admin.recipientSecret),
|
||||
getRecipientID(reader2.recipientSecret),
|
||||
]),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID2, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
editable.set(readerID, "revoked", "trusting");
|
||||
// expect(editable.get(readerID)).toEqual("revoked");
|
||||
});
|
||||
|
||||
expect(childObject.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
childContent = expectMap(childObject.getCurrentContent());
|
||||
childContent.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
expect(editable.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
// TODO: make sure these instances of multilogs sync between each other so this isn't necessary?
|
||||
childObjectAsReader = childObject.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
);
|
||||
childObjectAsReader2 = childObject.testWithDifferentCredentials(
|
||||
reader2,
|
||||
newRandomSessionID(reader2ID)
|
||||
);
|
||||
|
||||
expect(() => expectMap(childObjectAsReader.getCurrentContent())).toThrow(
|
||||
/readKey (.+?) not revealed for (.+?)/
|
||||
);
|
||||
expect(
|
||||
expectMap(childObjectAsReader2.getCurrentContent()).get("foo2")
|
||||
).toEqual("bar2");
|
||||
expect(() => {
|
||||
childObjectAsReader.getCurrentContent();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("Admins can set team read rey, make a private transaction in an owned object, rotate the read key, add two readers, rotate the read key again to kick out one reader, make another private transaction in the owned object, and only the remaining reader can read both transactions (high level)", () => {
|
||||
const { node, team, admin, adminID } = newTeamHighLevel();
|
||||
|
||||
let childObject = team.createMap();
|
||||
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo", "bar", "private");
|
||||
expect(editable.get("foo")).toEqual("bar");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
|
||||
team.rotateReadKey();
|
||||
|
||||
const secondReadKey = childObject.multiLog.getCurrentReadKey();
|
||||
|
||||
const reader = newRandomAgentCredential();
|
||||
const readerID = getAgentID(getAgent(reader));
|
||||
const reader2 = newRandomAgentCredential();
|
||||
const reader2ID = getAgentID(getAgent(reader2));
|
||||
|
||||
node.addKnownAgent(getAgent(reader));
|
||||
node.addKnownAgent(getAgent(reader2));
|
||||
|
||||
team.addMember(readerID, "reader");
|
||||
team.addMember(reader2ID, "reader");
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo2", "bar2", "private");
|
||||
expect(editable.get("foo2")).toEqual("bar2");
|
||||
});
|
||||
|
||||
expect(childObject.get("foo")).toEqual("bar");
|
||||
expect(childObject.get("foo2")).toEqual("bar2");
|
||||
|
||||
team.removeMember(readerID);
|
||||
|
||||
expect(childObject.multiLog.getCurrentReadKey()).not.toEqual(secondReadKey);
|
||||
|
||||
childObject = childObject.edit((editable) => {
|
||||
editable.set("foo3", "bar3", "private");
|
||||
expect(editable.get("foo3")).toEqual("bar3");
|
||||
});
|
||||
|
||||
const childContentAsReader2 = expectMap(childObject.multiLog.testWithDifferentCredentials(
|
||||
reader2,
|
||||
newRandomSessionID(reader2ID)
|
||||
).getCurrentContent());
|
||||
|
||||
expect(childContentAsReader2.get("foo")).toEqual("bar");
|
||||
expect(childContentAsReader2.get("foo2")).toEqual("bar2");
|
||||
expect(childContentAsReader2.get("foo3")).toEqual("bar3");
|
||||
|
||||
expect(() => childObject.multiLog.testWithDifferentCredentials(
|
||||
reader,
|
||||
newRandomSessionID(readerID)
|
||||
).getCurrentContent()).toThrow(/readKey (.+?) not revealed for (.+?)/);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { CoMap, CoValue, MapOpPayload } from "./coValue";
|
||||
import { JsonValue } from "./jsonValue";
|
||||
import { Encrypted, KeyID, KeySecret, RecipientID, SealedSet, SignatoryID } from "./crypto";
|
||||
import {
|
||||
Encrypted,
|
||||
KeyID,
|
||||
KeySecret,
|
||||
RecipientID,
|
||||
SealedSet,
|
||||
SignatoryID,
|
||||
encryptForTransaction,
|
||||
newRandomKeySecret,
|
||||
seal,
|
||||
sealKeySecret,
|
||||
} from "./crypto";
|
||||
import {
|
||||
AgentCredential,
|
||||
AgentID,
|
||||
MultiLog,
|
||||
MultiLogID,
|
||||
@@ -11,6 +23,7 @@ import {
|
||||
TrustingTransaction,
|
||||
agentIDfromSessionID,
|
||||
} from "./multilog";
|
||||
import { LocalNode } from ".";
|
||||
|
||||
export type PermissionsDef =
|
||||
| { type: "team"; initialAdmin: AgentID; parentTeams?: MultiLogID[] }
|
||||
@@ -101,7 +114,8 @@ export function determineValidTransactions(
|
||||
if (
|
||||
change.value !== "admin" &&
|
||||
change.value !== "writer" &&
|
||||
change.value !== "reader"
|
||||
change.value !== "reader" &&
|
||||
change.value !== "revoked"
|
||||
) {
|
||||
console.warn("Team transaction must set a valid role");
|
||||
continue;
|
||||
@@ -185,15 +199,150 @@ export function determineValidTransactions(
|
||||
}
|
||||
|
||||
export type TeamContent = { [key: AgentID]: Role } & {
|
||||
readKey: { keyID: KeyID; revelation: SealedSet, previousKeys?: {
|
||||
[key: KeyID]: Encrypted<KeySecret>
|
||||
} };
|
||||
readKey: {
|
||||
keyID: KeyID;
|
||||
revelation: SealedSet<KeySecret>;
|
||||
previousKeys?: {
|
||||
[key: KeyID]: Encrypted<
|
||||
KeySecret,
|
||||
{ sealed: KeyID; sealing: KeyID }
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function expectTeam(content: CoValue): CoMap<TeamContent, {}> {
|
||||
export function expectTeamContent(content: CoValue): CoMap<TeamContent, {}> {
|
||||
if (content.type !== "comap") {
|
||||
throw new Error("Expected map");
|
||||
}
|
||||
|
||||
return content as CoMap<TeamContent, {}>;
|
||||
}
|
||||
|
||||
export class Team {
|
||||
teamMap: CoMap<TeamContent, {}>;
|
||||
node: LocalNode;
|
||||
|
||||
constructor(teamMap: CoMap<TeamContent, {}>, node: LocalNode) {
|
||||
this.teamMap = teamMap;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
addMember(agentID: AgentID, role: Role) {
|
||||
this.teamMap = this.teamMap.edit((map) => {
|
||||
const agent = this.node.knownAgents[agentID];
|
||||
|
||||
if (!agent) {
|
||||
throw new Error("Unknown agent " + agentID);
|
||||
}
|
||||
|
||||
map.set(agentID, role, "trusting");
|
||||
if (map.get(agentID) !== role) {
|
||||
throw new Error("Failed to set role");
|
||||
}
|
||||
|
||||
const currentReadKey = this.teamMap.multiLog.getCurrentReadKey();
|
||||
|
||||
const revelation = seal(
|
||||
currentReadKey.secret,
|
||||
this.teamMap.multiLog.agentCredential.recipientSecret,
|
||||
new Set([agent.recipientID]),
|
||||
{
|
||||
in: this.teamMap.multiLog.id,
|
||||
tx: this.teamMap.multiLog.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
map.set(
|
||||
"readKey",
|
||||
{ keyID: currentReadKey.id, revelation },
|
||||
"trusting"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
rotateReadKey() {
|
||||
const currentlyPermittedReaders = this.teamMap.keys().filter((key) => {
|
||||
if (key.startsWith("agent_")) {
|
||||
const role = this.teamMap.get(key);
|
||||
return (
|
||||
role === "admin" || role === "writer" || role === "reader"
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}) as AgentID[];
|
||||
|
||||
const currentReadKey = this.teamMap.multiLog.getCurrentReadKey();
|
||||
|
||||
const newReadKey = newRandomKeySecret();
|
||||
|
||||
const newReadKeyRevelation = seal(
|
||||
newReadKey.secret,
|
||||
this.teamMap.multiLog.agentCredential.recipientSecret,
|
||||
new Set(
|
||||
currentlyPermittedReaders.map(
|
||||
(reader) => this.node.knownAgents[reader].recipientID
|
||||
)
|
||||
),
|
||||
{
|
||||
in: this.teamMap.multiLog.id,
|
||||
tx: this.teamMap.multiLog.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
this.teamMap = this.teamMap.edit((map) => {
|
||||
map.set(
|
||||
"readKey",
|
||||
{
|
||||
keyID: newReadKey.id,
|
||||
revelation: newReadKeyRevelation,
|
||||
previousKeys: {
|
||||
[currentReadKey.id]: sealKeySecret({
|
||||
sealing: newReadKey,
|
||||
toSeal: currentReadKey,
|
||||
}).encrypted,
|
||||
},
|
||||
},
|
||||
"trusting"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
removeMember(agentID: AgentID) {
|
||||
this.teamMap = this.teamMap.edit((map) => {
|
||||
map.set(agentID, "revoked", "trusting");
|
||||
});
|
||||
|
||||
this.rotateReadKey();
|
||||
}
|
||||
|
||||
createMap<M extends { [key: string]: JsonValue }, Meta extends JsonValue>(
|
||||
meta?: M
|
||||
): CoMap<M, Meta> {
|
||||
return this.node
|
||||
.createMultiLog({
|
||||
type: "comap",
|
||||
ruleset: {
|
||||
type: "ownedByTeam",
|
||||
team: this.teamMap.id,
|
||||
},
|
||||
meta: meta || null,
|
||||
})
|
||||
.getCurrentContent() as CoMap<M, Meta>;
|
||||
}
|
||||
|
||||
testWithDifferentCredentials(
|
||||
credential: AgentCredential,
|
||||
sessionId: SessionID
|
||||
): Team {
|
||||
return new Team(
|
||||
expectTeamContent(
|
||||
this.teamMap.multiLog
|
||||
.testWithDifferentCredentials(credential, sessionId)
|
||||
.getCurrentContent()
|
||||
),
|
||||
this.node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user