cojson 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +11 -8
- package/dist/coValue.d.ts +97 -0
- package/dist/coValue.js +381 -0
- package/dist/coValue.js.map +1 -0
- package/dist/coValue.test.d.ts +1 -0
- package/dist/coValue.test.js +78 -0
- package/dist/coValue.test.js.map +1 -0
- package/dist/contentType.d.ts +15 -0
- package/dist/contentType.js +7 -0
- package/dist/contentType.js.map +1 -0
- package/dist/contentType.test.d.ts +1 -0
- package/dist/contentType.test.js +149 -0
- package/dist/contentType.test.js.map +1 -0
- package/dist/contentTypes/coList.d.ts +11 -0
- package/dist/contentTypes/coList.js +16 -0
- package/dist/contentTypes/coList.js.map +1 -0
- package/dist/contentTypes/coMap.d.ts +56 -0
- package/dist/contentTypes/coMap.js +126 -0
- package/dist/contentTypes/coMap.js.map +1 -0
- package/dist/contentTypes/coStream.d.ts +11 -0
- package/dist/contentTypes/coStream.js +16 -0
- package/dist/contentTypes/coStream.js.map +1 -0
- package/dist/contentTypes/static.d.ts +11 -0
- package/dist/contentTypes/static.js +14 -0
- package/dist/contentTypes/static.js.map +1 -0
- package/dist/crypto.d.ts +97 -0
- package/dist/crypto.js +156 -0
- package/dist/crypto.js.map +1 -0
- package/dist/crypto.test.d.ts +1 -0
- package/dist/crypto.test.js +115 -0
- package/dist/crypto.test.js.map +1 -0
- package/dist/ids.d.ts +7 -0
- package/dist/ids.js +2 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonValue.d.ts +7 -0
- package/dist/jsonValue.js +2 -0
- package/dist/jsonValue.js.map +1 -0
- package/dist/node.d.ts +33 -0
- package/dist/node.js +113 -0
- package/dist/node.js.map +1 -0
- package/dist/permissions.d.ts +54 -0
- package/dist/permissions.js +218 -0
- package/dist/permissions.js.map +1 -0
- package/dist/permissions.test.d.ts +1 -0
- package/dist/permissions.test.js +794 -0
- package/dist/permissions.test.js.map +1 -0
- package/dist/sync.d.ts +80 -0
- package/dist/sync.js +271 -0
- package/dist/sync.js.map +1 -0
- package/dist/sync.test.d.ts +1 -0
- package/dist/sync.test.js +826 -0
- package/dist/sync.test.js.map +1 -0
- package/package.json +7 -6
- package/src/coValue.test.ts +3 -4
- package/src/coValue.ts +11 -11
- package/src/contentType.test.ts +3 -3
- package/src/contentType.ts +6 -6
- package/src/contentTypes/coList.ts +4 -4
- package/src/contentTypes/coMap.ts +6 -6
- package/src/contentTypes/coStream.ts +4 -4
- package/src/contentTypes/static.ts +5 -5
- package/src/crypto.test.ts +1 -1
- package/src/crypto.ts +2 -2
- package/src/index.ts +8 -8
- package/src/jsonValue.ts +1 -1
- package/src/node.ts +6 -7
- package/src/permissions.test.ts +5 -5
- package/src/permissions.ts +7 -7
- package/src/sync.test.ts +7 -7
- package/src/sync.ts +6 -6
- package/tsconfig.json +1 -7
- package/dist/coValue.mjs +0 -437
- package/dist/coValue.test.mjs +0 -122
- package/dist/contentType.mjs +0 -7
- package/dist/contentType.test.mjs +0 -179
- package/dist/contentTypes/coList.mjs +0 -18
- package/dist/contentTypes/coMap.mjs +0 -126
- package/dist/contentTypes/coStream.mjs +0 -18
- package/dist/contentTypes/static.mjs +0 -16
- package/dist/crypto.mjs +0 -207
- package/dist/crypto.test.mjs +0 -155
- package/dist/ids.mjs +0 -1
- package/dist/index.mjs +0 -21
- package/dist/jsonValue.mjs +0 -1
- package/dist/node.mjs +0 -144
- package/dist/permissions.mjs +0 -244
- package/dist/permissions.test.mjs +0 -985
- package/dist/sync.mjs +0 -318
- package/dist/sync.test.mjs +0 -861
package/dist/crypto.test.mjs
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import {
|
|
3
|
-
getRecipientID,
|
|
4
|
-
getSignatoryID,
|
|
5
|
-
secureHash,
|
|
6
|
-
newRandomRecipient,
|
|
7
|
-
newRandomSignatory,
|
|
8
|
-
seal,
|
|
9
|
-
sign,
|
|
10
|
-
openAs,
|
|
11
|
-
verify,
|
|
12
|
-
shortHash,
|
|
13
|
-
newRandomKeySecret,
|
|
14
|
-
encryptForTransaction,
|
|
15
|
-
decryptForTransaction,
|
|
16
|
-
sealKeySecret,
|
|
17
|
-
unsealKeySecret
|
|
18
|
-
} from "./crypto";
|
|
19
|
-
import { base58, base64url } from "@scure/base";
|
|
20
|
-
import { x25519 } from "@noble/curves/ed25519";
|
|
21
|
-
import { xsalsa20_poly1305 } from "@noble/ciphers/salsa";
|
|
22
|
-
import { blake3 } from "@noble/hashes/blake3";
|
|
23
|
-
import stableStringify from "fast-json-stable-stringify";
|
|
24
|
-
test("Signatures round-trip and use stable stringify", () => {
|
|
25
|
-
const data = { b: "world", a: "hello" };
|
|
26
|
-
const signatory = newRandomSignatory();
|
|
27
|
-
const signature = sign(signatory, data);
|
|
28
|
-
expect(signature).toMatch(/^signature_z/);
|
|
29
|
-
expect(
|
|
30
|
-
verify(signature, { a: "hello", b: "world" }, getSignatoryID(signatory))
|
|
31
|
-
).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
test("Invalid signatures don't verify", () => {
|
|
34
|
-
const data = { b: "world", a: "hello" };
|
|
35
|
-
const signatory = newRandomSignatory();
|
|
36
|
-
const signatory2 = newRandomSignatory();
|
|
37
|
-
const wrongSignature = sign(signatory2, data);
|
|
38
|
-
expect(verify(wrongSignature, data, getSignatoryID(signatory))).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
test("Sealing round-trips, but invalid receiver can't unseal", () => {
|
|
41
|
-
const data = { b: "world", a: "hello" };
|
|
42
|
-
const sender = newRandomRecipient();
|
|
43
|
-
const recipient1 = newRandomRecipient();
|
|
44
|
-
const recipient2 = newRandomRecipient();
|
|
45
|
-
const recipient3 = newRandomRecipient();
|
|
46
|
-
const nOnceMaterial = {
|
|
47
|
-
in: "co_zTEST",
|
|
48
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }
|
|
49
|
-
};
|
|
50
|
-
const sealed = seal(
|
|
51
|
-
data,
|
|
52
|
-
sender,
|
|
53
|
-
/* @__PURE__ */ new Set([getRecipientID(recipient1), getRecipientID(recipient2)]),
|
|
54
|
-
nOnceMaterial
|
|
55
|
-
);
|
|
56
|
-
expect(sealed[getRecipientID(recipient1)]).toMatch(/^sealed_U/);
|
|
57
|
-
expect(sealed[getRecipientID(recipient2)]).toMatch(/^sealed_U/);
|
|
58
|
-
expect(
|
|
59
|
-
openAs(sealed, recipient1, getRecipientID(sender), nOnceMaterial)
|
|
60
|
-
).toEqual(data);
|
|
61
|
-
expect(
|
|
62
|
-
openAs(sealed, recipient2, getRecipientID(sender), nOnceMaterial)
|
|
63
|
-
).toEqual(data);
|
|
64
|
-
expect(
|
|
65
|
-
openAs(sealed, recipient3, getRecipientID(sender), nOnceMaterial)
|
|
66
|
-
).toBeUndefined();
|
|
67
|
-
const nOnce = blake3(
|
|
68
|
-
new TextEncoder().encode(stableStringify(nOnceMaterial))
|
|
69
|
-
).slice(0, 24);
|
|
70
|
-
const recipient3priv = base58.decode(
|
|
71
|
-
recipient3.substring("recipientSecret_z".length)
|
|
72
|
-
);
|
|
73
|
-
const senderPub = base58.decode(
|
|
74
|
-
getRecipientID(sender).substring("recipient_z".length)
|
|
75
|
-
);
|
|
76
|
-
const sealedBytes = base64url.decode(
|
|
77
|
-
sealed[getRecipientID(recipient1)].substring("sealed_U".length)
|
|
78
|
-
);
|
|
79
|
-
const sharedSecret = x25519.getSharedSecret(recipient3priv, senderPub);
|
|
80
|
-
expect(() => {
|
|
81
|
-
const _ = xsalsa20_poly1305(sharedSecret, nOnce).decrypt(sealedBytes);
|
|
82
|
-
}).toThrow("Wrong tag");
|
|
83
|
-
});
|
|
84
|
-
test("Hashing is deterministic", () => {
|
|
85
|
-
expect(secureHash({ b: "world", a: "hello" })).toEqual(
|
|
86
|
-
secureHash({ a: "hello", b: "world" })
|
|
87
|
-
);
|
|
88
|
-
expect(shortHash({ b: "world", a: "hello" })).toEqual(
|
|
89
|
-
shortHash({ a: "hello", b: "world" })
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
test("Encryption for transactions round-trips", () => {
|
|
93
|
-
const { secret } = newRandomKeySecret();
|
|
94
|
-
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
95
|
-
in: "co_zTEST",
|
|
96
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }
|
|
97
|
-
});
|
|
98
|
-
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
99
|
-
in: "co_zTEST",
|
|
100
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }
|
|
101
|
-
});
|
|
102
|
-
const decrypted1 = decryptForTransaction(encrypted1, secret, {
|
|
103
|
-
in: "co_zTEST",
|
|
104
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }
|
|
105
|
-
});
|
|
106
|
-
const decrypted2 = decryptForTransaction(encrypted2, secret, {
|
|
107
|
-
in: "co_zTEST",
|
|
108
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }
|
|
109
|
-
});
|
|
110
|
-
expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
|
|
111
|
-
});
|
|
112
|
-
test("Encryption for transactions doesn't decrypt with a wrong key", () => {
|
|
113
|
-
const { secret } = newRandomKeySecret();
|
|
114
|
-
const { secret: secret2 } = newRandomKeySecret();
|
|
115
|
-
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
116
|
-
in: "co_zTEST",
|
|
117
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }
|
|
118
|
-
});
|
|
119
|
-
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
120
|
-
in: "co_zTEST",
|
|
121
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }
|
|
122
|
-
});
|
|
123
|
-
const decrypted1 = decryptForTransaction(encrypted1, secret2, {
|
|
124
|
-
in: "co_zTEST",
|
|
125
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }
|
|
126
|
-
});
|
|
127
|
-
const decrypted2 = decryptForTransaction(encrypted2, secret2, {
|
|
128
|
-
in: "co_zTEST",
|
|
129
|
-
tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }
|
|
130
|
-
});
|
|
131
|
-
expect([decrypted1, decrypted2]).toEqual([void 0, void 0]);
|
|
132
|
-
});
|
|
133
|
-
test("Encryption of keySecrets round-trips", () => {
|
|
134
|
-
const toSeal = newRandomKeySecret();
|
|
135
|
-
const sealing = newRandomKeySecret();
|
|
136
|
-
const keys = {
|
|
137
|
-
toSeal,
|
|
138
|
-
sealing
|
|
139
|
-
};
|
|
140
|
-
const sealed = sealKeySecret(keys);
|
|
141
|
-
const unsealed = unsealKeySecret(sealed, sealing.secret);
|
|
142
|
-
expect(unsealed).toEqual(toSeal.secret);
|
|
143
|
-
});
|
|
144
|
-
test("Encryption of keySecrets doesn't unseal with a wrong key", () => {
|
|
145
|
-
const toSeal = newRandomKeySecret();
|
|
146
|
-
const sealing = newRandomKeySecret();
|
|
147
|
-
const sealingWrong = newRandomKeySecret();
|
|
148
|
-
const keys = {
|
|
149
|
-
toSeal,
|
|
150
|
-
sealing
|
|
151
|
-
};
|
|
152
|
-
const sealed = sealKeySecret(keys);
|
|
153
|
-
const unsealed = unsealKeySecret(sealed, sealingWrong.secret);
|
|
154
|
-
expect(unsealed).toBeUndefined();
|
|
155
|
-
});
|
package/dist/ids.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
package/dist/index.mjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import {
|
|
3
|
-
CoValue,
|
|
4
|
-
agentCredentialFromBytes,
|
|
5
|
-
agentCredentialToBytes,
|
|
6
|
-
getAgent,
|
|
7
|
-
getAgentID,
|
|
8
|
-
newRandomAgentCredential,
|
|
9
|
-
newRandomSessionID
|
|
10
|
-
} from "./coValue";
|
|
11
|
-
import { LocalNode } from "./node";
|
|
12
|
-
import { CoMap } from "./contentTypes/coMap";
|
|
13
|
-
const internals = {
|
|
14
|
-
agentCredentialToBytes,
|
|
15
|
-
agentCredentialFromBytes,
|
|
16
|
-
getAgent,
|
|
17
|
-
getAgentID,
|
|
18
|
-
newRandomAgentCredential,
|
|
19
|
-
newRandomSessionID
|
|
20
|
-
};
|
|
21
|
-
export { LocalNode, CoValue, CoMap, internals };
|
package/dist/jsonValue.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
package/dist/node.mjs
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import { createdNowUnique, newRandomKeySecret, seal } from "./crypto";
|
|
3
|
-
import {
|
|
4
|
-
CoValue,
|
|
5
|
-
getAgent,
|
|
6
|
-
getAgentID,
|
|
7
|
-
getAgentCoValueHeader,
|
|
8
|
-
newRandomAgentCredential
|
|
9
|
-
} from "./coValue";
|
|
10
|
-
import { Team, expectTeamContent } from "./permissions";
|
|
11
|
-
import { SyncManager } from "./sync";
|
|
12
|
-
export class LocalNode {
|
|
13
|
-
coValues = {};
|
|
14
|
-
agentCredential;
|
|
15
|
-
agentID;
|
|
16
|
-
ownSessionID;
|
|
17
|
-
sync = new SyncManager(this);
|
|
18
|
-
constructor(agentCredential, ownSessionID) {
|
|
19
|
-
this.agentCredential = agentCredential;
|
|
20
|
-
const agent = getAgent(agentCredential);
|
|
21
|
-
const agentID = getAgentID(agent);
|
|
22
|
-
this.agentID = agentID;
|
|
23
|
-
this.ownSessionID = ownSessionID;
|
|
24
|
-
const agentCoValue = new CoValue(getAgentCoValueHeader(agent), this);
|
|
25
|
-
this.coValues[agentCoValue.id] = {
|
|
26
|
-
state: "loaded",
|
|
27
|
-
coValue: agentCoValue
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
createCoValue(header) {
|
|
31
|
-
const coValue = new CoValue(header, this);
|
|
32
|
-
this.coValues[coValue.id] = { state: "loaded", coValue };
|
|
33
|
-
void this.sync.syncCoValue(coValue);
|
|
34
|
-
return coValue;
|
|
35
|
-
}
|
|
36
|
-
loadCoValue(id) {
|
|
37
|
-
let entry = this.coValues[id];
|
|
38
|
-
if (!entry) {
|
|
39
|
-
entry = newLoadingState();
|
|
40
|
-
this.coValues[id] = entry;
|
|
41
|
-
this.sync.loadFromPeers(id);
|
|
42
|
-
}
|
|
43
|
-
if (entry.state === "loaded") {
|
|
44
|
-
return Promise.resolve(entry.coValue);
|
|
45
|
-
}
|
|
46
|
-
return entry.done;
|
|
47
|
-
}
|
|
48
|
-
async load(id) {
|
|
49
|
-
return (await this.loadCoValue(id)).getCurrentContent();
|
|
50
|
-
}
|
|
51
|
-
expectCoValueLoaded(id, expectation) {
|
|
52
|
-
const entry = this.coValues[id];
|
|
53
|
-
if (!entry) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`${expectation ? expectation + ": " : ""}Unknown CoValue ${id}`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
if (entry.state === "loading") {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded`
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
return entry.coValue;
|
|
64
|
-
}
|
|
65
|
-
createAgent(publicNickname) {
|
|
66
|
-
const agentCredential = newRandomAgentCredential(publicNickname);
|
|
67
|
-
this.createCoValue(getAgentCoValueHeader(getAgent(agentCredential)));
|
|
68
|
-
return agentCredential;
|
|
69
|
-
}
|
|
70
|
-
expectAgentLoaded(id, expectation) {
|
|
71
|
-
var _a;
|
|
72
|
-
const coValue = this.expectCoValueLoaded(
|
|
73
|
-
id,
|
|
74
|
-
expectation
|
|
75
|
-
);
|
|
76
|
-
if (coValue.header.type !== "comap" || coValue.header.ruleset.type !== "agent") {
|
|
77
|
-
throw new Error(
|
|
78
|
-
`${expectation ? expectation + ": " : ""}CoValue ${id} is not an agent`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
recipientID: coValue.header.ruleset.initialRecipientID,
|
|
83
|
-
signatoryID: coValue.header.ruleset.initialSignatoryID,
|
|
84
|
-
publicNickname: (_a = coValue.header.publicNickname) == null ? void 0 : _a.replace("agent-", "")
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
createTeam() {
|
|
88
|
-
const teamCoValue = this.createCoValue({
|
|
89
|
-
type: "comap",
|
|
90
|
-
ruleset: { type: "team", initialAdmin: this.agentID },
|
|
91
|
-
meta: null,
|
|
92
|
-
...createdNowUnique(),
|
|
93
|
-
publicNickname: "team"
|
|
94
|
-
});
|
|
95
|
-
let teamContent = expectTeamContent(teamCoValue.getCurrentContent());
|
|
96
|
-
teamContent = teamContent.edit((editable) => {
|
|
97
|
-
editable.set(this.agentID, "admin", "trusting");
|
|
98
|
-
const readKey = newRandomKeySecret();
|
|
99
|
-
const revelation = seal(
|
|
100
|
-
readKey.secret,
|
|
101
|
-
this.agentCredential.recipientSecret,
|
|
102
|
-
/* @__PURE__ */ new Set([getAgent(this.agentCredential).recipientID]),
|
|
103
|
-
{
|
|
104
|
-
in: teamCoValue.id,
|
|
105
|
-
tx: teamCoValue.nextTransactionID()
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
editable.set(
|
|
109
|
-
"readKey",
|
|
110
|
-
{ keyID: readKey.id, revelation },
|
|
111
|
-
"trusting"
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
return new Team(teamContent, this);
|
|
115
|
-
}
|
|
116
|
-
testWithDifferentCredentials(agentCredential, ownSessionID) {
|
|
117
|
-
const newNode = new LocalNode(agentCredential, ownSessionID);
|
|
118
|
-
newNode.coValues = Object.fromEntries(
|
|
119
|
-
Object.entries(this.coValues).map(([id, entry]) => {
|
|
120
|
-
if (entry.state === "loading") {
|
|
121
|
-
return void 0;
|
|
122
|
-
}
|
|
123
|
-
const newCoValue = new CoValue(
|
|
124
|
-
entry.coValue.header,
|
|
125
|
-
newNode
|
|
126
|
-
);
|
|
127
|
-
newCoValue.sessions = entry.coValue.sessions;
|
|
128
|
-
return [id, { state: "loaded", coValue: newCoValue }];
|
|
129
|
-
}).filter((x) => !!x)
|
|
130
|
-
);
|
|
131
|
-
return newNode;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
export function newLoadingState() {
|
|
135
|
-
let resolve;
|
|
136
|
-
const promise = new Promise((r) => {
|
|
137
|
-
resolve = r;
|
|
138
|
-
});
|
|
139
|
-
return {
|
|
140
|
-
state: "loading",
|
|
141
|
-
done: promise,
|
|
142
|
-
resolve
|
|
143
|
-
};
|
|
144
|
-
}
|
package/dist/permissions.mjs
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import {
|
|
3
|
-
createdNowUnique,
|
|
4
|
-
newRandomKeySecret,
|
|
5
|
-
seal,
|
|
6
|
-
sealKeySecret
|
|
7
|
-
} from "./crypto";
|
|
8
|
-
import {
|
|
9
|
-
agentIDfromSessionID
|
|
10
|
-
} from "./coValue";
|
|
11
|
-
export function determineValidTransactions(coValue) {
|
|
12
|
-
if (coValue.header.ruleset.type === "team") {
|
|
13
|
-
const allTrustingTransactionsSorted = Object.entries(
|
|
14
|
-
coValue.sessions
|
|
15
|
-
).flatMap(([sessionID, sessionLog]) => {
|
|
16
|
-
return sessionLog.transactions.map((tx, txIndex) => ({ sessionID, txIndex, tx })).filter(({ tx }) => {
|
|
17
|
-
if (tx.privacy === "trusting") {
|
|
18
|
-
return true;
|
|
19
|
-
} else {
|
|
20
|
-
console.warn("Unexpected private transaction in Team");
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
allTrustingTransactionsSorted.sort((a, b) => {
|
|
26
|
-
return a.tx.madeAt - b.tx.madeAt;
|
|
27
|
-
});
|
|
28
|
-
const initialAdmin = coValue.header.ruleset.initialAdmin;
|
|
29
|
-
if (!initialAdmin) {
|
|
30
|
-
throw new Error("Team must have initialAdmin");
|
|
31
|
-
}
|
|
32
|
-
const memberState = {};
|
|
33
|
-
const validTransactions = [];
|
|
34
|
-
for (const {
|
|
35
|
-
sessionID,
|
|
36
|
-
txIndex,
|
|
37
|
-
tx
|
|
38
|
-
} of allTrustingTransactionsSorted) {
|
|
39
|
-
const transactor = agentIDfromSessionID(sessionID);
|
|
40
|
-
const change = tx.changes[0];
|
|
41
|
-
if (tx.changes.length !== 1) {
|
|
42
|
-
console.warn("Team transaction must have exactly one change");
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (change.op !== "insert") {
|
|
46
|
-
console.warn("Team transaction must set a role or readKey");
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (change.key === "readKey") {
|
|
50
|
-
if (memberState[transactor] !== "admin") {
|
|
51
|
-
console.warn("Only admins can set readKeys");
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const affectedMember = change.key;
|
|
58
|
-
const assignedRole = change.value;
|
|
59
|
-
if (change.value !== "admin" && change.value !== "writer" && change.value !== "reader" && change.value !== "revoked") {
|
|
60
|
-
console.warn("Team transaction must set a valid role");
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const isFirstSelfAppointment = !memberState[transactor] && transactor === initialAdmin && change.op === "insert" && change.key === transactor && change.value === "admin";
|
|
64
|
-
if (!isFirstSelfAppointment) {
|
|
65
|
-
if (memberState[transactor] !== "admin") {
|
|
66
|
-
console.warn(
|
|
67
|
-
"Team transaction must be made by current admin"
|
|
68
|
-
);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (memberState[affectedMember] === "admin" && affectedMember !== transactor && assignedRole !== "admin") {
|
|
72
|
-
console.warn("Admins can only demote themselves.");
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
memberState[affectedMember] = change.value;
|
|
77
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
78
|
-
}
|
|
79
|
-
return validTransactions;
|
|
80
|
-
} else if (coValue.header.ruleset.type === "ownedByTeam") {
|
|
81
|
-
const teamContent = coValue.node.expectCoValueLoaded(
|
|
82
|
-
coValue.header.ruleset.team,
|
|
83
|
-
"Determining valid transaction in owned object but its team wasn't loaded"
|
|
84
|
-
).getCurrentContent();
|
|
85
|
-
if (teamContent.type !== "comap") {
|
|
86
|
-
throw new Error("Team must be a map");
|
|
87
|
-
}
|
|
88
|
-
return Object.entries(coValue.sessions).flatMap(
|
|
89
|
-
([sessionID, sessionLog]) => {
|
|
90
|
-
const transactor = agentIDfromSessionID(sessionID);
|
|
91
|
-
return sessionLog.transactions.filter((tx) => {
|
|
92
|
-
const transactorRoleAtTxTime = teamContent.getAtTime(
|
|
93
|
-
transactor,
|
|
94
|
-
tx.madeAt
|
|
95
|
-
);
|
|
96
|
-
return transactorRoleAtTxTime === "admin" || transactorRoleAtTxTime === "writer";
|
|
97
|
-
}).map((tx, txIndex) => ({
|
|
98
|
-
txID: { sessionID, txIndex },
|
|
99
|
-
tx
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
} else if (coValue.header.ruleset.type === "unsafeAllowAll") {
|
|
104
|
-
return Object.entries(coValue.sessions).flatMap(
|
|
105
|
-
([sessionID, sessionLog]) => {
|
|
106
|
-
return sessionLog.transactions.map((tx, txIndex) => ({
|
|
107
|
-
txID: { sessionID, txIndex },
|
|
108
|
-
tx
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
|
-
);
|
|
112
|
-
} else if (coValue.header.ruleset.type === "agent") {
|
|
113
|
-
return [];
|
|
114
|
-
} else {
|
|
115
|
-
throw new Error("Unknown ruleset type " + coValue.header.ruleset.type);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export function expectTeamContent(content) {
|
|
119
|
-
if (content.type !== "comap") {
|
|
120
|
-
throw new Error("Expected map");
|
|
121
|
-
}
|
|
122
|
-
return content;
|
|
123
|
-
}
|
|
124
|
-
export class Team {
|
|
125
|
-
teamMap;
|
|
126
|
-
node;
|
|
127
|
-
constructor(teamMap, node) {
|
|
128
|
-
this.teamMap = teamMap;
|
|
129
|
-
this.node = node;
|
|
130
|
-
}
|
|
131
|
-
get id() {
|
|
132
|
-
return this.teamMap.id;
|
|
133
|
-
}
|
|
134
|
-
addMember(agentID, role) {
|
|
135
|
-
this.teamMap = this.teamMap.edit((map) => {
|
|
136
|
-
const agent = this.node.expectAgentLoaded(agentID, "Expected to know agent to add them to team");
|
|
137
|
-
if (!agent) {
|
|
138
|
-
throw new Error("Unknown agent " + agentID);
|
|
139
|
-
}
|
|
140
|
-
map.set(agentID, role, "trusting");
|
|
141
|
-
if (map.get(agentID) !== role) {
|
|
142
|
-
throw new Error("Failed to set role");
|
|
143
|
-
}
|
|
144
|
-
const currentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
|
145
|
-
if (!currentReadKey.secret) {
|
|
146
|
-
throw new Error("Can't add member without read key secret");
|
|
147
|
-
}
|
|
148
|
-
const revelation = seal(
|
|
149
|
-
currentReadKey.secret,
|
|
150
|
-
this.teamMap.coValue.node.agentCredential.recipientSecret,
|
|
151
|
-
/* @__PURE__ */ new Set([agent.recipientID]),
|
|
152
|
-
{
|
|
153
|
-
in: this.teamMap.coValue.id,
|
|
154
|
-
tx: this.teamMap.coValue.nextTransactionID()
|
|
155
|
-
}
|
|
156
|
-
);
|
|
157
|
-
map.set(
|
|
158
|
-
"readKey",
|
|
159
|
-
{ keyID: currentReadKey.id, revelation },
|
|
160
|
-
"trusting"
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
rotateReadKey() {
|
|
165
|
-
const currentlyPermittedReaders = this.teamMap.keys().filter((key) => {
|
|
166
|
-
if (key.startsWith("co_agent")) {
|
|
167
|
-
const role = this.teamMap.get(key);
|
|
168
|
-
return role === "admin" || role === "writer" || role === "reader";
|
|
169
|
-
} else {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
|
174
|
-
if (!maybeCurrentReadKey.secret) {
|
|
175
|
-
throw new Error("Can't rotate read key secret we don't have access to");
|
|
176
|
-
}
|
|
177
|
-
const currentReadKey = {
|
|
178
|
-
id: maybeCurrentReadKey.id,
|
|
179
|
-
secret: maybeCurrentReadKey.secret
|
|
180
|
-
};
|
|
181
|
-
const newReadKey = newRandomKeySecret();
|
|
182
|
-
const newReadKeyRevelation = seal(
|
|
183
|
-
newReadKey.secret,
|
|
184
|
-
this.teamMap.coValue.node.agentCredential.recipientSecret,
|
|
185
|
-
new Set(
|
|
186
|
-
currentlyPermittedReaders.map(
|
|
187
|
-
(reader) => {
|
|
188
|
-
const readerAgent = this.node.expectAgentLoaded(reader, "Expected to know currently permitted reader");
|
|
189
|
-
if (!readerAgent) {
|
|
190
|
-
throw new Error("Unknown agent " + reader);
|
|
191
|
-
}
|
|
192
|
-
return readerAgent.recipientID;
|
|
193
|
-
}
|
|
194
|
-
)
|
|
195
|
-
),
|
|
196
|
-
{
|
|
197
|
-
in: this.teamMap.coValue.id,
|
|
198
|
-
tx: this.teamMap.coValue.nextTransactionID()
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
this.teamMap = this.teamMap.edit((map) => {
|
|
202
|
-
map.set(
|
|
203
|
-
"readKey",
|
|
204
|
-
{
|
|
205
|
-
keyID: newReadKey.id,
|
|
206
|
-
revelation: newReadKeyRevelation,
|
|
207
|
-
previousKeys: {
|
|
208
|
-
[currentReadKey.id]: sealKeySecret({
|
|
209
|
-
sealing: newReadKey,
|
|
210
|
-
toSeal: currentReadKey
|
|
211
|
-
}).encrypted
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
"trusting"
|
|
215
|
-
);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
removeMember(agentID) {
|
|
219
|
-
this.teamMap = this.teamMap.edit((map) => {
|
|
220
|
-
map.set(agentID, "revoked", "trusting");
|
|
221
|
-
});
|
|
222
|
-
this.rotateReadKey();
|
|
223
|
-
}
|
|
224
|
-
createMap(meta) {
|
|
225
|
-
return this.node.createCoValue({
|
|
226
|
-
type: "comap",
|
|
227
|
-
ruleset: {
|
|
228
|
-
type: "ownedByTeam",
|
|
229
|
-
team: this.teamMap.id
|
|
230
|
-
},
|
|
231
|
-
meta: meta || null,
|
|
232
|
-
...createdNowUnique(),
|
|
233
|
-
publicNickname: "map"
|
|
234
|
-
}).getCurrentContent();
|
|
235
|
-
}
|
|
236
|
-
testWithDifferentCredentials(credential, sessionId) {
|
|
237
|
-
return new Team(
|
|
238
|
-
expectTeamContent(
|
|
239
|
-
this.teamMap.coValue.testWithDifferentCredentials(credential, sessionId).getCurrentContent()
|
|
240
|
-
),
|
|
241
|
-
this.node
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
}
|