@learncard/lca-api-service 1.6.1 → 1.6.2
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/AGENTS.md +21 -0
- package/CHANGELOG.md +17 -0
- package/dist/app.d.ts +41 -0
- package/dist/cache/recoverySessions.d.ts +5 -0
- package/dist/helpers/accountSecureLink.d.ts +4 -0
- package/dist/helpers/accountSecureLink.test.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/models/EscrowHold.d.ts +4 -1
- package/dist/models/UserKey.d.ts +199 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/escrow.d.ts +4 -0
- package/dist/routes/keys.d.ts +47 -1
- package/dist/services/escrow-enclave/enrollmentLease.d.ts +8 -0
- package/dist/services/escrow-enclave/notifications.d.ts +1 -1
- package/package.json +10 -10
- package/src/cache/recoverySessions.ts +38 -0
- package/src/helpers/accountSecureLink.test.ts +26 -0
- package/src/helpers/accountSecureLink.ts +16 -0
- package/src/jobs/escrowBlobRewrap.ts +95 -3
- package/src/models/EscrowHold.ts +17 -3
- package/src/models/UserKey.escrowPin.test.ts +21 -0
- package/src/models/UserKey.ts +540 -48
- package/src/routes/escrow.ts +326 -67
- package/src/routes/keys.ts +416 -7
- package/src/services/delivery/adapters/postmark.adapter.ts +11 -0
- package/src/services/escrow-enclave/README.md +8 -3
- package/src/services/escrow-enclave/enrollmentLease.ts +124 -0
- package/src/services/escrow-enclave/notifications.test.ts +69 -0
- package/src/services/escrow-enclave/notifications.ts +19 -1
- package/test/keys-escrow.spec.ts +4 -0
- package/test/keys-pin-lease.spec.ts +1132 -0
- package/test/keys-pin-sign-in.spec.ts +1152 -0
package/AGENTS.md
CHANGED
|
@@ -127,6 +127,27 @@ The `escrow` tRPC router is disabled unless `ESCROW_ENCLAVE_MODE` is configured.
|
|
|
127
127
|
| `POST /keys/escrow/cancel-link` | `escrow.cancelRecoveryByLink` | Cancels a pending hold via the single-use email cancel link (no DID auth) |
|
|
128
128
|
| `POST /keys/escrow/complete` | `escrow.completeRecovery` | Claim an elapsed hold and release sealed recovery material once |
|
|
129
129
|
|
|
130
|
+
### Recovery sessions, rebind and PIN sign-in
|
|
131
|
+
|
|
132
|
+
Lost-login recovery issues single-use, 15-minute tokens (`src/cache/recoverySessions.ts`): a `'recover'` token from `/keys/recovery-session/verify`, then a `'rebind'` token from `useRecoverySession` or `/keys/escrow/complete`.
|
|
133
|
+
|
|
134
|
+
| Route | Procedure | Effect |
|
|
135
|
+
| ----------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| `POST /keys/recovery-session/rebind` | `keys.completeRecoveryRebind` | Wiping rebind: new login replaces the old, new split, history purged, methods `pending`, escrow cleared |
|
|
137
|
+
| `POST /keys/recovery-session/pin-sign-in` | `keys.completePinSignIn` | Keep mode: adds the new login (old one stays); shares, history, methods, escrow and contact unchanged. Only PIN-release tokens are accepted |
|
|
138
|
+
| `POST /keys/revoke-other-devices` | `keys.revokeOtherDevices` | "Secure my account": owner-authenticated security rotation (same wipe as rebind; only the caller's login stays), CAS on `expectedShareVersion` |
|
|
139
|
+
|
|
140
|
+
- Only `/keys/escrow/complete` for a `releasePolicy:'pin'` hold mints a `'rebind'` token with `origin:'pin-release'` plus the released `primaryDid`/`shareVersion`. Keep mode trusts those server-side fields, never client input. Waiting-period and `useRecoverySession` tokens can only use the wiping rebind.
|
|
141
|
+
- Every recovery token records `userKey.securityEpoch` (missing = 0) at mint; a `'rebind'` token from `useRecoverySession` inherits the `'recover'` session's epoch. Wiping rotations (`/rebind`, revoke) `$inc` the epoch in the same update, and `/rebind` and `/pin-sign-in` include the epoch in their update filter, so any token minted before a revoke or wiping rebind gets CONFLICT. `useRecoverySession` and escrow start (with a recovery session) also reject a stale epoch. Non-wiping rotations leave the epoch alone.
|
|
142
|
+
- Keep mode never spends the PIN-release token, so `/rebind` also only accepts one while `userKey.shareVersion` still equals the released version. `/pin-sign-in` peeks at the token before consuming it, so a rejected hold/phrase token or a wrong DID does not burn it.
|
|
143
|
+
- The revoke re-splits the same key; it does not evict a device that already holds the private key (see `services/escrow-enclave-app/SECURITY.md` D19). After the commit it emails the contact and verified recovery addresses (`account-secured`, awaited within a 3 s budget, fail-open); the copy assumes the reader may not have run it and may have lost their login, says the recovery methods were reset, and sends them to the tenant support address (or the organization, when there is none) rather than to lost-login recovery, which the revoke itself disabled.
|
|
144
|
+
- A successful PIN release sends the `escrow-pin-sign-in` notice ("Not you? Secure your account", link to `/account/secure` on the tenant app URL) instead of `escrow-hold-released`. It is sent fail-open within the escrow notification budget.
|
|
145
|
+
- `getEscrowPinStatus().wrongAttempts` counts verified wrong entries only and is omitted for legacy records that cannot tell (until a reservation initialises the verified counter to 0; clients therefore also read the status before a release).
|
|
146
|
+
- `escrow.enroll` accepts `expectedPinCharges: { attemptsRemaining, wrongAttempts }` with a new `pinSalt` (the keep-mode same-PIN refresh). It is checked against the current PIN, then (software mode) again in the write's filter, or (authority mode) by the lease claim below, so a guess reserved or resolved after the client's check makes it CONFLICT instead of being wiped by the fresh generation. A non-zero `wrongAttempts` is always refused.
|
|
147
|
+
- **PIN-operation lease (authority mode).** Every relayed owner-signed intent (`escrow.enroll`, `escrow.disable`/`remove`) runs under `applyEnrollmentIntentUnderLease` (`src/routes/escrow.ts`): `claimEscrowEnrollmentLease` sets `userKey.escrowEnrollmentLease` in one write conditioned on the read blob + PIN record, no other lease and no live `escrowPinCompares` marker; then the authority call; then `commitEscrowEnrollmentLease`, conditioned on the lease id and the replaced `blobHash` (not on charges), which also removes the lease. `reserveEscrowPinAttempt` refuses while any lease exists and pushes a compare marker (`compareId`) that `completeRecovery` pulls once the authority answers (kept until its TTL after a transport failure). `startRecovery` and `completeRecovery` answer 429 `Automatic recovery is being updated…` under a live lease, before creating a hold or charging; `completeRecovery` also refuses (FORBIDDEN, uncharged) a PIN hold whose `holdRecord.hold.blobHash` is not the current blob's. The rewrap job (`src/jobs/escrowBlobRewrap.ts`) claims the same lease (`action: 'rewrap'`) around `rewrapEscrowBlob` in authority mode. Releasing a compare marker is best effort (its TTL covers a miss). TTLs: `ESCROW_ENROLLMENT_LEASE_TTL_MS` / `ESCROW_PIN_COMPARE_TTL_MS` = 120 s (4x the 30 s max `ESCROW_ENCLAVE_REMOTE_TIMEOUT_MS`).
|
|
148
|
+
- **Reconciliation.** If the commit write misses or the authority call fails ambiguously, `settleEscrowEnrollmentLease` (`src/services/escrow-enclave/enrollmentLease.ts`) reads the authority state and adopts it when it is exactly the lease's commit (enrollment version +1, epoch, PIN generation, share version, returned envelope identity), using the salt/PIN action recorded in the lease; a definite refusal releases the lease; an unknown outcome keeps it until expiry. `settleExpiredEscrowLease` runs on start/complete/enroll/disable/authority-state, so a crash converges on the next request. A settle that finds the same commit already persisted counts as adopted. A lost response still fails the request even when its commit is adopted (no receipt to return). `/keys/auth-share` reports `escrowBlobIdentity` so authority-mode clients can detect and repair any copy mismatch that is left, and `escrowUpdating` while a lease is held so they do not mistake an in-progress operation for a stale copy. Live-Mongo coverage: `test/keys-pin-lease.spec.ts`.
|
|
149
|
+
- Software-mode PIN carry ignores releases completed before `escrowPin.enabledAt`, so a PIN re-set at the same version after a PIN sign-in survives the next rotation.
|
|
150
|
+
|
|
130
151
|
### Important Invariants
|
|
131
152
|
|
|
132
153
|
- **Auth shares are encrypted at rest** with a KEK derived from `SEED`. Losing `SEED` means every stored auth share is permanently unrecoverable.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @welibraryos/lca-api-service
|
|
2
2
|
|
|
3
|
+
## 1.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1905](https://github.com/learningeconomy/LearnCard/pull/1905) [`c20460f6fef685272227f506f536b6811f90b8a0`](https://github.com/learningeconomy/LearnCard/commit/c20460f6fef685272227f506f536b6811f90b8a0) Thanks [@Custard7](https://github.com/Custard7)! - Make the recovery PIN the main way to sign in on a new device, without resetting the account. A PIN sign-in now keeps the account's shares, history, recovery methods and escrow: the device stores the released recovery piece at the current version and re-sets the same PIN for a fresh attempt budget only when no wrong entry is recorded against it; otherwise the PIN keeps its reduced budget and the app asks for a new, different PIN (refusing the PIN just used, also after a restart on that device, via a tag keyed by the account key), asking again on each app start until one is chosen; the same-PIN refresh is skipped when another charge landed during the release, and lca-api writes it only while the PIN still shows the charges the client saw (`expectedPinCharges`) (a lost login is linked with the new `POST /keys/recovery-session/pin-sign-in`, accepted only after a PIN release). The server emails the account's verified addresses on every PIN sign-in ("New device signed in with your PIN. Not you? Secure your account."), and the PIN status reports earlier wrong entries. The app leads its recovery screen with PIN entry (no sign-out warnings on that path; the 7-day hold keeps them), asks once after signing in whether a device was lost or the PIN is known to someone else (warning about earlier wrong entries), and adds "Secure my account" / "Sign out of all other devices" (`POST /keys/revoke-other-devices`), reachable from that question, Account Recovery settings and the email link `/account/secure`. That action re-splits the key, purges older versions and unlinks every login except the current one; it does not change the key itself, so a device that is already signed in may keep working until it signs out (the app says so). The revoke emails the account's contact and verified recovery addresses (new `account-secured` template, written for an owner who did not do it and may have lost their login: it says the recovery methods were reset and points to the tenant's support address, or to the organization when there is none). Every recovery token (recovery-email code, phrase, passkey, backup, waiting-period hold and PIN) is now bound to an account security epoch that every revoke and wiping rebind advances, so no token minted before one can rebind or re-link after it. Recovery is one-at-a-time: the coordinator rejects a second recovery while one is pending (`RecoveryInProgressError`; after a cancel, a new recovery-email code can be requested at once) and the app disables switching methods while a PIN sign-in is in flight. `@learncard/email-templates` omits an absent `locale` rather than passing `undefined` (`exactOptionalPropertyTypes`-safe) and exports each template from one declaration.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`372c1431e78a9708454f9c4c2f2c99dd5486b8e9`](https://github.com/learningeconomy/LearnCard/commit/372c1431e78a9708454f9c4c2f2c99dd5486b8e9), [`c20460f6fef685272227f506f536b6811f90b8a0`](https://github.com/learningeconomy/LearnCard/commit/c20460f6fef685272227f506f536b6811f90b8a0)]:
|
|
10
|
+
- @learncard/sss-key-manager@0.2.6
|
|
11
|
+
- @learncard/types@5.24.1
|
|
12
|
+
- @learncard/email-templates@1.2.2
|
|
13
|
+
- @learncard/core@9.4.42
|
|
14
|
+
- @learncard/helpers@1.7.2
|
|
15
|
+
- @learncard/init@2.5.8
|
|
16
|
+
- @learncard/did-web-plugin@1.1.42
|
|
17
|
+
- @learncard/didkit-plugin@1.10.8
|
|
18
|
+
- @learncard/didkit-plugin-node@0.3.8
|
|
19
|
+
|
|
3
20
|
## 1.6.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/app.d.ts
CHANGED
|
@@ -1041,6 +1041,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1041
1041
|
state: "enabled" | "none" | "locked" | "stale";
|
|
1042
1042
|
enabled: boolean;
|
|
1043
1043
|
attemptsRemaining: number;
|
|
1044
|
+
wrongAttempts?: number | undefined;
|
|
1044
1045
|
salt?: string | undefined;
|
|
1045
1046
|
};
|
|
1046
1047
|
primaryDid: string | null;
|
|
@@ -1089,6 +1090,39 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1089
1090
|
};
|
|
1090
1091
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
1091
1092
|
}>;
|
|
1093
|
+
completePinSignIn: import("@trpc/server").TRPCMutationProcedure<{
|
|
1094
|
+
input: {
|
|
1095
|
+
recoverySessionToken: string;
|
|
1096
|
+
providerType: "firebase" | "supertokens" | "keycloak" | "oidc";
|
|
1097
|
+
primaryDid: string;
|
|
1098
|
+
newAuthToken?: string | undefined;
|
|
1099
|
+
};
|
|
1100
|
+
output: {
|
|
1101
|
+
success: true;
|
|
1102
|
+
shareVersion: number;
|
|
1103
|
+
linked: boolean;
|
|
1104
|
+
};
|
|
1105
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
1106
|
+
}>;
|
|
1107
|
+
revokeOtherDevices: import("@trpc/server").TRPCMutationProcedure<{
|
|
1108
|
+
input: {
|
|
1109
|
+
providerType: "firebase" | "supertokens" | "keycloak" | "oidc";
|
|
1110
|
+
primaryDid: string;
|
|
1111
|
+
authShare: {
|
|
1112
|
+
encryptedData: string;
|
|
1113
|
+
encryptedDek: string;
|
|
1114
|
+
iv: string;
|
|
1115
|
+
};
|
|
1116
|
+
expectedShareVersion: number;
|
|
1117
|
+
authToken?: string | undefined;
|
|
1118
|
+
};
|
|
1119
|
+
output: {
|
|
1120
|
+
success: true;
|
|
1121
|
+
shareVersion: number;
|
|
1122
|
+
recoveryMethodsRequireConfirmation: ("email" | "passkey" | "backup" | "phrase" | "escrow")[];
|
|
1123
|
+
};
|
|
1124
|
+
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
1125
|
+
}>;
|
|
1092
1126
|
issueDidChallenge: import("@trpc/server").TRPCMutationProcedure<{
|
|
1093
1127
|
input: {
|
|
1094
1128
|
did: string;
|
|
@@ -1127,11 +1161,14 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1127
1161
|
state: "enabled" | "none" | "locked" | "stale";
|
|
1128
1162
|
enabled: boolean;
|
|
1129
1163
|
attemptsRemaining: number;
|
|
1164
|
+
wrongAttempts?: number | undefined;
|
|
1130
1165
|
salt?: string | undefined;
|
|
1131
1166
|
};
|
|
1132
1167
|
priorEscrowEnrollment: boolean;
|
|
1133
1168
|
sssActivationState: "active" | "provisional";
|
|
1134
1169
|
escrowStale?: "mode-mismatch" | "key-rotated" | undefined;
|
|
1170
|
+
escrowBlobIdentity?: string | undefined;
|
|
1171
|
+
escrowUpdating?: boolean | undefined;
|
|
1135
1172
|
} | null;
|
|
1136
1173
|
meta: import("trpc-to-openapi").OpenApiMeta;
|
|
1137
1174
|
}>;
|
|
@@ -1433,6 +1470,10 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
1433
1470
|
iv: string;
|
|
1434
1471
|
ciphertext: string;
|
|
1435
1472
|
} | null | undefined;
|
|
1473
|
+
expectedPinCharges?: {
|
|
1474
|
+
attemptsRemaining: number;
|
|
1475
|
+
wrongAttempts: number;
|
|
1476
|
+
} | undefined;
|
|
1436
1477
|
};
|
|
1437
1478
|
output: {
|
|
1438
1479
|
success: true;
|
|
@@ -10,6 +10,10 @@ export interface RecoverySessionRecord {
|
|
|
10
10
|
type: AuthProviderType;
|
|
11
11
|
id: string;
|
|
12
12
|
};
|
|
13
|
+
origin?: 'pin-release';
|
|
14
|
+
primaryDid?: string;
|
|
15
|
+
shareVersion?: number;
|
|
16
|
+
securityEpoch?: number;
|
|
13
17
|
}
|
|
14
18
|
export interface RecoveryOtpRecord {
|
|
15
19
|
codeHash: string;
|
|
@@ -36,6 +40,7 @@ export declare const getRecoveryOtpLockCacheKey: (email: string) => string;
|
|
|
36
40
|
export declare const getRecoveryOtpSendCacheKey: (email: string) => string;
|
|
37
41
|
export declare const getRecoverySessionCacheKey: (token: string) => string;
|
|
38
42
|
export declare const createRecoverySession: (record: RecoverySessionRecord, ttl?: number) => Promise<string>;
|
|
43
|
+
export declare const peekRecoverySession: (token: string, expectedScope: RecoverySessionScope) => Promise<RecoverySessionRecord | null>;
|
|
39
44
|
export declare const consumeRecoverySession: (token: string, expectedScope: RecoverySessionScope) => Promise<RecoverySessionRecord | null>;
|
|
40
45
|
export declare const storeRecoveryOtp: (email: string, record: RecoveryOtpRecord) => Promise<void>;
|
|
41
46
|
export declare const verifyRecoveryOtp: (email: string, submittedCodeHash: string) => Promise<RecoveryOtpVerificationResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/index.js
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 167146968 bytes
|
|
4
|
+
sha256: e837ca38f23dbcecdd16d4cc36272db9d19b6ffac4d5142e91b4a5fd4c9af1cd
|
|
@@ -141,6 +141,7 @@ export declare const EscrowHoldValidator: z.ZodObject<{
|
|
|
141
141
|
"pin-locked": "pin-locked";
|
|
142
142
|
started: "started";
|
|
143
143
|
reminder: "reminder";
|
|
144
|
+
"pin-sign-in": "pin-sign-in";
|
|
144
145
|
}>;
|
|
145
146
|
sentAt: z.ZodDate;
|
|
146
147
|
}, z.core.$strip>>>;
|
|
@@ -166,7 +167,9 @@ export declare const cancelEscrowHold: (id: string, cancelledBy: "did" | "system
|
|
|
166
167
|
export declare const cancelEscrowHoldByCancelToken: (id: string, cancelTokenHash: string) => Promise<EscrowHold | null>;
|
|
167
168
|
export declare const recordEscrowHoldConfirmation: (id: string, confirmedAt: Date, releaseAfter: Date) => Promise<EscrowHold | null>;
|
|
168
169
|
export declare const completeEscrowHold: (id: string) => Promise<EscrowHold | null>;
|
|
169
|
-
export declare const hasCompletedEscrowHoldForVersion: (authProvider: AuthProviderMapping, shareVersion: number
|
|
170
|
+
export declare const hasCompletedEscrowHoldForVersion: (authProvider: AuthProviderMapping, shareVersion: number, options?: {
|
|
171
|
+
completedAfter?: Date;
|
|
172
|
+
}) => Promise<boolean>;
|
|
170
173
|
export declare const expireStaleEscrowHolds: (now: Date) => Promise<number>;
|
|
171
174
|
export declare const markClaimedEscrowHoldFailed: (id: string, reason: "pin-mismatch" | "pin-locked" | "release-failed", completedAt: Date) => Promise<void>;
|
|
172
175
|
export declare const retryClaimedEscrowHold: (id: string, completedAt: Date) => Promise<void>;
|
package/dist/models/UserKey.d.ts
CHANGED
|
@@ -126,6 +126,49 @@ export declare const EscrowPinValidator: z.ZodObject<{
|
|
|
126
126
|
disabledAt: z.ZodOptional<z.ZodDate>;
|
|
127
127
|
shareVersion: z.ZodNumber;
|
|
128
128
|
}, z.core.$strip>;
|
|
129
|
+
export declare const ESCROW_ENROLLMENT_LEASE_TTL_MS = 120000;
|
|
130
|
+
export declare const ESCROW_PIN_COMPARE_TTL_MS = 120000;
|
|
131
|
+
export declare const EscrowEnrollmentLeaseValidator: z.ZodObject<{
|
|
132
|
+
id: z.ZodString;
|
|
133
|
+
expiresAt: z.ZodDate;
|
|
134
|
+
action: z.ZodEnum<{
|
|
135
|
+
enroll: "enroll";
|
|
136
|
+
replace: "replace";
|
|
137
|
+
disable: "disable";
|
|
138
|
+
carry: "carry";
|
|
139
|
+
rewrap: "rewrap";
|
|
140
|
+
}>;
|
|
141
|
+
pinAction: z.ZodEnum<{
|
|
142
|
+
set: "set";
|
|
143
|
+
change: "change";
|
|
144
|
+
clear: "clear";
|
|
145
|
+
keep: "keep";
|
|
146
|
+
}>;
|
|
147
|
+
pin: z.ZodEnum<{
|
|
148
|
+
clear: "clear";
|
|
149
|
+
keep: "keep";
|
|
150
|
+
fresh: "fresh";
|
|
151
|
+
}>;
|
|
152
|
+
pinSalt: z.ZodOptional<z.ZodString>;
|
|
153
|
+
optOut: z.ZodOptional<z.ZodBoolean>;
|
|
154
|
+
shareVersion: z.ZodNumber;
|
|
155
|
+
authorityGeneration: z.ZodString;
|
|
156
|
+
expectedEnrollmentVersion: z.ZodNumber;
|
|
157
|
+
expectedPinGeneration: z.ZodOptional<z.ZodNumber>;
|
|
158
|
+
enrollmentEpoch: z.ZodNumber;
|
|
159
|
+
sourceBlobHash: z.ZodNullable<z.ZodString>;
|
|
160
|
+
measurements: z.ZodObject<{
|
|
161
|
+
imageSha384: z.ZodOptional<z.ZodString>;
|
|
162
|
+
pcr0: z.ZodOptional<z.ZodString>;
|
|
163
|
+
pcr1: z.ZodOptional<z.ZodString>;
|
|
164
|
+
pcr2: z.ZodOptional<z.ZodString>;
|
|
165
|
+
}, z.core.$strip>;
|
|
166
|
+
}, z.core.$strip>;
|
|
167
|
+
export type EscrowEnrollmentLease = z.infer<typeof EscrowEnrollmentLeaseValidator>;
|
|
168
|
+
export declare const EscrowPinCompareValidator: z.ZodObject<{
|
|
169
|
+
id: z.ZodString;
|
|
170
|
+
expiresAt: z.ZodDate;
|
|
171
|
+
}, z.core.$strip>;
|
|
129
172
|
export declare const EscrowPinStatusValidator: z.ZodObject<{
|
|
130
173
|
state: z.ZodEnum<{
|
|
131
174
|
none: "none";
|
|
@@ -135,6 +178,7 @@ export declare const EscrowPinStatusValidator: z.ZodObject<{
|
|
|
135
178
|
}>;
|
|
136
179
|
enabled: z.ZodBoolean;
|
|
137
180
|
attemptsRemaining: z.ZodNumber;
|
|
181
|
+
wrongAttempts: z.ZodOptional<z.ZodNumber>;
|
|
138
182
|
salt: z.ZodOptional<z.ZodString>;
|
|
139
183
|
}, z.core.$strip>;
|
|
140
184
|
export declare const getEscrowPinStatus: (userKey: MongoUserKeyType) => z.infer<typeof EscrowPinStatusValidator>;
|
|
@@ -192,6 +236,46 @@ declare const MongoUserKeyBaseValidator: z.ZodObject<{
|
|
|
192
236
|
shareVersion: z.ZodNumber;
|
|
193
237
|
}, z.core.$strip>>;
|
|
194
238
|
escrowOptedOutAt: z.ZodOptional<z.ZodDate>;
|
|
239
|
+
escrowEnrollmentLease: z.ZodOptional<z.ZodObject<{
|
|
240
|
+
id: z.ZodString;
|
|
241
|
+
expiresAt: z.ZodDate;
|
|
242
|
+
action: z.ZodEnum<{
|
|
243
|
+
enroll: "enroll";
|
|
244
|
+
replace: "replace";
|
|
245
|
+
disable: "disable";
|
|
246
|
+
carry: "carry";
|
|
247
|
+
rewrap: "rewrap";
|
|
248
|
+
}>;
|
|
249
|
+
pinAction: z.ZodEnum<{
|
|
250
|
+
set: "set";
|
|
251
|
+
change: "change";
|
|
252
|
+
clear: "clear";
|
|
253
|
+
keep: "keep";
|
|
254
|
+
}>;
|
|
255
|
+
pin: z.ZodEnum<{
|
|
256
|
+
clear: "clear";
|
|
257
|
+
keep: "keep";
|
|
258
|
+
fresh: "fresh";
|
|
259
|
+
}>;
|
|
260
|
+
pinSalt: z.ZodOptional<z.ZodString>;
|
|
261
|
+
optOut: z.ZodOptional<z.ZodBoolean>;
|
|
262
|
+
shareVersion: z.ZodNumber;
|
|
263
|
+
authorityGeneration: z.ZodString;
|
|
264
|
+
expectedEnrollmentVersion: z.ZodNumber;
|
|
265
|
+
expectedPinGeneration: z.ZodOptional<z.ZodNumber>;
|
|
266
|
+
enrollmentEpoch: z.ZodNumber;
|
|
267
|
+
sourceBlobHash: z.ZodNullable<z.ZodString>;
|
|
268
|
+
measurements: z.ZodObject<{
|
|
269
|
+
imageSha384: z.ZodOptional<z.ZodString>;
|
|
270
|
+
pcr0: z.ZodOptional<z.ZodString>;
|
|
271
|
+
pcr1: z.ZodOptional<z.ZodString>;
|
|
272
|
+
pcr2: z.ZodOptional<z.ZodString>;
|
|
273
|
+
}, z.core.$strip>;
|
|
274
|
+
}, z.core.$strip>>;
|
|
275
|
+
escrowPinCompares: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
276
|
+
id: z.ZodString;
|
|
277
|
+
expiresAt: z.ZodDate;
|
|
278
|
+
}, z.core.$strip>>>;
|
|
195
279
|
_id: z.ZodOptional<z.ZodString>;
|
|
196
280
|
contactMethod: z.ZodObject<{
|
|
197
281
|
type: z.ZodEnum<{
|
|
@@ -271,6 +355,7 @@ declare const MongoUserKeyBaseValidator: z.ZodObject<{
|
|
|
271
355
|
provisional: "provisional";
|
|
272
356
|
}>>;
|
|
273
357
|
provisionalCreatedAt: z.ZodOptional<z.ZodDate>;
|
|
358
|
+
securityEpoch: z.ZodOptional<z.ZodNumber>;
|
|
274
359
|
createdAt: z.ZodDate;
|
|
275
360
|
updatedAt: z.ZodDate;
|
|
276
361
|
}, z.core.$strip>;
|
|
@@ -328,6 +413,46 @@ export declare const MongoUserKeyValidator: z.ZodObject<{
|
|
|
328
413
|
shareVersion: z.ZodNumber;
|
|
329
414
|
}, z.core.$strip>>;
|
|
330
415
|
escrowOptedOutAt: z.ZodOptional<z.ZodDate>;
|
|
416
|
+
escrowEnrollmentLease: z.ZodOptional<z.ZodObject<{
|
|
417
|
+
id: z.ZodString;
|
|
418
|
+
expiresAt: z.ZodDate;
|
|
419
|
+
action: z.ZodEnum<{
|
|
420
|
+
enroll: "enroll";
|
|
421
|
+
replace: "replace";
|
|
422
|
+
disable: "disable";
|
|
423
|
+
carry: "carry";
|
|
424
|
+
rewrap: "rewrap";
|
|
425
|
+
}>;
|
|
426
|
+
pinAction: z.ZodEnum<{
|
|
427
|
+
set: "set";
|
|
428
|
+
change: "change";
|
|
429
|
+
clear: "clear";
|
|
430
|
+
keep: "keep";
|
|
431
|
+
}>;
|
|
432
|
+
pin: z.ZodEnum<{
|
|
433
|
+
clear: "clear";
|
|
434
|
+
keep: "keep";
|
|
435
|
+
fresh: "fresh";
|
|
436
|
+
}>;
|
|
437
|
+
pinSalt: z.ZodOptional<z.ZodString>;
|
|
438
|
+
optOut: z.ZodOptional<z.ZodBoolean>;
|
|
439
|
+
shareVersion: z.ZodNumber;
|
|
440
|
+
authorityGeneration: z.ZodString;
|
|
441
|
+
expectedEnrollmentVersion: z.ZodNumber;
|
|
442
|
+
expectedPinGeneration: z.ZodOptional<z.ZodNumber>;
|
|
443
|
+
enrollmentEpoch: z.ZodNumber;
|
|
444
|
+
sourceBlobHash: z.ZodNullable<z.ZodString>;
|
|
445
|
+
measurements: z.ZodObject<{
|
|
446
|
+
imageSha384: z.ZodOptional<z.ZodString>;
|
|
447
|
+
pcr0: z.ZodOptional<z.ZodString>;
|
|
448
|
+
pcr1: z.ZodOptional<z.ZodString>;
|
|
449
|
+
pcr2: z.ZodOptional<z.ZodString>;
|
|
450
|
+
}, z.core.$strip>;
|
|
451
|
+
}, z.core.$strip>>;
|
|
452
|
+
escrowPinCompares: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
453
|
+
id: z.ZodString;
|
|
454
|
+
expiresAt: z.ZodDate;
|
|
455
|
+
}, z.core.$strip>>>;
|
|
331
456
|
_id: z.ZodOptional<z.ZodString>;
|
|
332
457
|
contactMethod: z.ZodObject<{
|
|
333
458
|
type: z.ZodEnum<{
|
|
@@ -407,6 +532,7 @@ export declare const MongoUserKeyValidator: z.ZodObject<{
|
|
|
407
532
|
provisional: "provisional";
|
|
408
533
|
}>>;
|
|
409
534
|
provisionalCreatedAt: z.ZodOptional<z.ZodDate>;
|
|
535
|
+
securityEpoch: z.ZodOptional<z.ZodNumber>;
|
|
410
536
|
createdAt: z.ZodDate;
|
|
411
537
|
updatedAt: z.ZodDate;
|
|
412
538
|
}, z.core.$loose>;
|
|
@@ -519,6 +645,31 @@ export declare const getUserKeysCollection: () => import("mongodb").Collection<{
|
|
|
519
645
|
disabledAt?: Date | undefined;
|
|
520
646
|
} | undefined;
|
|
521
647
|
escrowOptedOutAt?: Date | undefined;
|
|
648
|
+
escrowEnrollmentLease?: {
|
|
649
|
+
id: string;
|
|
650
|
+
expiresAt: Date;
|
|
651
|
+
action: "enroll" | "replace" | "disable" | "carry" | "rewrap";
|
|
652
|
+
pinAction: "set" | "change" | "clear" | "keep";
|
|
653
|
+
pin: "clear" | "keep" | "fresh";
|
|
654
|
+
shareVersion: number;
|
|
655
|
+
authorityGeneration: string;
|
|
656
|
+
expectedEnrollmentVersion: number;
|
|
657
|
+
enrollmentEpoch: number;
|
|
658
|
+
sourceBlobHash: string | null;
|
|
659
|
+
measurements: {
|
|
660
|
+
imageSha384?: string | undefined;
|
|
661
|
+
pcr0?: string | undefined;
|
|
662
|
+
pcr1?: string | undefined;
|
|
663
|
+
pcr2?: string | undefined;
|
|
664
|
+
};
|
|
665
|
+
pinSalt?: string | undefined;
|
|
666
|
+
optOut?: boolean | undefined;
|
|
667
|
+
expectedPinGeneration?: number | undefined;
|
|
668
|
+
} | undefined;
|
|
669
|
+
escrowPinCompares?: {
|
|
670
|
+
id: string;
|
|
671
|
+
expiresAt: Date;
|
|
672
|
+
}[] | undefined;
|
|
522
673
|
_id?: string | undefined;
|
|
523
674
|
authShare?: {
|
|
524
675
|
encryptedData: string;
|
|
@@ -531,6 +682,7 @@ export declare const getUserKeysCollection: () => import("mongodb").Collection<{
|
|
|
531
682
|
migratedAt?: Date | undefined;
|
|
532
683
|
sssActivationState?: "active" | "provisional" | undefined;
|
|
533
684
|
provisionalCreatedAt?: Date | undefined;
|
|
685
|
+
securityEpoch?: number | undefined;
|
|
534
686
|
}>;
|
|
535
687
|
export declare const createUserKeysIndexes: () => Promise<void>;
|
|
536
688
|
export declare const ensureUserKeysIndexes: () => Promise<void>;
|
|
@@ -543,6 +695,7 @@ export declare const upsertUserKey: (contactMethod: ContactMethod, data: Partial
|
|
|
543
695
|
export declare const upsertUserKeyByAuthProvider: (contactMethod: ContactMethod, authProvider: AuthProviderMapping, data: Partial<Omit<MongoUserKeyType, "_id" | "contactMethod" | "createdAt" | "authProviders">>, expectedVersion?: number) => Promise<MongoUserKeyType>;
|
|
544
696
|
export declare const addAuthProviderToUserKey: (contactMethod: ContactMethod, authProvider: AuthProviderMapping) => Promise<void>;
|
|
545
697
|
export declare const addRecoveryMethodToUserKey: (contactMethod: ContactMethod, recoveryMethod: RecoveryMethod) => Promise<void>;
|
|
698
|
+
export declare const getSecurityEpoch: (userKey: Pick<MongoUserKeyType, "securityEpoch">) => number;
|
|
546
699
|
export declare const setEscrowBlobByAuthProvider: (authProvider: AuthProviderMapping, blob: Omit<EscrowBlob, "enrollmentEpoch" | "blobHash">, expectedShareVersion: number, pin?: {
|
|
547
700
|
salt: string;
|
|
548
701
|
}, carryFrom?: {
|
|
@@ -552,6 +705,28 @@ export declare const setEscrowBlobByAuthProvider: (authProvider: AuthProviderMap
|
|
|
552
705
|
enrollmentEpoch: number;
|
|
553
706
|
expectedBlob?: EscrowBlob;
|
|
554
707
|
expectedPin?: MongoUserKeyType["escrowPin"];
|
|
708
|
+
}, expectedPinCharges?: {
|
|
709
|
+
failedAttempts: number;
|
|
710
|
+
verifiedFailedAttempts: number;
|
|
711
|
+
}) => Promise<MongoUserKeyType | null>;
|
|
712
|
+
export interface ClaimEscrowEnrollmentLeaseInput {
|
|
713
|
+
lease: EscrowEnrollmentLease;
|
|
714
|
+
expectedBlob?: EscrowBlob;
|
|
715
|
+
expectedPin?: MongoUserKeyType['escrowPin'];
|
|
716
|
+
requireOptedIn: boolean;
|
|
717
|
+
}
|
|
718
|
+
export declare const claimEscrowEnrollmentLease: (authProvider: AuthProviderMapping, input: ClaimEscrowEnrollmentLeaseInput) => Promise<MongoUserKeyType | null>;
|
|
719
|
+
export declare const releaseEscrowEnrollmentLease: (authProvider: AuthProviderMapping, leaseId: string) => Promise<void>;
|
|
720
|
+
export interface EscrowAuthorityCommit {
|
|
721
|
+
envelope: EscrowBlob['envelope'] | null;
|
|
722
|
+
authorityGeneration: string;
|
|
723
|
+
enrollmentVersion: number;
|
|
724
|
+
enrollmentEpoch: number;
|
|
725
|
+
receipt?: AuthorityRecord;
|
|
726
|
+
stateProof?: AuthorityRecord;
|
|
727
|
+
}
|
|
728
|
+
export declare const commitEscrowEnrollmentLease: (authProvider: AuthProviderMapping, lease: EscrowEnrollmentLease, commit: EscrowAuthorityCommit, { adoptWithoutLease }?: {
|
|
729
|
+
adoptWithoutLease?: boolean;
|
|
555
730
|
}) => Promise<MongoUserKeyType | null>;
|
|
556
731
|
export declare const rewrapEscrowBlobByAuthProvider: (authProvider: AuthProviderMapping, oldBlob: EscrowBlob, oldPin: MongoUserKeyType["escrowPin"], newEnvelope: EscrowBlob["envelope"], identity: Pick<EscrowBlob, "enclaveKeyId" | "enclaveMode" | "measurements">, authority?: Pick<AuthorityMutationResult, "authorityGeneration" | "enrollmentVersion"> & {
|
|
557
732
|
receipt?: AuthorityRecord;
|
|
@@ -566,7 +741,8 @@ export declare const clearEscrowByAuthProvider: (authProvider: AuthProviderMappi
|
|
|
566
741
|
};
|
|
567
742
|
}) => Promise<MongoUserKeyType | null>;
|
|
568
743
|
export declare const setEscrowOptInByAuthProvider: (authProvider: AuthProviderMapping) => Promise<MongoUserKeyType | null>;
|
|
569
|
-
export declare const reserveEscrowPinAttempt: (authProvider: AuthProviderMapping, shareVersion: number, expectedCiphertext?: string) => Promise<MongoUserKeyType | null>;
|
|
744
|
+
export declare const reserveEscrowPinAttempt: (authProvider: AuthProviderMapping, shareVersion: number, expectedCiphertext?: string, compareId?: string) => Promise<MongoUserKeyType | null>;
|
|
745
|
+
export declare const releaseEscrowPinCompare: (authProvider: AuthProviderMapping, compareId: string) => Promise<void>;
|
|
570
746
|
export declare const refundEscrowPinAttempt: (authProvider: AuthProviderMapping, shareVersion: number, expectedCiphertext: string) => Promise<void>;
|
|
571
747
|
export declare const recordEscrowPinFailure: (authProvider: AuthProviderMapping, shareVersion: number, expectedCiphertext: string) => Promise<MongoUserKeyType | null>;
|
|
572
748
|
export declare const disableEscrowPin: (authProvider: AuthProviderMapping, shareVersion?: number, expectedCiphertext?: string) => Promise<boolean>;
|
|
@@ -591,8 +767,30 @@ export interface CompleteIdentityRebindInput {
|
|
|
591
767
|
newContactMethod: ContactMethod;
|
|
592
768
|
primaryDid: string;
|
|
593
769
|
authShare: ServerEncryptedShare;
|
|
770
|
+
expectedShareVersion?: number;
|
|
771
|
+
expectedSecurityEpoch: number;
|
|
594
772
|
}
|
|
595
773
|
export declare const completeIdentityRebind: (input: CompleteIdentityRebindInput) => Promise<{
|
|
596
774
|
shareVersion: number;
|
|
597
775
|
methodsRequiringConfirmation: RecoveryMethod["type"][];
|
|
598
776
|
} | null>;
|
|
777
|
+
export interface LinkAuthProviderKeepingSharesInput {
|
|
778
|
+
oldAuthProvider: AuthProviderMapping;
|
|
779
|
+
newAuthProvider: AuthProviderMapping;
|
|
780
|
+
primaryDid: string;
|
|
781
|
+
shareVersion: number;
|
|
782
|
+
securityEpoch: number;
|
|
783
|
+
}
|
|
784
|
+
export declare const linkAuthProviderKeepingShares: (input: LinkAuthProviderKeepingSharesInput) => Promise<{
|
|
785
|
+
shareVersion: number;
|
|
786
|
+
} | null>;
|
|
787
|
+
export interface RevokeOtherDeviceSharesInput {
|
|
788
|
+
authProvider: AuthProviderMapping;
|
|
789
|
+
primaryDid: string;
|
|
790
|
+
expectedShareVersion: number;
|
|
791
|
+
authShare: ServerEncryptedShare;
|
|
792
|
+
}
|
|
793
|
+
export declare const revokeOtherDeviceShares: (input: RevokeOtherDeviceSharesInput) => Promise<{
|
|
794
|
+
shareVersion: number;
|
|
795
|
+
methodsRequiringConfirmation: RecoveryMethod["type"][];
|
|
796
|
+
} | null>;
|