@peers-app/peers-sdk 0.27.5 → 0.28.0

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 (81) hide show
  1. package/dist/context/user-context.d.ts +7 -1
  2. package/dist/context/user-context.js +4 -9
  3. package/dist/contracts/contract-proxy.d.ts +5 -0
  4. package/dist/contracts/contract-proxy.js +2 -2
  5. package/dist/contracts/contract-resolvers.d.ts +2 -2
  6. package/dist/contracts/contract-resolvers.js +14 -15
  7. package/dist/contracts/system/network-contracts.d.ts +1 -1
  8. package/dist/contracts/tool-catalog-service.js +1 -1
  9. package/dist/data/assistants.d.ts +3 -3
  10. package/dist/data/authority-write-checks.d.ts +42 -0
  11. package/dist/data/authority-write-checks.js +324 -0
  12. package/dist/data/change-tracking.d.ts +4 -4
  13. package/dist/data/create-group.js +1 -0
  14. package/dist/data/devices.d.ts +27 -4
  15. package/dist/data/devices.js +31 -0
  16. package/dist/data/group-authority.d.ts +111 -0
  17. package/dist/data/group-authority.js +281 -0
  18. package/dist/data/group-members.d.ts +8 -2
  19. package/dist/data/group-members.js +41 -18
  20. package/dist/data/group-permissions.d.ts +15 -31
  21. package/dist/data/group-permissions.js +25 -95
  22. package/dist/data/group-resign.d.ts +38 -0
  23. package/dist/data/group-resign.js +71 -0
  24. package/dist/data/groups.d.ts +11 -0
  25. package/dist/data/groups.js +31 -13
  26. package/dist/data/index.d.ts +3 -0
  27. package/dist/data/index.js +3 -0
  28. package/dist/data/invites.d.ts +14 -0
  29. package/dist/data/orm/index.d.ts +1 -0
  30. package/dist/data/orm/index.js +1 -0
  31. package/dist/data/orm/sql.data-source.d.ts +2 -2
  32. package/dist/data/orm/sql.data-source.js +8 -9
  33. package/dist/data/orm/table-container.js +14 -8
  34. package/dist/data/orm/write-checks.d.ts +89 -0
  35. package/dist/data/orm/write-checks.js +115 -0
  36. package/dist/data/package-table-identity.d.ts +6 -0
  37. package/dist/data/package-table-identity.js +51 -0
  38. package/dist/data/package-version-resolver.d.ts +15 -0
  39. package/dist/data/package-version-resolver.js +58 -3
  40. package/dist/data/package-versions.d.ts +20 -14
  41. package/dist/data/package-versions.js +15 -9
  42. package/dist/data/packages.d.ts +6 -1
  43. package/dist/data/packages.js +15 -20
  44. package/dist/data/persistent-vars.js +2 -39
  45. package/dist/data/sync-recovery-queue.d.ts +4 -4
  46. package/dist/data/sync-recovery-queue.js +7 -1
  47. package/dist/data/tool-access-approvals.d.ts +3 -3
  48. package/dist/data/user-permissions.d.ts +33 -2
  49. package/dist/data/user-permissions.js +87 -10
  50. package/dist/data/users.d.ts +49 -12
  51. package/dist/data/users.js +21 -1
  52. package/dist/data/workflows.d.ts +1 -1
  53. package/dist/device/binary-peer-connection-v2.d.ts +1 -1
  54. package/dist/device/binary-peer-connection-v2.js +1 -1
  55. package/dist/device/connection.d.ts +1 -1
  56. package/dist/device/frame-cipher.d.ts +1 -1
  57. package/dist/device/frame-cipher.js +1 -1
  58. package/dist/device/get-trust-level-fn.js +3 -6
  59. package/dist/device-pairing/types.d.ts +26 -26
  60. package/dist/identity/user-row-change.d.ts +3 -1
  61. package/dist/identity/user-row-change.js +33 -18
  62. package/dist/invites/group-approval.js +23 -24
  63. package/dist/invites/invite-messages.d.ts +250 -28
  64. package/dist/invites/invite-token.d.ts +18 -0
  65. package/dist/invites/invite-token.js +2 -0
  66. package/dist/keys.d.ts +7 -0
  67. package/dist/keys.js +27 -0
  68. package/dist/package-installer/package-installer.d.ts +1 -1
  69. package/dist/package-installer/package-installer.js +8 -3
  70. package/dist/package-installer/package-propagation.d.ts +2 -0
  71. package/dist/package-installer/package-propagation.js +20 -38
  72. package/dist/package-installer/package-remote-checker.js +15 -3
  73. package/dist/package-loader/isolated-artifact.js +0 -2
  74. package/dist/package-loader/package-loader.js +6 -0
  75. package/dist/types/peer-device.d.ts +2 -0
  76. package/dist/types/workflow.d.ts +2 -2
  77. package/package.json +1 -1
  78. package/dist/data/package-permissions.d.ts +0 -14
  79. package/dist/data/package-permissions.js +0 -26
  80. package/dist/data/package-version-permissions.d.ts +0 -14
  81. package/dist/data/package-version-permissions.js +0 -30
