@peers-app/peers-sdk 0.25.14 → 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/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
|
@@ -1141,16 +1141,16 @@ export declare const devicePairingTranscriptSchema: z.ZodObject<{
|
|
|
1141
1141
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1142
1142
|
}, "strip", z.ZodTypeAny, {
|
|
1143
1143
|
publicKey: string;
|
|
1144
|
-
publicBoxKey: string;
|
|
1145
1144
|
userId: string;
|
|
1146
1145
|
deviceId: string;
|
|
1146
|
+
publicBoxKey: string;
|
|
1147
1147
|
userName?: string | undefined;
|
|
1148
1148
|
deviceName?: string | undefined;
|
|
1149
1149
|
}, {
|
|
1150
1150
|
publicKey: string;
|
|
1151
|
-
publicBoxKey: string;
|
|
1152
1151
|
userId: string;
|
|
1153
1152
|
deviceId: string;
|
|
1153
|
+
publicBoxKey: string;
|
|
1154
1154
|
userName?: string | undefined;
|
|
1155
1155
|
deviceName?: string | undefined;
|
|
1156
1156
|
}>;
|
|
@@ -1163,16 +1163,16 @@ export declare const devicePairingTranscriptSchema: z.ZodObject<{
|
|
|
1163
1163
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1164
1164
|
}, "strip", z.ZodTypeAny, {
|
|
1165
1165
|
publicKey: string;
|
|
1166
|
-
publicBoxKey: string;
|
|
1167
1166
|
userId: string;
|
|
1168
1167
|
deviceId: string;
|
|
1168
|
+
publicBoxKey: string;
|
|
1169
1169
|
userName?: string | undefined;
|
|
1170
1170
|
deviceName?: string | undefined;
|
|
1171
1171
|
}, {
|
|
1172
1172
|
publicKey: string;
|
|
1173
|
-
publicBoxKey: string;
|
|
1174
1173
|
userId: string;
|
|
1175
1174
|
deviceId: string;
|
|
1175
|
+
publicBoxKey: string;
|
|
1176
1176
|
userName?: string | undefined;
|
|
1177
1177
|
deviceName?: string | undefined;
|
|
1178
1178
|
}>;
|
|
@@ -1187,17 +1187,17 @@ export declare const devicePairingTranscriptSchema: z.ZodObject<{
|
|
|
1187
1187
|
nameplate: number;
|
|
1188
1188
|
sourceIdentity: {
|
|
1189
1189
|
publicKey: string;
|
|
1190
|
-
publicBoxKey: string;
|
|
1191
1190
|
userId: string;
|
|
1192
1191
|
deviceId: string;
|
|
1192
|
+
publicBoxKey: string;
|
|
1193
1193
|
userName?: string | undefined;
|
|
1194
1194
|
deviceName?: string | undefined;
|
|
1195
1195
|
};
|
|
1196
1196
|
destinationIdentity: {
|
|
1197
1197
|
publicKey: string;
|
|
1198
|
-
publicBoxKey: string;
|
|
1199
1198
|
userId: string;
|
|
1200
1199
|
deviceId: string;
|
|
1200
|
+
publicBoxKey: string;
|
|
1201
1201
|
userName?: string | undefined;
|
|
1202
1202
|
deviceName?: string | undefined;
|
|
1203
1203
|
};
|
|
@@ -1211,17 +1211,17 @@ export declare const devicePairingTranscriptSchema: z.ZodObject<{
|
|
|
1211
1211
|
nameplate: number;
|
|
1212
1212
|
sourceIdentity: {
|
|
1213
1213
|
publicKey: string;
|
|
1214
|
-
publicBoxKey: string;
|
|
1215
1214
|
userId: string;
|
|
1216
1215
|
deviceId: string;
|
|
1216
|
+
publicBoxKey: string;
|
|
1217
1217
|
userName?: string | undefined;
|
|
1218
1218
|
deviceName?: string | undefined;
|
|
1219
1219
|
};
|
|
1220
1220
|
destinationIdentity: {
|
|
1221
1221
|
publicKey: string;
|
|
1222
|
-
publicBoxKey: string;
|
|
1223
1222
|
userId: string;
|
|
1224
1223
|
deviceId: string;
|
|
1224
|
+
publicBoxKey: string;
|
|
1225
1225
|
userName?: string | undefined;
|
|
1226
1226
|
deviceName?: string | undefined;
|
|
1227
1227
|
};
|
|
@@ -1261,16 +1261,16 @@ export declare const pairingConfirmationContentsSchema: z.ZodObject<{
|
|
|
1261
1261
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1262
1262
|
}, "strip", z.ZodTypeAny, {
|
|
1263
1263
|
publicKey: string;
|
|
1264
|
-
publicBoxKey: string;
|
|
1265
1264
|
userId: string;
|
|
1266
1265
|
deviceId: string;
|
|
1266
|
+
publicBoxKey: string;
|
|
1267
1267
|
userName?: string | undefined;
|
|
1268
1268
|
deviceName?: string | undefined;
|
|
1269
1269
|
}, {
|
|
1270
1270
|
publicKey: string;
|
|
1271
|
-
publicBoxKey: string;
|
|
1272
1271
|
userId: string;
|
|
1273
1272
|
deviceId: string;
|
|
1273
|
+
publicBoxKey: string;
|
|
1274
1274
|
userName?: string | undefined;
|
|
1275
1275
|
deviceName?: string | undefined;
|
|
1276
1276
|
}>;
|
|
@@ -1292,9 +1292,9 @@ export declare const pairingConfirmationContentsSchema: z.ZodObject<{
|
|
|
1292
1292
|
version: 3;
|
|
1293
1293
|
device: {
|
|
1294
1294
|
publicKey: string;
|
|
1295
|
-
publicBoxKey: string;
|
|
1296
1295
|
userId: string;
|
|
1297
1296
|
deviceId: string;
|
|
1297
|
+
publicBoxKey: string;
|
|
1298
1298
|
userName?: string | undefined;
|
|
1299
1299
|
deviceName?: string | undefined;
|
|
1300
1300
|
};
|
|
@@ -1309,9 +1309,9 @@ export declare const pairingConfirmationContentsSchema: z.ZodObject<{
|
|
|
1309
1309
|
version: 3;
|
|
1310
1310
|
device: {
|
|
1311
1311
|
publicKey: string;
|
|
1312
|
-
publicBoxKey: string;
|
|
1313
1312
|
userId: string;
|
|
1314
1313
|
deviceId: string;
|
|
1314
|
+
publicBoxKey: string;
|
|
1315
1315
|
userName?: string | undefined;
|
|
1316
1316
|
deviceName?: string | undefined;
|
|
1317
1317
|
};
|
|
@@ -1341,16 +1341,16 @@ export declare const pairingConfirmationSchema: z.ZodObject<{
|
|
|
1341
1341
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1342
1342
|
}, "strip", z.ZodTypeAny, {
|
|
1343
1343
|
publicKey: string;
|
|
1344
|
-
publicBoxKey: string;
|
|
1345
1344
|
userId: string;
|
|
1346
1345
|
deviceId: string;
|
|
1346
|
+
publicBoxKey: string;
|
|
1347
1347
|
userName?: string | undefined;
|
|
1348
1348
|
deviceName?: string | undefined;
|
|
1349
1349
|
}, {
|
|
1350
1350
|
publicKey: string;
|
|
1351
|
-
publicBoxKey: string;
|
|
1352
1351
|
userId: string;
|
|
1353
1352
|
deviceId: string;
|
|
1353
|
+
publicBoxKey: string;
|
|
1354
1354
|
userName?: string | undefined;
|
|
1355
1355
|
deviceName?: string | undefined;
|
|
1356
1356
|
}>;
|
|
@@ -1374,9 +1374,9 @@ export declare const pairingConfirmationSchema: z.ZodObject<{
|
|
|
1374
1374
|
version: 3;
|
|
1375
1375
|
device: {
|
|
1376
1376
|
publicKey: string;
|
|
1377
|
-
publicBoxKey: string;
|
|
1378
1377
|
userId: string;
|
|
1379
1378
|
deviceId: string;
|
|
1379
|
+
publicBoxKey: string;
|
|
1380
1380
|
userName?: string | undefined;
|
|
1381
1381
|
deviceName?: string | undefined;
|
|
1382
1382
|
};
|
|
@@ -1392,9 +1392,9 @@ export declare const pairingConfirmationSchema: z.ZodObject<{
|
|
|
1392
1392
|
version: 3;
|
|
1393
1393
|
device: {
|
|
1394
1394
|
publicKey: string;
|
|
1395
|
-
publicBoxKey: string;
|
|
1396
1395
|
userId: string;
|
|
1397
1396
|
deviceId: string;
|
|
1397
|
+
publicBoxKey: string;
|
|
1398
1398
|
userName?: string | undefined;
|
|
1399
1399
|
deviceName?: string | undefined;
|
|
1400
1400
|
};
|
|
@@ -1459,16 +1459,16 @@ export declare const pairingInstallationReceiptContentsSchema: z.ZodObject<{
|
|
|
1459
1459
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1460
1460
|
}, "strip", z.ZodTypeAny, {
|
|
1461
1461
|
publicKey: string;
|
|
1462
|
-
publicBoxKey: string;
|
|
1463
1462
|
userId: string;
|
|
1464
1463
|
deviceId: string;
|
|
1464
|
+
publicBoxKey: string;
|
|
1465
1465
|
userName?: string | undefined;
|
|
1466
1466
|
deviceName?: string | undefined;
|
|
1467
1467
|
}, {
|
|
1468
1468
|
publicKey: string;
|
|
1469
|
-
publicBoxKey: string;
|
|
1470
1469
|
userId: string;
|
|
1471
1470
|
deviceId: string;
|
|
1471
|
+
publicBoxKey: string;
|
|
1472
1472
|
userName?: string | undefined;
|
|
1473
1473
|
deviceName?: string | undefined;
|
|
1474
1474
|
}>;
|
|
@@ -1478,9 +1478,9 @@ export declare const pairingInstallationReceiptContentsSchema: z.ZodObject<{
|
|
|
1478
1478
|
transcriptHash: string;
|
|
1479
1479
|
initializedDevice: {
|
|
1480
1480
|
publicKey: string;
|
|
1481
|
-
publicBoxKey: string;
|
|
1482
1481
|
userId: string;
|
|
1483
1482
|
deviceId: string;
|
|
1483
|
+
publicBoxKey: string;
|
|
1484
1484
|
userName?: string | undefined;
|
|
1485
1485
|
deviceName?: string | undefined;
|
|
1486
1486
|
};
|
|
@@ -1490,9 +1490,9 @@ export declare const pairingInstallationReceiptContentsSchema: z.ZodObject<{
|
|
|
1490
1490
|
transcriptHash: string;
|
|
1491
1491
|
initializedDevice: {
|
|
1492
1492
|
publicKey: string;
|
|
1493
|
-
publicBoxKey: string;
|
|
1494
1493
|
userId: string;
|
|
1495
1494
|
deviceId: string;
|
|
1495
|
+
publicBoxKey: string;
|
|
1496
1496
|
userName?: string | undefined;
|
|
1497
1497
|
deviceName?: string | undefined;
|
|
1498
1498
|
};
|
|
@@ -1514,16 +1514,16 @@ export declare const signedPairingInstallationReceiptSchema: z.ZodObject<{
|
|
|
1514
1514
|
deviceName: z.ZodOptional<z.ZodString>;
|
|
1515
1515
|
}, "strip", z.ZodTypeAny, {
|
|
1516
1516
|
publicKey: string;
|
|
1517
|
-
publicBoxKey: string;
|
|
1518
1517
|
userId: string;
|
|
1519
1518
|
deviceId: string;
|
|
1519
|
+
publicBoxKey: string;
|
|
1520
1520
|
userName?: string | undefined;
|
|
1521
1521
|
deviceName?: string | undefined;
|
|
1522
1522
|
}, {
|
|
1523
1523
|
publicKey: string;
|
|
1524
|
-
publicBoxKey: string;
|
|
1525
1524
|
userId: string;
|
|
1526
1525
|
deviceId: string;
|
|
1526
|
+
publicBoxKey: string;
|
|
1527
1527
|
userName?: string | undefined;
|
|
1528
1528
|
deviceName?: string | undefined;
|
|
1529
1529
|
}>;
|
|
@@ -1533,9 +1533,9 @@ export declare const signedPairingInstallationReceiptSchema: z.ZodObject<{
|
|
|
1533
1533
|
transcriptHash: string;
|
|
1534
1534
|
initializedDevice: {
|
|
1535
1535
|
publicKey: string;
|
|
1536
|
-
publicBoxKey: string;
|
|
1537
1536
|
userId: string;
|
|
1538
1537
|
deviceId: string;
|
|
1538
|
+
publicBoxKey: string;
|
|
1539
1539
|
userName?: string | undefined;
|
|
1540
1540
|
deviceName?: string | undefined;
|
|
1541
1541
|
};
|
|
@@ -1545,9 +1545,9 @@ export declare const signedPairingInstallationReceiptSchema: z.ZodObject<{
|
|
|
1545
1545
|
transcriptHash: string;
|
|
1546
1546
|
initializedDevice: {
|
|
1547
1547
|
publicKey: string;
|
|
1548
|
-
publicBoxKey: string;
|
|
1549
1548
|
userId: string;
|
|
1550
1549
|
deviceId: string;
|
|
1550
|
+
publicBoxKey: string;
|
|
1551
1551
|
userName?: string | undefined;
|
|
1552
1552
|
deviceName?: string | undefined;
|
|
1553
1553
|
};
|
|
@@ -1563,9 +1563,9 @@ export declare const signedPairingInstallationReceiptSchema: z.ZodObject<{
|
|
|
1563
1563
|
transcriptHash: string;
|
|
1564
1564
|
initializedDevice: {
|
|
1565
1565
|
publicKey: string;
|
|
1566
|
-
publicBoxKey: string;
|
|
1567
1566
|
userId: string;
|
|
1568
1567
|
deviceId: string;
|
|
1568
|
+
publicBoxKey: string;
|
|
1569
1569
|
userName?: string | undefined;
|
|
1570
1570
|
deviceName?: string | undefined;
|
|
1571
1571
|
};
|
|
@@ -1579,9 +1579,9 @@ export declare const signedPairingInstallationReceiptSchema: z.ZodObject<{
|
|
|
1579
1579
|
transcriptHash: string;
|
|
1580
1580
|
initializedDevice: {
|
|
1581
1581
|
publicKey: string;
|
|
1582
|
-
publicBoxKey: string;
|
|
1583
1582
|
userId: string;
|
|
1584
1583
|
deviceId: string;
|
|
1584
|
+
publicBoxKey: string;
|
|
1585
1585
|
userName?: string | undefined;
|
|
1586
1586
|
deviceName?: string | undefined;
|
|
1587
1587
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Why a signing key on this host was replaced. */
|
|
2
|
+
export declare const KEY_ROTATION_REASONS: readonly ["rotation", "compromised", "lost"];
|
|
3
|
+
/** One of {@link KEY_ROTATION_REASONS}. */
|
|
4
|
+
export type KeyRotationReason = (typeof KEY_ROTATION_REASONS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* The credential record every host keeps for the signed-in user.
|
|
7
|
+
*
|
|
8
|
+
* Electron stores it in the OS keychain, headless in `credentials.json`, the PWA in its
|
|
9
|
+
* IndexedDB vault. Besides the identity it carries `dbSecret`, the SQLCipher key for this
|
|
10
|
+
* host's database files, which is deliberately *not* derived from `secretKey` any more: that
|
|
11
|
+
* is what lets a user rotate their signing key without re-encrypting every database
|
|
12
|
+
* (`PRAGMA rekey` is unusable under WAL). Existing installs keep working because a missing
|
|
13
|
+
* `dbSecret` means "the legacy derived value", so migration changes no file on disk.
|
|
14
|
+
*/
|
|
15
|
+
/** A key this host's user retired, kept for display and for `getKeyInfo`. */
|
|
16
|
+
export interface IPreviousPublicKey {
|
|
17
|
+
publicKey: string;
|
|
18
|
+
/** ISO 8601 */
|
|
19
|
+
replacedAt: string;
|
|
20
|
+
reason?: KeyRotationReason;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The key a rotation retired, kept only until every secret in the personal database has been
|
|
24
|
+
* re-wrapped under the new one. Secrets are rewritten one at a time after the new key is
|
|
25
|
+
* stored; without this slot a crash in between would leave values nothing can open.
|
|
26
|
+
*/
|
|
27
|
+
export interface IRewrapSource {
|
|
28
|
+
publicKey: string;
|
|
29
|
+
secretKey: string;
|
|
30
|
+
}
|
|
31
|
+
/** Stored credentials; see the module description. */
|
|
32
|
+
export interface IHostCredentialRecord {
|
|
33
|
+
userId: string;
|
|
34
|
+
/** Current Ed25519 secret key, base64url (64-byte encoding). */
|
|
35
|
+
secretKey: string;
|
|
36
|
+
/** SQLCipher key for this host's databases. Never changes on key rotation. */
|
|
37
|
+
dbSecret: string;
|
|
38
|
+
/** Keys this user retired on this host, newest last. */
|
|
39
|
+
previousPublicKeys?: IPreviousPublicKey[];
|
|
40
|
+
/** Present while a rotation's re-wrap is unfinished; see {@link IRewrapSource}. */
|
|
41
|
+
rewrapFrom?: IRewrapSource;
|
|
42
|
+
}
|
|
43
|
+
/** The SQLCipher key hosts used before `dbSecret` existed: a hash of the user secret. */
|
|
44
|
+
export declare function legacyDbSecret(secretKey: string): string;
|
|
45
|
+
/** A fresh random SQLCipher key for a brand-new identity on this host. */
|
|
46
|
+
export declare function newDbSecret(): string;
|
|
47
|
+
/**
|
|
48
|
+
* Fills in `dbSecret` for a record that predates it (or was copied by pairing, which transfers
|
|
49
|
+
* only `userId` and `secretKey`).
|
|
50
|
+
*
|
|
51
|
+
* @param input Partial record with at least `userId` and `secretKey`
|
|
52
|
+
* @param opts.freshDbSecret When true a missing `dbSecret` is minted at random (new identity on
|
|
53
|
+
* this host, no database file exists yet). Default: the legacy derived value, so an existing
|
|
54
|
+
* database still opens.
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizeCredentialRecord(input: Partial<IHostCredentialRecord> & Pick<IHostCredentialRecord, "userId" | "secretKey">, opts?: {
|
|
57
|
+
freshDbSecret?: boolean;
|
|
58
|
+
}): IHostCredentialRecord;
|
|
59
|
+
/**
|
|
60
|
+
* Parses a stored credential string: the JSON form written by current hosts, or the legacy
|
|
61
|
+
* `userId::secretKey` form. Returns `null` when the value is neither.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseCredentialRecord(value: string | null | undefined): IHostCredentialRecord | null;
|
|
64
|
+
/** Serializes a record for storage (JSON, one line). */
|
|
65
|
+
export declare function serializeCredentialRecord(record: IHostCredentialRecord): string;
|
|
66
|
+
/**
|
|
67
|
+
* The record after a key rotation on this host: new secret, same `dbSecret`, old public key
|
|
68
|
+
* appended to `previousPublicKeys`, and the old secret kept in `rewrapFrom` until
|
|
69
|
+
* {@link completeCredentialRewrap} says every secret is readable under the new key.
|
|
70
|
+
*/
|
|
71
|
+
export declare function rotateCredentialRecord(current: IHostCredentialRecord, next: {
|
|
72
|
+
secretKey: string;
|
|
73
|
+
oldPublicKey: string;
|
|
74
|
+
reason?: KeyRotationReason;
|
|
75
|
+
at?: Date;
|
|
76
|
+
}): IHostCredentialRecord;
|
|
77
|
+
/** The record once the re-wrap after a rotation has finished: the retired secret is dropped. */
|
|
78
|
+
export declare function completeCredentialRewrap(record: IHostCredentialRecord): IHostCredentialRecord;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KEY_ROTATION_REASONS = void 0;
|
|
4
|
+
exports.legacyDbSecret = legacyDbSecret;
|
|
5
|
+
exports.newDbSecret = newDbSecret;
|
|
6
|
+
exports.normalizeCredentialRecord = normalizeCredentialRecord;
|
|
7
|
+
exports.parseCredentialRecord = parseCredentialRecord;
|
|
8
|
+
exports.serializeCredentialRecord = serializeCredentialRecord;
|
|
9
|
+
exports.rotateCredentialRecord = rotateCredentialRecord;
|
|
10
|
+
exports.completeCredentialRewrap = completeCredentialRewrap;
|
|
11
|
+
const keys_1 = require("./keys");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
/** Why a signing key on this host was replaced. */
|
|
14
|
+
exports.KEY_ROTATION_REASONS = ["rotation", "compromised", "lost"];
|
|
15
|
+
/** The SQLCipher key hosts used before `dbSecret` existed: a hash of the user secret. */
|
|
16
|
+
function legacyDbSecret(secretKey) {
|
|
17
|
+
return (0, keys_1.hashValue)(secretKey);
|
|
18
|
+
}
|
|
19
|
+
/** A fresh random SQLCipher key for a brand-new identity on this host. */
|
|
20
|
+
function newDbSecret() {
|
|
21
|
+
return (0, keys_1.newToken)(32);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fills in `dbSecret` for a record that predates it (or was copied by pairing, which transfers
|
|
25
|
+
* only `userId` and `secretKey`).
|
|
26
|
+
*
|
|
27
|
+
* @param input Partial record with at least `userId` and `secretKey`
|
|
28
|
+
* @param opts.freshDbSecret When true a missing `dbSecret` is minted at random (new identity on
|
|
29
|
+
* this host, no database file exists yet). Default: the legacy derived value, so an existing
|
|
30
|
+
* database still opens.
|
|
31
|
+
*/
|
|
32
|
+
function normalizeCredentialRecord(input, opts = {}) {
|
|
33
|
+
const dbSecret = input.dbSecret || (opts.freshDbSecret ? newDbSecret() : legacyDbSecret(input.secretKey));
|
|
34
|
+
return {
|
|
35
|
+
userId: input.userId,
|
|
36
|
+
secretKey: input.secretKey,
|
|
37
|
+
dbSecret,
|
|
38
|
+
...(input.previousPublicKeys?.length ? { previousPublicKeys: input.previousPublicKeys } : {}),
|
|
39
|
+
...(input.rewrapFrom?.publicKey && input.rewrapFrom.secretKey
|
|
40
|
+
? {
|
|
41
|
+
rewrapFrom: {
|
|
42
|
+
publicKey: input.rewrapFrom.publicKey,
|
|
43
|
+
secretKey: input.rewrapFrom.secretKey,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parses a stored credential string: the JSON form written by current hosts, or the legacy
|
|
51
|
+
* `userId::secretKey` form. Returns `null` when the value is neither.
|
|
52
|
+
*/
|
|
53
|
+
function parseCredentialRecord(value) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return null;
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
if (trimmed.startsWith("{")) {
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(trimmed);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!parsed ||
|
|
66
|
+
typeof parsed !== "object" ||
|
|
67
|
+
typeof parsed.userId !== "string" ||
|
|
68
|
+
typeof parsed.secretKey !== "string" ||
|
|
69
|
+
!parsed.userId ||
|
|
70
|
+
!parsed.secretKey) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return normalizeCredentialRecord({
|
|
74
|
+
userId: parsed.userId,
|
|
75
|
+
secretKey: parsed.secretKey,
|
|
76
|
+
dbSecret: typeof parsed.dbSecret === "string" ? parsed.dbSecret : undefined,
|
|
77
|
+
previousPublicKeys: Array.isArray(parsed.previousPublicKeys)
|
|
78
|
+
? parsed.previousPublicKeys.filter((p) => p && typeof p.publicKey === "string" && typeof p.replacedAt === "string")
|
|
79
|
+
: undefined,
|
|
80
|
+
rewrapFrom: parsed.rewrapFrom &&
|
|
81
|
+
typeof parsed.rewrapFrom.publicKey === "string" &&
|
|
82
|
+
typeof parsed.rewrapFrom.secretKey === "string"
|
|
83
|
+
? { publicKey: parsed.rewrapFrom.publicKey, secretKey: parsed.rewrapFrom.secretKey }
|
|
84
|
+
: undefined,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const separator = trimmed.indexOf("::");
|
|
88
|
+
if (separator <= 0)
|
|
89
|
+
return null;
|
|
90
|
+
const userId = trimmed.slice(0, separator);
|
|
91
|
+
const secretKey = trimmed.slice(separator + 2);
|
|
92
|
+
if (!userId || !secretKey)
|
|
93
|
+
return null;
|
|
94
|
+
return normalizeCredentialRecord({ userId, secretKey });
|
|
95
|
+
}
|
|
96
|
+
/** Serializes a record for storage (JSON, one line). */
|
|
97
|
+
function serializeCredentialRecord(record) {
|
|
98
|
+
return JSON.stringify(normalizeCredentialRecord(record));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The record after a key rotation on this host: new secret, same `dbSecret`, old public key
|
|
102
|
+
* appended to `previousPublicKeys`, and the old secret kept in `rewrapFrom` until
|
|
103
|
+
* {@link completeCredentialRewrap} says every secret is readable under the new key.
|
|
104
|
+
*/
|
|
105
|
+
function rotateCredentialRecord(current, next) {
|
|
106
|
+
if (!(0, utils_1.isid)(current.userId)) {
|
|
107
|
+
throw new Error(`Credential record has an invalid userId: ${current.userId}`);
|
|
108
|
+
}
|
|
109
|
+
return normalizeCredentialRecord({
|
|
110
|
+
userId: current.userId,
|
|
111
|
+
secretKey: next.secretKey,
|
|
112
|
+
dbSecret: current.dbSecret,
|
|
113
|
+
previousPublicKeys: [
|
|
114
|
+
...(current.previousPublicKeys ?? []),
|
|
115
|
+
{
|
|
116
|
+
publicKey: next.oldPublicKey,
|
|
117
|
+
replacedAt: (next.at ?? new Date()).toISOString(),
|
|
118
|
+
...(next.reason ? { reason: next.reason } : {}),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
rewrapFrom: { publicKey: next.oldPublicKey, secretKey: current.secretKey },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** The record once the re-wrap after a rotation has finished: the retired secret is dropped. */
|
|
125
|
+
function completeCredentialRewrap(record) {
|
|
126
|
+
const { rewrapFrom: _finished, ...rest } = record;
|
|
127
|
+
return normalizeCredentialRecord(rest);
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const host_credentials_1 = require("./host-credentials");
|
|
4
|
+
const keys_1 = require("./keys");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
describe("host credential record", () => {
|
|
7
|
+
const userId = (0, utils_1.newid)();
|
|
8
|
+
const k1 = (0, keys_1.newKeys)();
|
|
9
|
+
const k2 = (0, keys_1.newKeys)();
|
|
10
|
+
it("parses the legacy userId::secretKey form and derives the legacy db secret", () => {
|
|
11
|
+
const record = (0, host_credentials_1.parseCredentialRecord)(`${userId}::${k1.secretKey}`);
|
|
12
|
+
expect(record).toEqual({
|
|
13
|
+
userId,
|
|
14
|
+
secretKey: k1.secretKey,
|
|
15
|
+
dbSecret: (0, keys_1.hashValue)(k1.secretKey),
|
|
16
|
+
});
|
|
17
|
+
expect((0, host_credentials_1.legacyDbSecret)(k1.secretKey)).toBe((0, keys_1.hashValue)(k1.secretKey));
|
|
18
|
+
});
|
|
19
|
+
it("parses the JSON form, filling a missing dbSecret with the legacy value", () => {
|
|
20
|
+
expect((0, host_credentials_1.parseCredentialRecord)(JSON.stringify({ userId, secretKey: k1.secretKey }))).toEqual({
|
|
21
|
+
userId,
|
|
22
|
+
secretKey: k1.secretKey,
|
|
23
|
+
dbSecret: (0, keys_1.hashValue)(k1.secretKey),
|
|
24
|
+
});
|
|
25
|
+
const full = {
|
|
26
|
+
userId,
|
|
27
|
+
secretKey: k2.secretKey,
|
|
28
|
+
dbSecret: "db",
|
|
29
|
+
previousPublicKeys: [{ publicKey: k1.publicKey, replacedAt: "2026-10-02T00:00:00.000Z" }],
|
|
30
|
+
};
|
|
31
|
+
expect((0, host_credentials_1.parseCredentialRecord)((0, host_credentials_1.serializeCredentialRecord)(full))).toEqual(full);
|
|
32
|
+
});
|
|
33
|
+
it("returns null for garbage", () => {
|
|
34
|
+
expect((0, host_credentials_1.parseCredentialRecord)(null)).toBeNull();
|
|
35
|
+
expect((0, host_credentials_1.parseCredentialRecord)("")).toBeNull();
|
|
36
|
+
expect((0, host_credentials_1.parseCredentialRecord)("{not json")).toBeNull();
|
|
37
|
+
expect((0, host_credentials_1.parseCredentialRecord)(JSON.stringify({ userId }))).toBeNull();
|
|
38
|
+
expect((0, host_credentials_1.parseCredentialRecord)("::")).toBeNull();
|
|
39
|
+
expect((0, host_credentials_1.parseCredentialRecord)("nosep")).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it("mints a random dbSecret only when asked", () => {
|
|
42
|
+
const fresh = (0, host_credentials_1.normalizeCredentialRecord)({ userId, secretKey: k1.secretKey }, { freshDbSecret: true });
|
|
43
|
+
expect(fresh.dbSecret).not.toBe((0, keys_1.hashValue)(k1.secretKey));
|
|
44
|
+
expect(fresh.dbSecret.length).toBeGreaterThan(30);
|
|
45
|
+
const kept = (0, host_credentials_1.normalizeCredentialRecord)({ userId, secretKey: k1.secretKey, dbSecret: "keep" }, {
|
|
46
|
+
freshDbSecret: true,
|
|
47
|
+
});
|
|
48
|
+
expect(kept.dbSecret).toBe("keep");
|
|
49
|
+
});
|
|
50
|
+
it("rotates the secret while keeping dbSecret and remembering the old key", () => {
|
|
51
|
+
const current = (0, host_credentials_1.normalizeCredentialRecord)({ userId, secretKey: k1.secretKey });
|
|
52
|
+
const at = new Date("2026-10-03T00:00:00.000Z");
|
|
53
|
+
const rotated = (0, host_credentials_1.rotateCredentialRecord)(current, {
|
|
54
|
+
secretKey: k2.secretKey,
|
|
55
|
+
oldPublicKey: k1.publicKey,
|
|
56
|
+
reason: "compromised",
|
|
57
|
+
at,
|
|
58
|
+
});
|
|
59
|
+
expect(rotated).toEqual({
|
|
60
|
+
userId,
|
|
61
|
+
secretKey: k2.secretKey,
|
|
62
|
+
dbSecret: (0, keys_1.hashValue)(k1.secretKey),
|
|
63
|
+
previousPublicKeys: [
|
|
64
|
+
{ publicKey: k1.publicKey, replacedAt: at.toISOString(), reason: "compromised" },
|
|
65
|
+
],
|
|
66
|
+
rewrapFrom: { publicKey: k1.publicKey, secretKey: k1.secretKey },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
it("keeps the retired secret through storage until the re-wrap is finished", () => {
|
|
70
|
+
// Secrets in the personal database are re-wrapped one by one after the new key is stored.
|
|
71
|
+
// A crash in between must not lose the only key that can still open the rest.
|
|
72
|
+
const rotated = (0, host_credentials_1.rotateCredentialRecord)((0, host_credentials_1.normalizeCredentialRecord)({ userId, secretKey: k1.secretKey }), { secretKey: k2.secretKey, oldPublicKey: k1.publicKey });
|
|
73
|
+
const stored = (0, host_credentials_1.parseCredentialRecord)((0, host_credentials_1.serializeCredentialRecord)(rotated));
|
|
74
|
+
expect(stored?.rewrapFrom).toEqual({ publicKey: k1.publicKey, secretKey: k1.secretKey });
|
|
75
|
+
expect(stored?.secretKey).toBe(k2.secretKey);
|
|
76
|
+
const finished = (0, host_credentials_1.completeCredentialRewrap)(rotated);
|
|
77
|
+
expect(finished.rewrapFrom).toBeUndefined();
|
|
78
|
+
expect(finished.secretKey).toBe(k2.secretKey);
|
|
79
|
+
expect(finished.previousPublicKeys).toEqual(rotated.previousPublicKeys);
|
|
80
|
+
expect((0, host_credentials_1.parseCredentialRecord)((0, host_credentials_1.serializeCredentialRecord)(finished))?.rewrapFrom).toBeUndefined();
|
|
81
|
+
// A half-written slot is not a key to re-wrap from.
|
|
82
|
+
expect((0, host_credentials_1.parseCredentialRecord)(JSON.stringify({
|
|
83
|
+
userId,
|
|
84
|
+
secretKey: k2.secretKey,
|
|
85
|
+
rewrapFrom: { publicKey: k1.publicKey },
|
|
86
|
+
}))?.rewrapFrom).toBeUndefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ISignedObject } from "../keys";
|
|
2
|
+
/** Purpose string the current key signs to bind a recovery email. */
|
|
3
|
+
export declare const ACCOUNT_EMAIL_BIND_PURPOSE: "peers-account-email-bind";
|
|
4
|
+
/** Statement the current key signs so the registry can bind a recovery email. */
|
|
5
|
+
export interface IAccountEmailBindStatement {
|
|
6
|
+
purpose: typeof ACCOUNT_EMAIL_BIND_PURPOSE;
|
|
7
|
+
userId: string;
|
|
8
|
+
email: string;
|
|
9
|
+
issuedAt: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Signs a request to bind `email` to `userId` with the key on record.
|
|
13
|
+
*
|
|
14
|
+
* @param statement The user, the mailbox, and when the request was made
|
|
15
|
+
* @param secretKey Secret half of the key on record
|
|
16
|
+
*/
|
|
17
|
+
export declare function signEmailBind(statement: IAccountEmailBindStatement, secretKey: string): ISignedObject<IAccountEmailBindStatement>;
|
|
18
|
+
/**
|
|
19
|
+
* Checks that `signed` is the record key's signature over a bind statement for this user
|
|
20
|
+
* and mailbox. A different signer, user, or email is not a bind.
|
|
21
|
+
*/
|
|
22
|
+
export declare function verifyEmailBind(params: {
|
|
23
|
+
signed: ISignedObject<IAccountEmailBindStatement>;
|
|
24
|
+
userId: string;
|
|
25
|
+
email: string;
|
|
26
|
+
recordPublicKey: string;
|
|
27
|
+
}): boolean;
|
|
28
|
+
/** Purpose string the current key signs to read whether a recovery email is bound. */
|
|
29
|
+
export declare const ACCOUNT_EMAIL_STATUS_PURPOSE: "peers-account-email-status";
|
|
30
|
+
/** Statement the current key signs to read the bound recovery email. */
|
|
31
|
+
export interface IAccountEmailStatusStatement {
|
|
32
|
+
purpose: typeof ACCOUNT_EMAIL_STATUS_PURPOSE;
|
|
33
|
+
userId: string;
|
|
34
|
+
issuedAt: string;
|
|
35
|
+
}
|
|
36
|
+
/** Signs a request to read the recovery email bound to `userId`. */
|
|
37
|
+
export declare function signEmailStatus(statement: IAccountEmailStatusStatement, secretKey: string): ISignedObject<IAccountEmailStatusStatement>;
|
|
38
|
+
/** Checks that `signed` is the record key asking for this user's recovery email. */
|
|
39
|
+
export declare function verifyEmailStatus(params: {
|
|
40
|
+
signed: ISignedObject<IAccountEmailStatusStatement>;
|
|
41
|
+
userId: string;
|
|
42
|
+
recordPublicKey: string;
|
|
43
|
+
}): boolean;
|
|
44
|
+
/** Purpose string the current key signs to remove a recovery email. */
|
|
45
|
+
export declare const ACCOUNT_EMAIL_UNBIND_PURPOSE: "peers-account-email-unbind";
|
|
46
|
+
/** Statement the current key signs to remove the recovery email. */
|
|
47
|
+
export interface IAccountEmailUnbindStatement {
|
|
48
|
+
purpose: typeof ACCOUNT_EMAIL_UNBIND_PURPOSE;
|
|
49
|
+
userId: string;
|
|
50
|
+
issuedAt: string;
|
|
51
|
+
}
|
|
52
|
+
/** Signs a request to remove the recovery email bound to `userId`. */
|
|
53
|
+
export declare function signEmailUnbind(statement: IAccountEmailUnbindStatement, secretKey: string): ISignedObject<IAccountEmailUnbindStatement>;
|
|
54
|
+
/** Checks that `signed` is the record key removing this user's recovery email. */
|
|
55
|
+
export declare function verifyEmailUnbind(params: {
|
|
56
|
+
signed: ISignedObject<IAccountEmailUnbindStatement>;
|
|
57
|
+
userId: string;
|
|
58
|
+
recordPublicKey: string;
|
|
59
|
+
}): boolean;
|