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.
Files changed (75) hide show
  1. package/README.md +2 -2
  2. package/dist/account.d.ts +57 -0
  3. package/dist/account.js +76 -0
  4. package/dist/account.js.map +1 -0
  5. package/dist/account.test.d.ts +1 -0
  6. package/dist/account.test.js +40 -0
  7. package/dist/account.test.js.map +1 -0
  8. package/dist/coValue.d.ts +16 -35
  9. package/dist/coValue.js +49 -112
  10. package/dist/coValue.js.map +1 -1
  11. package/dist/coValue.test.js +16 -16
  12. package/dist/coValue.test.js.map +1 -1
  13. package/dist/contentType.d.ts +9 -9
  14. package/dist/contentType.js.map +1 -1
  15. package/dist/contentType.test.js +13 -17
  16. package/dist/contentType.test.js.map +1 -1
  17. package/dist/contentTypes/coList.d.ts +3 -3
  18. package/dist/contentTypes/coList.js.map +1 -1
  19. package/dist/contentTypes/coMap.d.ts +31 -21
  20. package/dist/contentTypes/coMap.js +28 -0
  21. package/dist/contentTypes/coMap.js.map +1 -1
  22. package/dist/contentTypes/coStream.d.ts +3 -3
  23. package/dist/contentTypes/coStream.js.map +1 -1
  24. package/dist/contentTypes/static.d.ts +4 -4
  25. package/dist/contentTypes/static.js.map +1 -1
  26. package/dist/crypto.d.ts +45 -39
  27. package/dist/crypto.js +68 -49
  28. package/dist/crypto.js.map +1 -1
  29. package/dist/crypto.test.js +45 -49
  30. package/dist/crypto.test.js.map +1 -1
  31. package/dist/ids.d.ts +5 -3
  32. package/dist/ids.js +3 -1
  33. package/dist/ids.js.map +1 -1
  34. package/dist/index.d.ts +12 -14
  35. package/dist/index.js +6 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/jsonValue.d.ts +2 -2
  38. package/dist/node.d.ts +25 -15
  39. package/dist/node.js +88 -33
  40. package/dist/node.js.map +1 -1
  41. package/dist/permissions.d.ts +27 -33
  42. package/dist/permissions.js +55 -47
  43. package/dist/permissions.js.map +1 -1
  44. package/dist/permissions.test.js +231 -314
  45. package/dist/permissions.test.js.map +1 -1
  46. package/dist/sync.d.ts +26 -28
  47. package/dist/sync.js +67 -63
  48. package/dist/sync.js.map +1 -1
  49. package/dist/sync.test.js +181 -298
  50. package/dist/sync.test.js.map +1 -1
  51. package/dist/testUtils.d.ts +37 -0
  52. package/dist/testUtils.js +157 -0
  53. package/dist/testUtils.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/account.test.ts +67 -0
  56. package/src/account.ts +152 -0
  57. package/src/coValue.test.ts +17 -31
  58. package/src/coValue.ts +93 -179
  59. package/src/contentType.test.ts +18 -45
  60. package/src/contentType.ts +15 -13
  61. package/src/contentTypes/coList.ts +4 -4
  62. package/src/contentTypes/coMap.ts +55 -29
  63. package/src/contentTypes/coStream.ts +4 -4
  64. package/src/contentTypes/static.ts +5 -5
  65. package/src/crypto.test.ts +53 -59
  66. package/src/crypto.ts +123 -95
  67. package/src/ids.ts +9 -3
  68. package/src/index.ts +14 -25
  69. package/src/jsonValue.ts +2 -2
  70. package/src/node.ts +189 -61
  71. package/src/permissions.test.ts +370 -404
  72. package/src/permissions.ts +126 -109
  73. package/src/sync.test.ts +258 -432
  74. package/src/sync.ts +95 -98
  75. package/src/testUtils.ts +229 -0
@@ -1,36 +1,30 @@
1
- import { ContentType } from './contentType.js';
2
- import { CoMap, MapOpPayload } from './contentTypes/coMap.js';
3
- import { JsonValue } from './jsonValue.js';
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
- sealKeySecret,
15
- } from './crypto.js';
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
- agentIDfromSessionID,
22
- } from './coValue.js';
19
+ accountOrAgentIDfromSessionID,
20
+ } from "./coValue.js";
23
21
  import { LocalNode } from "./node.js";
24
- import { AgentID, RawCoValueID, SessionID, TransactionID } from './ids.js';
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: AgentID; parentTeams?: RawCoValueID[] }
28
- | { type: "ownedByTeam"; team: RawCoValueID }
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: AgentID]: Role } = {};
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 = agentIDfromSessionID(sessionID);
76
+ const transactor = accountOrAgentIDfromSessionID(sessionID);
83
77
 
84
78
  const change = tx.changes[0] as
85
- | MapOpPayload<AgentID, Role>
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
- coValue.node.expectCoValueLoaded(
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
- ).getCurrentContent();
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 = agentIDfromSessionID(sessionID as SessionID);
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("Unknown ruleset type " + (coValue.header.ruleset as any).type);
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 = { [key: AgentID]: Role } & {
204
- readKey: {
205
- keyID: KeyID;
206
- revelation: SealedSet<KeySecret>;
207
- previousKeys?: {
208
- [key: KeyID]: Encrypted<
209
- KeySecret,
210
- { sealed: KeyID; sealing: KeyID }
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(content: ContentType): CoMap<TeamContent, {}> {
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, {}>, node: LocalNode) {
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(): RawCoValueID {
246
+ get id(): CoID<CoMap<TeamContent, JsonObject | null>> {
234
247
  return this.teamMap.id;
235
248
  }
236
249
 
237
- addMember(agentID: AgentID, role: Role) {
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 revelation = seal(
257
- currentReadKey.secret,
258
- this.teamMap.coValue.node.agentCredential.recipientSecret,
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
- "readKey",
268
- { keyID: currentReadKey.id, revelation },
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("co_agent")) {
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 AgentID[];
295
+ }) as AccountIDOrAgentID[];
285
296
 
286
297
  const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
287
298
 
288
299
  if (!maybeCurrentReadKey.secret) {
289
- throw new Error("Can't rotate read key secret we don't have access to");
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
- const newReadKeyRevelation = seal(
300
- newReadKey.secret,
301
- this.teamMap.coValue.node.agentCredential.recipientSecret,
302
- new Set(
303
- currentlyPermittedReaders.map(
304
- (reader) => {
305
- const readerAgent = this.node.expectAgentLoaded(reader, "Expected to know currently permitted reader");
306
- if (!readerAgent) {
307
- throw new Error("Unknown agent " + reader);
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
- return readerAgent.recipientID
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
- "readKey",
322
- {
323
- keyID: newReadKey.id,
324
- revelation: newReadKeyRevelation,
325
- previousKeys: {
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(agentID: AgentID) {
347
+ removeMember(accountID: AccountIDOrAgentID) {
338
348
  this.teamMap = this.teamMap.edit((map) => {
339
- map.set(agentID, "revoked", "trusting");
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 JsonValue>(
346
- meta?: M
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
- testWithDifferentCredentials(
363
- credential: AgentCredential,
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
- .testWithDifferentCredentials(credential, sessionId)
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
+ }