@peers-app/peers-sdk 0.25.13 → 0.25.15

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 (79) hide show
  1. package/dist/context/user-context-identity.test.d.ts +1 -0
  2. package/dist/context/user-context-identity.test.js +220 -0
  3. package/dist/context/user-context.d.ts +5 -1
  4. package/dist/context/user-context.js +63 -8
  5. package/dist/contracts/index.d.ts +1 -1
  6. package/dist/contracts/system/identity-contract.d.ts +156 -0
  7. package/dist/contracts/system/identity-contract.js +172 -0
  8. package/dist/contracts/system/index.d.ts +1 -1
  9. package/dist/data/assistants.d.ts +4 -4
  10. package/dist/data/devices.d.ts +70 -4
  11. package/dist/data/devices.js +3 -0
  12. package/dist/data/group-members.d.ts +2 -2
  13. package/dist/data/groups.d.ts +2 -2
  14. package/dist/data/invites.d.ts +93 -25
  15. package/dist/data/persistent-vars.d.ts +8 -0
  16. package/dist/data/persistent-vars.js +16 -0
  17. package/dist/data/tool-access-approvals.d.ts +2 -2
  18. package/dist/data/user-permissions.d.ts +10 -2
  19. package/dist/data/user-permissions.js +35 -4
  20. package/dist/data/user-permissions.test.js +26 -0
  21. package/dist/data/users.d.ts +48 -4
  22. package/dist/data/users.js +61 -5
  23. package/dist/data/workflows.d.ts +1 -1
  24. package/dist/device/connection.d.ts +7 -1
  25. package/dist/device/connection.js +38 -6
  26. package/dist/device/connection.test.js +33 -4
  27. package/dist/device/device.d.ts +2 -2
  28. package/dist/device/device.js +4 -2
  29. package/dist/device/get-trust-level-fn.d.ts +1 -0
  30. package/dist/device/get-trust-level-fn.js +28 -4
  31. package/dist/device/get-trust-level-fn.test.js +95 -0
  32. package/dist/device-pairing/types.d.ts +26 -26
  33. package/dist/host-credentials.d.ts +78 -0
  34. package/dist/host-credentials.js +128 -0
  35. package/dist/host-credentials.test.d.ts +1 -0
  36. package/dist/host-credentials.test.js +88 -0
  37. package/dist/identity/account-email.d.ts +59 -0
  38. package/dist/identity/account-email.js +87 -0
  39. package/dist/identity/account-email.test.d.ts +1 -0
  40. package/dist/identity/account-email.test.js +59 -0
  41. package/dist/identity/anchor-document.d.ts +205 -0
  42. package/dist/identity/anchor-document.js +119 -0
  43. package/dist/identity/anchor-document.test.d.ts +1 -0
  44. package/dist/identity/anchor-document.test.js +16 -0
  45. package/dist/identity/anchor-resolution.d.ts +61 -0
  46. package/dist/identity/anchor-resolution.js +117 -0
  47. package/dist/identity/anchor-resolution.test.d.ts +1 -0
  48. package/dist/identity/anchor-resolution.test.js +130 -0
  49. package/dist/identity/key-change.d.ts +142 -0
  50. package/dist/identity/key-change.js +472 -0
  51. package/dist/identity/key-change.test.d.ts +1 -0
  52. package/dist/identity/key-change.test.js +811 -0
  53. package/dist/identity/key-contest.d.ts +91 -0
  54. package/dist/identity/key-contest.js +55 -0
  55. package/dist/identity/key-resolutions.d.ts +55 -0
  56. package/dist/identity/key-resolutions.js +42 -0
  57. package/dist/identity/passkey.d.ts +22 -0
  58. package/dist/identity/passkey.js +33 -0
  59. package/dist/identity/user-row-change.d.ts +45 -0
  60. package/dist/identity/user-row-change.js +81 -0
  61. package/dist/index.d.ts +9 -0
  62. package/dist/index.js +9 -0
  63. package/dist/invites/invite-messages.d.ts +512 -116
  64. package/dist/invites/invite-token.d.ts +114 -24
  65. package/dist/invites/invite-token.js +6 -0
  66. package/dist/keys.d.ts +7 -0
  67. package/dist/keys.js +20 -0
  68. package/dist/package-installer/package-author-signing.test.js +11 -0
  69. package/dist/package-installer/package-publisher.d.ts +5 -1
  70. package/dist/package-installer/package-publisher.js +1 -1
  71. package/dist/package-installer/package-publisher.test.js +11 -0
  72. package/dist/package-installer/package-remote-checker.js +3 -0
  73. package/dist/system-ids.d.ts +16 -0
  74. package/dist/system-ids.js +16 -0
  75. package/dist/types/workflow.d.ts +2 -2
  76. package/dist/utils.d.ts +10 -0
  77. package/dist/utils.js +43 -0
  78. package/dist/utils.test.js +23 -0
  79. package/package.json +1 -1
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ const anchor_document_1 = require("./anchor-document");
5
+ const anchor_resolution_1 = require("./anchor-resolution");
6
+ const userId = (0, index_1.newid)();
7
+ const record = (0, index_1.newKeys)();
8
+ const next = (0, index_1.newKeys)();
9
+ const anchor = {
10
+ type: "https",
11
+ url: "https://example.com/.well-known/peers-identity.json",
12
+ };
13
+ function document(keys) {
14
+ return {
15
+ version: 1,
16
+ userId,
17
+ keys,
18
+ anchors: [anchor],
19
+ updatedAt: "2026-10-03T18:00:00.000Z",
20
+ };
21
+ }
22
+ function confirm(entry) {
23
+ return { outcome: "document", document: document([entry]), maxAgeSeconds: 30 };
24
+ }
25
+ describe("resolvePresentedKey", () => {
26
+ const issuedAt = "2026-10-03T18:00:00.000Z";
27
+ it("confirms a rotation when the key on record co-signed and nobody vetoes", () => {
28
+ const signed = (0, anchor_document_1.signSuccession)({ purpose: "peers-key-succession", userId, publicKey: next.publicKey, issuedAt }, record.secretKey);
29
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
30
+ userId,
31
+ recordPublicKey: record.publicKey,
32
+ presentedPublicKey: next.publicKey,
33
+ outcomes: [
34
+ confirm({
35
+ publicKey: next.publicKey,
36
+ issuedAt,
37
+ reason: "rotation",
38
+ authorizedBy: { publicKey: signed.publicKey, signature: signed.signature },
39
+ }),
40
+ { outcome: "abstain" },
41
+ ],
42
+ });
43
+ expect(resolution.state).toBe("confirmed-rotation");
44
+ expect(resolution.successionByRecordKey).toBe(true);
45
+ expect(resolution.cacheSeconds).toBe(30);
46
+ });
47
+ it("treats a listed key without the record key's signature as recovery", () => {
48
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
49
+ userId,
50
+ recordPublicKey: record.publicKey,
51
+ presentedPublicKey: next.publicKey,
52
+ outcomes: [confirm({ publicKey: next.publicKey, issuedAt, reason: "lost" })],
53
+ });
54
+ expect(resolution.state).toBe("confirmed-recovery");
55
+ });
56
+ it("vetoes when any anchor omits the presented key", () => {
57
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
58
+ userId,
59
+ recordPublicKey: record.publicKey,
60
+ presentedPublicKey: next.publicKey,
61
+ outcomes: [
62
+ confirm({ publicKey: next.publicKey, issuedAt }),
63
+ confirm({ publicKey: record.publicKey, issuedAt }),
64
+ ],
65
+ });
66
+ expect(resolution.state).toBe("vetoed");
67
+ });
68
+ it("vetoes a document for another user and a 404", () => {
69
+ const other = document([{ publicKey: next.publicKey, issuedAt }]);
70
+ other.userId = (0, index_1.newid)();
71
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
72
+ userId,
73
+ recordPublicKey: record.publicKey,
74
+ presentedPublicKey: next.publicKey,
75
+ outcomes: [{ outcome: "document", document: other }, { outcome: "veto" }],
76
+ });
77
+ expect(resolution.state).toBe("vetoed");
78
+ });
79
+ it("stays pending when every anchor abstains", () => {
80
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
81
+ userId,
82
+ recordPublicKey: record.publicKey,
83
+ presentedPublicKey: next.publicKey,
84
+ outcomes: [{ outcome: "abstain" }, { outcome: "abstain" }],
85
+ });
86
+ expect(resolution.state).toBe("pending");
87
+ });
88
+ it("rejects a co-signature that is not the key on record", () => {
89
+ const imposter = (0, index_1.newKeys)();
90
+ const signed = (0, anchor_document_1.signSuccession)({ purpose: "peers-key-succession", userId, publicKey: next.publicKey, issuedAt }, imposter.secretKey);
91
+ const resolution = (0, anchor_resolution_1.resolvePresentedKey)({
92
+ userId,
93
+ recordPublicKey: record.publicKey,
94
+ presentedPublicKey: next.publicKey,
95
+ outcomes: [
96
+ confirm({
97
+ publicKey: next.publicKey,
98
+ issuedAt,
99
+ authorizedBy: { publicKey: signed.publicKey, signature: signed.signature },
100
+ }),
101
+ ],
102
+ });
103
+ expect(resolution.state).toBe("confirmed-recovery");
104
+ });
105
+ });
106
+ describe("resolvePresentedAnchors", () => {
107
+ const next = [anchor, { type: "https", url: "https://example.com/other.json" }];
108
+ it("confirms when an anchor publishes the new list and none veto", () => {
109
+ const decision = (0, anchor_resolution_1.resolvePresentedAnchors)({
110
+ userId,
111
+ presented: next,
112
+ outcomes: [
113
+ { outcome: "document", document: { ...document([]), anchors: [...next].reverse() } },
114
+ { outcome: "abstain" },
115
+ ],
116
+ });
117
+ expect(decision).toBe("confirmed");
118
+ });
119
+ it("vetoes when an anchor still publishes a different list", () => {
120
+ const decision = (0, anchor_resolution_1.resolvePresentedAnchors)({
121
+ userId,
122
+ presented: next,
123
+ outcomes: [
124
+ { outcome: "document", document: document([]) },
125
+ { outcome: "document", document: { ...document([]), anchors: next } },
126
+ ],
127
+ });
128
+ expect(decision).toBe("vetoed");
129
+ });
130
+ });
@@ -0,0 +1,142 @@
1
+ import type { DataContext } from "../context/data-context";
2
+ import { type IUser } from "../data/users";
3
+ import { Emitter } from "../events";
4
+ import { type ISignedObject } from "../keys";
5
+ import type { IAnchorRef } from "./anchor-document";
6
+ import { type IKeyContestStatement } from "./key-contest";
7
+ /** How long a recovery waits before peers adopt it. Not a user setting. */
8
+ export declare const DEFAULT_RECOVERY_DELAY_MS: number;
9
+ /**
10
+ * Delay before a confirmed recovery replaces the key on record.
11
+ * `PEERS_RECOVERY_DELAY_MS` shortens it for tests. Unset means {@link DEFAULT_RECOVERY_DELAY_MS}.
12
+ */
13
+ export declare function recoveryDelayMs(): number;
14
+ /** On-record identity used when the personal `Users` row is not the row being compared. */
15
+ export type KeyRecord = Pick<IUser, "userId" | "publicKey" | "publicBoxKey" | "anchors">;
16
+ /** Decision for a handshake or row that presents a key. */
17
+ export type PresentedKeyDecision = {
18
+ accepted: true;
19
+ state: "current" | "confirmed-rotation" | "confirmed-recovery";
20
+ } | {
21
+ accepted: false;
22
+ state: "pending" | "recovery-pending" | "vetoed" | "box-mismatch" | "no-record";
23
+ };
24
+ /** Inputs for {@link evaluatePresentedKey}. */
25
+ export interface EvaluatePresentedKeyParams {
26
+ /** Personal data context, where the on-record row and the resolution cache live. */
27
+ dataContext: DataContext;
28
+ userId: string;
29
+ presentedKey: string;
30
+ presentedBoxKey: string;
31
+ /**
32
+ * Key and anchors to resolve against when the personal row is missing or still
33
+ * holds an older key. Defaults to the personal row.
34
+ */
35
+ record?: KeyRecord;
36
+ /**
37
+ * When false, a confirmed rotation is reported but the personal row is left
38
+ * alone. A device recognising that its own key was rotated away passes false
39
+ * so it does not store a key it cannot sign.
40
+ */
41
+ adopt?: boolean;
42
+ }
43
+ /** Payload of {@link identityRotatedAwayEvent}. */
44
+ export interface IIdentityRotatedAway {
45
+ userId: string;
46
+ /** This device's key, which the anchors no longer list as current. */
47
+ oldPublicKey: string;
48
+ /** The key the anchors confirmed. */
49
+ newPublicKey: string;
50
+ /** Device that presented the new key, when the evidence was a handshake. */
51
+ seenFromDeviceId?: string;
52
+ detectedAt: string;
53
+ }
54
+ /**
55
+ * Fired on a device whose own key is no longer the key the anchors confirm.
56
+ * The device must be paired again. Hosts subscribe to show the notice.
57
+ */
58
+ export declare const identityRotatedAwayEvent: Emitter<IIdentityRotatedAway>;
59
+ /**
60
+ * Device-scoped record of the rotated-away state so the UI can show it after a restart.
61
+ */
62
+ export declare const identityRotatedAwayVar: import("../data/persistent-vars").PersistentVar<IIdentityRotatedAway | null>;
63
+ /** A recovery this device has seen and not yet adopted. `own` is a key this device still holds. */
64
+ export interface IIdentityRecoveryPending {
65
+ userId: string;
66
+ recordPublicKey: string;
67
+ pendingPublicKey: string;
68
+ firstSeenAt: string;
69
+ readyAt: string;
70
+ contested: boolean;
71
+ own: boolean;
72
+ }
73
+ /**
74
+ * Recoveries waiting on the delay or on a contest.
75
+ * The own-device banner and a contact's decision both read this.
76
+ */
77
+ export declare const identityRecoveryNoticesVar: import("../data/persistent-vars").PersistentVar<IIdentityRecoveryPending[]>;
78
+ /** Asks an anchor which contests it is holding. Installed by the host. */
79
+ export type ContestFetcher = (userId: string) => Promise<ISignedObject<IKeyContestStatement>[]>;
80
+ /** Installs the contest lookup {@link evaluatePresentedKey} uses. */
81
+ export declare function setContestFetcher(fetcher: ContestFetcher | undefined): void;
82
+ /** The contest this device is carrying in its handshake, if it has signed one. */
83
+ export declare function peekLocalKeyContest(): ISignedObject<IKeyContestStatement> | undefined;
84
+ /** Remembers a contest this device signed, so later handshakes carry it, across restarts. */
85
+ export declare function setLocalKeyContest(signed: ISignedObject<IKeyContestStatement> | undefined): Promise<void>;
86
+ /** Remembers a contest received from a peer or an anchor. */
87
+ export declare function holdKeyContest(signed: ISignedObject<IKeyContestStatement>): void;
88
+ /**
89
+ * Decides whether `presentedKey` may replace the key on record for `userId`.
90
+ *
91
+ * The anchors already stored for the user are fetched (or a fresh cached answer is reused).
92
+ * A confirmed rotation updates the personal `Users` row immediately. A confirmed recovery
93
+ * waits out {@link recoveryDelayMs} and is held if a holder of the key on record contests
94
+ * it. A veto or no answer leaves the key on record in place.
95
+ */
96
+ export declare function evaluatePresentedKey(params: EvaluatePresentedKeyParams): Promise<PresentedKeyDecision>;
97
+ /** Evidence that another holder of our user id is using a different key. */
98
+ export interface RotatedAwayEvidence {
99
+ userId: string;
100
+ publicKey: string;
101
+ publicBoxKey: string;
102
+ deviceId?: string;
103
+ }
104
+ /**
105
+ * When another device of this user presents a key the anchors confirm, records that this
106
+ * device holds the old key and must be paired again. A pending or vetoed key does not.
107
+ */
108
+ export declare function detectIdentityRotatedAway(me: {
109
+ userId: string;
110
+ publicKey: string;
111
+ }, evidence: RotatedAwayEvidence): Promise<IIdentityRotatedAway | undefined>;
112
+ /**
113
+ * Drops the rotated-away notice when it no longer describes this device: it now holds the
114
+ * key the notice named, or a key the anchors have since confirmed (`anchorsConfirmed`).
115
+ * A device that still holds the retired key keeps its notice.
116
+ */
117
+ export declare function clearRotatedAwayNotice(params: {
118
+ userId: string;
119
+ deviceKey: string;
120
+ anchorsConfirmed: boolean;
121
+ }): Promise<void>;
122
+ /**
123
+ * Adopts a recovery before the delay when the person at this device says to.
124
+ * A contest keeps the change pending until this runs.
125
+ */
126
+ export declare function acceptRecovery(dataContext: DataContext, userId: string, pendingKey: string): Promise<void>;
127
+ /**
128
+ * Records a contest against `pendingKey` and holds the recovery.
129
+ * Returns false when the signature is not the key on record.
130
+ */
131
+ export declare function applyKeyContest(dataContext: DataContext, signed: ISignedObject<IKeyContestStatement>): Promise<boolean>;
132
+ /**
133
+ * Decides whether `presentedAnchors` may replace the list on record.
134
+ * The anchors already stored are fetched. On confirmation the personal row's list is updated.
135
+ * The signing key is left as it is.
136
+ */
137
+ export declare function evaluatePresentedAnchors(params: {
138
+ dataContext: DataContext;
139
+ userId: string;
140
+ recordAnchors: IAnchorRef[];
141
+ presentedAnchors: IAnchorRef[];
142
+ }): Promise<"confirmed" | "pending" | "vetoed">;