cojson 0.18.28 → 0.18.30

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 (144) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/PeerState.d.ts +23 -14
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +74 -23
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts +3 -3
  8. package/dist/SyncStateManager.d.ts.map +1 -1
  9. package/dist/SyncStateManager.js +18 -44
  10. package/dist/SyncStateManager.js.map +1 -1
  11. package/dist/coValueContentMessage.d.ts.map +1 -1
  12. package/dist/coValueContentMessage.js +2 -1
  13. package/dist/coValueContentMessage.js.map +1 -1
  14. package/dist/coValueCore/PeerKnownState.d.ts +21 -0
  15. package/dist/coValueCore/PeerKnownState.d.ts.map +1 -0
  16. package/dist/coValueCore/PeerKnownState.js +52 -0
  17. package/dist/coValueCore/PeerKnownState.js.map +1 -0
  18. package/dist/coValueCore/coValueCore.d.ts +39 -8
  19. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  20. package/dist/coValueCore/coValueCore.js +139 -40
  21. package/dist/coValueCore/coValueCore.js.map +1 -1
  22. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts.map +1 -1
  23. package/dist/coValueCore/decryptTransactionChangesAndMeta.js +0 -5
  24. package/dist/coValueCore/decryptTransactionChangesAndMeta.js.map +1 -1
  25. package/dist/coValueCore/verifiedState.d.ts +0 -14
  26. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  27. package/dist/coValueCore/verifiedState.js +2 -32
  28. package/dist/coValueCore/verifiedState.js.map +1 -1
  29. package/dist/coValues/coList.d.ts +3 -4
  30. package/dist/coValues/coList.d.ts.map +1 -1
  31. package/dist/coValues/coList.js +4 -4
  32. package/dist/coValues/coList.js.map +1 -1
  33. package/dist/coValues/coMap.d.ts +3 -4
  34. package/dist/coValues/coMap.d.ts.map +1 -1
  35. package/dist/coValues/coMap.js +5 -4
  36. package/dist/coValues/coMap.js.map +1 -1
  37. package/dist/coValues/coStream.d.ts +3 -3
  38. package/dist/coValues/coStream.d.ts.map +1 -1
  39. package/dist/coValues/coStream.js +3 -4
  40. package/dist/coValues/coStream.js.map +1 -1
  41. package/dist/coValues/group.d.ts +3 -3
  42. package/dist/coValues/group.d.ts.map +1 -1
  43. package/dist/coValues/group.js +74 -52
  44. package/dist/coValues/group.js.map +1 -1
  45. package/dist/exports.d.ts +2 -2
  46. package/dist/exports.d.ts.map +1 -1
  47. package/dist/exports.js +2 -2
  48. package/dist/exports.js.map +1 -1
  49. package/dist/localNode.d.ts.map +1 -1
  50. package/dist/localNode.js +7 -5
  51. package/dist/localNode.js.map +1 -1
  52. package/dist/permissions.d.ts +5 -1
  53. package/dist/permissions.d.ts.map +1 -1
  54. package/dist/permissions.js +173 -109
  55. package/dist/permissions.js.map +1 -1
  56. package/dist/sync.d.ts.map +1 -1
  57. package/dist/sync.js +33 -44
  58. package/dist/sync.js.map +1 -1
  59. package/dist/tests/PeerKnownState.test.d.ts +2 -0
  60. package/dist/tests/PeerKnownState.test.d.ts.map +1 -0
  61. package/dist/tests/PeerKnownState.test.js +342 -0
  62. package/dist/tests/PeerKnownState.test.js.map +1 -0
  63. package/dist/tests/PeerState.test.js +17 -16
  64. package/dist/tests/PeerState.test.js.map +1 -1
  65. package/dist/tests/StorageApiAsync.test.js +12 -12
  66. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  67. package/dist/tests/StorageApiSync.test.js +11 -11
  68. package/dist/tests/StorageApiSync.test.js.map +1 -1
  69. package/dist/tests/SyncStateManager.test.js +16 -21
  70. package/dist/tests/SyncStateManager.test.js.map +1 -1
  71. package/dist/tests/coValueCore.dependencies.test.js +59 -0
  72. package/dist/tests/coValueCore.dependencies.test.js.map +1 -1
  73. package/dist/tests/coValueCore.test.js +41 -21
  74. package/dist/tests/coValueCore.test.js.map +1 -1
  75. package/dist/tests/group.addMember.test.js +266 -219
  76. package/dist/tests/group.addMember.test.js.map +1 -1
  77. package/dist/tests/group.inheritance.test.js +12 -0
  78. package/dist/tests/group.inheritance.test.js.map +1 -1
  79. package/dist/tests/group.invite.test.js +77 -0
  80. package/dist/tests/group.invite.test.js.map +1 -1
  81. package/dist/tests/group.removeMember.test.js +64 -7
  82. package/dist/tests/group.removeMember.test.js.map +1 -1
  83. package/dist/tests/group.roleOf.test.js +14 -4
  84. package/dist/tests/group.roleOf.test.js.map +1 -1
  85. package/dist/tests/permissions.test.js +51 -202
  86. package/dist/tests/permissions.test.js.map +1 -1
  87. package/dist/tests/sync.content.test.js +2 -2
  88. package/dist/tests/sync.content.test.js.map +1 -1
  89. package/dist/tests/sync.invite.test.js +6 -6
  90. package/dist/tests/sync.load.test.js +22 -22
  91. package/dist/tests/sync.mesh.test.js +9 -9
  92. package/dist/tests/sync.storage.test.js +13 -7
  93. package/dist/tests/sync.storage.test.js.map +1 -1
  94. package/dist/tests/sync.storageAsync.test.js +3 -3
  95. package/dist/tests/sync.test.js +13 -33
  96. package/dist/tests/sync.test.js.map +1 -1
  97. package/dist/tests/sync.upload.test.js +2 -2
  98. package/package.json +3 -3
  99. package/src/PeerState.ts +86 -34
  100. package/src/SyncStateManager.ts +25 -60
  101. package/src/coValueContentMessage.ts +3 -1
  102. package/src/coValueCore/PeerKnownState.ts +74 -0
  103. package/src/coValueCore/coValueCore.ts +180 -49
  104. package/src/coValueCore/decryptTransactionChangesAndMeta.ts +0 -6
  105. package/src/coValueCore/verifiedState.ts +2 -37
  106. package/src/coValues/coList.ts +7 -7
  107. package/src/coValues/coMap.ts +9 -7
  108. package/src/coValues/coStream.ts +6 -5
  109. package/src/coValues/group.ts +99 -60
  110. package/src/exports.ts +2 -1
  111. package/src/localNode.ts +7 -5
  112. package/src/permissions.ts +204 -123
  113. package/src/sync.ts +37 -53
  114. package/src/tests/PeerKnownState.test.ts +426 -0
  115. package/src/tests/PeerState.test.ts +24 -24
  116. package/src/tests/StorageApiAsync.test.ts +12 -12
  117. package/src/tests/StorageApiSync.test.ts +11 -11
  118. package/src/tests/SyncStateManager.test.ts +23 -53
  119. package/src/tests/coValueCore.dependencies.test.ts +87 -0
  120. package/src/tests/coValueCore.test.ts +64 -22
  121. package/src/tests/group.addMember.test.ts +384 -345
  122. package/src/tests/group.inheritance.test.ts +33 -0
  123. package/src/tests/group.invite.test.ts +117 -0
  124. package/src/tests/group.removeMember.test.ts +95 -9
  125. package/src/tests/group.roleOf.test.ts +16 -4
  126. package/src/tests/permissions.test.ts +56 -295
  127. package/src/tests/sync.content.test.ts +2 -2
  128. package/src/tests/sync.invite.test.ts +6 -6
  129. package/src/tests/sync.load.test.ts +22 -22
  130. package/src/tests/sync.mesh.test.ts +9 -9
  131. package/src/tests/sync.storage.test.ts +13 -8
  132. package/src/tests/sync.storageAsync.test.ts +3 -3
  133. package/src/tests/sync.test.ts +21 -50
  134. package/src/tests/sync.upload.test.ts +2 -2
  135. package/dist/PeerKnownStates.d.ts +0 -19
  136. package/dist/PeerKnownStates.d.ts.map +0 -1
  137. package/dist/PeerKnownStates.js +0 -64
  138. package/dist/PeerKnownStates.js.map +0 -1
  139. package/dist/tests/PeerKnownStates.test.d.ts +0 -2
  140. package/dist/tests/PeerKnownStates.test.d.ts.map +0 -1
  141. package/dist/tests/PeerKnownStates.test.js +0 -77
  142. package/dist/tests/PeerKnownStates.test.js.map +0 -1
  143. package/src/PeerKnownStates.ts +0 -93
  144. package/src/tests/PeerKnownStates.test.ts +0 -99
