@learncard/sss-key-manager 0.2.5 → 0.2.7
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/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/sss-key-manager.cjs.development.js +603 -134
- package/dist/sss-key-manager.cjs.development.js.map +3 -3
- package/dist/sss-key-manager.cjs.production.min.js +32 -32
- package/dist/sss-key-manager.cjs.production.min.js.map +3 -3
- package/dist/sss-key-manager.esm.js +603 -134
- package/dist/sss-key-manager.esm.js.map +3 -3
- package/dist/sss-strategy.d.ts.map +1 -1
- package/dist/types.d.ts +39 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +8 -1
- package/src/sss-authority.test.ts +294 -1
- package/src/sss-strategy.test.ts +875 -8
- package/src/sss-strategy.ts +869 -159
- package/src/types.ts +49 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sss-strategy.d.ts","sourceRoot":"","sources":["../src/sss-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACR,wBAAwB,EAaxB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"sss-strategy.d.ts","sourceRoot":"","sources":["../src/sss-strategy.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Re-exports provider-agnostic interfaces from @learncard/types
|
|
5
5
|
* and defines SSS-specific types (recovery shapes, backup files, etc.).
|
|
6
6
|
*/
|
|
7
|
-
export { AuthSessionError, type AuthProviderType, type AuthUser, type AuthProvider, type RecoveryMethodInfo, type RecoveryResult, type IdentityRecoverySession, type ServerKeyStatus, type SssActivationState, type KeyDerivationStrategy, type DidAuthVpSigner, type EscrowEnrollmentOptions, type EscrowEnrollmentState, type EscrowBlobStaleReason, type EscrowPinStatus, } from '@learncard/types';
|
|
7
|
+
export { AuthSessionError, type AuthProviderType, type AuthUser, type AuthProvider, type RecoveryMethodInfo, type RecoveryResult, type RevokeOtherDevicesResult, type IdentityRecoverySession, type ServerKeyStatus, type SssActivationState, type KeyDerivationStrategy, type DidAuthVpSigner, type EscrowEnrollmentOptions, type EscrowEnrollmentState, type EscrowBlobStaleReason, type EscrowPinStatus, } from '@learncard/types';
|
|
8
8
|
import type { AuthProvider, AuthProviderType, KeyDerivationStrategy, RecoveryMethodInfo, SssActivationState } from '@learncard/types';
|
|
9
9
|
export type ContactMethodType = 'email' | 'phone';
|
|
10
10
|
export interface ContactMethod {
|
|
@@ -78,6 +78,28 @@ export type EscrowRecoveryStart = EscrowHoldStatus & {
|
|
|
78
78
|
export declare class EscrowPinUnavailableError extends Error {
|
|
79
79
|
constructor();
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* What this client saw during one PIN sign-in flow (own-typos rule, E). A flow starts
|
|
83
|
+
* with the first PIN status read before the first attempt and ends when the sign-in
|
|
84
|
+
* completes; a new modal or app start begins a new flow.
|
|
85
|
+
*/
|
|
86
|
+
export interface EscrowPinAttemptContext {
|
|
87
|
+
/**
|
|
88
|
+
* Verified wrong entries recorded against the PIN when the flow started, before
|
|
89
|
+
* any attempt here. Undefined when the server could not say (legacy record, failed
|
|
90
|
+
* read): treated as "wrong entries recorded".
|
|
91
|
+
*/
|
|
92
|
+
priorWrongAttempts?: number;
|
|
93
|
+
/** Wrong entries this client itself submitted in this flow (mismatch responses). */
|
|
94
|
+
ownWrongAttempts: number;
|
|
95
|
+
/**
|
|
96
|
+
* Salt of the PIN generation the counts belong to (from the flow's first status).
|
|
97
|
+
* A status read during the release that shows another salt (a new PIN, or a
|
|
98
|
+
* same-PIN re-set elsewhere) means the counts describe a different PIN, so they
|
|
99
|
+
* are ignored and the stricter #1905 rule applies.
|
|
100
|
+
*/
|
|
101
|
+
pinSalt?: string;
|
|
102
|
+
}
|
|
81
103
|
/** A failed PIN attempt consumes its hold; another attempt must start a fresh hold. */
|
|
82
104
|
export declare class EscrowPinMismatchError extends Error {
|
|
83
105
|
readonly attemptsRemaining: number;
|
|
@@ -96,6 +118,14 @@ export declare class EscrowHoldRestartThrottledError extends Error {
|
|
|
96
118
|
export declare class EscrowPinLockedError extends Error {
|
|
97
119
|
constructor();
|
|
98
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* A PIN sign-in released the key, but the released share version is no longer on the
|
|
123
|
+
* server (another device secured the account or recovered meanwhile). Nothing was
|
|
124
|
+
* stored; a revoke is never silently undone.
|
|
125
|
+
*/
|
|
126
|
+
export declare class PinSignInStaleError extends Error {
|
|
127
|
+
constructor();
|
|
128
|
+
}
|
|
99
129
|
export interface PasskeyRecoveryMethod {
|
|
100
130
|
type: 'passkey';
|
|
101
131
|
credentialId?: string;
|
|
@@ -118,6 +148,14 @@ export type RecoveryInput = {
|
|
|
118
148
|
method: 'escrow-pin';
|
|
119
149
|
pin: string;
|
|
120
150
|
expectedDid?: string;
|
|
151
|
+
/** Verified recovery-email session; set only when no login is bound yet. */
|
|
152
|
+
recoverySessionToken?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Own-typos rule (E): wrong entries this client observed in the current PIN
|
|
155
|
+
* sign-in flow. Omitted by older callers, which keeps the stricter rule (any
|
|
156
|
+
* recorded wrong entry blocks the same-PIN re-set).
|
|
157
|
+
*/
|
|
158
|
+
pinAttempts?: EscrowPinAttemptContext;
|
|
121
159
|
} | {
|
|
122
160
|
method: 'escrow';
|
|
123
161
|
holdId: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/sss-key-manager",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Shamir Secret Sharing key manager for LearnCard - replaces Web3Auth SFA",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/sss-key-manager.esm.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"vitest": "4.1.11"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@learncard/types": "5.24.
|
|
42
|
+
"@learncard/types": "5.24.2",
|
|
43
43
|
"@noble/ed25519": "^2.0.0",
|
|
44
44
|
"@peculiar/x509": "^2.1.0",
|
|
45
45
|
"@scure/bip39": "^1.2.1",
|
package/src/index.ts
CHANGED
|
@@ -108,7 +108,12 @@ export type {
|
|
|
108
108
|
} from './escrow-authority.types';
|
|
109
109
|
export { verifyNitroAttestationDocument, NitroAttestationError } from './escrow-nitro-attestation';
|
|
110
110
|
export type { NitroAttestationReason } from './escrow-nitro-attestation';
|
|
111
|
-
export type {
|
|
111
|
+
export type {
|
|
112
|
+
EscrowAttestationPolicy,
|
|
113
|
+
EscrowHoldStatus,
|
|
114
|
+
EscrowPinAttemptContext,
|
|
115
|
+
EscrowRecoveryStart,
|
|
116
|
+
} from './types';
|
|
112
117
|
export type {
|
|
113
118
|
EscrowEnrollmentOptions,
|
|
114
119
|
EscrowEnrollmentState,
|
|
@@ -121,6 +126,7 @@ export {
|
|
|
121
126
|
EscrowPinThrottledError,
|
|
122
127
|
EscrowHoldRestartThrottledError,
|
|
123
128
|
EscrowPinUnavailableError,
|
|
129
|
+
PinSignInStaleError,
|
|
124
130
|
} from './types';
|
|
125
131
|
export {
|
|
126
132
|
PIN_MIN_LENGTH,
|
|
@@ -234,6 +240,7 @@ export type {
|
|
|
234
240
|
RecoveryMethodInfo,
|
|
235
241
|
RecoveryInput,
|
|
236
242
|
RecoveryResult,
|
|
243
|
+
RevokeOtherDevicesResult,
|
|
237
244
|
RecoverySetupInput,
|
|
238
245
|
RecoverySetupResult,
|
|
239
246
|
RecoveryConfirmationInput,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import type { EscrowEnvelope } from './escrow-crypto';
|
|
21
21
|
import { encodeEscrowEnrollmentIntent, getEscrowBlobIdentity } from './escrow-enrollment-intent';
|
|
22
22
|
import { splitPrivateKey } from './sss';
|
|
23
|
+
import { derivePinProof } from './escrow-pin';
|
|
23
24
|
import type {
|
|
24
25
|
EscrowAuthorityRecord,
|
|
25
26
|
EscrowAuthorityState,
|
|
@@ -151,6 +152,8 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
151
152
|
attemptsUsed: 0,
|
|
152
153
|
};
|
|
153
154
|
let version = 1;
|
|
155
|
+
// lca-api's charged PIN budget: like the server, every PIN release is charged.
|
|
156
|
+
let releaseCharges = 0;
|
|
154
157
|
const recoveryShares = await splitPrivateKey(owner.seedHex);
|
|
155
158
|
let authShare = recoveryShares.authShare;
|
|
156
159
|
let pendingHold: EscrowHoldRecordLike | undefined;
|
|
@@ -180,6 +183,7 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
180
183
|
const calls: string[] = [];
|
|
181
184
|
const nonces: string[] = [];
|
|
182
185
|
const intents: SignedEscrowEnrollmentIntent[] = [];
|
|
186
|
+
const enrollments: Array<{ pinSalt?: unknown; clearPin?: unknown; pinVerifier?: string }> = [];
|
|
183
187
|
const faults: {
|
|
184
188
|
snapshotNonce?: boolean;
|
|
185
189
|
returnedEnvelope?: boolean;
|
|
@@ -199,6 +203,17 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
199
203
|
oldServer?: boolean;
|
|
200
204
|
/** A compromised lca-api forging post-rebind status for a healthy account. */
|
|
201
205
|
forgedRebindStatus?: boolean;
|
|
206
|
+
/** lca-api's verified wrong-entry count for the current PIN, when reported. */
|
|
207
|
+
wrongPinAttempts?: number;
|
|
208
|
+
/**
|
|
209
|
+
* lca-api's escrow copy identity on /keys/auth-share: 'authority' reports the
|
|
210
|
+
* authority's current copy; a string is a stale copy (a lost PIN-operation write).
|
|
211
|
+
*/
|
|
212
|
+
serverBlobIdentity?: 'authority' | string;
|
|
213
|
+
/** lca-api reports a PIN operation holding its lease (`escrowUpdating`). */
|
|
214
|
+
serverUpdating?: boolean;
|
|
215
|
+
/** lca-api refuses the next PIN change (e.g. a concurrent compare): 409. */
|
|
216
|
+
refusePinChange?: boolean;
|
|
202
217
|
} = {};
|
|
203
218
|
const signHold = async (record: EscrowHoldRecordLike): Promise<EscrowHoldRecordLike> => {
|
|
204
219
|
const h = record.hold;
|
|
@@ -265,9 +280,31 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
265
280
|
...(faults.oldServer
|
|
266
281
|
? {}
|
|
267
282
|
: { priorEscrowEnrollment: !enrolled && !!faults.missingPinMetadata }),
|
|
283
|
+
...(faults.serverBlobIdentity === undefined
|
|
284
|
+
? {}
|
|
285
|
+
: {
|
|
286
|
+
escrowBlobIdentity:
|
|
287
|
+
faults.serverBlobIdentity === 'authority'
|
|
288
|
+
? state.envelope &&
|
|
289
|
+
(await getEscrowBlobIdentity(state.envelope))
|
|
290
|
+
: faults.serverBlobIdentity,
|
|
291
|
+
}),
|
|
292
|
+
...(faults.serverUpdating ? { escrowUpdating: true } : {}),
|
|
268
293
|
...(faults.missingPinMetadata
|
|
269
294
|
? { escrowPin: { state: 'none', enabled: false, attemptsRemaining: 0 } }
|
|
270
|
-
:
|
|
295
|
+
: faults.wrongPinAttempts !== undefined
|
|
296
|
+
? {
|
|
297
|
+
escrowPin: {
|
|
298
|
+
state: 'enabled',
|
|
299
|
+
enabled: true,
|
|
300
|
+
attemptsRemaining: Math.max(
|
|
301
|
+
0,
|
|
302
|
+
10 - state.attemptsUsed - releaseCharges
|
|
303
|
+
),
|
|
304
|
+
wrongAttempts: faults.wrongPinAttempts,
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
271
308
|
});
|
|
272
309
|
}
|
|
273
310
|
if (path === '/keys/escrow/attestation') return response({ attestation: {} });
|
|
@@ -297,6 +334,13 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
297
334
|
expect(signed.intent.expectedEnrollmentVersion).toBe(state.enrollmentVersion);
|
|
298
335
|
intents.push(signed);
|
|
299
336
|
if (faults.failEnrollment) throw new Error('Temporary enrollment outage');
|
|
337
|
+
if (faults.refusePinChange && signed.intent.pinAction === 'change') {
|
|
338
|
+
faults.refusePinChange = false;
|
|
339
|
+
return response(
|
|
340
|
+
{ message: 'Automatic recovery changed; please refresh.' },
|
|
341
|
+
409
|
|
342
|
+
);
|
|
343
|
+
}
|
|
300
344
|
// Mirror escrow.enroll: rebind removes Mongo metadata, so carry cannot work.
|
|
301
345
|
if (
|
|
302
346
|
faults.missingPinMetadata &&
|
|
@@ -315,6 +359,11 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
315
359
|
parseEscrowEnvelope(body.envelope),
|
|
316
360
|
keys.privateKey
|
|
317
361
|
);
|
|
362
|
+
enrollments.push({
|
|
363
|
+
pinSalt: body.pinSalt,
|
|
364
|
+
clearPin: body.clearPin,
|
|
365
|
+
pinVerifier: plaintext.pinVerifier,
|
|
366
|
+
});
|
|
318
367
|
if (signed.intent.action === 'carry')
|
|
319
368
|
plaintext.pinVerifier = (
|
|
320
369
|
await decryptEscrowBlob(state.envelope!, keys.privateKey)
|
|
@@ -333,6 +382,7 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
333
382
|
pinGeneration: state.pinGeneration + Number(pinChange),
|
|
334
383
|
attemptsUsed: pinChange ? 0 : state.attemptsUsed,
|
|
335
384
|
};
|
|
385
|
+
if (pinChange) releaseCharges = 0;
|
|
336
386
|
enrolled = !!output;
|
|
337
387
|
const receipt = await sign('commit', {
|
|
338
388
|
commit: {
|
|
@@ -413,6 +463,9 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
413
463
|
requestedAt: new Date().toISOString(),
|
|
414
464
|
releaseAfter: new Date(Date.now() + 604800000).toISOString(),
|
|
415
465
|
holdRecord,
|
|
466
|
+
...(body.releasePolicy === 'pin'
|
|
467
|
+
? { pinSalt: 'AAAAAAAAAAAAAAAAAAAAAA==' }
|
|
468
|
+
: {}),
|
|
416
469
|
});
|
|
417
470
|
}
|
|
418
471
|
if (path === '/keys/escrow/status') {
|
|
@@ -494,6 +547,7 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
494
547
|
}
|
|
495
548
|
if (path === '/keys/escrow/complete') {
|
|
496
549
|
if (!pendingHold) throw new Error('No test hold');
|
|
550
|
+
if (body.pinProof !== undefined) releaseCharges++;
|
|
497
551
|
return response({
|
|
498
552
|
sealedShare: await sealEscrowRelease(
|
|
499
553
|
{
|
|
@@ -560,6 +614,7 @@ const setup = async (legacyEnrolled = false) => {
|
|
|
560
614
|
calls,
|
|
561
615
|
nonces,
|
|
562
616
|
intents,
|
|
617
|
+
enrollments,
|
|
563
618
|
faults,
|
|
564
619
|
config,
|
|
565
620
|
identity,
|
|
@@ -616,6 +671,244 @@ describe('Nitro recovery initiating-client bindings and measurement rollover', (
|
|
|
616
671
|
expect(f.state()).toMatchObject({ status: 'active', hasPin: false });
|
|
617
672
|
expect(f.calls.filter(call => call === 'PUT:/keys/auth-share')).toHaveLength(writes);
|
|
618
673
|
});
|
|
674
|
+
it('keeps the account and re-sets the proven PIN with an owner-signed change after a PIN sign-in', async () => {
|
|
675
|
+
const f = await setup();
|
|
676
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
677
|
+
...f.params,
|
|
678
|
+
options: { ...f.params.options, pin: '135790' },
|
|
679
|
+
});
|
|
680
|
+
f.spend();
|
|
681
|
+
// Charged attempts (e.g. earlier successful releases), but no verified wrong entry.
|
|
682
|
+
f.faults.wrongPinAttempts = 0;
|
|
683
|
+
const generation = f.state().pinGeneration;
|
|
684
|
+
const shareVersion = f.intents.at(-1)!.intent.shareVersion;
|
|
685
|
+
const writes = f.calls.filter(call => call === 'PUT:/keys/auth-share').length;
|
|
686
|
+
const recovered = await f.strategy.executeRecovery({
|
|
687
|
+
token: 'token',
|
|
688
|
+
providerType: 'firebase',
|
|
689
|
+
input: { method: 'escrow-pin', pin: '135790', expectedDid: owner.intent.did },
|
|
690
|
+
didFromPrivateKey: async () => owner.intent.did,
|
|
691
|
+
signDidAuthVp: f.params.signDidAuthVp,
|
|
692
|
+
options: f.params.options,
|
|
693
|
+
});
|
|
694
|
+
expect(recovered).toMatchObject({
|
|
695
|
+
escrowEnrolled: true,
|
|
696
|
+
escrowRecovery: true,
|
|
697
|
+
pinSignIn: { mode: 'kept', pinRefreshed: true, newPinRequired: false },
|
|
698
|
+
});
|
|
699
|
+
// Keep mode: no rebind, no re-split; the re-set targets the released version.
|
|
700
|
+
expect(f.calls).not.toContain('POST:/keys/recovery-session/rebind');
|
|
701
|
+
expect(f.calls.filter(call => call === 'PUT:/keys/auth-share')).toHaveLength(writes);
|
|
702
|
+
expect(f.intents.at(-1)?.intent).toMatchObject({
|
|
703
|
+
shareVersion,
|
|
704
|
+
action: 'replace',
|
|
705
|
+
pinAction: 'change',
|
|
706
|
+
recoveryEmail: 'owner@example.com',
|
|
707
|
+
recoveryLanguage: 'en',
|
|
708
|
+
});
|
|
709
|
+
expect(f.state()).toMatchObject({
|
|
710
|
+
status: 'active',
|
|
711
|
+
hasPin: true,
|
|
712
|
+
pinGeneration: generation + 1,
|
|
713
|
+
attemptsUsed: 0,
|
|
714
|
+
});
|
|
715
|
+
const enrollment = f.enrollments.at(-1)!;
|
|
716
|
+
expect(enrollment.clearPin).toBeUndefined();
|
|
717
|
+
expect(typeof enrollment.pinSalt).toBe('string');
|
|
718
|
+
expect(enrollment.pinVerifier).toBe(
|
|
719
|
+
await derivePinProof('135790', String(enrollment.pinSalt))
|
|
720
|
+
);
|
|
721
|
+
// The repair intent is not needed: a current PIN enrollment needs no change.
|
|
722
|
+
const intents = f.intents.length;
|
|
723
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toMatchObject({
|
|
724
|
+
enrolled: true,
|
|
725
|
+
changed: false,
|
|
726
|
+
});
|
|
727
|
+
expect(f.intents).toHaveLength(intents);
|
|
728
|
+
});
|
|
729
|
+
it('signs no PIN change after a PIN sign-in when wrong entries are recorded', async () => {
|
|
730
|
+
const f = await setup();
|
|
731
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
732
|
+
...f.params,
|
|
733
|
+
options: { ...f.params.options, pin: '135790' },
|
|
734
|
+
});
|
|
735
|
+
f.spend();
|
|
736
|
+
f.faults.wrongPinAttempts = 2;
|
|
737
|
+
const before = { ...f.state() };
|
|
738
|
+
const intents = f.intents.length;
|
|
739
|
+
await expect(
|
|
740
|
+
f.strategy.executeRecovery({
|
|
741
|
+
token: 'token',
|
|
742
|
+
providerType: 'firebase',
|
|
743
|
+
input: { method: 'escrow-pin', pin: '135790', expectedDid: owner.intent.did },
|
|
744
|
+
didFromPrivateKey: async () => owner.intent.did,
|
|
745
|
+
signDidAuthVp: f.params.signDidAuthVp,
|
|
746
|
+
options: f.params.options,
|
|
747
|
+
})
|
|
748
|
+
).resolves.toMatchObject({
|
|
749
|
+
escrowEnrolled: true,
|
|
750
|
+
pinSignIn: { mode: 'kept', pinRefreshed: false, newPinRequired: true },
|
|
751
|
+
});
|
|
752
|
+
// No owner-signed change: same PIN generation, the used attempts stand.
|
|
753
|
+
expect(f.intents).toHaveLength(intents);
|
|
754
|
+
expect(f.state()).toMatchObject({
|
|
755
|
+
pinGeneration: before.pinGeneration,
|
|
756
|
+
attemptsUsed: before.attemptsUsed,
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
it('reports a refused same-PIN refresh instead of a silent success, and keeps the PIN', async () => {
|
|
760
|
+
const f = await setup();
|
|
761
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
762
|
+
...f.params,
|
|
763
|
+
options: { ...f.params.options, pin: '135790' },
|
|
764
|
+
});
|
|
765
|
+
f.spend();
|
|
766
|
+
f.faults.wrongPinAttempts = 0;
|
|
767
|
+
f.faults.refusePinChange = true;
|
|
768
|
+
const before = { ...f.state() };
|
|
769
|
+
const recovered = await f.strategy.executeRecovery({
|
|
770
|
+
token: 'token',
|
|
771
|
+
providerType: 'firebase',
|
|
772
|
+
input: { method: 'escrow-pin', pin: '135790', expectedDid: owner.intent.did },
|
|
773
|
+
didFromPrivateKey: async () => owner.intent.did,
|
|
774
|
+
signDidAuthVp: f.params.signDidAuthVp,
|
|
775
|
+
options: f.params.options,
|
|
776
|
+
});
|
|
777
|
+
expect(recovered).toMatchObject({
|
|
778
|
+
escrowEnrolled: true,
|
|
779
|
+
pinSignIn: {
|
|
780
|
+
mode: 'kept',
|
|
781
|
+
pinRefreshed: false,
|
|
782
|
+
newPinRequired: false,
|
|
783
|
+
pinRefreshFailed: true,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
// Nothing committed: the PIN keeps its generation and reduced budget.
|
|
787
|
+
expect(f.state()).toMatchObject({
|
|
788
|
+
pinGeneration: before.pinGeneration,
|
|
789
|
+
attemptsUsed: before.attemptsUsed,
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
it('does not report a refresh failure when none was attempted', async () => {
|
|
793
|
+
const f = await setup();
|
|
794
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
795
|
+
...f.params,
|
|
796
|
+
options: { ...f.params.options, pin: '135790' },
|
|
797
|
+
});
|
|
798
|
+
f.spend();
|
|
799
|
+
f.faults.wrongPinAttempts = 2;
|
|
800
|
+
const recovered = await f.strategy.executeRecovery({
|
|
801
|
+
token: 'token',
|
|
802
|
+
providerType: 'firebase',
|
|
803
|
+
input: { method: 'escrow-pin', pin: '135790', expectedDid: owner.intent.did },
|
|
804
|
+
didFromPrivateKey: async () => owner.intent.did,
|
|
805
|
+
signDidAuthVp: f.params.signDidAuthVp,
|
|
806
|
+
options: f.params.options,
|
|
807
|
+
});
|
|
808
|
+
expect(recovered.pinSignIn).not.toHaveProperty('pinRefreshFailed');
|
|
809
|
+
});
|
|
810
|
+
it('treats an enrollment whose lca-api copy differs from the authority as unenrolled', async () => {
|
|
811
|
+
const f = await setup();
|
|
812
|
+
await f.strategy.ensureEscrowEnrollment!(f.params);
|
|
813
|
+
expect(f.state()).toMatchObject({ status: 'active', hasPin: false });
|
|
814
|
+
f.faults.serverBlobIdentity = 'authority';
|
|
815
|
+
const intents = f.intents.length;
|
|
816
|
+
// Matching copies: nothing to repair.
|
|
817
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toMatchObject({
|
|
818
|
+
enrolled: true,
|
|
819
|
+
changed: false,
|
|
820
|
+
});
|
|
821
|
+
expect(f.intents).toHaveLength(intents);
|
|
822
|
+
// lca-api kept an old copy the authority already replaced: re-enroll (no PIN).
|
|
823
|
+
f.faults.serverBlobIdentity = 'ef'.repeat(32);
|
|
824
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toMatchObject({
|
|
825
|
+
enrolled: true,
|
|
826
|
+
changed: true,
|
|
827
|
+
});
|
|
828
|
+
expect(f.intents.length).toBeGreaterThan(intents);
|
|
829
|
+
});
|
|
830
|
+
it('leaves a mismatch alone while lca-api reports a PIN operation in progress', async () => {
|
|
831
|
+
const f = await setup();
|
|
832
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
833
|
+
...f.params,
|
|
834
|
+
options: { ...f.params.options, pin: '135790' },
|
|
835
|
+
});
|
|
836
|
+
// Authority committed, lca-api's lease-held write not landed yet.
|
|
837
|
+
f.faults.serverBlobIdentity = 'ef'.repeat(32);
|
|
838
|
+
f.faults.serverUpdating = true;
|
|
839
|
+
const intents = f.intents.length;
|
|
840
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toEqual({
|
|
841
|
+
enrolled: true,
|
|
842
|
+
changed: false,
|
|
843
|
+
});
|
|
844
|
+
expect(f.intents).toHaveLength(intents);
|
|
845
|
+
// Once the lease is gone, a copy still left behind is a real mismatch.
|
|
846
|
+
f.faults.serverUpdating = false;
|
|
847
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toEqual({
|
|
848
|
+
enrolled: false,
|
|
849
|
+
reason: 'pin-reset-required',
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
it('asks for a new PIN, signing nothing, when a PIN-protected copy is stale in lca-api', async () => {
|
|
853
|
+
const f = await setup();
|
|
854
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
855
|
+
...f.params,
|
|
856
|
+
options: { ...f.params.options, pin: '135790' },
|
|
857
|
+
});
|
|
858
|
+
f.faults.serverBlobIdentity = 'ef'.repeat(32);
|
|
859
|
+
const intents = f.intents.length;
|
|
860
|
+
const writes = f.calls.filter(call => call === 'PUT:/keys/auth-share').length;
|
|
861
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toEqual({
|
|
862
|
+
enrolled: false,
|
|
863
|
+
reason: 'pin-reset-required',
|
|
864
|
+
});
|
|
865
|
+
expect(f.intents).toHaveLength(intents);
|
|
866
|
+
expect(f.calls.filter(call => call === 'PUT:/keys/auth-share')).toHaveLength(writes);
|
|
867
|
+
// A user-chosen PIN repairs it.
|
|
868
|
+
await f.strategy.setEscrowPin!({ ...f.params, pin: '246813' });
|
|
869
|
+
expect(f.intents.at(-1)?.intent).toMatchObject({ pinAction: 'change' });
|
|
870
|
+
});
|
|
871
|
+
it('re-reads lca-api before repairing, so a copy settled by the state read is not re-enrolled', async () => {
|
|
872
|
+
const f = await setup();
|
|
873
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
874
|
+
...f.params,
|
|
875
|
+
options: { ...f.params.options, pin: '135790' },
|
|
876
|
+
});
|
|
877
|
+
f.faults.serverBlobIdentity = 'ef'.repeat(32);
|
|
878
|
+
// lca-api adopts the authority's copy while serving the authority-state read.
|
|
879
|
+
f.faults.beforeSnapshot = async () => {
|
|
880
|
+
f.faults.serverBlobIdentity = 'authority';
|
|
881
|
+
};
|
|
882
|
+
const intents = f.intents.length;
|
|
883
|
+
await expect(f.strategy.ensureEscrowEnrollment!(f.params)).resolves.toMatchObject({
|
|
884
|
+
enrolled: true,
|
|
885
|
+
changed: false,
|
|
886
|
+
});
|
|
887
|
+
expect(f.intents).toHaveLength(intents);
|
|
888
|
+
});
|
|
889
|
+
it('still clears the PIN after a hold recovery', async () => {
|
|
890
|
+
const f = await setup();
|
|
891
|
+
await f.strategy.ensureEscrowEnrollment!({
|
|
892
|
+
...f.params,
|
|
893
|
+
options: { ...f.params.options, pin: '135790' },
|
|
894
|
+
});
|
|
895
|
+
const hold = await f.strategy.startEscrowRecovery!({
|
|
896
|
+
recoverySessionToken: 'otp-session',
|
|
897
|
+
expectedDid: owner.intent.did,
|
|
898
|
+
});
|
|
899
|
+
await f.strategy.executeRecovery({
|
|
900
|
+
token: '',
|
|
901
|
+
providerType: 'firebase',
|
|
902
|
+
input: { method: 'escrow', ...hold, resumeToken: hold.resumeToken! },
|
|
903
|
+
didFromPrivateKey: async () => owner.intent.did,
|
|
904
|
+
});
|
|
905
|
+
await expect(f.strategy.completeIdentityRecovery!(f.params)).resolves.toMatchObject({
|
|
906
|
+
escrowEnrolled: true,
|
|
907
|
+
});
|
|
908
|
+
expect(f.intents.at(-1)?.intent).toMatchObject({ action: 'replace', pinAction: 'clear' });
|
|
909
|
+
expect(f.enrollments.at(-1)?.pinSalt).toBeUndefined();
|
|
910
|
+
expect(f.state()).toMatchObject({ status: 'active', hasPin: false });
|
|
911
|
+
});
|
|
619
912
|
it('treats a successful recovered clear-PIN enrollment as satisfying the repair', async () => {
|
|
620
913
|
const f = await setup();
|
|
621
914
|
await f.strategy.ensureEscrowEnrollment!({
|