@peers-app/peers-sdk 0.25.14 → 0.25.16
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/dist/context/user-context-identity.test.d.ts +1 -0
- package/dist/context/user-context-identity.test.js +220 -0
- package/dist/context/user-context.d.ts +5 -1
- package/dist/context/user-context.js +63 -8
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/system/identity-contract.d.ts +156 -0
- package/dist/contracts/system/identity-contract.js +172 -0
- package/dist/contracts/system/index.d.ts +1 -1
- package/dist/data/assistants.d.ts +4 -4
- package/dist/data/devices.d.ts +70 -4
- package/dist/data/devices.js +3 -0
- package/dist/data/group-members.d.ts +2 -2
- package/dist/data/groups.d.ts +2 -2
- package/dist/data/invites.d.ts +93 -25
- package/dist/data/persistent-vars.d.ts +8 -0
- package/dist/data/persistent-vars.js +16 -0
- package/dist/data/tool-access-approvals.d.ts +2 -2
- package/dist/data/user-permissions.d.ts +10 -2
- package/dist/data/user-permissions.js +35 -4
- package/dist/data/user-permissions.test.js +26 -0
- package/dist/data/users.d.ts +48 -4
- package/dist/data/users.js +61 -5
- package/dist/data/workflows.d.ts +1 -1
- package/dist/device/connection.d.ts +7 -1
- package/dist/device/connection.js +38 -6
- package/dist/device/connection.test.js +33 -4
- package/dist/device/device.d.ts +2 -2
- package/dist/device/device.js +4 -2
- package/dist/device/get-trust-level-fn.d.ts +1 -0
- package/dist/device/get-trust-level-fn.js +28 -4
- package/dist/device/get-trust-level-fn.test.js +95 -0
- package/dist/device-pairing/types.d.ts +26 -26
- package/dist/host-credentials.d.ts +78 -0
- package/dist/host-credentials.js +128 -0
- package/dist/host-credentials.test.d.ts +1 -0
- package/dist/host-credentials.test.js +88 -0
- package/dist/identity/account-email.d.ts +59 -0
- package/dist/identity/account-email.js +87 -0
- package/dist/identity/account-email.test.d.ts +1 -0
- package/dist/identity/account-email.test.js +59 -0
- package/dist/identity/anchor-document.d.ts +205 -0
- package/dist/identity/anchor-document.js +119 -0
- package/dist/identity/anchor-document.test.d.ts +1 -0
- package/dist/identity/anchor-document.test.js +16 -0
- package/dist/identity/anchor-resolution.d.ts +61 -0
- package/dist/identity/anchor-resolution.js +117 -0
- package/dist/identity/anchor-resolution.test.d.ts +1 -0
- package/dist/identity/anchor-resolution.test.js +130 -0
- package/dist/identity/key-change.d.ts +142 -0
- package/dist/identity/key-change.js +472 -0
- package/dist/identity/key-change.test.d.ts +1 -0
- package/dist/identity/key-change.test.js +811 -0
- package/dist/identity/key-contest.d.ts +91 -0
- package/dist/identity/key-contest.js +55 -0
- package/dist/identity/key-resolutions.d.ts +55 -0
- package/dist/identity/key-resolutions.js +42 -0
- package/dist/identity/passkey.d.ts +22 -0
- package/dist/identity/passkey.js +33 -0
- package/dist/identity/user-row-change.d.ts +45 -0
- package/dist/identity/user-row-change.js +81 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/invites/invite-messages.d.ts +512 -116
- package/dist/invites/invite-token.d.ts +114 -24
- package/dist/invites/invite-token.js +6 -0
- package/dist/keys.d.ts +7 -0
- package/dist/keys.js +20 -0
- package/dist/system-ids.d.ts +16 -0
- package/dist/system-ids.js +16 -0
- package/dist/types/workflow.d.ts +2 -2
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +43 -0
- package/dist/utils.test.js +23 -0
- package/package.json +1 -1
package/dist/data/invites.d.ts
CHANGED
|
@@ -37,15 +37,33 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
37
37
|
publicKey: z.ZodString;
|
|
38
38
|
publicBoxKey: z.ZodString;
|
|
39
39
|
displayName: z.ZodOptional<z.ZodString>;
|
|
40
|
+
anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
41
|
+
type: z.ZodEnum<["peers-services", "https"]>;
|
|
42
|
+
url: z.ZodString;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
type: "peers-services" | "https";
|
|
45
|
+
url: string;
|
|
46
|
+
}, {
|
|
47
|
+
type: "peers-services" | "https";
|
|
48
|
+
url: string;
|
|
49
|
+
}>, "many">>;
|
|
40
50
|
}, "strict", z.ZodTypeAny, {
|
|
41
51
|
publicKey: string;
|
|
42
|
-
publicBoxKey: string;
|
|
43
52
|
userId: string;
|
|
53
|
+
publicBoxKey: string;
|
|
54
|
+
anchors?: {
|
|
55
|
+
type: "peers-services" | "https";
|
|
56
|
+
url: string;
|
|
57
|
+
}[] | undefined;
|
|
44
58
|
displayName?: string | undefined;
|
|
45
59
|
}, {
|
|
46
60
|
publicKey: string;
|
|
47
|
-
publicBoxKey: string;
|
|
48
61
|
userId: string;
|
|
62
|
+
publicBoxKey: string;
|
|
63
|
+
anchors?: {
|
|
64
|
+
type: "peers-services" | "https";
|
|
65
|
+
url: string;
|
|
66
|
+
}[] | undefined;
|
|
49
67
|
displayName?: string | undefined;
|
|
50
68
|
}>>;
|
|
51
69
|
/** Device the counterpart used, when they told us; lets the network layer connect early. */
|
|
@@ -67,15 +85,33 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
67
85
|
publicKey: z.ZodString;
|
|
68
86
|
publicBoxKey: z.ZodString;
|
|
69
87
|
displayName: z.ZodOptional<z.ZodString>;
|
|
88
|
+
anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
89
|
+
type: z.ZodEnum<["peers-services", "https"]>;
|
|
90
|
+
url: z.ZodString;
|
|
91
|
+
}, "strip", z.ZodTypeAny, {
|
|
92
|
+
type: "peers-services" | "https";
|
|
93
|
+
url: string;
|
|
94
|
+
}, {
|
|
95
|
+
type: "peers-services" | "https";
|
|
96
|
+
url: string;
|
|
97
|
+
}>, "many">>;
|
|
70
98
|
}, "strict", z.ZodTypeAny, {
|
|
71
99
|
publicKey: string;
|
|
72
|
-
publicBoxKey: string;
|
|
73
100
|
userId: string;
|
|
101
|
+
publicBoxKey: string;
|
|
102
|
+
anchors?: {
|
|
103
|
+
type: "peers-services" | "https";
|
|
104
|
+
url: string;
|
|
105
|
+
}[] | undefined;
|
|
74
106
|
displayName?: string | undefined;
|
|
75
107
|
}, {
|
|
76
108
|
publicKey: string;
|
|
77
|
-
publicBoxKey: string;
|
|
78
109
|
userId: string;
|
|
110
|
+
publicBoxKey: string;
|
|
111
|
+
anchors?: {
|
|
112
|
+
type: "peers-services" | "https";
|
|
113
|
+
url: string;
|
|
114
|
+
}[] | undefined;
|
|
79
115
|
displayName?: string | undefined;
|
|
80
116
|
}>;
|
|
81
117
|
expiresAt: z.ZodString;
|
|
@@ -93,15 +129,15 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
93
129
|
name: string;
|
|
94
130
|
role: GroupMemberRole;
|
|
95
131
|
publicKey: string;
|
|
96
|
-
groupId: string;
|
|
97
132
|
publicBoxKey: string;
|
|
133
|
+
groupId: string;
|
|
98
134
|
mode: "open" | "approve";
|
|
99
135
|
}, {
|
|
100
136
|
name: string;
|
|
101
137
|
role: GroupMemberRole;
|
|
102
138
|
publicKey: string;
|
|
103
|
-
groupId: string;
|
|
104
139
|
publicBoxKey: string;
|
|
140
|
+
groupId: string;
|
|
105
141
|
mode: "open" | "approve";
|
|
106
142
|
}>>;
|
|
107
143
|
signature: z.ZodString;
|
|
@@ -113,8 +149,12 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
113
149
|
v: 1;
|
|
114
150
|
inviter: {
|
|
115
151
|
publicKey: string;
|
|
116
|
-
publicBoxKey: string;
|
|
117
152
|
userId: string;
|
|
153
|
+
publicBoxKey: string;
|
|
154
|
+
anchors?: {
|
|
155
|
+
type: "peers-services" | "https";
|
|
156
|
+
url: string;
|
|
157
|
+
}[] | undefined;
|
|
118
158
|
displayName?: string | undefined;
|
|
119
159
|
};
|
|
120
160
|
epoch: number;
|
|
@@ -123,8 +163,8 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
123
163
|
name: string;
|
|
124
164
|
role: GroupMemberRole;
|
|
125
165
|
publicKey: string;
|
|
126
|
-
groupId: string;
|
|
127
166
|
publicBoxKey: string;
|
|
167
|
+
groupId: string;
|
|
128
168
|
mode: "open" | "approve";
|
|
129
169
|
} | undefined;
|
|
130
170
|
}, {
|
|
@@ -135,8 +175,12 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
135
175
|
v: 1;
|
|
136
176
|
inviter: {
|
|
137
177
|
publicKey: string;
|
|
138
|
-
publicBoxKey: string;
|
|
139
178
|
userId: string;
|
|
179
|
+
publicBoxKey: string;
|
|
180
|
+
anchors?: {
|
|
181
|
+
type: "peers-services" | "https";
|
|
182
|
+
url: string;
|
|
183
|
+
}[] | undefined;
|
|
140
184
|
displayName?: string | undefined;
|
|
141
185
|
};
|
|
142
186
|
epoch: number;
|
|
@@ -145,8 +189,8 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
145
189
|
name: string;
|
|
146
190
|
role: GroupMemberRole;
|
|
147
191
|
publicKey: string;
|
|
148
|
-
groupId: string;
|
|
149
192
|
publicBoxKey: string;
|
|
193
|
+
groupId: string;
|
|
150
194
|
mode: "open" | "approve";
|
|
151
195
|
} | undefined;
|
|
152
196
|
}>>;
|
|
@@ -181,10 +225,10 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
181
225
|
}, "strip", z.ZodTypeAny, {
|
|
182
226
|
type: "group" | "device" | "contact";
|
|
183
227
|
direction: "in" | "out";
|
|
184
|
-
status: "pending" | "expired" | "accepted" | "
|
|
228
|
+
status: "pending" | "expired" | "accepted" | "revoked" | "declined";
|
|
185
229
|
createdAt: Date;
|
|
186
230
|
expiresAt: Date;
|
|
187
|
-
mode: "direct" | "open" | "
|
|
231
|
+
mode: "direct" | "open" | "profile" | "approve" | "contact";
|
|
188
232
|
epoch: number;
|
|
189
233
|
inviteId: string;
|
|
190
234
|
role?: GroupMemberRole | undefined;
|
|
@@ -203,8 +247,12 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
203
247
|
v: 1;
|
|
204
248
|
inviter: {
|
|
205
249
|
publicKey: string;
|
|
206
|
-
publicBoxKey: string;
|
|
207
250
|
userId: string;
|
|
251
|
+
publicBoxKey: string;
|
|
252
|
+
anchors?: {
|
|
253
|
+
type: "peers-services" | "https";
|
|
254
|
+
url: string;
|
|
255
|
+
}[] | undefined;
|
|
208
256
|
displayName?: string | undefined;
|
|
209
257
|
};
|
|
210
258
|
epoch: number;
|
|
@@ -213,15 +261,19 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
213
261
|
name: string;
|
|
214
262
|
role: GroupMemberRole;
|
|
215
263
|
publicKey: string;
|
|
216
|
-
groupId: string;
|
|
217
264
|
publicBoxKey: string;
|
|
265
|
+
groupId: string;
|
|
218
266
|
mode: "open" | "approve";
|
|
219
267
|
} | undefined;
|
|
220
268
|
} | undefined;
|
|
221
269
|
counterpart?: {
|
|
222
270
|
publicKey: string;
|
|
223
|
-
publicBoxKey: string;
|
|
224
271
|
userId: string;
|
|
272
|
+
publicBoxKey: string;
|
|
273
|
+
anchors?: {
|
|
274
|
+
type: "peers-services" | "https";
|
|
275
|
+
url: string;
|
|
276
|
+
}[] | undefined;
|
|
225
277
|
displayName?: string | undefined;
|
|
226
278
|
} | undefined;
|
|
227
279
|
counterpartDeviceId?: string | undefined;
|
|
@@ -235,12 +287,12 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
235
287
|
type: "group" | "device" | "contact";
|
|
236
288
|
direction: "in" | "out";
|
|
237
289
|
expiresAt: Date;
|
|
238
|
-
mode: "direct" | "open" | "
|
|
290
|
+
mode: "direct" | "open" | "profile" | "approve" | "contact";
|
|
239
291
|
epoch: number;
|
|
240
292
|
inviteId: string;
|
|
241
293
|
role?: GroupMemberRole | undefined;
|
|
242
294
|
nonce?: string | undefined;
|
|
243
|
-
status?: "pending" | "expired" | "accepted" | "
|
|
295
|
+
status?: "pending" | "expired" | "accepted" | "revoked" | "declined" | undefined;
|
|
244
296
|
groupId?: string | undefined;
|
|
245
297
|
createdAt?: Date | undefined;
|
|
246
298
|
payload?: {
|
|
@@ -256,8 +308,12 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
256
308
|
v: 1;
|
|
257
309
|
inviter: {
|
|
258
310
|
publicKey: string;
|
|
259
|
-
publicBoxKey: string;
|
|
260
311
|
userId: string;
|
|
312
|
+
publicBoxKey: string;
|
|
313
|
+
anchors?: {
|
|
314
|
+
type: "peers-services" | "https";
|
|
315
|
+
url: string;
|
|
316
|
+
}[] | undefined;
|
|
261
317
|
displayName?: string | undefined;
|
|
262
318
|
};
|
|
263
319
|
epoch: number;
|
|
@@ -266,15 +322,19 @@ export declare const inviteSchema: z.ZodObject<{
|
|
|
266
322
|
name: string;
|
|
267
323
|
role: GroupMemberRole;
|
|
268
324
|
publicKey: string;
|
|
269
|
-
groupId: string;
|
|
270
325
|
publicBoxKey: string;
|
|
326
|
+
groupId: string;
|
|
271
327
|
mode: "open" | "approve";
|
|
272
328
|
} | undefined;
|
|
273
329
|
} | undefined;
|
|
274
330
|
counterpart?: {
|
|
275
331
|
publicKey: string;
|
|
276
|
-
publicBoxKey: string;
|
|
277
332
|
userId: string;
|
|
333
|
+
publicBoxKey: string;
|
|
334
|
+
anchors?: {
|
|
335
|
+
type: "peers-services" | "https";
|
|
336
|
+
url: string;
|
|
337
|
+
}[] | undefined;
|
|
278
338
|
displayName?: string | undefined;
|
|
279
339
|
} | undefined;
|
|
280
340
|
counterpartDeviceId?: string | undefined;
|
|
@@ -295,10 +355,10 @@ export type IInvite = z.infer<typeof inviteSchema>;
|
|
|
295
355
|
export declare function Invites(dataContext?: DataContext): import("..").Table<{
|
|
296
356
|
type: "group" | "device" | "contact";
|
|
297
357
|
direction: "in" | "out";
|
|
298
|
-
status: "pending" | "expired" | "accepted" | "
|
|
358
|
+
status: "pending" | "expired" | "accepted" | "revoked" | "declined";
|
|
299
359
|
createdAt: Date;
|
|
300
360
|
expiresAt: Date;
|
|
301
|
-
mode: "direct" | "open" | "
|
|
361
|
+
mode: "direct" | "open" | "profile" | "approve" | "contact";
|
|
302
362
|
epoch: number;
|
|
303
363
|
inviteId: string;
|
|
304
364
|
role?: GroupMemberRole | undefined;
|
|
@@ -317,8 +377,12 @@ export declare function Invites(dataContext?: DataContext): import("..").Table<{
|
|
|
317
377
|
v: 1;
|
|
318
378
|
inviter: {
|
|
319
379
|
publicKey: string;
|
|
320
|
-
publicBoxKey: string;
|
|
321
380
|
userId: string;
|
|
381
|
+
publicBoxKey: string;
|
|
382
|
+
anchors?: {
|
|
383
|
+
type: "peers-services" | "https";
|
|
384
|
+
url: string;
|
|
385
|
+
}[] | undefined;
|
|
322
386
|
displayName?: string | undefined;
|
|
323
387
|
};
|
|
324
388
|
epoch: number;
|
|
@@ -327,15 +391,19 @@ export declare function Invites(dataContext?: DataContext): import("..").Table<{
|
|
|
327
391
|
name: string;
|
|
328
392
|
role: GroupMemberRole;
|
|
329
393
|
publicKey: string;
|
|
330
|
-
groupId: string;
|
|
331
394
|
publicBoxKey: string;
|
|
395
|
+
groupId: string;
|
|
332
396
|
mode: "open" | "approve";
|
|
333
397
|
} | undefined;
|
|
334
398
|
} | undefined;
|
|
335
399
|
counterpart?: {
|
|
336
400
|
publicKey: string;
|
|
337
|
-
publicBoxKey: string;
|
|
338
401
|
userId: string;
|
|
402
|
+
publicBoxKey: string;
|
|
403
|
+
anchors?: {
|
|
404
|
+
type: "peers-services" | "https";
|
|
405
|
+
url: string;
|
|
406
|
+
}[] | undefined;
|
|
339
407
|
displayName?: string | undefined;
|
|
340
408
|
} | undefined;
|
|
341
409
|
counterpartDeviceId?: string | undefined;
|
|
@@ -63,6 +63,14 @@ export declare class PersistentVarsTable extends Table<IPersistentVar> {
|
|
|
63
63
|
update(persistentVar: IPersistentVar): Promise<IPersistentVar>;
|
|
64
64
|
/** Save a variable, assigning its deterministic ID and encrypting new secret plaintext. */
|
|
65
65
|
save(persistentVar: IPersistentVar, opts?: ISaveOptions): Promise<IPersistentVar>;
|
|
66
|
+
/**
|
|
67
|
+
* Replace a secret variable's stored ciphertext as-is, without passing it through
|
|
68
|
+
* `rpcServerCalls.encryptData` again. Used by key rotation, which re-wraps every personal
|
|
69
|
+
* secret from the old user key to the new one and has already produced the new ciphertext.
|
|
70
|
+
*
|
|
71
|
+
* @throws When the variable is not marked `isSecret`
|
|
72
|
+
*/
|
|
73
|
+
replaceEncryptedValue(persistentVar: IPersistentVar, encryptedValue: string): Promise<IPersistentVar>;
|
|
66
74
|
/**
|
|
67
75
|
* Replace a legacy random record ID without re-encrypting its already-stored value.
|
|
68
76
|
*
|
|
@@ -109,11 +109,14 @@ function persistentVarIdentityWinner(left, right) {
|
|
|
109
109
|
let PersistentVarsTable = (() => {
|
|
110
110
|
let _classSuper = table_1.Table;
|
|
111
111
|
let _instanceExtraInitializers = [];
|
|
112
|
+
let _replaceEncryptedValue_decorators;
|
|
112
113
|
let _migrateLegacyId_decorators;
|
|
113
114
|
return class PersistentVarsTable extends _classSuper {
|
|
114
115
|
static {
|
|
115
116
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
117
|
+
_replaceEncryptedValue_decorators = [(0, decorators_1.ProxyClientTableMethodCalls)()];
|
|
116
118
|
_migrateLegacyId_decorators = [(0, decorators_1.ProxyClientTableMethodCalls)()];
|
|
119
|
+
__esDecorate(this, null, _replaceEncryptedValue_decorators, { kind: "method", name: "replaceEncryptedValue", static: false, private: false, access: { has: obj => "replaceEncryptedValue" in obj, get: obj => obj.replaceEncryptedValue }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
117
120
|
__esDecorate(this, null, _migrateLegacyId_decorators, { kind: "method", name: "migrateLegacyId", static: false, private: false, access: { has: obj => "migrateLegacyId" in obj, get: obj => obj.migrateLegacyId }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
118
121
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
119
122
|
}
|
|
@@ -145,6 +148,19 @@ let PersistentVarsTable = (() => {
|
|
|
145
148
|
opts = { saveAsSnapshot: true, ...opts };
|
|
146
149
|
return super.save(persistentVar, opts);
|
|
147
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Replace a secret variable's stored ciphertext as-is, without passing it through
|
|
153
|
+
* `rpcServerCalls.encryptData` again. Used by key rotation, which re-wraps every personal
|
|
154
|
+
* secret from the old user key to the new one and has already produced the new ciphertext.
|
|
155
|
+
*
|
|
156
|
+
* @throws When the variable is not marked `isSecret`
|
|
157
|
+
*/
|
|
158
|
+
async replaceEncryptedValue(persistentVar, encryptedValue) {
|
|
159
|
+
if (!persistentVar.isSecret) {
|
|
160
|
+
throw new Error(`Persistent variable ${persistentVar.name} is not a secret`);
|
|
161
|
+
}
|
|
162
|
+
return super.save({ ...persistentVar, value: { value: encryptedValue }, modifiedAt: Date.now() }, { saveAsSnapshot: true });
|
|
163
|
+
}
|
|
148
164
|
/**
|
|
149
165
|
* Replace a legacy random record ID without re-encrypting its already-stored value.
|
|
150
166
|
*
|
|
@@ -46,9 +46,9 @@ export declare const toolAccessApprovalSchema: z.ZodObject<{
|
|
|
46
46
|
}, "strip", z.ZodTypeAny, {
|
|
47
47
|
nonce: string;
|
|
48
48
|
status: ToolAccessApprovalStatus;
|
|
49
|
-
scope: ToolAccessApprovalScope;
|
|
50
49
|
userId: string;
|
|
51
50
|
toolId: string;
|
|
51
|
+
scope: ToolAccessApprovalScope;
|
|
52
52
|
dataContextId: string;
|
|
53
53
|
createdAt: Date;
|
|
54
54
|
expiresAt: Date;
|
|
@@ -102,9 +102,9 @@ export type IToolAccessApproval = z.infer<typeof toolAccessApprovalSchema>;
|
|
|
102
102
|
export declare function ToolAccessApprovals(dataContext?: DataContext): import("..").Table<{
|
|
103
103
|
nonce: string;
|
|
104
104
|
status: ToolAccessApprovalStatus;
|
|
105
|
-
scope: ToolAccessApprovalScope;
|
|
106
105
|
userId: string;
|
|
107
106
|
toolId: string;
|
|
107
|
+
scope: ToolAccessApprovalScope;
|
|
108
108
|
dataContextId: string;
|
|
109
109
|
createdAt: Date;
|
|
110
110
|
expiresAt: Date;
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import type { IUser } from "./users";
|
|
2
2
|
/**
|
|
3
|
-
* Signs a user object with the provided secret key
|
|
3
|
+
* Signs a user object with the provided secret key. The `anchors` list is kept on the row
|
|
4
|
+
* but is not covered by the signature; see {@link verifyUserSignature}.
|
|
4
5
|
* @param user The user object to sign
|
|
5
6
|
* @param secretKey The secret key to sign with
|
|
6
7
|
* @returns IUser The signed user object
|
|
7
8
|
*/
|
|
8
9
|
export declare function signUserObject(user: IUser, secretKey: string): IUser;
|
|
9
10
|
/**
|
|
10
|
-
* Verifies that a user update signature is valid, throws on invalid signature
|
|
11
|
+
* Verifies that a user update signature is valid, throws on invalid signature.
|
|
12
|
+
* The `anchors` list is not part of what is verified.
|
|
11
13
|
* @param user The user record to verify
|
|
12
14
|
* @param oldUser The existing user record (if any)
|
|
13
15
|
* @throws Error if signature is invalid or unauthorized
|
|
14
16
|
*/
|
|
15
17
|
export declare function verifyUserSignature(user: IUser, oldUser?: IUser): void;
|
|
18
|
+
/**
|
|
19
|
+
* Checks that `user` is signed by its own `publicKey`, ignoring any key already on record.
|
|
20
|
+
* Used after the anchors have confirmed a key change: the row still has to be the new key's.
|
|
21
|
+
* @throws Error if the signature is missing, invalid, or by another key
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyUserSignedBySelf(user: IUser): void;
|
|
@@ -2,18 +2,34 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.signUserObject = signUserObject;
|
|
4
4
|
exports.verifyUserSignature = verifyUserSignature;
|
|
5
|
+
exports.verifyUserSignedBySelf = verifyUserSignedBySelf;
|
|
5
6
|
const keys_1 = require("../keys");
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* The part of a `Users` row the owner signs: everything but `anchors`.
|
|
9
|
+
*
|
|
10
|
+
* The anchor list on record is decided by the anchors themselves (the on-record rule), not
|
|
11
|
+
* by whoever wrote the row. A peer that confirms a key change stores the row with the list
|
|
12
|
+
* on record, and a peer on an older release stores the row without the field at all. Both
|
|
13
|
+
* must still verify, so the list is carried beside the signature rather than under it.
|
|
14
|
+
*/
|
|
15
|
+
function signedPart(user) {
|
|
16
|
+
const { anchors: _anchors, ...rest } = user;
|
|
17
|
+
return rest;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Signs a user object with the provided secret key. The `anchors` list is kept on the row
|
|
21
|
+
* but is not covered by the signature; see {@link verifyUserSignature}.
|
|
8
22
|
* @param user The user object to sign
|
|
9
23
|
* @param secretKey The secret key to sign with
|
|
10
24
|
* @returns IUser The signed user object
|
|
11
25
|
*/
|
|
12
26
|
function signUserObject(user, secretKey) {
|
|
13
|
-
|
|
27
|
+
const signed = (0, keys_1.addSignatureToObject)(signedPart(user), secretKey);
|
|
28
|
+
return user.anchors ? { ...signed, anchors: user.anchors } : signed;
|
|
14
29
|
}
|
|
15
30
|
/**
|
|
16
|
-
* Verifies that a user update signature is valid, throws on invalid signature
|
|
31
|
+
* Verifies that a user update signature is valid, throws on invalid signature.
|
|
32
|
+
* The `anchors` list is not part of what is verified.
|
|
17
33
|
* @param user The user record to verify
|
|
18
34
|
* @param oldUser The existing user record (if any)
|
|
19
35
|
* @throws Error if signature is invalid or unauthorized
|
|
@@ -26,7 +42,7 @@ function verifyUserSignature(user, oldUser) {
|
|
|
26
42
|
}
|
|
27
43
|
return;
|
|
28
44
|
}
|
|
29
|
-
(0, keys_1.verifyObjectSignature)(user);
|
|
45
|
+
(0, keys_1.verifyObjectSignature)(signedPart(user));
|
|
30
46
|
const signerPublicKey = (0, keys_1.getPublicKeyFromObjectSignature)(user) ?? "";
|
|
31
47
|
// New user: signature must match their own public key
|
|
32
48
|
if (!oldUser) {
|
|
@@ -44,3 +60,18 @@ function verifyUserSignature(user, oldUser) {
|
|
|
44
60
|
throw new Error("Public key changes are not currently supported");
|
|
45
61
|
}
|
|
46
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Checks that `user` is signed by its own `publicKey`, ignoring any key already on record.
|
|
65
|
+
* Used after the anchors have confirmed a key change: the row still has to be the new key's.
|
|
66
|
+
* @throws Error if the signature is missing, invalid, or by another key
|
|
67
|
+
*/
|
|
68
|
+
function verifyUserSignedBySelf(user) {
|
|
69
|
+
if (!user.signature) {
|
|
70
|
+
throw new Error("A confirmed key change still has to be signed by the new key");
|
|
71
|
+
}
|
|
72
|
+
(0, keys_1.verifyObjectSignature)(signedPart(user));
|
|
73
|
+
const signerPublicKey = (0, keys_1.getPublicKeyFromObjectSignature)(user) ?? "";
|
|
74
|
+
if (signerPublicKey !== user.publicKey) {
|
|
75
|
+
throw new Error("User signature must be signed with their own public key");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -201,6 +201,32 @@ describe("User Permissions Logic", () => {
|
|
|
201
201
|
expect(() => (0, user_permissions_1.verifyUserSignature)(signedIncoming, existingStub)).not.toThrow();
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
|
+
describe("verifyUserSignature - anchors are not part of the claim", () => {
|
|
205
|
+
// The anchor list on record is decided by the anchors, not by the row's author. A peer
|
|
206
|
+
// stores a confirmed row with the list on record, and a peer on an older release stores
|
|
207
|
+
// the row without the field at all. Both must still verify.
|
|
208
|
+
const anchorA = { type: "https", url: "https://a.example/peers-identity.json" };
|
|
209
|
+
const anchorB = { type: "https", url: "https://b.example/peers-identity.json" };
|
|
210
|
+
it("verifies a signed row after its anchor list is replaced or removed", () => {
|
|
211
|
+
const signed = (0, user_permissions_1.signUserObject)({ ...testUser, anchors: [anchorA] }, userKeys.secretKey);
|
|
212
|
+
expect(() => (0, user_permissions_1.verifyUserSignature)({ ...signed, anchors: [anchorB] })).not.toThrow();
|
|
213
|
+
const { anchors: _dropped, ...withoutAnchors } = signed;
|
|
214
|
+
expect(() => (0, user_permissions_1.verifyUserSignature)(withoutAnchors)).not.toThrow();
|
|
215
|
+
});
|
|
216
|
+
it("verifies a row signed without anchors after a list is added", () => {
|
|
217
|
+
const signed = (0, user_permissions_1.signUserObject)(testUser, userKeys.secretKey);
|
|
218
|
+
expect(() => (0, user_permissions_1.verifyUserSignature)({ ...signed, anchors: [anchorA] })).not.toThrow();
|
|
219
|
+
});
|
|
220
|
+
it("still rejects a change to any other field", () => {
|
|
221
|
+
const signed = (0, user_permissions_1.signUserObject)({ ...testUser, anchors: [anchorA] }, userKeys.secretKey);
|
|
222
|
+
expect(() => (0, user_permissions_1.verifyUserSignature)({ ...signed, name: "Someone else" })).toThrow();
|
|
223
|
+
expect(() => (0, user_permissions_1.verifyUserSignature)({ ...signed, publicBoxKey: otherUserKeys.publicBoxKey })).toThrow();
|
|
224
|
+
});
|
|
225
|
+
it("does not leave the anchors off the signed row", () => {
|
|
226
|
+
const signed = (0, user_permissions_1.signUserObject)({ ...testUser, anchors: [anchorA] }, userKeys.secretKey);
|
|
227
|
+
expect(signed.anchors).toEqual([anchorA]);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
204
230
|
describe("edge cases and error handling", () => {
|
|
205
231
|
it("should handle undefined user gracefully", () => {
|
|
206
232
|
// @ts-expect-error - Testing edge case
|
package/dist/data/users.d.ts
CHANGED
|
@@ -8,28 +8,72 @@ export declare const userSchema: z.ZodObject<{
|
|
|
8
8
|
name: z.ZodString;
|
|
9
9
|
publicKey: z.ZodString;
|
|
10
10
|
publicBoxKey: z.ZodString;
|
|
11
|
+
anchors: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12
|
+
type: z.ZodEnum<["peers-services", "https"]>;
|
|
13
|
+
url: z.ZodString;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
type: "peers-services" | "https";
|
|
16
|
+
url: string;
|
|
17
|
+
}, {
|
|
18
|
+
type: "peers-services" | "https";
|
|
19
|
+
url: string;
|
|
20
|
+
}>, "many">>;
|
|
11
21
|
signature: z.ZodOptional<z.ZodString>;
|
|
12
22
|
}, "strip", z.ZodTypeAny, {
|
|
13
23
|
name: string;
|
|
14
24
|
publicKey: string;
|
|
15
|
-
publicBoxKey: string;
|
|
16
25
|
userId: string;
|
|
26
|
+
publicBoxKey: string;
|
|
17
27
|
signature?: string | undefined;
|
|
28
|
+
anchors?: {
|
|
29
|
+
type: "peers-services" | "https";
|
|
30
|
+
url: string;
|
|
31
|
+
}[] | undefined;
|
|
18
32
|
}, {
|
|
19
33
|
name: string;
|
|
20
34
|
publicKey: string;
|
|
21
|
-
publicBoxKey: string;
|
|
22
35
|
userId: string;
|
|
36
|
+
publicBoxKey: string;
|
|
23
37
|
signature?: string | undefined;
|
|
38
|
+
anchors?: {
|
|
39
|
+
type: "peers-services" | "https";
|
|
40
|
+
url: string;
|
|
41
|
+
}[] | undefined;
|
|
24
42
|
}>;
|
|
25
43
|
/** A signed user profile shared across personal and group contexts. */
|
|
26
44
|
export type IUser = z.infer<typeof userSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Writes a key the resolver already confirmed. Bypasses the contact-row guard,
|
|
47
|
+
* which would otherwise call the resolver again.
|
|
48
|
+
*/
|
|
49
|
+
export declare function writeResolvedUserKey(dataContext: DataContext, user: IUser): Promise<IUser>;
|
|
50
|
+
/** Outcome of a {@link UserRowChangeGuard}. See `resolveUserRowChange` in the identity module. */
|
|
51
|
+
export type UserRowChangeVerdict = {
|
|
52
|
+
action: "apply";
|
|
53
|
+
row: IUser;
|
|
54
|
+
} | {
|
|
55
|
+
action: "defer";
|
|
56
|
+
reason: string;
|
|
57
|
+
} | {
|
|
58
|
+
action: "refuse";
|
|
59
|
+
reason: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Consulted by {@link UsersTable.save} when another user's row already exists in the table.
|
|
63
|
+
* Decides whether a different key or anchor list may be stored, against the anchors on
|
|
64
|
+
* record. `apply` carries the row to store, whose `anchors` may have been replaced by the
|
|
65
|
+
* list on record.
|
|
66
|
+
*/
|
|
67
|
+
export type UserRowChangeGuard = (personalContext: DataContext, incoming: IUser, existing: IUser) => Promise<UserRowChangeVerdict>;
|
|
68
|
+
/** Installs the identity check used by {@link UsersTable.save}. The identity module sets this. */
|
|
69
|
+
export declare function setUserRowChangeGuard(guard: UserRowChangeGuard | undefined): void;
|
|
27
70
|
/** User table with owner-signature enforcement outside the personal context. */
|
|
28
71
|
export declare class UsersTable extends Table<IUser> {
|
|
29
72
|
static isPassthrough: boolean;
|
|
30
73
|
/**
|
|
31
|
-
* Save a user row.
|
|
32
|
-
* signature (or be an unsigned stub where nothing signed exists yet).
|
|
74
|
+
* Save a user row. A contact row, in the personal table or a group, must carry a
|
|
75
|
+
* valid owner signature (or be an unsigned stub where nothing signed exists yet).
|
|
76
|
+
* The owner's own personal row is unrestricted.
|
|
33
77
|
*
|
|
34
78
|
* An unsigned `weakInsert` is a discovery placeholder (connection handshake, invite
|
|
35
79
|
* identity, join approval). It is written `insertOnly`: seed the row when the table has
|
package/dist/data/users.js
CHANGED
|
@@ -35,10 +35,14 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.UsersTable = exports.userSchema = void 0;
|
|
38
|
+
exports.writeResolvedUserKey = writeResolvedUserKey;
|
|
39
|
+
exports.setUserRowChangeGuard = setUserRowChangeGuard;
|
|
38
40
|
exports.Users = Users;
|
|
39
41
|
exports.getMe = getMe;
|
|
40
42
|
const zod_1 = require("zod");
|
|
41
43
|
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
44
|
+
const anchor_document_1 = require("../identity/anchor-document");
|
|
45
|
+
const anchor_resolution_1 = require("../identity/anchor-resolution");
|
|
42
46
|
const zod_types_1 = require("../types/zod-types");
|
|
43
47
|
const devices_1 = require("./devices");
|
|
44
48
|
const decorators_1 = require("./orm/decorators");
|
|
@@ -54,6 +58,10 @@ exports.userSchema = zod_1.z.object({
|
|
|
54
58
|
publicBoxKey: zod_1.z
|
|
55
59
|
.string()
|
|
56
60
|
.describe("The public key to use to encrypt data that only this user can decrypt"),
|
|
61
|
+
anchors: zod_1.z
|
|
62
|
+
.array(anchor_document_1.anchorRefSchema)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Anchors on record for this user. A peer that already has a list updates it from those anchors, not from a row that arrives with a new key. Not covered by the row's signature"),
|
|
57
65
|
signature: zod_1.z
|
|
58
66
|
.string()
|
|
59
67
|
.optional()
|
|
@@ -66,6 +74,34 @@ const metaData = {
|
|
|
66
74
|
fields: (0, types_1.schemaToFields)(exports.userSchema),
|
|
67
75
|
indexes: [{ fields: ["name"] }, { fields: ["publicKey"] }, { fields: ["publicBoxKey"] }],
|
|
68
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Marks a save performed by the resolver after anchors confirmed a key.
|
|
79
|
+
* A symbol so a proxied client cannot set it: JSON drops symbols, and a
|
|
80
|
+
* remote save of a contact row still has to pass the anchor check.
|
|
81
|
+
*/
|
|
82
|
+
const IDENTITY_RESOLUTION = Symbol("identityResolution");
|
|
83
|
+
function isResolutionWrite(opts) {
|
|
84
|
+
return Boolean(opts && opts[IDENTITY_RESOLUTION]);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Writes a key the resolver already confirmed. Bypasses the contact-row guard,
|
|
88
|
+
* which would otherwise call the resolver again.
|
|
89
|
+
*/
|
|
90
|
+
async function writeResolvedUserKey(dataContext, user) {
|
|
91
|
+
return Users(dataContext).save(user, {
|
|
92
|
+
saveAsSnapshot: true,
|
|
93
|
+
[IDENTITY_RESOLUTION]: true,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Until the identity module installs its guard, any key or anchor change is refused. */
|
|
97
|
+
const refuseIdentityChanges = async (_personalContext, incoming, existing) => incoming.publicKey !== existing.publicKey || !(0, anchor_resolution_1.sameAnchorList)(existing.anchors, incoming.anchors)
|
|
98
|
+
? { action: "refuse", reason: "Identity changes need the identity module" }
|
|
99
|
+
: { action: "apply", row: incoming };
|
|
100
|
+
let userRowChangeGuard = refuseIdentityChanges;
|
|
101
|
+
/** Installs the identity check used by {@link UsersTable.save}. The identity module sets this. */
|
|
102
|
+
function setUserRowChangeGuard(guard) {
|
|
103
|
+
userRowChangeGuard = guard ?? refuseIdentityChanges;
|
|
104
|
+
}
|
|
69
105
|
/** User table with owner-signature enforcement outside the personal context. */
|
|
70
106
|
let UsersTable = (() => {
|
|
71
107
|
let _classSuper = table_1.Table;
|
|
@@ -80,8 +116,9 @@ let UsersTable = (() => {
|
|
|
80
116
|
}
|
|
81
117
|
static isPassthrough = false;
|
|
82
118
|
/**
|
|
83
|
-
* Save a user row.
|
|
84
|
-
* signature (or be an unsigned stub where nothing signed exists yet).
|
|
119
|
+
* Save a user row. A contact row, in the personal table or a group, must carry a
|
|
120
|
+
* valid owner signature (or be an unsigned stub where nothing signed exists yet).
|
|
121
|
+
* The owner's own personal row is unrestricted.
|
|
85
122
|
*
|
|
86
123
|
* An unsigned `weakInsert` is a discovery placeholder (connection handshake, invite
|
|
87
124
|
* identity, join approval). It is written `insertOnly`: seed the row when the table has
|
|
@@ -101,8 +138,12 @@ let UsersTable = (() => {
|
|
|
101
138
|
}
|
|
102
139
|
const userContext = await (0, user_context_singleton_1.getUserContext)();
|
|
103
140
|
const userContextUserTable = Users(userContext.userDataContext);
|
|
104
|
-
if (
|
|
105
|
-
|
|
141
|
+
if (isResolutionWrite(opts)) {
|
|
142
|
+
return super.save(user, opts);
|
|
143
|
+
}
|
|
144
|
+
if (this === userContextUserTable && user.userId === userContext.userId) {
|
|
145
|
+
// The owner can change their own row. A contact's row in this same table
|
|
146
|
+
// is the on-record key and anchor list, so it goes through the checks below.
|
|
106
147
|
return super.save(user, opts);
|
|
107
148
|
}
|
|
108
149
|
// Get the old user record if it exists
|
|
@@ -110,8 +151,23 @@ let UsersTable = (() => {
|
|
|
110
151
|
if (opts.insertOnly && oldUser) {
|
|
111
152
|
return oldUser;
|
|
112
153
|
}
|
|
154
|
+
if (!oldUser) {
|
|
155
|
+
await (0, user_permissions_1.verifyUserSignature)(user, oldUser);
|
|
156
|
+
return super.save(user, opts);
|
|
157
|
+
}
|
|
158
|
+
// A different key or anchor list is judged against the anchors on record. A direct
|
|
159
|
+
// save cannot wait for an answer, so "not yet" is refused the same as "no".
|
|
160
|
+
const verdict = await userRowChangeGuard(userContext.userDataContext, user, oldUser);
|
|
161
|
+
if (verdict.action !== "apply") {
|
|
162
|
+
throw new Error(verdict.reason);
|
|
163
|
+
}
|
|
164
|
+
if (user.publicKey !== oldUser.publicKey) {
|
|
165
|
+
// The anchors confirmed the new key; the row still has to be signed by it.
|
|
166
|
+
(0, user_permissions_1.verifyUserSignedBySelf)(user);
|
|
167
|
+
return super.save(verdict.row, opts);
|
|
168
|
+
}
|
|
113
169
|
await (0, user_permissions_1.verifyUserSignature)(user, oldUser);
|
|
114
|
-
return super.save(
|
|
170
|
+
return super.save(verdict.row, opts);
|
|
115
171
|
}
|
|
116
172
|
async signAndSave(user, opts) {
|
|
117
173
|
if (!UsersTable.addSignatureToUser) {
|