@@ -1,17 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveUserRowChange = resolveUserRowChange;
4
+ const user_permissions_1 = require("../data/user-permissions");
4
5
  const users_1 = require("../data/users");
5
6
  const anchor_resolution_1 = require("./anchor-resolution");
6
7
  const key_change_1 = require("./key-change");
7
- function withAnchors(row, anchors) {
8
- if ((0, anchor_resolution_1.sameAnchorList)(row.anchors, anchors))
9
- return row;
10
- if (!anchors?.length) {
11
- const { anchors: _dropped, ...rest } = row;
12
- return rest;
13
- }
14
- return { ...row, anchors };
8
+ /** `row` carrying `source`'s anchor list and the owner's signature over it, or no list. */
9
+ function withListOf(row, source) {
10
+ const { anchors: _anchors, anchorsSignature: _signature, ...profile } = row;
11
+ return { ...profile, ...(0, user_permissions_1.anchorFields)(source) };
12
+ }
13
+ /** `row` as a first record: its list only when the owner signed it, else no list. */
14
+ function bootstrap(row) {
15
+ return (0, user_permissions_1.hasSignedUserAnchors)(row) ? row : withListOf(row, {});
15
16
  }
16
17
  /**
17
18
  * The one decision for every path that writes another user's `Users` row: a direct save, a
@@ -20,9 +21,11 @@ function withAnchors(row, anchors) {
20
21
  * The record is the personal `Users` row when it exists, else the row already stored in the
21
22
  * table being written. The rule:
22
23
  *
23
- * - Nothing stored yet: the row is the bootstrap and is applied as it is.
24
+ * - Nothing stored yet: bootstrap the identity; its initial anchors need an owner signature.
24
25
  * - A different key: the anchors on record decide ({@link evaluatePresentedKey}). The
25
26
  * presented anchor list is ignored; the applied row keeps the list on record.
27
+ * - The user's own row with their current keys: takes the anchor list from their personal row.
28
+ * - A row with no list keeps the list on record.
26
29
  * - The same key and a different list: when nothing is on record the list is accepted as the
27
30
  * bootstrap (an upgraded contact announcing its first anchors); when the personal record
28
31
  * already holds the list it is accepted; otherwise the anchors on record must publish it
@@ -31,12 +34,19 @@ function withAnchors(row, anchors) {
31
34
  */
32
35
  async function resolveUserRowChange(params) {
33
36
  const { existing, incoming, personalContext } = params;
34
- if (!existing?.publicKey)
35
- return { action: "apply", row: incoming };
36
37
  const personal = await (0, users_1.Users)(personalContext).get(incoming.userId);
37
38
  const record = personal?.publicKey ? personal : existing;
38
- const recordAnchors = personal?.anchors?.length ? personal.anchors : (existing.anchors ?? []);
39
- if (incoming.publicKey !== existing.publicKey) {
39
+ if (!record?.publicKey)
40
+ return { action: "apply", row: bootstrap(incoming) };
41
+ const recordAnchors = record.anchors ?? [];
42
+ // My own row with my current keys: my personal copy is the record of my anchors, so a copy
43
+ // anywhere else carries that list (a stale or replayed copy can't move it either way).
44
+ if (incoming.userId === personalContext.userContext.userId &&
45
+ personal?.publicKey === incoming.publicKey &&
46
+ personal.publicBoxKey === incoming.publicBoxKey) {
47
+ return { action: "apply", row: withListOf(incoming, personal) };
48
+ }
49
+ if (incoming.publicKey !== record.publicKey || incoming.publicBoxKey !== record.publicBoxKey) {
40
50
  const decision = await (0, key_change_1.evaluatePresentedKey)({
41
51
  dataContext: personalContext,
42
52
  userId: incoming.userId,
@@ -47,18 +57,23 @@ async function resolveUserRowChange(params) {
47
57
  if (decision.accepted) {
48
58
  // The resolver may have just stored a confirmed list on the personal row.
49
59
  const confirmed = await (0, users_1.Users)(personalContext).get(incoming.userId);
50
- const anchors = confirmed?.anchors?.length ? confirmed.anchors : recordAnchors;
51
- return { action: "apply", row: withAnchors(incoming, anchors) };
60
+ return {
61
+ action: "apply",
62
+ row: withListOf(incoming, confirmed?.anchors?.length ? confirmed : record),
63
+ };
52
64
  }
53
65
  if (decision.state === "pending" || decision.state === "recovery-pending") {
54
66
  return { action: "defer", reason: "Key change is not confirmed by the anchors on record" };
55
67
  }
56
68
  return { action: "refuse", reason: "Key change is not confirmed by the anchors on record" };
57
69
  }
58
- if ((0, anchor_resolution_1.sameAnchorList)(existing.anchors, incoming.anchors))
70
+ if ((0, anchor_resolution_1.sameAnchorList)(record.anchors, incoming.anchors))
59
71
  return { action: "apply", row: incoming };
72
+ // A row with no list (a stub, a copy that dropped it) proposes no change: keep the record's.
73
+ if (!incoming.anchors?.length)
74
+ return { action: "apply", row: withListOf(incoming, record) };
60
75
  if (recordAnchors.length === 0)
61
- return { action: "apply", row: incoming };
76
+ return { action: "apply", row: bootstrap(incoming) };
62
77
  if ((0, anchor_resolution_1.sameAnchorList)(personal?.anchors, incoming.anchors)) {
63
78
  return { action: "apply", row: incoming };
64
79
  }
@@ -76,6 +91,6 @@ async function resolveUserRowChange(params) {
76
91
  reason: "Anchor list change is not confirmed by the anchors on record",
77
92
  };
78
93
  }
79
- return { action: "apply", row: withAnchors(incoming, recordAnchors) };
94
+ return { action: "apply", row: withListOf(incoming, record) };
80
95
  }
81
96
  (0, users_1.setUserRowChangeGuard)((personalContext, incoming, existing) => resolveUserRowChange({ personalContext, incoming, existing }));
@@ -35,13 +35,15 @@ async function buildGroupApproval(userContext, input) {
35
35
  groupId: input.groupId,
36
36
  userId: userContext.userId,
37
37
  role: data_1.GroupMemberRole.Founder,
38
+ publicKey: input.approverKeys.publicKey,
38
39
  signature: "",
39
40
  });
40
41
  }
41
- if (!approvingMember || approvingMember.deleted || approvingMember.role < data_1.GroupMemberRole.Admin) {
42
+ const approvingRole = await (0, data_1.getUserRole)(input.groupId, userContext.userId, userContext);
43
+ if (!approvingMember || approvingRole < data_1.GroupMemberRole.Admin) {
42
44
  throw new Error("Only Admin or higher members can approve group joins");
43
45
  }
44
- if (input.role > approvingMember.role) {
46
+ if (input.role > approvingRole) {
45
47
  throw new Error("Cannot grant a role higher than your own");
46
48
  }
47
49
  const groupSecretKey = await (0, data_1.readGroupSecretKey)(userContext, input.groupId, input.approverKeys.secretKey);
@@ -58,27 +60,24 @@ async function buildGroupApproval(userContext, input) {
58
60
  groupId: input.groupId,
59
61
  userId: input.joiner.userId,
60
62
  });
61
- let membership;
62
- if (existingMember) {
63
- existingMember.role = input.role;
64
- delete existingMember.deleted;
65
- membership = await groupMembersTable.signAndSave(existingMember);
66
- }
67
- else {
68
- // The same id on each of the approver's devices: a join several of them approve at once
69
- // is one row, not rows that collide on (groupId, userId).
70
- membership = await groupMembersTable.signAndSave({
71
- groupMemberId: (0, utils_1.deterministicGroupMemberId)(input.groupId, input.joiner.userId),
72
- groupId: input.groupId,
73
- userId: input.joiner.userId,
74
- role: input.role,
75
- signature: "",
76
- });
77
- }
78
- let adminMembers = await groupMembersTable.list({ deleted: { $ne: true }, role: { $gte: data_1.GroupMemberRole.Admin } }, { pageSize: 5, sortBy: ["-groupMemberId"] });
79
- if (!adminMembers.some((member) => member.groupMemberId === approvingMember.groupMemberId)) {
80
- adminMembers = [approvingMember, ...adminMembers];
81
- }
63
+ // The same id on each approving device, while preserving an existing row's identity.
64
+ const membership = await groupMembersTable.signAndSave({
65
+ groupMemberId: (0, utils_1.deterministicGroupMemberId)(input.groupId, input.joiner.userId),
66
+ signature: "",
67
+ ...existingMember,
68
+ groupId: input.groupId,
69
+ userId: input.joiner.userId,
70
+ role: input.role,
71
+ publicKey: input.joiner.publicKey,
72
+ deleted: undefined,
73
+ });
74
+ const authority = await (0, data_1.getGroupAuthority)(userContext, input.groupId);
75
+ const adminMembers = (await groupMembersTable.list({ groupId: input.groupId })).filter((member) => !member.deleted &&
76
+ member.role >= data_1.GroupMemberRole.Admin &&
77
+ authority.roles.get(member.userId) === member.role);
78
+ // A joiner must verify the complete grant chain before it can admit its first peer: the
79
+ // verified profile of each member whose key counts, and no other contact of the approver's.
80
+ const seedUsers = [...authority.keys.keys()].flatMap((userId) => authority.users.get(userId) ?? []);
82
81
  const me = await (0, data_1.Users)(userContext.userDataContext).get(userContext.userId);
83
82
  if (!me) {
84
83
  throw new Error("My user object was not found in the DB. Is the user setup?");
@@ -100,7 +99,7 @@ async function buildGroupApproval(userContext, input) {
100
99
  membership,
101
100
  seed: {
102
101
  groupMembers: adminMembers,
103
- users: [me],
102
+ users: seedUsers,
104
103
  devices: seedDevices,
105
104
  },
106
105
  approver: {