@learncard/sss-key-manager 0.1.24 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -95
- package/dist/api-client.d.ts +44 -7
- package/dist/api-client.d.ts.map +1 -1
- package/dist/atomic-operations.d.ts +10 -2
- package/dist/atomic-operations.d.ts.map +1 -1
- package/dist/email-relay-crypto.d.ts +49 -0
- package/dist/email-relay-crypto.d.ts.map +1 -0
- package/dist/escrow-attestation.d.ts +8 -0
- package/dist/escrow-attestation.d.ts.map +1 -0
- package/dist/escrow-crypto.d.ts +52 -0
- package/dist/escrow-crypto.d.ts.map +1 -0
- package/dist/escrow-pin.d.ts +22 -0
- package/dist/escrow-pin.d.ts.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/key-manager.d.ts +2 -0
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/qr-login.d.ts.map +1 -1
- package/dist/recovery-phrase.d.ts +6 -0
- package/dist/recovery-phrase.d.ts.map +1 -1
- package/dist/sss-key-manager.cjs.development.js +2891 -355
- package/dist/sss-key-manager.cjs.development.js.map +4 -4
- package/dist/sss-key-manager.cjs.production.min.js +33 -33
- package/dist/sss-key-manager.cjs.production.min.js.map +4 -4
- package/dist/sss-key-manager.esm.js +2891 -355
- package/dist/sss-key-manager.esm.js.map +4 -4
- package/dist/sss-strategy.d.ts +74 -4
- package/dist/sss-strategy.d.ts.map +1 -1
- package/dist/storage.d.ts +1 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/types.d.ts +87 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api-client.test.ts +122 -0
- package/src/api-client.ts +107 -116
- package/src/atomic-operations.test.ts +56 -16
- package/src/atomic-operations.ts +55 -19
- package/src/critical-paths.test.ts +51 -1
- package/src/email-relay-crypto.test.ts +87 -0
- package/src/email-relay-crypto.ts +293 -0
- package/src/escrow-attestation.test.ts +85 -0
- package/src/escrow-attestation.ts +64 -0
- package/src/escrow-crypto.test.ts +381 -0
- package/src/escrow-crypto.ts +319 -0
- package/src/escrow-pin.test.ts +119 -0
- package/src/escrow-pin.ts +83 -0
- package/src/index.ts +63 -1
- package/src/key-manager.ts +4 -9
- package/src/qr-login.ts +22 -15
- package/src/recovery-phrase.ts +40 -0
- package/src/sss-session-writes.test.ts +109 -0
- package/src/sss-strategy.test.ts +2642 -110
- package/src/sss-strategy.ts +2186 -224
- package/src/storage.test.ts +66 -8
- package/src/storage.ts +41 -29
- package/src/types.ts +107 -6
package/src/storage.test.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import 'fake-indexeddb/auto';
|
|
16
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
16
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
storeDeviceShare,
|
|
@@ -25,6 +25,11 @@ import {
|
|
|
25
25
|
storeShareVersion,
|
|
26
26
|
getShareVersion,
|
|
27
27
|
} from './storage';
|
|
28
|
+
import {
|
|
29
|
+
createSSSStrategy,
|
|
30
|
+
readPendingShareCandidates,
|
|
31
|
+
writePendingShareCandidates,
|
|
32
|
+
} from './sss-strategy';
|
|
28
33
|
|
|
29
34
|
// ---------------------------------------------------------------------------
|
|
30
35
|
// Helpers
|
|
@@ -249,7 +254,7 @@ describe('SSS Storage', () => {
|
|
|
249
254
|
expect(await getShareVersion(id)).toBeNull();
|
|
250
255
|
});
|
|
251
256
|
|
|
252
|
-
it(
|
|
257
|
+
it("deleting one user does not affect another user's version", async () => {
|
|
253
258
|
const idA = 'sss-device-share:user-a';
|
|
254
259
|
const idB = 'sss-device-share:user-b';
|
|
255
260
|
|
|
@@ -380,7 +385,10 @@ describe('SSS Storage', () => {
|
|
|
380
385
|
const t = db.transaction('shares', 'readwrite');
|
|
381
386
|
const s = t.objectStore('shares');
|
|
382
387
|
// Store garbage data that will fail AES-GCM decryption
|
|
383
|
-
const req = s.put(
|
|
388
|
+
const req = s.put(
|
|
389
|
+
{ version: 1, iv: 'bad', cipher: 'bad', keyVersion: 1 },
|
|
390
|
+
orphanId
|
|
391
|
+
);
|
|
384
392
|
req.onsuccess = () => resolve();
|
|
385
393
|
req.onerror = () => reject(req.error);
|
|
386
394
|
});
|
|
@@ -427,11 +435,7 @@ describe('SSS Storage', () => {
|
|
|
427
435
|
// Session-only storage (public computer mode)
|
|
428
436
|
// ---------------------------------------------------------------------------
|
|
429
437
|
|
|
430
|
-
import {
|
|
431
|
-
isPublicComputerMode,
|
|
432
|
-
setPublicComputerMode,
|
|
433
|
-
createAdaptiveStorage,
|
|
434
|
-
} from './storage';
|
|
438
|
+
import { isPublicComputerMode, setPublicComputerMode, createAdaptiveStorage } from './storage';
|
|
435
439
|
|
|
436
440
|
describe('Session-only storage (public computer mode)', () => {
|
|
437
441
|
beforeEach(() => {
|
|
@@ -528,3 +532,57 @@ describe('Session-only storage (public computer mode)', () => {
|
|
|
528
532
|
});
|
|
529
533
|
});
|
|
530
534
|
});
|
|
535
|
+
|
|
536
|
+
describe.each([false, true])(
|
|
537
|
+
'SSS automatic cleanup with adaptive storage (public mode=%s)',
|
|
538
|
+
publicMode => {
|
|
539
|
+
beforeEach(async () => {
|
|
540
|
+
sessionStorage.clear();
|
|
541
|
+
await clearAllShares();
|
|
542
|
+
setPublicComputerMode(publicMode);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
afterEach(async () => {
|
|
546
|
+
sessionStorage.clear();
|
|
547
|
+
await clearAllShares();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('preserves scoped and unscoped pending writes while clearing stale main shares', async () => {
|
|
551
|
+
const storage = createAdaptiveStorage();
|
|
552
|
+
const scoped = createSSSStrategy({ serverUrl: 'https://unused.test', storage });
|
|
553
|
+
const accountId = 'sss-device-share:account-a';
|
|
554
|
+
const otherId = 'sss-device-share:account-b';
|
|
555
|
+
const scopedPending = [{ share: 'pending-a', createdAt: Date.now() }];
|
|
556
|
+
const legacyPending = [{ share: 'pending-legacy', createdAt: Date.now() }];
|
|
557
|
+
const otherPending = [{ share: 'pending-b', createdAt: Date.now() }];
|
|
558
|
+
|
|
559
|
+
scoped.setActiveUser!('account-a');
|
|
560
|
+
await scoped.storeLocalKey('stale-a');
|
|
561
|
+
await scoped.storeLocalShareVersion!(3);
|
|
562
|
+
await writePendingShareCandidates(storage, scopedPending, accountId);
|
|
563
|
+
await storage.storeDeviceShare('share-b', otherId);
|
|
564
|
+
await storage.storeShareVersion(7, otherId);
|
|
565
|
+
await writePendingShareCandidates(storage, otherPending, otherId);
|
|
566
|
+
|
|
567
|
+
await scoped.clearLocalKeys({ preservePending: true });
|
|
568
|
+
expect(await storage.getDeviceShare(accountId)).toBeNull();
|
|
569
|
+
expect(await storage.getShareVersion(accountId)).toBeNull();
|
|
570
|
+
expect(await readPendingShareCandidates(storage, accountId)).toEqual(scopedPending);
|
|
571
|
+
expect(await scoped.getLocalKey()).toBe('pending-a');
|
|
572
|
+
|
|
573
|
+
const legacy = createSSSStrategy({ serverUrl: 'https://unused.test', storage });
|
|
574
|
+
await legacy.storeLocalKey('stale-legacy');
|
|
575
|
+
await legacy.storeLocalShareVersion!(2);
|
|
576
|
+
await writePendingShareCandidates(storage, legacyPending);
|
|
577
|
+
await legacy.clearLocalKeys({ preservePending: true });
|
|
578
|
+
expect(await storage.getDeviceShare()).toBeNull();
|
|
579
|
+
expect(await storage.getShareVersion()).toBeNull();
|
|
580
|
+
expect(await readPendingShareCandidates(storage)).toEqual(legacyPending);
|
|
581
|
+
expect(await legacy.getLocalKey()).toBe('pending-legacy');
|
|
582
|
+
|
|
583
|
+
expect(await storage.getDeviceShare(otherId)).toBe('share-b');
|
|
584
|
+
expect(await storage.getShareVersion(otherId)).toBe(7);
|
|
585
|
+
expect(await readPendingShareCandidates(storage, otherId)).toEqual(otherPending);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
);
|
package/src/storage.ts
CHANGED
|
@@ -70,11 +70,10 @@ async function getOrCreateMasterKey(): Promise<CryptoKey> {
|
|
|
70
70
|
|
|
71
71
|
if (existing) return existing;
|
|
72
72
|
|
|
73
|
-
const key = await crypto.subtle.generateKey(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
73
|
+
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, [
|
|
74
|
+
'encrypt',
|
|
75
|
+
'decrypt',
|
|
76
|
+
]);
|
|
78
77
|
|
|
79
78
|
await tx(db, KEYS_STORE, 'readwrite', s => s.put(key, 'master-key'));
|
|
80
79
|
|
|
@@ -145,7 +144,10 @@ async function decryptShare(payload: EncryptedPayload, id: string): Promise<stri
|
|
|
145
144
|
return new TextDecoder().decode(plainBuffer);
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
export async function storeDeviceShare(
|
|
147
|
+
export async function storeDeviceShare(
|
|
148
|
+
share: string,
|
|
149
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
150
|
+
): Promise<void> {
|
|
149
151
|
const db = await openDB();
|
|
150
152
|
|
|
151
153
|
try {
|
|
@@ -160,7 +162,10 @@ export async function storeDeviceShare(share: string, id: string = DEFAULT_DEVIC
|
|
|
160
162
|
* Store the share version alongside a device share.
|
|
161
163
|
* Stored as a separate key `{id}:version` to avoid changing the encryption format.
|
|
162
164
|
*/
|
|
163
|
-
export async function storeShareVersion(
|
|
165
|
+
export async function storeShareVersion(
|
|
166
|
+
version: number,
|
|
167
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
168
|
+
): Promise<void> {
|
|
164
169
|
const db = await openDB();
|
|
165
170
|
|
|
166
171
|
try {
|
|
@@ -174,7 +179,9 @@ export async function storeShareVersion(version: number, id: string = DEFAULT_DE
|
|
|
174
179
|
* Retrieve the share version for a device share.
|
|
175
180
|
* Returns null if no version is stored (legacy shares).
|
|
176
181
|
*/
|
|
177
|
-
export async function getShareVersion(
|
|
182
|
+
export async function getShareVersion(
|
|
183
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
184
|
+
): Promise<number | null> {
|
|
178
185
|
const db = await openDB();
|
|
179
186
|
|
|
180
187
|
try {
|
|
@@ -192,9 +199,7 @@ export async function getDeviceShare(id: string = DEFAULT_DEVICE_SHARE_ID): Prom
|
|
|
192
199
|
const db = await openDB();
|
|
193
200
|
|
|
194
201
|
try {
|
|
195
|
-
const raw = await tx<unknown>(db, SHARES_STORE, 'readonly', s =>
|
|
196
|
-
s.get(id)
|
|
197
|
-
);
|
|
202
|
+
const raw = await tx<unknown>(db, SHARES_STORE, 'readonly', s => s.get(id));
|
|
198
203
|
|
|
199
204
|
if (raw == null) {
|
|
200
205
|
return null;
|
|
@@ -288,7 +293,9 @@ export async function listAllDeviceShares(): Promise<DeviceShareEntry[]> {
|
|
|
288
293
|
// Orphaned entry — value exists but can't be decrypted.
|
|
289
294
|
// Auto-clean it (and its companion :version key) to prevent
|
|
290
295
|
// phantom "(decrypt failed)" entries from accumulating.
|
|
291
|
-
console.warn(
|
|
296
|
+
console.warn(
|
|
297
|
+
`SSS Storage: removing orphaned entry that failed to decrypt: ${id}`
|
|
298
|
+
);
|
|
292
299
|
await deleteDeviceShare(id);
|
|
293
300
|
continue;
|
|
294
301
|
}
|
|
@@ -321,8 +328,10 @@ const SESSION_PREFIX = 'sss:';
|
|
|
321
328
|
|
|
322
329
|
export function isPublicComputerMode(): boolean {
|
|
323
330
|
try {
|
|
324
|
-
return
|
|
325
|
-
sessionStorage
|
|
331
|
+
return (
|
|
332
|
+
typeof sessionStorage !== 'undefined' &&
|
|
333
|
+
sessionStorage.getItem('lc-session-mode') === 'public'
|
|
334
|
+
);
|
|
326
335
|
} catch {
|
|
327
336
|
return false;
|
|
328
337
|
}
|
|
@@ -340,7 +349,10 @@ export function setPublicComputerMode(enabled: boolean): void {
|
|
|
340
349
|
}
|
|
341
350
|
}
|
|
342
351
|
|
|
343
|
-
async function sessionStoreDeviceShare(
|
|
352
|
+
async function sessionStoreDeviceShare(
|
|
353
|
+
share: string,
|
|
354
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
355
|
+
): Promise<void> {
|
|
344
356
|
sessionStorage.setItem(`${SESSION_PREFIX}${id}`, share);
|
|
345
357
|
}
|
|
346
358
|
|
|
@@ -357,11 +369,16 @@ async function sessionDeleteDeviceShare(id: string = DEFAULT_DEVICE_SHARE_ID): P
|
|
|
357
369
|
sessionStorage.removeItem(`${SESSION_PREFIX}${id}:version`);
|
|
358
370
|
}
|
|
359
371
|
|
|
360
|
-
async function sessionStoreShareVersion(
|
|
372
|
+
async function sessionStoreShareVersion(
|
|
373
|
+
version: number,
|
|
374
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
375
|
+
): Promise<void> {
|
|
361
376
|
sessionStorage.setItem(`${SESSION_PREFIX}${id}:version`, String(version));
|
|
362
377
|
}
|
|
363
378
|
|
|
364
|
-
async function sessionGetShareVersion(
|
|
379
|
+
async function sessionGetShareVersion(
|
|
380
|
+
id: string = DEFAULT_DEVICE_SHARE_ID
|
|
381
|
+
): Promise<number | null> {
|
|
365
382
|
const raw = sessionStorage.getItem(`${SESSION_PREFIX}${id}:version`);
|
|
366
383
|
|
|
367
384
|
return raw !== null ? Number(raw) : null;
|
|
@@ -400,19 +417,16 @@ export function createAdaptiveStorage() {
|
|
|
400
417
|
: storeDeviceShare(share, id),
|
|
401
418
|
|
|
402
419
|
getDeviceShare: (id?: string) =>
|
|
403
|
-
isPublicComputerMode()
|
|
404
|
-
? sessionGetDeviceShare(id)
|
|
405
|
-
: getDeviceShare(id),
|
|
420
|
+
isPublicComputerMode() ? sessionGetDeviceShare(id) : getDeviceShare(id),
|
|
406
421
|
|
|
407
422
|
hasDeviceShare: (id?: string) =>
|
|
408
|
-
isPublicComputerMode()
|
|
409
|
-
? sessionHasDeviceShare(id)
|
|
410
|
-
: hasDeviceShare(id),
|
|
423
|
+
isPublicComputerMode() ? sessionHasDeviceShare(id) : hasDeviceShare(id),
|
|
411
424
|
|
|
412
425
|
clearAllShares: (id?: string) =>
|
|
413
|
-
isPublicComputerMode()
|
|
414
|
-
|
|
415
|
-
|
|
426
|
+
isPublicComputerMode() ? sessionClearAllShares(id) : clearAllShares(id),
|
|
427
|
+
|
|
428
|
+
deleteDeviceShare: (id?: string) =>
|
|
429
|
+
isPublicComputerMode() ? sessionDeleteDeviceShare(id) : deleteDeviceShare(id),
|
|
416
430
|
|
|
417
431
|
storeShareVersion: (version: number, id?: string) =>
|
|
418
432
|
isPublicComputerMode()
|
|
@@ -420,9 +434,7 @@ export function createAdaptiveStorage() {
|
|
|
420
434
|
: storeShareVersion(version, id),
|
|
421
435
|
|
|
422
436
|
getShareVersion: (id?: string) =>
|
|
423
|
-
isPublicComputerMode()
|
|
424
|
-
? sessionGetShareVersion(id)
|
|
425
|
-
: getShareVersion(id),
|
|
437
|
+
isPublicComputerMode() ? sessionGetShareVersion(id) : getShareVersion(id),
|
|
426
438
|
};
|
|
427
439
|
}
|
|
428
440
|
|
package/src/types.ts
CHANGED
|
@@ -16,11 +16,23 @@ export {
|
|
|
16
16
|
type AuthProvider,
|
|
17
17
|
type RecoveryMethodInfo,
|
|
18
18
|
type RecoveryResult,
|
|
19
|
+
type IdentityRecoverySession,
|
|
19
20
|
type ServerKeyStatus,
|
|
21
|
+
type SssActivationState,
|
|
20
22
|
type KeyDerivationStrategy,
|
|
23
|
+
type DidAuthVpSigner,
|
|
24
|
+
type EscrowEnrollmentOptions,
|
|
25
|
+
type EscrowEnrollmentState,
|
|
26
|
+
type EscrowPinStatus,
|
|
21
27
|
} from '@learncard/types';
|
|
22
28
|
|
|
23
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
AuthProvider,
|
|
31
|
+
AuthProviderType,
|
|
32
|
+
KeyDerivationStrategy,
|
|
33
|
+
RecoveryMethodInfo,
|
|
34
|
+
SssActivationState,
|
|
35
|
+
} from '@learncard/types';
|
|
24
36
|
|
|
25
37
|
// ---------------------------------------------------------------------------
|
|
26
38
|
// SSS-specific: Contact & auth provider mapping
|
|
@@ -54,7 +66,70 @@ export const SecurityLevels: readonly SecurityLevel[] = ['basic', 'enhanced', 'a
|
|
|
54
66
|
* SSS recovery method type identifiers.
|
|
55
67
|
* These are the specific recovery methods supported by the SSS strategy.
|
|
56
68
|
*/
|
|
57
|
-
export type RecoveryMethodType = 'passkey' | 'backup' | 'phrase' | 'email';
|
|
69
|
+
export type RecoveryMethodType = 'passkey' | 'backup' | 'phrase' | 'email' | 'escrow';
|
|
70
|
+
|
|
71
|
+
/** Trust policy for the enclave that receives recovery material. */
|
|
72
|
+
export type EscrowAttestationPolicy =
|
|
73
|
+
| { mode: 'software'; pinnedPublicKeys: string[] }
|
|
74
|
+
| { mode: 'nitro'; pinnedMeasurements: { imageSha384: string }[]; rootCertificatePem?: string };
|
|
75
|
+
|
|
76
|
+
/** Public status of an escrow recovery waiting period. */
|
|
77
|
+
export interface EscrowHoldStatus {
|
|
78
|
+
holdId: string;
|
|
79
|
+
releasePolicy: 'hold' | 'pin';
|
|
80
|
+
status: 'pending' | 'cancelled' | 'completed' | 'expired';
|
|
81
|
+
requestedAt: string;
|
|
82
|
+
releaseAfter: string;
|
|
83
|
+
cancelledAt?: string;
|
|
84
|
+
completedAt?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Persist these secrets securely until completion; a null token means an existing hold. */
|
|
88
|
+
export type EscrowRecoveryStart = EscrowHoldStatus & {
|
|
89
|
+
resumeToken: string | null;
|
|
90
|
+
clientEphemeralPrivateKey: string;
|
|
91
|
+
pinSalt?: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** The account has no available PIN release policy. */
|
|
95
|
+
export class EscrowPinUnavailableError extends Error {
|
|
96
|
+
constructor() {
|
|
97
|
+
super('PIN recovery is not available for this account.');
|
|
98
|
+
this.name = 'EscrowPinUnavailableError';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A failed PIN attempt consumes its hold; another attempt must start a fresh hold. */
|
|
103
|
+
export class EscrowPinMismatchError extends Error {
|
|
104
|
+
constructor(public readonly attemptsRemaining: number) {
|
|
105
|
+
super('Incorrect PIN.');
|
|
106
|
+
this.name = 'EscrowPinMismatchError';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Temporary throttle: retry after cooldown with a fresh hold, never retry /complete. */
|
|
111
|
+
export class EscrowPinThrottledError extends Error {
|
|
112
|
+
constructor() {
|
|
113
|
+
super('Too many tries right now. Wait a minute and try again.');
|
|
114
|
+
this.name = 'EscrowPinThrottledError';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A pending waiting-period request is too recent to restart. */
|
|
119
|
+
export class EscrowHoldRestartThrottledError extends Error {
|
|
120
|
+
constructor(public readonly retryAfter?: string) {
|
|
121
|
+
super('A recovery request was started recently. Please wait before restarting.');
|
|
122
|
+
this.name = 'EscrowHoldRestartThrottledError';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** PIN recovery is exhausted; delayed escrow recovery remains available. */
|
|
127
|
+
export class EscrowPinLockedError extends Error {
|
|
128
|
+
constructor() {
|
|
129
|
+
super('Too many incorrect PIN attempts. You can still recover by waiting.');
|
|
130
|
+
this.name = 'EscrowPinLockedError';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
58
133
|
|
|
59
134
|
export interface PasskeyRecoveryMethod {
|
|
60
135
|
type: 'passkey';
|
|
@@ -73,12 +148,15 @@ export interface RecoveryPhraseRecoveryMethod {
|
|
|
73
148
|
}
|
|
74
149
|
|
|
75
150
|
/** @deprecated Use RecoveryInput instead. Kept for legacy SSSKeyManager class. */
|
|
76
|
-
export type RecoveryMethod =
|
|
151
|
+
export type RecoveryMethod =
|
|
152
|
+
PasskeyRecoveryMethod | BackupFileRecoveryMethod | RecoveryPhraseRecoveryMethod;
|
|
77
153
|
|
|
78
154
|
/**
|
|
79
155
|
* SSS-specific recovery input — what the user provides to recover their key.
|
|
80
156
|
*/
|
|
81
157
|
export type RecoveryInput =
|
|
158
|
+
| { method: 'escrow-pin'; pin: string }
|
|
159
|
+
| { method: 'escrow'; holdId: string; resumeToken: string; clientEphemeralPrivateKey: string }
|
|
82
160
|
| { method: 'passkey'; credentialId: string }
|
|
83
161
|
| { method: 'phrase'; phrase: string }
|
|
84
162
|
| { method: 'backup'; fileContents: string; password: string }
|
|
@@ -88,20 +166,34 @@ export type RecoveryInput =
|
|
|
88
166
|
* SSS-specific recovery setup input — what the user provides to set up a method.
|
|
89
167
|
*/
|
|
90
168
|
export type RecoverySetupInput =
|
|
169
|
+
| { method: 'escrow'; pin?: string }
|
|
91
170
|
| { method: 'passkey' }
|
|
92
171
|
| { method: 'phrase' }
|
|
93
172
|
| { method: 'backup'; password: string; did: string }
|
|
94
|
-
| { method: 'email' };
|
|
173
|
+
| { method: 'email'; email: string };
|
|
95
174
|
|
|
96
175
|
/**
|
|
97
176
|
* SSS-specific recovery setup result.
|
|
98
177
|
*/
|
|
99
178
|
export type RecoverySetupResult =
|
|
179
|
+
| { method: 'escrow'; shareVersion: number }
|
|
100
180
|
| { method: 'passkey'; credentialId: string }
|
|
101
|
-
| {
|
|
181
|
+
| {
|
|
182
|
+
method: 'phrase';
|
|
183
|
+
phrase: string;
|
|
184
|
+
challengeWordIndices: number[];
|
|
185
|
+
/** Shuffled choices per challenged word (the answer plus decoys not in the phrase). */
|
|
186
|
+
challengeWordOptions: string[][];
|
|
187
|
+
}
|
|
102
188
|
| { method: 'backup'; backupFile: BackupFile }
|
|
103
189
|
| { method: 'email' };
|
|
104
190
|
|
|
191
|
+
/** Proof supplied after a pending recovery method has been created. */
|
|
192
|
+
export type RecoveryConfirmationInput =
|
|
193
|
+
| { method: 'phrase'; challengeWords: string[] }
|
|
194
|
+
| { method: 'backup'; fileContents: string; password: string }
|
|
195
|
+
| { method: 'email'; code: string };
|
|
196
|
+
|
|
105
197
|
// ---------------------------------------------------------------------------
|
|
106
198
|
// SSS-specific: Share & encryption types
|
|
107
199
|
// ---------------------------------------------------------------------------
|
|
@@ -133,6 +225,8 @@ export interface UserKeyRecord {
|
|
|
133
225
|
recoveryMethods: RecoveryMethodInfo[];
|
|
134
226
|
migratedFromWeb3Auth: boolean;
|
|
135
227
|
migratedAt?: Date;
|
|
228
|
+
sssActivationState?: SssActivationState;
|
|
229
|
+
provisionalCreatedAt?: Date;
|
|
136
230
|
createdAt: Date;
|
|
137
231
|
updatedAt: Date;
|
|
138
232
|
}
|
|
@@ -142,6 +236,8 @@ export interface BackupFile {
|
|
|
142
236
|
createdAt: string;
|
|
143
237
|
primaryDid: string;
|
|
144
238
|
shareVersion?: number;
|
|
239
|
+
/** SHA-256 checksum of the decrypted share, used to verify a just-written file. */
|
|
240
|
+
shareChecksum?: string;
|
|
145
241
|
encryptedShare: {
|
|
146
242
|
ciphertext: string;
|
|
147
243
|
iv: string;
|
|
@@ -173,7 +269,12 @@ export interface SSSKeyManagerConfig {
|
|
|
173
269
|
* The SSS key derivation strategy — a KeyDerivationStrategy narrowed
|
|
174
270
|
* with SSS-specific recovery input/output types.
|
|
175
271
|
*/
|
|
176
|
-
export type SSSKeyDerivationStrategy = KeyDerivationStrategy<
|
|
272
|
+
export type SSSKeyDerivationStrategy = KeyDerivationStrategy<
|
|
273
|
+
RecoveryInput,
|
|
274
|
+
RecoverySetupInput,
|
|
275
|
+
RecoverySetupResult,
|
|
276
|
+
RecoveryConfirmationInput
|
|
277
|
+
>;
|
|
177
278
|
|
|
178
279
|
// ---------------------------------------------------------------------------
|
|
179
280
|
// Legacy types (deprecated)
|