Merge pull request #7 from gardencmp/anselm/gar-94-simpler-team-logic
Implement simpler team logic without history
This commit is contained in:
@@ -9,14 +9,14 @@ import {
|
||||
KeySecret,
|
||||
Signature,
|
||||
StreamingHash,
|
||||
openAs,
|
||||
unseal,
|
||||
shortHash,
|
||||
sign,
|
||||
verify,
|
||||
encryptForTransaction,
|
||||
decryptForTransaction,
|
||||
KeyID,
|
||||
unsealKeySecret,
|
||||
decryptKeySecret,
|
||||
getAgentSignatoryID,
|
||||
getAgentRecipientID,
|
||||
} from "./crypto.js";
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Team,
|
||||
determineValidTransactions,
|
||||
expectTeamContent,
|
||||
isKeyForKeyField,
|
||||
} from "./permissions.js";
|
||||
import { LocalNode } from "./node.js";
|
||||
import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
||||
@@ -158,7 +159,7 @@ export class CoValue {
|
||||
newSignature: Signature
|
||||
): boolean {
|
||||
const signatoryID = getAgentSignatoryID(
|
||||
this.node.resolveAccount(
|
||||
this.node.resolveAccountAgent(
|
||||
accountOrAgentIDfromSessionID(sessionID),
|
||||
"Expected to know signatory of transaction"
|
||||
)
|
||||
@@ -376,7 +377,7 @@ export class CoValue {
|
||||
if (this.header.ruleset.type === "team") {
|
||||
const content = expectTeamContent(this.getCurrentContent());
|
||||
|
||||
const currentKeyId = content.get("readKey")?.keyID;
|
||||
const currentKeyId = content.get("readKey");
|
||||
|
||||
if (!currentKeyId) {
|
||||
throw new Error("No readKey set");
|
||||
@@ -403,63 +404,63 @@ export class CoValue {
|
||||
if (this.header.ruleset.type === "team") {
|
||||
const content = expectTeamContent(this.getCurrentContent());
|
||||
|
||||
const readKeyHistory = content.getHistory("readKey");
|
||||
// Try to find key revelation for us
|
||||
|
||||
// Try to find direct relevation of key for us
|
||||
const readKeyEntry = content.getLastEntry(`${keyID}_for_${this.node.account.id}`);
|
||||
|
||||
for (const entry of readKeyHistory) {
|
||||
if (entry.value?.keyID === keyID) {
|
||||
const revealer = accountOrAgentIDfromSessionID(
|
||||
entry.txID.sessionID
|
||||
);
|
||||
const revealerAgent = this.node.resolveAccount(
|
||||
revealer,
|
||||
"Expected to know revealer"
|
||||
);
|
||||
if (readKeyEntry) {
|
||||
const revealer = accountOrAgentIDfromSessionID(
|
||||
readKeyEntry.txID.sessionID
|
||||
);
|
||||
const revealerAgent = this.node.resolveAccountAgent(
|
||||
revealer,
|
||||
"Expected to know revealer"
|
||||
);
|
||||
|
||||
const secret = openAs(
|
||||
entry.value.revelation,
|
||||
this.node.account.currentRecipientSecret(),
|
||||
getAgentRecipientID(revealerAgent),
|
||||
{
|
||||
in: this.id,
|
||||
tx: entry.txID,
|
||||
}
|
||||
);
|
||||
const secret = unseal(
|
||||
readKeyEntry.value,
|
||||
this.node.account.currentRecipientSecret(),
|
||||
getAgentRecipientID(revealerAgent),
|
||||
{
|
||||
in: this.id,
|
||||
tx: readKeyEntry.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) {
|
||||
const encryptedPreviousKey = entry.value?.previousKeys?.[keyID];
|
||||
if (entry.value && encryptedPreviousKey) {
|
||||
const sealingKeyID = entry.value.keyID;
|
||||
const sealingKeySecret = this.getReadKey(sealingKeyID);
|
||||
for (const field of content.keys()) {
|
||||
if (isKeyForKeyField(field) && field.startsWith(keyID)) {
|
||||
const encryptingKeyID = field.split("_for_")[1] as KeyID;
|
||||
const encryptingKeySecret = this.getReadKey(encryptingKeyID);
|
||||
|
||||
if (!sealingKeySecret) {
|
||||
if (!encryptingKeySecret) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const secret = unsealKeySecret(
|
||||
const encryptedPreviousKey = content.get(field)!;
|
||||
|
||||
const secret = decryptKeySecret(
|
||||
{
|
||||
sealed: keyID,
|
||||
sealing: sealingKeyID,
|
||||
encryptedID: keyID,
|
||||
encryptingID: encryptingKeyID,
|
||||
encrypted: encryptedPreviousKey,
|
||||
},
|
||||
sealingKeySecret
|
||||
encryptingKeySecret
|
||||
);
|
||||
|
||||
if (secret) {
|
||||
return secret;
|
||||
} else {
|
||||
console.error(
|
||||
`Sealing ${sealingKeyID} key didn't unseal ${keyID}`
|
||||
`Encrypting ${encryptingKeyID} key didn't decrypt ${keyID}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -551,6 +552,4 @@ export class CoValue {
|
||||
? [this.header.ruleset.team]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
export { SessionID };
|
||||
}
|
||||
@@ -116,6 +116,21 @@ export class CoMap<
|
||||
return lastEntry.txID;
|
||||
}
|
||||
|
||||
getLastEntry<KK extends K>(key: KK): { at: number; txID: TransactionID; value: M[KK]; } | undefined {
|
||||
const ops = this.ops[key];
|
||||
if (!ops) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lastEntry = ops[ops.length - 1]!;
|
||||
|
||||
if (lastEntry.op === "delete") {
|
||||
return undefined;
|
||||
} else {
|
||||
return { at: lastEntry.madeAt, txID: lastEntry.txID, value: lastEntry.value };
|
||||
}
|
||||
}
|
||||
|
||||
getHistory<KK extends K>(key: KK): { at: number; txID: TransactionID; value: M[KK] | undefined; }[] {
|
||||
const ops = this.ops[key];
|
||||
if (!ops) {
|
||||
|
||||
@@ -6,14 +6,14 @@ import {
|
||||
newRandomSignatory,
|
||||
seal,
|
||||
sign,
|
||||
openAs,
|
||||
unseal,
|
||||
verify,
|
||||
shortHash,
|
||||
newRandomKeySecret,
|
||||
encryptForTransaction,
|
||||
decryptForTransaction,
|
||||
sealKeySecret,
|
||||
unsealKeySecret,
|
||||
encryptKeySecret,
|
||||
decryptKeySecret,
|
||||
} from './crypto.js';
|
||||
import { base58, base64url } from "@scure/base";
|
||||
import { x25519 } from "@noble/curves/ed25519";
|
||||
@@ -41,12 +41,11 @@ test("Invalid signatures don't verify", () => {
|
||||
expect(verify(wrongSignature, data, getSignatoryID(signatory))).toBe(false);
|
||||
});
|
||||
|
||||
test("Sealing round-trips, but invalid receiver can't unseal", () => {
|
||||
test("encrypting round-trips, but invalid receiver can't unseal", () => {
|
||||
const data = { b: "world", a: "hello" };
|
||||
const sender = newRandomRecipient();
|
||||
const recipient1 = newRandomRecipient();
|
||||
const recipient2 = newRandomRecipient();
|
||||
const recipient3 = newRandomRecipient();
|
||||
const recipient = newRandomRecipient();
|
||||
const wrongRecipient = newRandomRecipient();
|
||||
|
||||
const nOnceMaterial = {
|
||||
in: "co_zTEST",
|
||||
@@ -56,34 +55,29 @@ test("Sealing round-trips, but invalid receiver can't unseal", () => {
|
||||
const sealed = seal(
|
||||
data,
|
||||
sender,
|
||||
new Set([getRecipientID(recipient1), getRecipientID(recipient2)]),
|
||||
getRecipientID(recipient),
|
||||
nOnceMaterial
|
||||
);
|
||||
|
||||
expect(sealed[getRecipientID(recipient1)]).toMatch(/^sealed_U/);
|
||||
expect(sealed[getRecipientID(recipient2)]).toMatch(/^sealed_U/);
|
||||
expect(
|
||||
openAs(sealed, recipient1, getRecipientID(sender), nOnceMaterial)
|
||||
unseal(sealed, recipient, getRecipientID(sender), nOnceMaterial)
|
||||
).toEqual(data);
|
||||
expect(
|
||||
openAs(sealed, recipient2, getRecipientID(sender), nOnceMaterial)
|
||||
).toEqual(data);
|
||||
expect(
|
||||
openAs(sealed, recipient3, getRecipientID(sender), nOnceMaterial)
|
||||
).toBeUndefined();
|
||||
() => unseal(sealed, wrongRecipient, getRecipientID(sender), nOnceMaterial)
|
||||
).toThrow(/Wrong tag/);
|
||||
|
||||
// trying with wrong recipient secret, by hand
|
||||
const nOnce = blake3(
|
||||
new TextEncoder().encode(stableStringify(nOnceMaterial))
|
||||
).slice(0, 24);
|
||||
const recipient3priv = base58.decode(
|
||||
recipient3.substring("recipientSecret_z".length)
|
||||
wrongRecipient.substring("recipientSecret_z".length)
|
||||
);
|
||||
const senderPub = base58.decode(
|
||||
getRecipientID(sender).substring("recipient_z".length)
|
||||
);
|
||||
const sealedBytes = base64url.decode(
|
||||
sealed[getRecipientID(recipient1)]!.substring("sealed_U".length)
|
||||
sealed.substring("sealed_U".length)
|
||||
);
|
||||
const sharedSecret = x25519.getSharedSecret(recipient3priv, senderPub);
|
||||
|
||||
@@ -156,34 +150,34 @@ test("Encryption for transactions doesn't decrypt with a wrong key", () => {
|
||||
});
|
||||
|
||||
test("Encryption of keySecrets round-trips", () => {
|
||||
const toSeal = newRandomKeySecret();
|
||||
const sealing = newRandomKeySecret();
|
||||
const toEncrypt = newRandomKeySecret();
|
||||
const encrypting = newRandomKeySecret();
|
||||
|
||||
const keys = {
|
||||
toSeal,
|
||||
sealing,
|
||||
toEncrypt,
|
||||
encrypting,
|
||||
};
|
||||
|
||||
const sealed = sealKeySecret(keys);
|
||||
const encrypted = encryptKeySecret(keys);
|
||||
|
||||
const unsealed = unsealKeySecret(sealed, sealing.secret);
|
||||
const decrypted = decryptKeySecret(encrypted, encrypting.secret);
|
||||
|
||||
expect(unsealed).toEqual(toSeal.secret);
|
||||
expect(decrypted).toEqual(toEncrypt.secret);
|
||||
});
|
||||
|
||||
test("Encryption of keySecrets doesn't unseal with a wrong key", () => {
|
||||
const toSeal = newRandomKeySecret();
|
||||
const sealing = newRandomKeySecret();
|
||||
const sealingWrong = newRandomKeySecret();
|
||||
test("Encryption of keySecrets doesn't decrypt with a wrong key", () => {
|
||||
const toEncrypt = newRandomKeySecret();
|
||||
const encrypting = newRandomKeySecret();
|
||||
const encryptingWrong = newRandomKeySecret();
|
||||
|
||||
const keys = {
|
||||
toSeal,
|
||||
sealing,
|
||||
toEncrypt,
|
||||
encrypting,
|
||||
};
|
||||
|
||||
const sealed = sealKeySecret(keys);
|
||||
const encrypted = encryptKeySecret(keys);
|
||||
|
||||
const unsealed = unsealKeySecret(sealed, sealingWrong.secret);
|
||||
const decrypted = decryptKeySecret(encrypted, encryptingWrong.secret);
|
||||
|
||||
expect(unsealed).toBeUndefined();
|
||||
expect(decrypted).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -127,53 +127,40 @@ export function getAgentRecipientSecret(agentSecret: AgentSecret): RecipientSecr
|
||||
return agentSecret.split("/")[0] as RecipientSecret;
|
||||
}
|
||||
|
||||
export type SealedSet<T> = {
|
||||
[recipient: RecipientID]: Sealed<T>;
|
||||
};
|
||||
|
||||
export function seal<T extends JsonValue>(
|
||||
message: T,
|
||||
from: RecipientSecret,
|
||||
to: Set<RecipientID>,
|
||||
to: RecipientID,
|
||||
nOnceMaterial: { in: RawCoValueID; tx: TransactionID }
|
||||
): SealedSet<T> {
|
||||
): Sealed<T> {
|
||||
const nOnce = blake3(
|
||||
textEncoder.encode(stableStringify(nOnceMaterial))
|
||||
).slice(0, 24);
|
||||
|
||||
const recipientsSorted = Array.from(to).sort();
|
||||
const recipientPubs = recipientsSorted.map((recipient) => {
|
||||
return base58.decode(recipient.substring("recipient_z".length));
|
||||
});
|
||||
const recipientPub = base58.decode(to.substring("recipient_z".length));
|
||||
|
||||
const senderPriv = base58.decode(
|
||||
from.substring("recipientSecret_z".length)
|
||||
);
|
||||
|
||||
const plaintext = textEncoder.encode(stableStringify(message));
|
||||
|
||||
const sealedSet: SealedSet<T> = {};
|
||||
const sharedSecret = x25519.getSharedSecret(
|
||||
senderPriv,
|
||||
recipientPub
|
||||
);
|
||||
|
||||
for (let i = 0; i < recipientsSorted.length; i++) {
|
||||
const recipient = recipientsSorted[i]!;
|
||||
const sharedSecret = x25519.getSharedSecret(
|
||||
senderPriv,
|
||||
recipientPubs[i]!
|
||||
);
|
||||
const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt(
|
||||
plaintext
|
||||
);
|
||||
|
||||
const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt(
|
||||
plaintext
|
||||
);
|
||||
|
||||
sealedSet[recipient] = `sealed_U${base64url.encode(
|
||||
sealedBytes
|
||||
)}` as Sealed<T>;
|
||||
}
|
||||
|
||||
return sealedSet;
|
||||
return `sealed_U${base64url.encode(
|
||||
sealedBytes
|
||||
)}` as Sealed<T>
|
||||
}
|
||||
|
||||
export function openAs<T extends JsonValue>(
|
||||
sealedSet: SealedSet<T>,
|
||||
export function unseal<T extends JsonValue>(
|
||||
sealed: Sealed<T>,
|
||||
recipient: RecipientSecret,
|
||||
from: RecipientID,
|
||||
nOnceMaterial: { in: RawCoValueID; tx: TransactionID }
|
||||
@@ -188,12 +175,6 @@ export function openAs<T extends JsonValue>(
|
||||
|
||||
const senderPub = base58.decode(from.substring("recipient_z".length));
|
||||
|
||||
const sealed = sealedSet[getRecipientID(recipient)];
|
||||
|
||||
if (!sealed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sealedBytes = base64url.decode(sealed.substring("sealed_U".length));
|
||||
|
||||
const sharedSecret = x25519.getSharedSecret(recipientPriv, senderPub);
|
||||
@@ -287,25 +268,25 @@ export function encryptForTransaction<T extends JsonValue>(
|
||||
return encrypt(value, keySecret, nOnceMaterial);
|
||||
}
|
||||
|
||||
export function sealKeySecret(keys: {
|
||||
toSeal: { id: KeyID; secret: KeySecret };
|
||||
sealing: { id: KeyID; secret: KeySecret };
|
||||
export function encryptKeySecret(keys: {
|
||||
toEncrypt: { id: KeyID; secret: KeySecret };
|
||||
encrypting: { id: KeyID; secret: KeySecret };
|
||||
}): {
|
||||
sealed: KeyID;
|
||||
sealing: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { sealed: KeyID; sealing: KeyID }>;
|
||||
encryptedID: KeyID;
|
||||
encryptingID: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { encryptedID: KeyID; encryptingID: KeyID }>;
|
||||
} {
|
||||
const nOnceMaterial = {
|
||||
sealed: keys.toSeal.id,
|
||||
sealing: keys.sealing.id,
|
||||
encryptedID: keys.toEncrypt.id,
|
||||
encryptingID: keys.encrypting.id,
|
||||
};
|
||||
|
||||
return {
|
||||
sealed: keys.toSeal.id,
|
||||
sealing: keys.sealing.id,
|
||||
encryptedID: keys.toEncrypt.id,
|
||||
encryptingID: keys.encrypting.id,
|
||||
encrypted: encrypt(
|
||||
keys.toSeal.secret,
|
||||
keys.sealing.secret,
|
||||
keys.toEncrypt.secret,
|
||||
keys.encrypting.secret,
|
||||
nOnceMaterial
|
||||
),
|
||||
};
|
||||
@@ -343,20 +324,20 @@ export function decryptForTransaction<T extends JsonValue>(
|
||||
return decrypt(encrypted, keySecret, nOnceMaterial);
|
||||
}
|
||||
|
||||
export function unsealKeySecret(
|
||||
sealedInfo: {
|
||||
sealed: KeyID;
|
||||
sealing: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { sealed: KeyID; sealing: KeyID }>;
|
||||
export function decryptKeySecret(
|
||||
encryptedInfo: {
|
||||
encryptedID: KeyID;
|
||||
encryptingID: KeyID;
|
||||
encrypted: Encrypted<KeySecret, { encryptedID: KeyID; encryptingID: KeyID }>;
|
||||
},
|
||||
sealingSecret: KeySecret
|
||||
): KeySecret | undefined {
|
||||
const nOnceMaterial = {
|
||||
sealed: sealedInfo.sealed,
|
||||
sealing: sealedInfo.sealing,
|
||||
encryptedID: encryptedInfo.encryptedID,
|
||||
encryptingID: encryptedInfo.encryptingID,
|
||||
};
|
||||
|
||||
return decrypt(sealedInfo.encrypted, sealingSecret, nOnceMaterial);
|
||||
return decrypt(encryptedInfo.encrypted, sealingSecret, nOnceMaterial);
|
||||
}
|
||||
|
||||
export function uniquenessForHeader(): `z${string}` {
|
||||
|
||||
53
src/node.ts
53
src/node.ts
@@ -87,27 +87,31 @@ export class LocalNode {
|
||||
|
||||
const account = this.createCoValue(
|
||||
accountHeaderForInitialAgentSecret(agentSecret)
|
||||
).testWithDifferentAccount(new AnonymousControlledAccount(agentSecret), newRandomSessionID(getAgentID(agentSecret)));
|
||||
).testWithDifferentAccount(
|
||||
new AnonymousControlledAccount(agentSecret),
|
||||
newRandomSessionID(getAgentID(agentSecret))
|
||||
);
|
||||
|
||||
expectTeamContent(account.getCurrentContent()).edit((editable) => {
|
||||
editable.set(getAgentID(agentSecret), "admin", "trusting");
|
||||
|
||||
const readKey = newRandomKeySecret();
|
||||
const revelation = seal(
|
||||
readKey.secret,
|
||||
getAgentRecipientSecret(agentSecret),
|
||||
new Set([getAgentRecipientID(getAgentID(agentSecret))]),
|
||||
{
|
||||
in: account.id,
|
||||
tx: account.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKey.id, revelation },
|
||||
`${readKey.id}_for_${getAgentID(agentSecret)}`,
|
||||
seal(
|
||||
readKey.secret,
|
||||
getAgentRecipientSecret(agentSecret),
|
||||
getAgentRecipientID(getAgentID(agentSecret)),
|
||||
{
|
||||
in: account.id,
|
||||
tx: account.nextTransactionID(),
|
||||
}
|
||||
),
|
||||
"trusting"
|
||||
);
|
||||
|
||||
editable.set('readKey', readKey.id, "trusting");
|
||||
});
|
||||
|
||||
return new ControlledAccount(
|
||||
@@ -117,7 +121,7 @@ export class LocalNode {
|
||||
);
|
||||
}
|
||||
|
||||
resolveAccount(id: AccountIDOrAgentID, expectation?: string): AgentID {
|
||||
resolveAccountAgent(id: AccountIDOrAgentID, expectation?: string): AgentID {
|
||||
if (isAgentID(id)) {
|
||||
return id;
|
||||
}
|
||||
@@ -159,21 +163,22 @@ export class LocalNode {
|
||||
editable.set(this.account.id, "admin", "trusting");
|
||||
|
||||
const readKey = newRandomKeySecret();
|
||||
const revelation = seal(
|
||||
readKey.secret,
|
||||
this.account.currentRecipientSecret(),
|
||||
new Set([this.account.currentRecipientID()]),
|
||||
{
|
||||
in: teamCoValue.id,
|
||||
tx: teamCoValue.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKey.id, revelation },
|
||||
`${readKey.id}_for_${this.account.id}`,
|
||||
seal(
|
||||
readKey.secret,
|
||||
this.account.currentRecipientSecret(),
|
||||
this.account.currentRecipientID(),
|
||||
{
|
||||
in: teamCoValue.id,
|
||||
tx: teamCoValue.nextTransactionID(),
|
||||
}
|
||||
),
|
||||
"trusting"
|
||||
);
|
||||
|
||||
editable.set('readKey', readKey.id, "trusting");
|
||||
});
|
||||
|
||||
return new Team(teamContent, this);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getRecipientID,
|
||||
newRandomKeySecret,
|
||||
seal,
|
||||
sealKeySecret,
|
||||
encryptKeySecret,
|
||||
} from "./crypto.js";
|
||||
import {
|
||||
newTeam,
|
||||
@@ -424,17 +424,23 @@ test("Admins can set team read key and then use it to create and read private tr
|
||||
const revelation = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation, "trusting");
|
||||
|
||||
expect(editable.get(`${readKeyID}_for_${admin.id}`)).toEqual(
|
||||
revelation
|
||||
);
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
|
||||
expect(editable.get("readKey")).toEqual(readKeyID);
|
||||
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
@@ -483,16 +489,31 @@ test("Admins can set team read key and then writers can use it to create and rea
|
||||
editable.set(writer.id, "writer", "trusting");
|
||||
expect(editable.get(writer.id)).toEqual("writer");
|
||||
|
||||
const revelation = seal(
|
||||
const revelation1 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID(), writer.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation1, "trusting");
|
||||
|
||||
const revelation2 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
writer.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${writer.id}`, revelation2, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
});
|
||||
|
||||
const childObject = node.createCoValue({
|
||||
@@ -560,16 +581,31 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
editable.set(reader.id, "reader", "trusting");
|
||||
expect(editable.get(reader.id)).toEqual("reader");
|
||||
|
||||
const revelation = seal(
|
||||
const revelation1 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID(), reader.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation1, "trusting");
|
||||
|
||||
const revelation2 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
reader.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${reader.id}`, revelation2, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
});
|
||||
|
||||
const childObject = node.createCoValue({
|
||||
@@ -640,32 +676,28 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
const revelation1 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID(), reader1.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKeyID, revelation: revelation1 },
|
||||
"trusting"
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation1, "trusting");
|
||||
|
||||
const revelation2 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([reader2.currentRecipientID()]),
|
||||
reader1.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set(
|
||||
"readKey",
|
||||
{ keyID: readKeyID, revelation: revelation2 },
|
||||
"trusting"
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${reader1.id}`, revelation2, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
});
|
||||
|
||||
const childObject = node.createCoValue({
|
||||
@@ -694,6 +726,20 @@ test("Admins can set team read key and then use it to create private transaction
|
||||
|
||||
expect(childContentAsReader1.get("foo")).toEqual("bar");
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation3 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
reader2.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${reader2.id}`, revelation3, "trusting");
|
||||
});
|
||||
|
||||
const childObjectAsReader2 = childObject.testWithDifferentAccount(
|
||||
reader2,
|
||||
newRandomSessionID(reader2.id)
|
||||
@@ -753,17 +799,17 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
const revelation = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
expect(editable.get("readKey")).toEqual(readKeyID);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
@@ -791,18 +837,17 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
const revelation = seal(
|
||||
readKey2,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set("readKey", { keyID: readKeyID2, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
editable.set(`${readKeyID2}_for_${admin.id}`, revelation, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID2, "trusting");
|
||||
expect(editable.get("readKey")).toEqual(readKeyID2);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
});
|
||||
|
||||
@@ -863,17 +908,17 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
const revelation = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
expect(editable.get("readKey")).toEqual(readKeyID);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
});
|
||||
|
||||
@@ -892,34 +937,42 @@ test("Admins can set team read key, make a private transaction in an owned objec
|
||||
const { secret: readKey2, id: readKeyID2 } = newRandomKeySecret();
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation = seal(
|
||||
const revelation2 = seal(
|
||||
readKey2,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID(), reader.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(
|
||||
"readKey",
|
||||
editable.set(`${readKeyID2}_for_${admin.id}`, revelation2, "trusting");
|
||||
|
||||
const revelation3 = seal(
|
||||
readKey2,
|
||||
admin.currentRecipientSecret(),
|
||||
reader.currentRecipientID(),
|
||||
{
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
previousKeys: {
|
||||
[readKeyID]: sealKeySecret({
|
||||
toSeal: { id: readKeyID, secret: readKey },
|
||||
sealing: { id: readKeyID2, secret: readKey2 },
|
||||
}).encrypted,
|
||||
},
|
||||
},
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID2}_for_${reader.id}`, revelation3, "trusting");
|
||||
|
||||
editable.set(
|
||||
`${readKeyID}_for_${readKeyID2}`,
|
||||
encryptKeySecret({
|
||||
toEncrypt: { id: readKeyID, secret: readKey },
|
||||
encrypting: { id: readKeyID2, secret: readKey2 },
|
||||
}).encrypted,
|
||||
"trusting"
|
||||
);
|
||||
expect(editable.get("readKey")).toMatchObject({
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set("readKey", readKeyID2, "trusting");
|
||||
|
||||
expect(editable.get("readKey")).toEqual(readKeyID2);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
editable.set(reader.id, "reader", "trusting");
|
||||
@@ -1001,24 +1054,44 @@ test("Admins can set team read rey, make a private transaction in an owned objec
|
||||
const reader2 = node.createAccount("reader2");
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation = seal(
|
||||
const revelation1 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([
|
||||
admin.currentRecipientID(),
|
||||
reader.currentRecipientID(),
|
||||
reader2.currentRecipientID(),
|
||||
]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set(`${readKeyID}_for_${admin.id}`, revelation1, "trusting");
|
||||
|
||||
const revelation2 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
reader.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${reader.id}`, revelation2, "trusting");
|
||||
|
||||
const revelation3 = seal(
|
||||
readKey,
|
||||
admin.currentRecipientSecret(),
|
||||
reader2.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(`${readKeyID}_for_${reader2.id}`, revelation3, "trusting");
|
||||
|
||||
editable.set("readKey", readKeyID, "trusting");
|
||||
expect(editable.get("readKey")).toEqual(readKeyID);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey);
|
||||
|
||||
editable.set(reader.id, "reader", "trusting");
|
||||
@@ -1058,20 +1131,40 @@ test("Admins can set team read rey, make a private transaction in an owned objec
|
||||
const { secret: readKey2, id: readKeyID2 } = newRandomKeySecret();
|
||||
|
||||
teamContent.edit((editable) => {
|
||||
const revelation = seal(
|
||||
const newRevelation1 = seal(
|
||||
readKey2,
|
||||
admin.currentRecipientSecret(),
|
||||
new Set([admin.currentRecipientID(), reader2.currentRecipientID()]),
|
||||
admin.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
editable.set("readKey", { keyID: readKeyID2, revelation }, "trusting");
|
||||
expect(editable.get("readKey")).toEqual({
|
||||
keyID: readKeyID2,
|
||||
revelation,
|
||||
});
|
||||
|
||||
editable.set(
|
||||
`${readKeyID2}_for_${admin.id}`,
|
||||
newRevelation1,
|
||||
"trusting"
|
||||
);
|
||||
|
||||
const newRevelation2 = seal(
|
||||
readKey2,
|
||||
admin.currentRecipientSecret(),
|
||||
reader2.currentRecipientID(),
|
||||
{
|
||||
in: team.id,
|
||||
tx: team.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
editable.set(
|
||||
`${readKeyID2}_for_${reader2.id}`,
|
||||
newRevelation2,
|
||||
"trusting"
|
||||
);
|
||||
|
||||
editable.set("readKey", readKeyID2, "trusting");
|
||||
expect(editable.get("readKey")).toEqual(readKeyID2);
|
||||
expect(team.getCurrentReadKey().secret).toEqual(readKey2);
|
||||
|
||||
editable.set(reader.id, "revoked", "trusting");
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { CoValueID, ContentType } from './contentType.js';
|
||||
import { CoMap, MapOpPayload } from './contentTypes/coMap.js';
|
||||
import { JsonValue } from './jsonValue.js';
|
||||
import { CoValueID, ContentType } from "./contentType.js";
|
||||
import { CoMap, MapOpPayload } from "./contentTypes/coMap.js";
|
||||
import { JsonValue } from "./jsonValue.js";
|
||||
import {
|
||||
Encrypted,
|
||||
KeyID,
|
||||
KeySecret,
|
||||
SealedSet,
|
||||
createdNowUnique,
|
||||
newRandomKeySecret,
|
||||
seal,
|
||||
sealKeySecret,
|
||||
getAgentRecipientID
|
||||
} from './crypto.js';
|
||||
encryptKeySecret,
|
||||
getAgentRecipientID,
|
||||
Sealed,
|
||||
} from "./crypto.js";
|
||||
import {
|
||||
CoValue,
|
||||
Transaction,
|
||||
TrustingTransaction,
|
||||
accountOrAgentIDfromSessionID,
|
||||
} from './coValue.js';
|
||||
} from "./coValue.js";
|
||||
import { LocalNode } from "./node.js";
|
||||
import { RawCoValueID, SessionID, TransactionID, isAgentID } from './ids.js';
|
||||
import { AccountIDOrAgentID, GeneralizedControlledAccount } from './account.js';
|
||||
import { RawCoValueID, SessionID, TransactionID, isAgentID } from "./ids.js";
|
||||
import { AccountIDOrAgentID, GeneralizedControlledAccount } from "./account.js";
|
||||
|
||||
export type PermissionsDef =
|
||||
| { type: "team"; initialAdmin: AccountIDOrAgentID; }
|
||||
| { type: "team"; initialAdmin: AccountIDOrAgentID }
|
||||
| { type: "ownedByTeam"; team: RawCoValueID }
|
||||
| { type: "unsafeAllowAll" };
|
||||
|
||||
@@ -94,6 +94,14 @@ export function determineValidTransactions(
|
||||
continue;
|
||||
}
|
||||
|
||||
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
||||
continue;
|
||||
} else if (isKeyForKeyField(change.key) || isKeyForAccountField(change.key)) {
|
||||
if (memberState[transactor] !== "admin") {
|
||||
console.warn("Only admins can reveal keys");
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: check validity of agents who the key is revealed to?
|
||||
|
||||
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
||||
@@ -146,11 +154,12 @@ export function determineValidTransactions(
|
||||
|
||||
return validTransactions;
|
||||
} else if (coValue.header.ruleset.type === "ownedByTeam") {
|
||||
const teamContent =
|
||||
coValue.node.expectCoValueLoaded(
|
||||
const teamContent = coValue.node
|
||||
.expectCoValueLoaded(
|
||||
coValue.header.ruleset.team,
|
||||
"Determining valid transaction in owned object but its team wasn't loaded"
|
||||
).getCurrentContent();
|
||||
)
|
||||
.getCurrentContent();
|
||||
|
||||
if (teamContent.type !== "comap") {
|
||||
throw new Error("Team must be a map");
|
||||
@@ -158,7 +167,9 @@ export function determineValidTransactions(
|
||||
|
||||
return Object.entries(coValue.sessions).flatMap(
|
||||
([sessionID, sessionLog]) => {
|
||||
const transactor = accountOrAgentIDfromSessionID(sessionID as SessionID);
|
||||
const transactor = accountOrAgentIDfromSessionID(
|
||||
sessionID as SessionID
|
||||
);
|
||||
return sessionLog.transactions
|
||||
.filter((tx) => {
|
||||
const transactorRoleAtTxTime = teamContent.getAtTime(
|
||||
@@ -187,24 +198,25 @@ export function determineValidTransactions(
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unknown ruleset type " + (coValue.header.ruleset as any).type);
|
||||
throw new Error(
|
||||
"Unknown ruleset type " + (coValue.header.ruleset as any).type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type TeamContent = { [key: AccountIDOrAgentID]: Role } & {
|
||||
readKey: {
|
||||
keyID: KeyID;
|
||||
revelation: SealedSet<KeySecret>;
|
||||
previousKeys?: {
|
||||
[key: KeyID]: Encrypted<
|
||||
KeySecret,
|
||||
{ sealed: KeyID; sealing: KeyID }
|
||||
>;
|
||||
};
|
||||
};
|
||||
export type TeamContent = {
|
||||
[key: AccountIDOrAgentID]: Role;
|
||||
readKey: KeyID;
|
||||
[revelationFor: `${KeyID}_for_${AccountIDOrAgentID}`]: Sealed<KeySecret>;
|
||||
[oldKeyForNewKey: `${KeyID}_for_${KeyID}`]: Encrypted<
|
||||
KeySecret,
|
||||
{ encryptedID: KeyID; encryptingID: KeyID }
|
||||
>;
|
||||
};
|
||||
|
||||
export function expectTeamContent(content: ContentType): CoMap<TeamContent, {}> {
|
||||
export function expectTeamContent(
|
||||
content: ContentType
|
||||
): CoMap<TeamContent, {}> {
|
||||
if (content.type !== "comap") {
|
||||
throw new Error("Expected map");
|
||||
}
|
||||
@@ -227,36 +239,34 @@ export class Team {
|
||||
|
||||
addMember(accountID: AccountIDOrAgentID, role: Role) {
|
||||
this.teamMap = this.teamMap.edit((map) => {
|
||||
const agent = this.node.resolveAccount(accountID, "Expected to know agent to add them to team");
|
||||
|
||||
if (!agent) {
|
||||
throw new Error("Unknown account/agent " + accountID);
|
||||
}
|
||||
|
||||
map.set(accountID, role, "trusting");
|
||||
if (map.get(accountID) !== role) {
|
||||
throw new Error("Failed to set role");
|
||||
}
|
||||
|
||||
const currentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
||||
|
||||
if (!currentReadKey.secret) {
|
||||
throw new Error("Can't add member without read key secret");
|
||||
}
|
||||
|
||||
const revelation = seal(
|
||||
currentReadKey.secret,
|
||||
this.teamMap.coValue.node.account.currentRecipientSecret(),
|
||||
new Set([getAgentRecipientID(agent)]),
|
||||
{
|
||||
in: this.teamMap.coValue.id,
|
||||
tx: this.teamMap.coValue.nextTransactionID(),
|
||||
}
|
||||
const agent = this.node.resolveAccountAgent(
|
||||
accountID,
|
||||
"Expected to know agent to add them to team"
|
||||
);
|
||||
|
||||
map.set(accountID, role, "trusting");
|
||||
|
||||
if (map.get(accountID) !== role) {
|
||||
throw new Error("Failed to set role");
|
||||
}
|
||||
|
||||
map.set(
|
||||
"readKey",
|
||||
{ keyID: currentReadKey.id, revelation },
|
||||
`${currentReadKey.id}_for_${accountID}`,
|
||||
seal(
|
||||
currentReadKey.secret,
|
||||
this.teamMap.coValue.node.account.currentRecipientSecret(),
|
||||
getAgentRecipientID(agent),
|
||||
{
|
||||
in: this.teamMap.coValue.id,
|
||||
tx: this.teamMap.coValue.nextTransactionID(),
|
||||
}
|
||||
),
|
||||
"trusting"
|
||||
);
|
||||
});
|
||||
@@ -277,7 +287,9 @@ export class Team {
|
||||
const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
||||
|
||||
if (!maybeCurrentReadKey.secret) {
|
||||
throw new Error("Can't rotate read key secret we don't have access to");
|
||||
throw new Error(
|
||||
"Can't rotate read key secret we don't have access to"
|
||||
);
|
||||
}
|
||||
|
||||
const currentReadKey = {
|
||||
@@ -287,41 +299,38 @@ export class Team {
|
||||
|
||||
const newReadKey = newRandomKeySecret();
|
||||
|
||||
const newReadKeyRevelation = seal(
|
||||
newReadKey.secret,
|
||||
this.teamMap.coValue.node.account.currentRecipientSecret(),
|
||||
new Set(
|
||||
currentlyPermittedReaders.map(
|
||||
(reader) => {
|
||||
const readerAgent = this.node.resolveAccount(reader, "Expected to know currently permitted reader");
|
||||
if (!readerAgent) {
|
||||
throw new Error("Unknown agent " + reader);
|
||||
}
|
||||
return getAgentRecipientID(readerAgent)
|
||||
}
|
||||
)
|
||||
),
|
||||
{
|
||||
in: this.teamMap.coValue.id,
|
||||
tx: this.teamMap.coValue.nextTransactionID(),
|
||||
}
|
||||
);
|
||||
|
||||
this.teamMap = this.teamMap.edit((map) => {
|
||||
for (const readerID of currentlyPermittedReaders) {
|
||||
const reader = this.node.resolveAccountAgent(
|
||||
readerID,
|
||||
"Expected to know currently permitted reader"
|
||||
);
|
||||
|
||||
map.set(
|
||||
`${newReadKey.id}_for_${readerID}`,
|
||||
seal(
|
||||
newReadKey.secret,
|
||||
this.teamMap.coValue.node.account.currentRecipientSecret(),
|
||||
getAgentRecipientID(reader),
|
||||
{
|
||||
in: this.teamMap.coValue.id,
|
||||
tx: this.teamMap.coValue.nextTransactionID(),
|
||||
}
|
||||
),
|
||||
"trusting"
|
||||
);
|
||||
}
|
||||
|
||||
map.set(
|
||||
"readKey",
|
||||
{
|
||||
keyID: newReadKey.id,
|
||||
revelation: newReadKeyRevelation,
|
||||
previousKeys: {
|
||||
[currentReadKey.id]: sealKeySecret({
|
||||
sealing: newReadKey,
|
||||
toSeal: currentReadKey,
|
||||
}).encrypted,
|
||||
},
|
||||
},
|
||||
`${currentReadKey.id}_for_${newReadKey.id}`,
|
||||
encryptKeySecret({
|
||||
encrypting: newReadKey,
|
||||
toEncrypt: currentReadKey,
|
||||
}).encrypted,
|
||||
"trusting"
|
||||
);
|
||||
|
||||
map.set("readKey", newReadKey.id, "trusting");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -364,3 +373,11 @@ export class Team {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function isKeyForKeyField(field: string): field is `${KeyID}_for_${KeyID}` {
|
||||
return field.startsWith("key_") && field.includes("_for_key");
|
||||
}
|
||||
|
||||
export function isKeyForAccountField(field: string): field is `${KeyID}_for_${AccountIDOrAgentID}` {
|
||||
return field.startsWith("key_") && (field.includes("_for_recipient") || field.includes("_for_co"));
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from "./crypto.js";
|
||||
import { SessionID, newRandomSessionID } from "./coValue.js";
|
||||
import { newRandomSessionID } from "./coValue.js";
|
||||
import { LocalNode } from "./node.js";
|
||||
import { expectTeamContent } from "./permissions.js";
|
||||
import { AnonymousControlledAccount } from "./account.js";
|
||||
import { SessionID } from "./ids.js";
|
||||
|
||||
export function randomAnonymousAccountAndSessionID(): [AnonymousControlledAccount, SessionID] {
|
||||
const agentSecret = newRandomAgentSecret();
|
||||
|
||||
Reference in New Issue
Block a user