@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.
- 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/package-installer/package-author-signing.test.js +11 -0
- package/dist/package-installer/package-publisher.d.ts +5 -1
- package/dist/package-installer/package-publisher.js +1 -1
- package/dist/package-installer/package-publisher.test.js +11 -0
- package/dist/package-installer/package-remote-checker.js +3 -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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ISignedObject } from "../keys";
|
|
3
|
+
/** Purpose string a holder of the key on record signs to object to a recovery. */
|
|
4
|
+
export declare const KEY_CONTEST_PURPOSE: "peers-key-contest";
|
|
5
|
+
/** Statement that says the key on record did not ask for `pendingKey`. */
|
|
6
|
+
export declare const keyContestStatementSchema: z.ZodObject<{
|
|
7
|
+
purpose: z.ZodLiteral<"peers-key-contest">;
|
|
8
|
+
userId: z.ZodEffects<z.ZodString, string, string>;
|
|
9
|
+
/** Key on record. The signature must be this key. */
|
|
10
|
+
publicKey: z.ZodString;
|
|
11
|
+
/** Key the anchors are offering without this key's co-signature. */
|
|
12
|
+
pendingKey: z.ZodString;
|
|
13
|
+
at: z.ZodString;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
at: string;
|
|
16
|
+
publicKey: string;
|
|
17
|
+
purpose: "peers-key-contest";
|
|
18
|
+
userId: string;
|
|
19
|
+
pendingKey: string;
|
|
20
|
+
}, {
|
|
21
|
+
at: string;
|
|
22
|
+
publicKey: string;
|
|
23
|
+
purpose: "peers-key-contest";
|
|
24
|
+
userId: string;
|
|
25
|
+
pendingKey: string;
|
|
26
|
+
}>;
|
|
27
|
+
/** Statement that says the key on record did not ask for a recovery. */
|
|
28
|
+
export type IKeyContestStatement = z.infer<typeof keyContestStatementSchema>;
|
|
29
|
+
/** Signed contest carried in a handshake and posted to an anchor. */
|
|
30
|
+
export declare const keyContestSignedSchema: z.ZodObject<{
|
|
31
|
+
contents: z.ZodObject<{
|
|
32
|
+
purpose: z.ZodLiteral<"peers-key-contest">;
|
|
33
|
+
userId: z.ZodEffects<z.ZodString, string, string>;
|
|
34
|
+
/** Key on record. The signature must be this key. */
|
|
35
|
+
publicKey: z.ZodString;
|
|
36
|
+
/** Key the anchors are offering without this key's co-signature. */
|
|
37
|
+
pendingKey: z.ZodString;
|
|
38
|
+
at: z.ZodString;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
at: string;
|
|
41
|
+
publicKey: string;
|
|
42
|
+
purpose: "peers-key-contest";
|
|
43
|
+
userId: string;
|
|
44
|
+
pendingKey: string;
|
|
45
|
+
}, {
|
|
46
|
+
at: string;
|
|
47
|
+
publicKey: string;
|
|
48
|
+
purpose: "peers-key-contest";
|
|
49
|
+
userId: string;
|
|
50
|
+
pendingKey: string;
|
|
51
|
+
}>;
|
|
52
|
+
signature: z.ZodString;
|
|
53
|
+
publicKey: z.ZodString;
|
|
54
|
+
}, "strip", z.ZodTypeAny, {
|
|
55
|
+
publicKey: string;
|
|
56
|
+
signature: string;
|
|
57
|
+
contents: {
|
|
58
|
+
at: string;
|
|
59
|
+
publicKey: string;
|
|
60
|
+
purpose: "peers-key-contest";
|
|
61
|
+
userId: string;
|
|
62
|
+
pendingKey: string;
|
|
63
|
+
};
|
|
64
|
+
}, {
|
|
65
|
+
publicKey: string;
|
|
66
|
+
signature: string;
|
|
67
|
+
contents: {
|
|
68
|
+
at: string;
|
|
69
|
+
publicKey: string;
|
|
70
|
+
purpose: "peers-key-contest";
|
|
71
|
+
userId: string;
|
|
72
|
+
pendingKey: string;
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Signs a contest with the key on record.
|
|
77
|
+
*
|
|
78
|
+
* @param statement The user, the key on record, and the key being refused
|
|
79
|
+
* @param recordSecretKey Secret half of the key on record
|
|
80
|
+
*/
|
|
81
|
+
export declare function signKeyContest(statement: IKeyContestStatement, recordSecretKey: string): ISignedObject<IKeyContestStatement>;
|
|
82
|
+
/**
|
|
83
|
+
* Checks that `signed` is the key on record objecting to `pendingKey`.
|
|
84
|
+
* A different signer, user, or pending key is not a contest.
|
|
85
|
+
*/
|
|
86
|
+
export declare function verifyKeyContest(params: {
|
|
87
|
+
signed: ISignedObject<IKeyContestStatement>;
|
|
88
|
+
userId: string;
|
|
89
|
+
recordPublicKey: string;
|
|
90
|
+
pendingKey: string;
|
|
91
|
+
}): boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.keyContestSignedSchema = exports.keyContestStatementSchema = exports.KEY_CONTEST_PURPOSE = void 0;
|
|
4
|
+
exports.signKeyContest = signKeyContest;
|
|
5
|
+
exports.verifyKeyContest = verifyKeyContest;
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const keys_1 = require("../keys");
|
|
8
|
+
const zod_types_1 = require("../types/zod-types");
|
|
9
|
+
/** Purpose string a holder of the key on record signs to object to a recovery. */
|
|
10
|
+
exports.KEY_CONTEST_PURPOSE = "peers-key-contest";
|
|
11
|
+
/** Statement that says the key on record did not ask for `pendingKey`. */
|
|
12
|
+
exports.keyContestStatementSchema = zod_1.z.object({
|
|
13
|
+
purpose: zod_1.z.literal(exports.KEY_CONTEST_PURPOSE),
|
|
14
|
+
userId: zod_types_1.zodPeerId,
|
|
15
|
+
/** Key on record. The signature must be this key. */
|
|
16
|
+
publicKey: zod_1.z.string().min(1),
|
|
17
|
+
/** Key the anchors are offering without this key's co-signature. */
|
|
18
|
+
pendingKey: zod_1.z.string().min(1),
|
|
19
|
+
at: zod_1.z.string().min(1),
|
|
20
|
+
});
|
|
21
|
+
/** Signed contest carried in a handshake and posted to an anchor. */
|
|
22
|
+
exports.keyContestSignedSchema = zod_1.z.object({
|
|
23
|
+
contents: exports.keyContestStatementSchema,
|
|
24
|
+
signature: zod_1.z.string(),
|
|
25
|
+
publicKey: zod_1.z.string(),
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Signs a contest with the key on record.
|
|
29
|
+
*
|
|
30
|
+
* @param statement The user, the key on record, and the key being refused
|
|
31
|
+
* @param recordSecretKey Secret half of the key on record
|
|
32
|
+
*/
|
|
33
|
+
function signKeyContest(statement, recordSecretKey) {
|
|
34
|
+
const parsed = exports.keyContestStatementSchema.parse(statement);
|
|
35
|
+
return (0, keys_1.signObjectWithSecretKey)(parsed, recordSecretKey);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks that `signed` is the key on record objecting to `pendingKey`.
|
|
39
|
+
* A different signer, user, or pending key is not a contest.
|
|
40
|
+
*/
|
|
41
|
+
function verifyKeyContest(params) {
|
|
42
|
+
if (params.signed.publicKey !== params.recordPublicKey)
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
const contents = (0, keys_1.openSignedObject)(params.signed);
|
|
46
|
+
const parsed = exports.keyContestStatementSchema.safeParse(contents);
|
|
47
|
+
return (parsed.success &&
|
|
48
|
+
parsed.data.userId === params.userId &&
|
|
49
|
+
parsed.data.publicKey === params.recordPublicKey &&
|
|
50
|
+
parsed.data.pendingKey === params.pendingKey);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DataContext } from "../context/data-context";
|
|
3
|
+
/**
|
|
4
|
+
* Local memory of a key this device has been shown and has not yet accepted.
|
|
5
|
+
* `firstSeenAt` is when *this* device first saw the presented key. The table is
|
|
6
|
+
* `localOnly` so that moment is not copied to another device.
|
|
7
|
+
*/
|
|
8
|
+
export declare const keyResolutionSchema: z.ZodObject<{
|
|
9
|
+
resolutionId: z.ZodEffects<z.ZodString, string, string>;
|
|
10
|
+
userId: z.ZodEffects<z.ZodString, string, string>;
|
|
11
|
+
presentedKey: z.ZodString;
|
|
12
|
+
/** ISO 8601. When this device first saw `presentedKey`. */
|
|
13
|
+
firstSeenAt: z.ZodString;
|
|
14
|
+
/** ISO 8601. When the anchors were last fetched for this row. */
|
|
15
|
+
lastCheckedAt: z.ZodString;
|
|
16
|
+
state: z.ZodEnum<["pending", "vetoed", "confirmed-rotation", "confirmed-recovery"]>;
|
|
17
|
+
successionByRecordKey: z.ZodBoolean;
|
|
18
|
+
contested: z.ZodBoolean;
|
|
19
|
+
/** How long this answer may be reused, in seconds. */
|
|
20
|
+
cacheSeconds: z.ZodNumber;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
state: "pending" | "vetoed" | "confirmed-rotation" | "confirmed-recovery";
|
|
23
|
+
userId: string;
|
|
24
|
+
resolutionId: string;
|
|
25
|
+
presentedKey: string;
|
|
26
|
+
firstSeenAt: string;
|
|
27
|
+
lastCheckedAt: string;
|
|
28
|
+
successionByRecordKey: boolean;
|
|
29
|
+
contested: boolean;
|
|
30
|
+
cacheSeconds: number;
|
|
31
|
+
}, {
|
|
32
|
+
state: "pending" | "vetoed" | "confirmed-rotation" | "confirmed-recovery";
|
|
33
|
+
userId: string;
|
|
34
|
+
resolutionId: string;
|
|
35
|
+
presentedKey: string;
|
|
36
|
+
firstSeenAt: string;
|
|
37
|
+
lastCheckedAt: string;
|
|
38
|
+
successionByRecordKey: boolean;
|
|
39
|
+
contested: boolean;
|
|
40
|
+
cacheSeconds: number;
|
|
41
|
+
}>;
|
|
42
|
+
/** One local resolution of a presented key. */
|
|
43
|
+
export type IKeyResolution = z.infer<typeof keyResolutionSchema>;
|
|
44
|
+
/** Returns the local key-resolution table for a data context. */
|
|
45
|
+
export declare function KeyResolutions(dataContext?: DataContext): import("..").Table<{
|
|
46
|
+
state: "pending" | "vetoed" | "confirmed-rotation" | "confirmed-recovery";
|
|
47
|
+
userId: string;
|
|
48
|
+
resolutionId: string;
|
|
49
|
+
presentedKey: string;
|
|
50
|
+
firstSeenAt: string;
|
|
51
|
+
lastCheckedAt: string;
|
|
52
|
+
successionByRecordKey: boolean;
|
|
53
|
+
contested: boolean;
|
|
54
|
+
cacheSeconds: number;
|
|
55
|
+
}>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.keyResolutionSchema = void 0;
|
|
4
|
+
exports.KeyResolutions = KeyResolutions;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
7
|
+
const table_definitions_system_1 = require("../data/orm/table-definitions.system");
|
|
8
|
+
const types_1 = require("../data/orm/types");
|
|
9
|
+
const zod_types_1 = require("../types/zod-types");
|
|
10
|
+
const anchor_resolution_1 = require("./anchor-resolution");
|
|
11
|
+
/**
|
|
12
|
+
* Local memory of a key this device has been shown and has not yet accepted.
|
|
13
|
+
* `firstSeenAt` is when *this* device first saw the presented key. The table is
|
|
14
|
+
* `localOnly` so that moment is not copied to another device.
|
|
15
|
+
*/
|
|
16
|
+
exports.keyResolutionSchema = zod_1.z.object({
|
|
17
|
+
resolutionId: zod_types_1.zodPeerId,
|
|
18
|
+
userId: zod_types_1.zodPeerId,
|
|
19
|
+
presentedKey: zod_1.z.string(),
|
|
20
|
+
/** ISO 8601. When this device first saw `presentedKey`. */
|
|
21
|
+
firstSeenAt: zod_1.z.string(),
|
|
22
|
+
/** ISO 8601. When the anchors were last fetched for this row. */
|
|
23
|
+
lastCheckedAt: zod_1.z.string(),
|
|
24
|
+
state: zod_1.z.enum(anchor_resolution_1.KEY_RESOLUTION_STATES),
|
|
25
|
+
successionByRecordKey: zod_1.z.boolean(),
|
|
26
|
+
contested: zod_1.z.boolean(),
|
|
27
|
+
/** How long this answer may be reused, in seconds. */
|
|
28
|
+
cacheSeconds: zod_1.z.number(),
|
|
29
|
+
});
|
|
30
|
+
const metaData = {
|
|
31
|
+
name: "KeyResolutions",
|
|
32
|
+
description: "Local cache of key changes waiting on anchors",
|
|
33
|
+
primaryKeyName: "resolutionId",
|
|
34
|
+
fields: (0, types_1.schemaToFields)(exports.keyResolutionSchema),
|
|
35
|
+
indexes: [{ fields: ["userId", "presentedKey"], unique: true }],
|
|
36
|
+
localOnly: true,
|
|
37
|
+
};
|
|
38
|
+
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.keyResolutionSchema);
|
|
39
|
+
/** Returns the local key-resolution table for a data context. */
|
|
40
|
+
function KeyResolutions(dataContext) {
|
|
41
|
+
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.keyResolutionSchema);
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ISignedObject } from "../keys";
|
|
2
|
+
/** Purpose string the current key signs to start registering a passkey. */
|
|
3
|
+
export declare const ACCOUNT_PASSKEY_REGISTER_PURPOSE: "peers-account-passkey-register";
|
|
4
|
+
/** Statement the current key signs so a passkey can be bound. */
|
|
5
|
+
export interface IAccountPasskeyRegisterStatement {
|
|
6
|
+
purpose: typeof ACCOUNT_PASSKEY_REGISTER_PURPOSE;
|
|
7
|
+
userId: string;
|
|
8
|
+
issuedAt: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Signs a request to start passkey registration for `userId`.
|
|
12
|
+
*
|
|
13
|
+
* @param statement The user and when the request was made
|
|
14
|
+
* @param secretKey Secret half of the key on record
|
|
15
|
+
*/
|
|
16
|
+
export declare function signPasskeyRegister(statement: IAccountPasskeyRegisterStatement, secretKey: string): ISignedObject<IAccountPasskeyRegisterStatement>;
|
|
17
|
+
/** Checks that `signed` is the record key starting passkey registration. */
|
|
18
|
+
export declare function verifyPasskeyRegister(params: {
|
|
19
|
+
signed: ISignedObject<IAccountPasskeyRegisterStatement>;
|
|
20
|
+
userId: string;
|
|
21
|
+
recordPublicKey: string;
|
|
22
|
+
}): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ACCOUNT_PASSKEY_REGISTER_PURPOSE = void 0;
|
|
4
|
+
exports.signPasskeyRegister = signPasskeyRegister;
|
|
5
|
+
exports.verifyPasskeyRegister = verifyPasskeyRegister;
|
|
6
|
+
const keys_1 = require("../keys");
|
|
7
|
+
/** Purpose string the current key signs to start registering a passkey. */
|
|
8
|
+
exports.ACCOUNT_PASSKEY_REGISTER_PURPOSE = "peers-account-passkey-register";
|
|
9
|
+
/**
|
|
10
|
+
* Signs a request to start passkey registration for `userId`.
|
|
11
|
+
*
|
|
12
|
+
* @param statement The user and when the request was made
|
|
13
|
+
* @param secretKey Secret half of the key on record
|
|
14
|
+
*/
|
|
15
|
+
function signPasskeyRegister(statement, secretKey) {
|
|
16
|
+
return (0, keys_1.signObjectWithSecretKey)({
|
|
17
|
+
purpose: exports.ACCOUNT_PASSKEY_REGISTER_PURPOSE,
|
|
18
|
+
userId: statement.userId,
|
|
19
|
+
issuedAt: statement.issuedAt,
|
|
20
|
+
}, secretKey);
|
|
21
|
+
}
|
|
22
|
+
/** Checks that `signed` is the record key starting passkey registration. */
|
|
23
|
+
function verifyPasskeyRegister(params) {
|
|
24
|
+
if (params.signed.publicKey !== params.recordPublicKey)
|
|
25
|
+
return false;
|
|
26
|
+
try {
|
|
27
|
+
const contents = (0, keys_1.openSignedObject)(params.signed);
|
|
28
|
+
return (contents.purpose === exports.ACCOUNT_PASSKEY_REGISTER_PURPOSE && contents.userId === params.userId);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DataContext } from "../context/data-context";
|
|
2
|
+
import { type IUser } from "../data/users";
|
|
3
|
+
/**
|
|
4
|
+
* What to do with a `Users` row that differs from the one already stored.
|
|
5
|
+
*
|
|
6
|
+
* - `apply`: write `row`. Its `anchors` may have been replaced by the list on record.
|
|
7
|
+
* - `defer`: the anchors on record have not answered yet; keep the stored row and try again.
|
|
8
|
+
* - `refuse`: the anchors on record vetoed the key; keep the stored row and stop.
|
|
9
|
+
*/
|
|
10
|
+
export type UserRowChangeDecision = {
|
|
11
|
+
action: "apply";
|
|
12
|
+
row: IUser;
|
|
13
|
+
} | {
|
|
14
|
+
action: "defer";
|
|
15
|
+
reason: string;
|
|
16
|
+
} | {
|
|
17
|
+
action: "refuse";
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
/** Inputs for {@link resolveUserRowChange}. */
|
|
21
|
+
export interface ResolveUserRowChangeParams {
|
|
22
|
+
/** Personal data context: where the on-record row and the resolution cache live. */
|
|
23
|
+
personalContext: DataContext;
|
|
24
|
+
/** The row already stored in the table being written, if any. */
|
|
25
|
+
existing: IUser | undefined;
|
|
26
|
+
/** The row that wants to replace it. Not mutated. */
|
|
27
|
+
incoming: IUser;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The one decision for every path that writes another user's `Users` row: a direct save, a
|
|
31
|
+
* group row, or a row reconstructed from a synced change log.
|
|
32
|
+
*
|
|
33
|
+
* The record is the personal `Users` row when it exists, else the row already stored in the
|
|
34
|
+
* table being written. The rule:
|
|
35
|
+
*
|
|
36
|
+
* - Nothing stored yet: the row is the bootstrap and is applied as it is.
|
|
37
|
+
* - A different key: the anchors on record decide ({@link evaluatePresentedKey}). The
|
|
38
|
+
* presented anchor list is ignored; the applied row keeps the list on record.
|
|
39
|
+
* - The same key and a different list: when nothing is on record the list is accepted as the
|
|
40
|
+
* bootstrap (an upgraded contact announcing its first anchors); when the personal record
|
|
41
|
+
* already holds the list it is accepted; otherwise the anchors on record must publish it
|
|
42
|
+
* ({@link evaluatePresentedAnchors}). A veto keeps the profile fields and the list on record,
|
|
43
|
+
* because the row is still signed by the key on record and only the list is unproven.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveUserRowChange(params: ResolveUserRowChangeParams): Promise<UserRowChangeDecision>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveUserRowChange = resolveUserRowChange;
|
|
4
|
+
const users_1 = require("../data/users");
|
|
5
|
+
const anchor_resolution_1 = require("./anchor-resolution");
|
|
6
|
+
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 };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The one decision for every path that writes another user's `Users` row: a direct save, a
|
|
18
|
+
* group row, or a row reconstructed from a synced change log.
|
|
19
|
+
*
|
|
20
|
+
* The record is the personal `Users` row when it exists, else the row already stored in the
|
|
21
|
+
* table being written. The rule:
|
|
22
|
+
*
|
|
23
|
+
* - Nothing stored yet: the row is the bootstrap and is applied as it is.
|
|
24
|
+
* - A different key: the anchors on record decide ({@link evaluatePresentedKey}). The
|
|
25
|
+
* presented anchor list is ignored; the applied row keeps the list on record.
|
|
26
|
+
* - The same key and a different list: when nothing is on record the list is accepted as the
|
|
27
|
+
* bootstrap (an upgraded contact announcing its first anchors); when the personal record
|
|
28
|
+
* already holds the list it is accepted; otherwise the anchors on record must publish it
|
|
29
|
+
* ({@link evaluatePresentedAnchors}). A veto keeps the profile fields and the list on record,
|
|
30
|
+
* because the row is still signed by the key on record and only the list is unproven.
|
|
31
|
+
*/
|
|
32
|
+
async function resolveUserRowChange(params) {
|
|
33
|
+
const { existing, incoming, personalContext } = params;
|
|
34
|
+
if (!existing?.publicKey)
|
|
35
|
+
return { action: "apply", row: incoming };
|
|
36
|
+
const personal = await (0, users_1.Users)(personalContext).get(incoming.userId);
|
|
37
|
+
const record = personal?.publicKey ? personal : existing;
|
|
38
|
+
const recordAnchors = personal?.anchors?.length ? personal.anchors : (existing.anchors ?? []);
|
|
39
|
+
if (incoming.publicKey !== existing.publicKey) {
|
|
40
|
+
const decision = await (0, key_change_1.evaluatePresentedKey)({
|
|
41
|
+
dataContext: personalContext,
|
|
42
|
+
userId: incoming.userId,
|
|
43
|
+
presentedKey: incoming.publicKey,
|
|
44
|
+
presentedBoxKey: incoming.publicBoxKey,
|
|
45
|
+
record,
|
|
46
|
+
});
|
|
47
|
+
if (decision.accepted) {
|
|
48
|
+
// The resolver may have just stored a confirmed list on the personal row.
|
|
49
|
+
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) };
|
|
52
|
+
}
|
|
53
|
+
if (decision.state === "pending" || decision.state === "recovery-pending") {
|
|
54
|
+
return { action: "defer", reason: "Key change is not confirmed by the anchors on record" };
|
|
55
|
+
}
|
|
56
|
+
return { action: "refuse", reason: "Key change is not confirmed by the anchors on record" };
|
|
57
|
+
}
|
|
58
|
+
if ((0, anchor_resolution_1.sameAnchorList)(existing.anchors, incoming.anchors))
|
|
59
|
+
return { action: "apply", row: incoming };
|
|
60
|
+
if (recordAnchors.length === 0)
|
|
61
|
+
return { action: "apply", row: incoming };
|
|
62
|
+
if ((0, anchor_resolution_1.sameAnchorList)(personal?.anchors, incoming.anchors)) {
|
|
63
|
+
return { action: "apply", row: incoming };
|
|
64
|
+
}
|
|
65
|
+
const verdict = await (0, key_change_1.evaluatePresentedAnchors)({
|
|
66
|
+
dataContext: personalContext,
|
|
67
|
+
userId: incoming.userId,
|
|
68
|
+
recordAnchors,
|
|
69
|
+
presentedAnchors: incoming.anchors ?? [],
|
|
70
|
+
});
|
|
71
|
+
if (verdict === "confirmed")
|
|
72
|
+
return { action: "apply", row: incoming };
|
|
73
|
+
if (verdict === "pending") {
|
|
74
|
+
return {
|
|
75
|
+
action: "defer",
|
|
76
|
+
reason: "Anchor list change is not confirmed by the anchors on record",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return { action: "apply", row: withAnchors(incoming, recordAnchors) };
|
|
80
|
+
}
|
|
81
|
+
(0, users_1.setUserRowChangeGuard)((personalContext, incoming, existing) => resolveUserRowChange({ personalContext, incoming, existing }));
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export * from "./device/tx-encoding";
|
|
|
17
17
|
export * from "./device-pairing";
|
|
18
18
|
export * from "./events";
|
|
19
19
|
export * from "./hlc";
|
|
20
|
+
export * from "./host-credentials";
|
|
21
|
+
export * from "./identity/account-email";
|
|
22
|
+
export * from "./identity/anchor-document";
|
|
23
|
+
export * from "./identity/anchor-resolution";
|
|
24
|
+
export * from "./identity/key-change";
|
|
25
|
+
export * from "./identity/key-contest";
|
|
26
|
+
export * from "./identity/key-resolutions";
|
|
27
|
+
export * from "./identity/passkey";
|
|
28
|
+
export * from "./identity/user-row-change";
|
|
20
29
|
export * from "./invites";
|
|
21
30
|
export * from "./keys";
|
|
22
31
|
export * from "./logging";
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,15 @@ __exportStar(require("./device/tx-encoding"), exports);
|
|
|
38
38
|
__exportStar(require("./device-pairing"), exports);
|
|
39
39
|
__exportStar(require("./events"), exports);
|
|
40
40
|
__exportStar(require("./hlc"), exports);
|
|
41
|
+
__exportStar(require("./host-credentials"), exports);
|
|
42
|
+
__exportStar(require("./identity/account-email"), exports);
|
|
43
|
+
__exportStar(require("./identity/anchor-document"), exports);
|
|
44
|
+
__exportStar(require("./identity/anchor-resolution"), exports);
|
|
45
|
+
__exportStar(require("./identity/key-change"), exports);
|
|
46
|
+
__exportStar(require("./identity/key-contest"), exports);
|
|
47
|
+
__exportStar(require("./identity/key-resolutions"), exports);
|
|
48
|
+
__exportStar(require("./identity/passkey"), exports);
|
|
49
|
+
__exportStar(require("./identity/user-row-change"), exports);
|
|
41
50
|
__exportStar(require("./invites"), exports);
|
|
42
51
|
__exportStar(require("./keys"), exports);
|
|
43
52
|
__exportStar(require("./logging"), exports);
|