cojson 0.0.11 → 0.0.12
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/README.md +2 -2
- package/dist/account.d.ts +57 -0
- package/dist/account.js +76 -0
- package/dist/account.js.map +1 -0
- package/dist/account.test.d.ts +1 -0
- package/dist/account.test.js +40 -0
- package/dist/account.test.js.map +1 -0
- package/dist/coValue.d.ts +16 -35
- package/dist/coValue.js +49 -112
- package/dist/coValue.js.map +1 -1
- package/dist/coValue.test.js +16 -16
- package/dist/coValue.test.js.map +1 -1
- package/dist/contentType.d.ts +9 -9
- package/dist/contentType.js.map +1 -1
- package/dist/contentType.test.js +13 -17
- package/dist/contentType.test.js.map +1 -1
- package/dist/contentTypes/coList.d.ts +3 -3
- package/dist/contentTypes/coList.js.map +1 -1
- package/dist/contentTypes/coMap.d.ts +31 -21
- package/dist/contentTypes/coMap.js +28 -0
- package/dist/contentTypes/coMap.js.map +1 -1
- package/dist/contentTypes/coStream.d.ts +3 -3
- package/dist/contentTypes/coStream.js.map +1 -1
- package/dist/contentTypes/static.d.ts +4 -4
- package/dist/contentTypes/static.js.map +1 -1
- package/dist/crypto.d.ts +45 -39
- package/dist/crypto.js +68 -49
- package/dist/crypto.js.map +1 -1
- package/dist/crypto.test.js +45 -49
- package/dist/crypto.test.js.map +1 -1
- package/dist/ids.d.ts +5 -3
- package/dist/ids.js +3 -1
- package/dist/ids.js.map +1 -1
- package/dist/index.d.ts +12 -14
- package/dist/index.js +6 -8
- package/dist/index.js.map +1 -1
- package/dist/jsonValue.d.ts +2 -2
- package/dist/node.d.ts +25 -15
- package/dist/node.js +88 -33
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +27 -33
- package/dist/permissions.js +55 -47
- package/dist/permissions.js.map +1 -1
- package/dist/permissions.test.js +231 -314
- package/dist/permissions.test.js.map +1 -1
- package/dist/sync.d.ts +26 -28
- package/dist/sync.js +67 -63
- package/dist/sync.js.map +1 -1
- package/dist/sync.test.js +181 -298
- package/dist/sync.test.js.map +1 -1
- package/dist/testUtils.d.ts +37 -0
- package/dist/testUtils.js +157 -0
- package/dist/testUtils.js.map +1 -0
- package/package.json +1 -1
- package/src/account.test.ts +67 -0
- package/src/account.ts +152 -0
- package/src/coValue.test.ts +17 -31
- package/src/coValue.ts +93 -179
- package/src/contentType.test.ts +18 -45
- package/src/contentType.ts +15 -13
- package/src/contentTypes/coList.ts +4 -4
- package/src/contentTypes/coMap.ts +55 -29
- package/src/contentTypes/coStream.ts +4 -4
- package/src/contentTypes/static.ts +5 -5
- package/src/crypto.test.ts +53 -59
- package/src/crypto.ts +123 -95
- package/src/ids.ts +9 -3
- package/src/index.ts +14 -25
- package/src/jsonValue.ts +2 -2
- package/src/node.ts +189 -61
- package/src/permissions.test.ts +370 -404
- package/src/permissions.ts +126 -109
- package/src/sync.test.ts +258 -432
- package/src/sync.ts +95 -98
- package/src/testUtils.ts +229 -0
package/src/permissions.ts
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
import { ContentType } from
|
|
2
|
-
import { CoMap, MapOpPayload } from
|
|
3
|
-
import { JsonValue } from
|
|
1
|
+
import { CoID, ContentType } from "./contentType.js";
|
|
2
|
+
import { CoMap, MapOpPayload } from "./contentTypes/coMap.js";
|
|
3
|
+
import { JsonObject, JsonValue } from "./jsonValue.js";
|
|
4
4
|
import {
|
|
5
5
|
Encrypted,
|
|
6
6
|
KeyID,
|
|
7
7
|
KeySecret,
|
|
8
|
-
RecipientID,
|
|
9
|
-
SealedSet,
|
|
10
|
-
SignatoryID,
|
|
11
8
|
createdNowUnique,
|
|
12
9
|
newRandomKeySecret,
|
|
13
10
|
seal,
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
encryptKeySecret,
|
|
12
|
+
getAgentSealerID,
|
|
13
|
+
Sealed,
|
|
14
|
+
} from "./crypto.js";
|
|
16
15
|
import {
|
|
17
|
-
AgentCredential,
|
|
18
16
|
CoValue,
|
|
19
17
|
Transaction,
|
|
20
18
|
TrustingTransaction,
|
|
21
|
-
|
|
22
|
-
} from
|
|
19
|
+
accountOrAgentIDfromSessionID,
|
|
20
|
+
} from "./coValue.js";
|
|
23
21
|
import { LocalNode } from "./node.js";
|
|
24
|
-
import {
|
|
22
|
+
import { RawCoID, SessionID, TransactionID, isAgentID } from "./ids.js";
|
|
23
|
+
import { AccountIDOrAgentID, GeneralizedControlledAccount, Profile } from "./account.js";
|
|
25
24
|
|
|
26
25
|
export type PermissionsDef =
|
|
27
|
-
| { type: "team"; initialAdmin:
|
|
28
|
-
| { type: "ownedByTeam"; team:
|
|
29
|
-
| {
|
|
30
|
-
type: "agent";
|
|
31
|
-
initialSignatoryID: SignatoryID;
|
|
32
|
-
initialRecipientID: RecipientID;
|
|
33
|
-
}
|
|
26
|
+
| { type: "team"; initialAdmin: AccountIDOrAgentID }
|
|
27
|
+
| { type: "ownedByTeam"; team: RawCoID }
|
|
34
28
|
| { type: "unsafeAllowAll" };
|
|
35
29
|
|
|
36
30
|
export type Role = "reader" | "writer" | "admin" | "revoked";
|
|
@@ -68,7 +62,7 @@ export function determineValidTransactions(
|
|
|
68
62
|
throw new Error("Team must have initialAdmin");
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
const memberState: { [agent:
|
|
65
|
+
const memberState: { [agent: AccountIDOrAgentID]: Role } = {};
|
|
72
66
|
|
|
73
67
|
const validTransactions: { txID: TransactionID; tx: Transaction }[] =
|
|
74
68
|
[];
|
|
@@ -79,11 +73,12 @@ export function determineValidTransactions(
|
|
|
79
73
|
tx,
|
|
80
74
|
} of allTrustingTransactionsSorted) {
|
|
81
75
|
// console.log("before", { memberState, validTransactions });
|
|
82
|
-
const transactor =
|
|
76
|
+
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
83
77
|
|
|
84
78
|
const change = tx.changes[0] as
|
|
85
|
-
| MapOpPayload<
|
|
86
|
-
| MapOpPayload<"readKey", JsonValue
|
|
79
|
+
| MapOpPayload<AccountIDOrAgentID, Role>
|
|
80
|
+
| MapOpPayload<"readKey", JsonValue>
|
|
81
|
+
| MapOpPayload<"profile", CoID<Profile>>;
|
|
87
82
|
if (tx.changes.length !== 1) {
|
|
88
83
|
console.warn("Team transaction must have exactly one change");
|
|
89
84
|
continue;
|
|
@@ -100,6 +95,22 @@ export function determineValidTransactions(
|
|
|
100
95
|
continue;
|
|
101
96
|
}
|
|
102
97
|
|
|
98
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
99
|
+
continue;
|
|
100
|
+
} else if (change.key === 'profile') {
|
|
101
|
+
if (memberState[transactor] !== "admin") {
|
|
102
|
+
console.warn("Only admins can set profile");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
107
|
+
continue;
|
|
108
|
+
} else if (isKeyForKeyField(change.key) || isKeyForAccountField(change.key)) {
|
|
109
|
+
if (memberState[transactor] !== "admin") {
|
|
110
|
+
console.warn("Only admins can reveal keys");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
// TODO: check validity of agents who the key is revealed to?
|
|
104
115
|
|
|
105
116
|
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
@@ -152,11 +163,12 @@ export function determineValidTransactions(
|
|
|
152
163
|
|
|
153
164
|
return validTransactions;
|
|
154
165
|
} else if (coValue.header.ruleset.type === "ownedByTeam") {
|
|
155
|
-
const teamContent =
|
|
156
|
-
|
|
166
|
+
const teamContent = coValue.node
|
|
167
|
+
.expectCoValueLoaded(
|
|
157
168
|
coValue.header.ruleset.team,
|
|
158
169
|
"Determining valid transaction in owned object but its team wasn't loaded"
|
|
159
|
-
)
|
|
170
|
+
)
|
|
171
|
+
.getCurrentContent();
|
|
160
172
|
|
|
161
173
|
if (teamContent.type !== "comap") {
|
|
162
174
|
throw new Error("Team must be a map");
|
|
@@ -164,7 +176,9 @@ export function determineValidTransactions(
|
|
|
164
176
|
|
|
165
177
|
return Object.entries(coValue.sessions).flatMap(
|
|
166
178
|
([sessionID, sessionLog]) => {
|
|
167
|
-
const transactor =
|
|
179
|
+
const transactor = accountOrAgentIDfromSessionID(
|
|
180
|
+
sessionID as SessionID
|
|
181
|
+
);
|
|
168
182
|
return sessionLog.transactions
|
|
169
183
|
.filter((tx) => {
|
|
170
184
|
const transactorRoleAtTxTime = teamContent.getAtTime(
|
|
@@ -192,80 +206,77 @@ export function determineValidTransactions(
|
|
|
192
206
|
}));
|
|
193
207
|
}
|
|
194
208
|
);
|
|
195
|
-
} else if (coValue.header.ruleset.type === "agent") {
|
|
196
|
-
// TODO
|
|
197
|
-
return [];
|
|
198
209
|
} else {
|
|
199
|
-
throw new Error(
|
|
210
|
+
throw new Error(
|
|
211
|
+
"Unknown ruleset type " + (coValue.header.ruleset as any).type
|
|
212
|
+
);
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
|
|
203
|
-
export type TeamContent = {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
213
|
-
};
|
|
216
|
+
export type TeamContent = {
|
|
217
|
+
profile: CoID<Profile> | null;
|
|
218
|
+
[key: AccountIDOrAgentID]: Role;
|
|
219
|
+
readKey: KeyID;
|
|
220
|
+
[revelationFor: `${KeyID}_for_${AccountIDOrAgentID}`]: Sealed<KeySecret>;
|
|
221
|
+
[oldKeyForNewKey: `${KeyID}_for_${KeyID}`]: Encrypted<
|
|
222
|
+
KeySecret,
|
|
223
|
+
{ encryptedID: KeyID; encryptingID: KeyID }
|
|
224
|
+
>;
|
|
214
225
|
};
|
|
215
226
|
|
|
216
|
-
export function expectTeamContent(
|
|
227
|
+
export function expectTeamContent(
|
|
228
|
+
content: ContentType
|
|
229
|
+
): CoMap<TeamContent, JsonObject | null> {
|
|
217
230
|
if (content.type !== "comap") {
|
|
218
231
|
throw new Error("Expected map");
|
|
219
232
|
}
|
|
220
233
|
|
|
221
|
-
return content as CoMap<TeamContent,
|
|
234
|
+
return content as CoMap<TeamContent, JsonObject | null>;
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
export class Team {
|
|
225
|
-
teamMap: CoMap<TeamContent,
|
|
238
|
+
teamMap: CoMap<TeamContent, JsonObject | null>;
|
|
226
239
|
node: LocalNode;
|
|
227
240
|
|
|
228
|
-
constructor(teamMap: CoMap<TeamContent,
|
|
241
|
+
constructor(teamMap: CoMap<TeamContent, JsonObject | null>, node: LocalNode) {
|
|
229
242
|
this.teamMap = teamMap;
|
|
230
243
|
this.node = node;
|
|
231
244
|
}
|
|
232
245
|
|
|
233
|
-
get id():
|
|
246
|
+
get id(): CoID<CoMap<TeamContent, JsonObject | null>> {
|
|
234
247
|
return this.teamMap.id;
|
|
235
248
|
}
|
|
236
249
|
|
|
237
|
-
addMember(
|
|
250
|
+
addMember(accountID: AccountIDOrAgentID, role: Role) {
|
|
238
251
|
this.teamMap = this.teamMap.edit((map) => {
|
|
239
|
-
const agent = this.node.expectAgentLoaded(agentID, "Expected to know agent to add them to team");
|
|
240
|
-
|
|
241
|
-
if (!agent) {
|
|
242
|
-
throw new Error("Unknown agent " + agentID);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
map.set(agentID, role, "trusting");
|
|
246
|
-
if (map.get(agentID) !== role) {
|
|
247
|
-
throw new Error("Failed to set role");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
252
|
const currentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
|
251
253
|
|
|
252
254
|
if (!currentReadKey.secret) {
|
|
253
255
|
throw new Error("Can't add member without read key secret");
|
|
254
256
|
}
|
|
255
257
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
new Set([agent.recipientID]),
|
|
260
|
-
{
|
|
261
|
-
in: this.teamMap.coValue.id,
|
|
262
|
-
tx: this.teamMap.coValue.nextTransactionID(),
|
|
263
|
-
}
|
|
258
|
+
const agent = this.node.resolveAccountAgent(
|
|
259
|
+
accountID,
|
|
260
|
+
"Expected to know agent to add them to team"
|
|
264
261
|
);
|
|
265
262
|
|
|
263
|
+
map.set(accountID, role, "trusting");
|
|
264
|
+
|
|
265
|
+
if (map.get(accountID) !== role) {
|
|
266
|
+
throw new Error("Failed to set role");
|
|
267
|
+
}
|
|
268
|
+
|
|
266
269
|
map.set(
|
|
267
|
-
|
|
268
|
-
|
|
270
|
+
`${currentReadKey.id}_for_${accountID}`,
|
|
271
|
+
seal(
|
|
272
|
+
currentReadKey.secret,
|
|
273
|
+
this.teamMap.coValue.node.account.currentSealerSecret(),
|
|
274
|
+
getAgentSealerID(agent),
|
|
275
|
+
{
|
|
276
|
+
in: this.teamMap.coValue.id,
|
|
277
|
+
tx: this.teamMap.coValue.nextTransactionID(),
|
|
278
|
+
}
|
|
279
|
+
),
|
|
269
280
|
"trusting"
|
|
270
281
|
);
|
|
271
282
|
});
|
|
@@ -273,7 +284,7 @@ export class Team {
|
|
|
273
284
|
|
|
274
285
|
rotateReadKey() {
|
|
275
286
|
const currentlyPermittedReaders = this.teamMap.keys().filter((key) => {
|
|
276
|
-
if (key.startsWith("
|
|
287
|
+
if (key.startsWith("co_") || isAgentID(key)) {
|
|
277
288
|
const role = this.teamMap.get(key);
|
|
278
289
|
return (
|
|
279
290
|
role === "admin" || role === "writer" || role === "reader"
|
|
@@ -281,12 +292,14 @@ export class Team {
|
|
|
281
292
|
} else {
|
|
282
293
|
return false;
|
|
283
294
|
}
|
|
284
|
-
}) as
|
|
295
|
+
}) as AccountIDOrAgentID[];
|
|
285
296
|
|
|
286
297
|
const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
|
|
287
298
|
|
|
288
299
|
if (!maybeCurrentReadKey.secret) {
|
|
289
|
-
throw new Error(
|
|
300
|
+
throw new Error(
|
|
301
|
+
"Can't rotate read key secret we don't have access to"
|
|
302
|
+
);
|
|
290
303
|
}
|
|
291
304
|
|
|
292
305
|
const currentReadKey = {
|
|
@@ -296,54 +309,51 @@ export class Team {
|
|
|
296
309
|
|
|
297
310
|
const newReadKey = newRandomKeySecret();
|
|
298
311
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
312
|
+
this.teamMap = this.teamMap.edit((map) => {
|
|
313
|
+
for (const readerID of currentlyPermittedReaders) {
|
|
314
|
+
const reader = this.node.resolveAccountAgent(
|
|
315
|
+
readerID,
|
|
316
|
+
"Expected to know currently permitted reader"
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
map.set(
|
|
320
|
+
`${newReadKey.id}_for_${readerID}`,
|
|
321
|
+
seal(
|
|
322
|
+
newReadKey.secret,
|
|
323
|
+
this.teamMap.coValue.node.account.currentSealerSecret(),
|
|
324
|
+
getAgentSealerID(reader),
|
|
325
|
+
{
|
|
326
|
+
in: this.teamMap.coValue.id,
|
|
327
|
+
tx: this.teamMap.coValue.nextTransactionID(),
|
|
308
328
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
)
|
|
312
|
-
),
|
|
313
|
-
{
|
|
314
|
-
in: this.teamMap.coValue.id,
|
|
315
|
-
tx: this.teamMap.coValue.nextTransactionID(),
|
|
329
|
+
),
|
|
330
|
+
"trusting"
|
|
331
|
+
);
|
|
316
332
|
}
|
|
317
|
-
);
|
|
318
333
|
|
|
319
|
-
this.teamMap = this.teamMap.edit((map) => {
|
|
320
334
|
map.set(
|
|
321
|
-
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
[currentReadKey.id]: sealKeySecret({
|
|
327
|
-
sealing: newReadKey,
|
|
328
|
-
toSeal: currentReadKey,
|
|
329
|
-
}).encrypted,
|
|
330
|
-
},
|
|
331
|
-
},
|
|
335
|
+
`${currentReadKey.id}_for_${newReadKey.id}`,
|
|
336
|
+
encryptKeySecret({
|
|
337
|
+
encrypting: newReadKey,
|
|
338
|
+
toEncrypt: currentReadKey,
|
|
339
|
+
}).encrypted,
|
|
332
340
|
"trusting"
|
|
333
341
|
);
|
|
342
|
+
|
|
343
|
+
map.set("readKey", newReadKey.id, "trusting");
|
|
334
344
|
});
|
|
335
345
|
}
|
|
336
346
|
|
|
337
|
-
removeMember(
|
|
347
|
+
removeMember(accountID: AccountIDOrAgentID) {
|
|
338
348
|
this.teamMap = this.teamMap.edit((map) => {
|
|
339
|
-
map.set(
|
|
349
|
+
map.set(accountID, "revoked", "trusting");
|
|
340
350
|
});
|
|
341
351
|
|
|
342
352
|
this.rotateReadKey();
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
createMap<M extends { [key: string]: JsonValue }, Meta extends
|
|
346
|
-
meta?:
|
|
355
|
+
createMap<M extends { [key: string]: JsonValue }, Meta extends JsonObject | null>(
|
|
356
|
+
meta?: Meta
|
|
347
357
|
): CoMap<M, Meta> {
|
|
348
358
|
return this.node
|
|
349
359
|
.createCoValue({
|
|
@@ -354,22 +364,29 @@ export class Team {
|
|
|
354
364
|
},
|
|
355
365
|
meta: meta || null,
|
|
356
366
|
...createdNowUnique(),
|
|
357
|
-
publicNickname: "map",
|
|
358
367
|
})
|
|
359
368
|
.getCurrentContent() as CoMap<M, Meta>;
|
|
360
369
|
}
|
|
361
370
|
|
|
362
|
-
|
|
363
|
-
|
|
371
|
+
testWithDifferentAccount(
|
|
372
|
+
account: GeneralizedControlledAccount,
|
|
364
373
|
sessionId: SessionID
|
|
365
374
|
): Team {
|
|
366
375
|
return new Team(
|
|
367
376
|
expectTeamContent(
|
|
368
377
|
this.teamMap.coValue
|
|
369
|
-
.
|
|
378
|
+
.testWithDifferentAccount(account, sessionId)
|
|
370
379
|
.getCurrentContent()
|
|
371
380
|
),
|
|
372
381
|
this.node
|
|
373
382
|
);
|
|
374
383
|
}
|
|
375
384
|
}
|
|
385
|
+
|
|
386
|
+
export function isKeyForKeyField(field: string): field is `${KeyID}_for_${KeyID}` {
|
|
387
|
+
return field.startsWith("key_") && field.includes("_for_key");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function isKeyForAccountField(field: string): field is `${KeyID}_for_${AccountIDOrAgentID}` {
|
|
391
|
+
return field.startsWith("key_") && (field.includes("_for_sealer") || field.includes("_for_co"));
|
|
392
|
+
}
|