@@ -1,6 +1,5 @@
1
1
  import { CoID } from "./coValue.js";
2
2
  import { CoValueCore } from "./coValueCore/coValueCore.js";
3
- import { Transaction } from "./coValueCore/verifiedState.js";
4
3
  import { RawAccount, RawAccountID, RawProfile } from "./coValues/account.js";
5
4
  import { MapOpPayload, RawCoMap } from "./coValues/coMap.js";
6
5
  import {
@@ -15,10 +14,8 @@ import {
15
14
  AgentID,
16
15
  ParentGroupReference,
17
16
  RawCoID,
18
- TransactionID,
19
17
  getParentGroupId,
20
18
  } from "./ids.js";
21
- import { parseJSON } from "./jsonStringify.js";
22
19
  import { JsonValue } from "./jsonValue.js";
23
20
  import { logger } from "./logger.js";
24
21
  import { expectGroup } from "./typeUtils/expectGroup.js";
@@ -41,6 +38,10 @@ export type AccountRole =
41
38
  * Can read and write to the group, and change group member roles
42
39
  */
43
40
  | "admin"
41
+ /**
42
+ * Can read and write, invite and revoke members except admin
43
+ */
44
+ | "manager"
44
45
  /**
45
46
  * Can only write to the group's CoValues and read their own changes
46
47
  */
@@ -49,6 +50,7 @@ export type AccountRole =
49
50
  export type Role =
50
51
  | AccountRole
51
52
  | "revoked"
53
+ | "managerInvite"
52
54
  | "adminInvite"
53
55
  | "writerInvite"
54
56
  | "readerInvite"
@@ -56,6 +58,7 @@ export type Role =
56
58
 
57
59
  export function isAccountRole(role?: Role): role is AccountRole {
58
60
  return (
61
+ role === "manager" ||
59
62
  role === "admin" ||
60
63
  role === "writer" ||
61
64
  role === "reader" ||
@@ -63,7 +66,10 @@ export function isAccountRole(role?: Role): role is AccountRole {
63
66
  );
64
67
  }
65
68
 
66
- type ValidTransactionsResult = { txID: TransactionID; tx: Transaction };
69
+ function canAdmin(role: Role | undefined): boolean {
70
+ return role === "admin" || role === "manager";
71
+ }
72
+
67
73
  type MemberState = { [agent: RawAccountID | AgentID]: Role; [EVERYONE]?: Role };
68
74
 
69
75
  let logPermissionErrors = true;
@@ -83,11 +89,12 @@ function logPermissionError(
83
89
  logger.debug("Permission error: " + message, attributes);
84
90
  }
85
91
 
86
- export function determineValidTransactions(coValue: CoValueCore) {
92
+ export function determineValidTransactions(coValue: CoValueCore): void {
87
93
  if (!coValue.isAvailable()) {
88
94
  throw new Error("determineValidTransactions CoValue is not available");
89
95
  }
90
96
 
97
+ // The CoValue is a group
91
98
  if (coValue.verified.header.ruleset.type === "group") {
92
99
  const initialAdmin = coValue.verified.header.ruleset.initialAdmin;
93
100
  if (!initialAdmin) {
@@ -95,7 +102,11 @@ export function determineValidTransactions(coValue: CoValueCore) {
95
102
  }
96
103
 
97
104
  determineValidTransactionsForGroup(coValue, initialAdmin);
98
- } else if (coValue.verified.header.ruleset.type === "ownedByGroup") {
105
+ return;
106
+ }
107
+
108
+ // The CoValue is owned by a group
109
+ if (coValue.verified.header.ruleset.type === "ownedByGroup") {
99
110
  const groupContent = expectGroup(
100
111
  coValue.node
101
112
  .expectCoValueLoaded(
@@ -109,12 +120,7 @@ export function determineValidTransactions(coValue: CoValueCore) {
109
120
  throw new Error("Group must be a map");
110
121
  }
111
122
 
112
- for (const tx of coValue.verifiedTransactions) {
113
- if (tx.isValidated) {
114
- continue;
115
- }
116
-
117
- tx.isValidated = true;
123
+ for (const tx of coValue.toValidateTransactions) {
118
124
  // We use the original made at to get the group at the original time when the transaction was made
119
125
  // madeAt might be changed by the meta field (e.g. merged transactions), and so can't be used for permissions checks
120
126
  const groupAtTime = groupContent.atTime(tx.currentMadeAt);
@@ -124,7 +130,7 @@ export function determineValidTransactions(coValue: CoValueCore) {
124
130
  );
125
131
 
126
132
  if (!effectiveTransactor) {
127
- tx.isValid = false;
133
+ tx.markInvalid();
128
134
  continue;
129
135
  }
130
136
 
@@ -142,32 +148,37 @@ export function determineValidTransactions(coValue: CoValueCore) {
142
148
  ownerId: tx.meta.ownerId,
143
149
  };
144
150
  tx.changes = [];
145
- tx.isValid = true;
151
+ tx.markValid();
146
152
  continue;
147
153
  }
148
154
 
149
155
  if (
150
156
  transactorRoleAtTxTime !== "admin" &&
157
+ transactorRoleAtTxTime !== "manager" &&
151
158
  transactorRoleAtTxTime !== "writer" &&
152
159
  transactorRoleAtTxTime !== "writeOnly"
153
160
  ) {
154
- tx.isValid = false;
161
+ tx.markInvalid();
155
162
  continue;
156
163
  }
157
164
 
158
- tx.isValid = true;
165
+ tx.markValid();
159
166
  }
160
- } else if (coValue.verified.header.ruleset.type === "unsafeAllowAll") {
161
- for (const tx of coValue.verifiedTransactions) {
162
- tx.isValid = true;
163
- tx.isValidated = true;
167
+ return;
168
+ }
169
+
170
+ // The CoValue has unsafeAllowAll ruleset
171
+ if (coValue.verified.header.ruleset.type === "unsafeAllowAll") {
172
+ for (const tx of coValue.toValidateTransactions) {
173
+ tx.markValid();
164
174
  }
165
- } else {
166
- throw new Error(
167
- "Unknown ruleset type " +
168
- (coValue.verified.header.ruleset as { type: string }).type,
169
- );
175
+ return;
170
176
  }
177
+
178
+ throw new Error(
179
+ "Unknown ruleset type " +
180
+ (coValue.verified.header.ruleset as { type: string }).type,
181
+ );
171
182
  }
172
183
 
173
184
  function isHigherRole(a: Role, b: Role | undefined) {
@@ -176,6 +187,9 @@ function isHigherRole(a: Role, b: Role | undefined) {
176
187
  if (b === "admin") return false;
177
188
  if (a === "admin") return true;
178
189
 
190
+ if (b === "manager") return false;
191
+ if (a === "manager") return true;
192
+
179
193
  return a === "writer" && b === "reader";
180
194
  }
181
195
 
@@ -236,19 +250,17 @@ function determineValidTransactionsForGroup(
236
250
 
237
251
  const memberState: MemberState = {};
238
252
  const writeOnlyKeys: Record<RawAccountID | AgentID, KeyID> = {};
239
-
240
253
  const writeKeys = new Set<string>();
241
254
 
242
255
  for (const transaction of coValue.verifiedTransactions) {
243
256
  const transactor = transaction.author;
244
-
245
- transaction.isValidated = true;
257
+ const transactorRole = memberState[transactor];
246
258
 
247
259
  const tx = transaction.tx;
248
260
 
249
261
  if (tx.privacy === "private") {
250
262
  if (memberState[transactor] === "admin") {
251
- transaction.isValid = true;
263
+ transaction.markValid();
252
264
  continue;
253
265
  } else {
254
266
  logPermissionError(
@@ -274,66 +286,70 @@ function determineValidTransactionsForGroup(
274
286
 
275
287
  if (changes.length !== 1) {
276
288
  logPermissionError("Group transaction must have exactly one change");
277
- transaction.isValid = false;
289
+ transaction.markInvalid();
278
290
  continue;
279
291
  }
280
292
 
281
293
  if (change.op !== "set") {
282
294
  logPermissionError("Group transaction must set a role or readKey");
283
- transaction.isValid = false;
295
+ transaction.markInvalid();
284
296
  continue;
285
297
  }
286
298
 
287
299
  if (change.key === "readKey") {
288
- if (memberState[transactor] !== "admin") {
300
+ if (!canAdmin(transactorRole)) {
289
301
  logPermissionError("Only admins can set readKeys");
290
- transaction.isValid = false;
302
+ transaction.markInvalid();
291
303
  continue;
292
304
  }
293
305
 
294
- transaction.isValid = true;
306
+ transaction.markValid();
295
307
  continue;
296
308
  } else if (change.key === "profile") {
297
- if (memberState[transactor] !== "admin") {
309
+ if (!canAdmin(transactorRole)) {
298
310
  logPermissionError("Only admins can set profile");
299
- transaction.isValid = false;
311
+ transaction.markInvalid();
300
312
  continue;
301
313
  }
302
314
 
303
- transaction.isValid = true;
315
+ transaction.markValid();
304
316
  continue;
305
317
  } else if (change.key === "root") {
306
- if (memberState[transactor] !== "admin") {
318
+ if (!canAdmin(transactorRole)) {
307
319
  logPermissionError("Only admins can set root");
308
320
  continue;
309
321
  }
310
322
 
311
- transaction.isValid = true;
323
+ transaction.markValid();
312
324
  continue;
313
325
  } else if (
314
326
  isKeyForKeyField(change.key) ||
315
327
  isKeyForAccountField(change.key)
316
328
  ) {
317
329
  if (
318
- memberState[transactor] !== "admin" &&
319
- memberState[transactor] !== "adminInvite" &&
320
- memberState[transactor] !== "writerInvite" &&
321
- memberState[transactor] !== "readerInvite" &&
322
- memberState[transactor] !== "writeOnlyInvite" &&
330
+ transactorRole !== "admin" &&
331
+ transactorRole !== "adminInvite" &&
332
+ transactorRole !== "manager" &&
333
+ transactorRole !== "managerInvite" &&
334
+ transactorRole !== "writerInvite" &&
335
+ transactorRole !== "readerInvite" &&
336
+ transactorRole !== "writeOnlyInvite" &&
323
337
  !isOwnWriteKeyRevelation(change.key, transactor, writeOnlyKeys)
324
338
  ) {
325
- logPermissionError("Only admins can reveal keys");
326
- transaction.isValid = false;
339
+ logPermissionError("Only admins and managers can reveal keys");
340
+ transaction.markInvalid();
327
341
  continue;
328
342
  }
329
343
 
330
344
  // TODO: check validity of agents who the key is revealed to?
331
- transaction.isValid = true;
345
+ transaction.markValid();
332
346
  continue;
333
347
  } else if (isParentExtension(change.key)) {
334
- if (memberState[transactor] !== "admin") {
335
- logPermissionError("Only admins can set parent extensions");
336
- transaction.isValid = false;
348
+ if (!canAdmin(transactorRole)) {
349
+ logPermissionError(
350
+ "Only admins and managers can set parent extensions",
351
+ );
352
+ transaction.markInvalid();
337
353
  continue;
338
354
  }
339
355
 
@@ -352,25 +368,27 @@ function determineValidTransactionsForGroup(
352
368
  logPermissionError(
353
369
  "Circular extend detected, dropping the transaction",
354
370
  );
355
- transaction.isValid = false;
371
+ transaction.markInvalid();
356
372
  continue;
357
373
  }
358
374
 
359
- transaction.isValid = true;
375
+ transaction.markValid();
360
376
  continue;
361
377
  } else if (isChildExtension(change.key)) {
362
- transaction.isValid = true;
378
+ logPermissionError("Child extensions are not allowed anymore");
379
+ transaction.markInvalid();
363
380
  continue;
364
381
  } else if (isWriteKeyForMember(change.key)) {
365
382
  const memberKey = getAccountOrAgentFromWriteKeyForMember(change.key);
366
383
 
367
384
  if (
368
- memberState[transactor] !== "admin" &&
369
- memberState[transactor] !== "writeOnlyInvite" &&
385
+ transactorRole !== "admin" &&
386
+ transactorRole !== "manager" &&
387
+ transactorRole !== "writeOnlyInvite" &&
370
388
  memberKey !== transactor
371
389
  ) {
372
- logPermissionError("Only admins can set writeKeys");
373
- transaction.isValid = false;
390
+ logPermissionError("Only admins and managers can set writeKeys");
391
+ transaction.markInvalid();
374
392
  continue;
375
393
  }
376
394
 
@@ -384,17 +402,17 @@ function determineValidTransactionsForGroup(
384
402
  * write keys, otherwise they could hide a write key to other writeOnly users
385
403
  * blocking them from accessing the group.ß
386
404
  */
387
- if (writeKeys.has(change.key) && memberState[transactor] !== "admin") {
405
+ if (writeKeys.has(change.key) && !canAdmin(transactorRole)) {
388
406
  logPermissionError(
389
407
  "Write key already exists and can't be overridden by invite",
390
408
  );
391
- transaction.isValid = false;
409
+ transaction.markInvalid();
392
410
  continue;
393
411
  }
394
412
 
395
413
  writeKeys.add(change.key);
396
414
 
397
- transaction.isValid = true;
415
+ transaction.markValid();
398
416
  continue;
399
417
  }
400
418
 
@@ -402,93 +420,156 @@ function determineValidTransactionsForGroup(
402
420
  const assignedRole = change.value;
403
421
 
404
422
  if (
405
- change.value !== "admin" &&
406
- change.value !== "writer" &&
407
- change.value !== "reader" &&
408
- change.value !== "writeOnly" &&
409
- change.value !== "revoked" &&
410
- change.value !== "adminInvite" &&
411
- change.value !== "writerInvite" &&
412
- change.value !== "readerInvite" &&
413
- change.value !== "writeOnlyInvite"
423
+ assignedRole !== "admin" &&
424
+ assignedRole !== "manager" &&
425
+ assignedRole !== "writer" &&
426
+ assignedRole !== "reader" &&
427
+ assignedRole !== "writeOnly" &&
428
+ assignedRole !== "revoked" &&
429
+ assignedRole !== "managerInvite" &&
430
+ assignedRole !== "adminInvite" &&
431
+ assignedRole !== "writerInvite" &&
432
+ assignedRole !== "readerInvite" &&
433
+ assignedRole !== "writeOnlyInvite"
414
434
  ) {
415
435
  logPermissionError("Group transaction must set a valid role");
416
- transaction.isValid = false;
436
+ transaction.markInvalid();
417
437
  continue;
418
438
  }
419
439
 
420
440
  if (
421
441
  affectedMember === EVERYONE &&
422
442
  !(
423
- change.value === "reader" ||
424
- change.value === "writer" ||
425
- change.value === "writeOnly" ||
426
- change.value === "revoked"
443
+ assignedRole === "reader" ||
444
+ assignedRole === "writer" ||
445
+ assignedRole === "writeOnly" ||
446
+ assignedRole === "revoked"
427
447
  )
428
448
  ) {
429
449
  logPermissionError(
430
450
  "Everyone can only be set to reader, writer, writeOnly or revoked",
431
451
  );
432
- transaction.isValid = false;
452
+ transaction.markInvalid();
433
453
  continue;
434
454
  }
435
455
 
436
- const isFirstSelfAppointment =
437
- !memberState[transactor] &&
456
+ function markTransactionSetRoleAsValid(
457
+ change: MapOpPayload<RawAccountID | AgentID | Everyone, Role>,
458
+ ) {
459
+ if (change.op !== "set") {
460
+ throw new Error("Expected set operation");
461
+ }
462
+
463
+ memberState[change.key] = change.value;
464
+ transaction.markValid();
465
+ }
466
+
467
+ function markTransactionAsInvalid(message: string) {
468
+ logPermissionError(message);
469
+ transaction.markInvalid();
470
+ }
471
+
472
+ // is first self promotion to admin
473
+ if (
474
+ transactorRole === undefined &&
438
475
  transactor === initialAdmin &&
439
- change.op === "set" &&
440
- change.key === transactor &&
441
- change.value === "admin";
476
+ affectedMember === transactor &&
477
+ assignedRole === "admin"
478
+ ) {
479
+ markTransactionSetRoleAsValid(change);
480
+ continue;
481
+ }
442
482
 
443
- const isSelfRevoke =
444
- transactor === change.key && change.value === "revoked";
483
+ // if I'm self revoking, it is always valid
484
+ if (transactor === change.key && change.value === "revoked") {
485
+ markTransactionSetRoleAsValid(change);
486
+ continue;
487
+ }
445
488
 
446
- if (!isFirstSelfAppointment && !isSelfRevoke) {
447
- if (memberState[transactor] === "admin") {
448
- if (
449
- memberState[affectedMember] === "admin" &&
450
- affectedMember !== transactor &&
451
- assignedRole !== "admin"
452
- ) {
453
- logPermissionError("Admins can only demote themselves.");
454
- transaction.isValid = false;
455
- continue;
456
- }
457
- } else if (memberState[transactor] === "adminInvite") {
458
- if (change.value !== "admin") {
459
- logPermissionError("AdminInvites can only create admins.");
460
- transaction.isValid = false;
461
- continue;
462
- }
463
- } else if (memberState[transactor] === "writerInvite") {
464
- if (change.value !== "writer") {
465
- logPermissionError("WriterInvites can only create writers.");
466
- transaction.isValid = false;
467
- continue;
468
- }
469
- } else if (memberState[transactor] === "readerInvite") {
470
- if (change.value !== "reader") {
471
- logPermissionError("ReaderInvites can only create reader.");
472
- transaction.isValid = false;
473
- continue;
474
- }
475
- } else if (memberState[transactor] === "writeOnlyInvite") {
476
- if (change.value !== "writeOnly") {
477
- logPermissionError("WriteOnlyInvites can only create writeOnly.");
478
- transaction.isValid = false;
479
- continue;
480
- }
481
- } else {
482
- logPermissionError(
483
- "Group transaction must be made by current admin or invite",
484
- );
485
- transaction.isValid = false;
489
+ const affectedMemberRole = memberState[affectedMember];
490
+
491
+ /**
492
+ * Admins can't:
493
+ * - demote other admins
494
+ */
495
+ if (transactorRole === "admin") {
496
+ if (
497
+ affectedMemberRole === "admin" &&
498
+ assignedRole !== "admin" &&
499
+ affectedMember !== transactor
500
+ ) {
501
+ markTransactionAsInvalid("Admins can't demote admins.");
502
+ continue;
503
+ }
504
+
505
+ markTransactionSetRoleAsValid(change);
506
+ continue;
507
+ }
508
+
509
+ /**
510
+ * Managers can't:
511
+ * - demote other admins
512
+ * - invite new admins
513
+ * - promote to admin
514
+ */
515
+ if (transactorRole === "manager") {
516
+ if (affectedMemberRole === "admin") {
517
+ markTransactionAsInvalid("Managers can't demote admins.");
518
+ continue;
519
+ }
520
+ if (change.value === "admin") {
521
+ markTransactionAsInvalid("Managers can't promote to admin.");
486
522
  continue;
487
523
  }
524
+
525
+ if (change.value === "adminInvite") {
526
+ markTransactionAsInvalid("Managers can't invite admins.");
527
+ continue;
528
+ }
529
+ if (change.value === "managerInvite") {
530
+ markTransactionAsInvalid("Managers can't invite managers.");
531
+ continue;
532
+ }
533
+
534
+ markTransactionSetRoleAsValid(change);
535
+ continue;
536
+ }
537
+
538
+ if (transactorRole === "adminInvite") {
539
+ if (change.value !== "admin") {
540
+ logPermissionError("AdminInvites can only create admins.");
541
+ transaction.markInvalid();
542
+ continue;
543
+ }
544
+ } else if (transactorRole === "managerInvite") {
545
+ if (change.value !== "manager") {
546
+ markTransactionAsInvalid("managerInvite can only create managers.");
547
+ continue;
548
+ }
549
+ } else if (transactorRole === "writerInvite") {
550
+ if (change.value !== "writer") {
551
+ markTransactionAsInvalid("WriterInvites can only create writers.");
552
+ continue;
553
+ }
554
+ } else if (transactorRole === "readerInvite") {
555
+ if (change.value !== "reader") {
556
+ markTransactionAsInvalid("ReaderInvites can only create reader.");
557
+ continue;
558
+ }
559
+ } else if (transactorRole === "writeOnlyInvite") {
560
+ if (change.value !== "writeOnly") {
561
+ markTransactionAsInvalid("WriteOnlyInvites can only create writeOnly.");
562
+ continue;
563
+ }
564
+ } else {
565
+ markTransactionAsInvalid(
566
+ "Group transaction must be made by current admin, manager, or invite",
567
+ );
568
+ continue;
488
569
  }
489
570
 
490
571
  memberState[affectedMember] = change.value;
491
- transaction.isValid = true;
572
+ transaction.markValid();
492
573
  }
493
574
 
494
575
  return { memberState };