@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
|
@@ -56,6 +56,8 @@ var require_types_cjs_development = __commonJS({
|
|
|
56
56
|
AchievementSubjectValidator: /* @__PURE__ */ __name(() => AchievementSubjectValidator, "AchievementSubjectValidator"),
|
|
57
57
|
AchievementTypeValidator: /* @__PURE__ */ __name(() => AchievementTypeValidator, "AchievementTypeValidator"),
|
|
58
58
|
AchievementValidator: /* @__PURE__ */ __name(() => AchievementValidator, "AchievementValidator"),
|
|
59
|
+
AcknowledgeViewInputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewInputValidator, "AcknowledgeViewInputValidator"),
|
|
60
|
+
AcknowledgeViewOutputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewOutputValidator, "AcknowledgeViewOutputValidator"),
|
|
59
61
|
AddTagInputValidator: /* @__PURE__ */ __name(() => AddTagInputValidator, "AddTagInputValidator"),
|
|
60
62
|
AddressValidator: /* @__PURE__ */ __name(() => AddressValidator, "AddressValidator"),
|
|
61
63
|
AgeRatingValidator: /* @__PURE__ */ __name(() => AgeRatingValidator, "AgeRatingValidator"),
|
|
@@ -133,6 +135,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
133
135
|
CreateContactMethodSessionValidator: /* @__PURE__ */ __name(() => CreateContactMethodSessionValidator, "CreateContactMethodSessionValidator"),
|
|
134
136
|
CreateManagedFrameworkBatchInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkBatchInputValidator, "CreateManagedFrameworkBatchInputValidator"),
|
|
135
137
|
CreateManagedFrameworkInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkInputValidator, "CreateManagedFrameworkInputValidator"),
|
|
138
|
+
CreateShareLinkInputValidator: /* @__PURE__ */ __name(() => CreateShareLinkInputValidator, "CreateShareLinkInputValidator"),
|
|
136
139
|
CreateSkillInputValidator: /* @__PURE__ */ __name(() => CreateSkillInputValidator, "CreateSkillInputValidator"),
|
|
137
140
|
CreateSkillsBatchInputValidator: /* @__PURE__ */ __name(() => CreateSkillsBatchInputValidator, "CreateSkillsBatchInputValidator"),
|
|
138
141
|
CredentialActivityEventTypeValidator: /* @__PURE__ */ __name(() => CredentialActivityEventTypeValidator, "CredentialActivityEventTypeValidator"),
|
|
@@ -164,10 +167,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
164
167
|
DeleteSkillInputValidator: /* @__PURE__ */ __name(() => DeleteSkillInputValidator, "DeleteSkillInputValidator"),
|
|
165
168
|
DidDocumentValidator: /* @__PURE__ */ __name(() => DidDocumentValidator, "DidDocumentValidator"),
|
|
166
169
|
EMPTY_IN_APP_MESSAGES_FLAG: /* @__PURE__ */ __name(() => EMPTY_IN_APP_MESSAGES_FLAG, "EMPTY_IN_APP_MESSAGES_FLAG"),
|
|
170
|
+
ESCROW_PIN_LOCKED_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_LOCKED_MESSAGE2, "ESCROW_PIN_LOCKED_MESSAGE"),
|
|
171
|
+
ESCROW_PIN_MISMATCH_PATTERN: /* @__PURE__ */ __name(() => ESCROW_PIN_MISMATCH_PATTERN2, "ESCROW_PIN_MISMATCH_PATTERN"),
|
|
172
|
+
ESCROW_PIN_UNAVAILABLE_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_UNAVAILABLE_MESSAGE2, "ESCROW_PIN_UNAVAILABLE_MESSAGE"),
|
|
167
173
|
EncryptedCredentialRecordValidator: /* @__PURE__ */ __name(() => EncryptedCredentialRecordValidator, "EncryptedCredentialRecordValidator"),
|
|
168
174
|
EncryptedRecordValidator: /* @__PURE__ */ __name(() => EncryptedRecordValidator, "EncryptedRecordValidator"),
|
|
169
175
|
EndorsementCredentialValidator: /* @__PURE__ */ __name(() => EndorsementCredentialValidator, "EndorsementCredentialValidator"),
|
|
170
176
|
EndorsementSubjectValidator: /* @__PURE__ */ __name(() => EndorsementSubjectValidator, "EndorsementSubjectValidator"),
|
|
177
|
+
EscrowPinStatusValidator: /* @__PURE__ */ __name(() => EscrowPinStatusValidator, "EscrowPinStatusValidator"),
|
|
171
178
|
EvidenceValidator: /* @__PURE__ */ __name(() => EvidenceValidator, "EvidenceValidator"),
|
|
172
179
|
FlatAuthGrantValidator: /* @__PURE__ */ __name(() => FlatAuthGrantValidator, "FlatAuthGrantValidator"),
|
|
173
180
|
FrameworkWithSkillsValidator: /* @__PURE__ */ __name(() => FrameworkWithSkillsValidator, "FrameworkWithSkillsValidator"),
|
|
@@ -249,6 +256,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
249
256
|
LaunchTypeValidator: /* @__PURE__ */ __name(() => LaunchTypeValidator, "LaunchTypeValidator"),
|
|
250
257
|
LearnCardRefreshAuthorizationValidator: /* @__PURE__ */ __name(() => LearnCardRefreshAuthorizationValidator, "LearnCardRefreshAuthorizationValidator"),
|
|
251
258
|
LinkProviderFrameworkInputValidator: /* @__PURE__ */ __name(() => LinkProviderFrameworkInputValidator, "LinkProviderFrameworkInputValidator"),
|
|
259
|
+
ListShareLinksInputValidator: /* @__PURE__ */ __name(() => ListShareLinksInputValidator, "ListShareLinksInputValidator"),
|
|
260
|
+
MAX_ENDORSEMENTS: /* @__PURE__ */ __name(() => MAX_ENDORSEMENTS, "MAX_ENDORSEMENTS"),
|
|
261
|
+
MAX_SELECTED_CREDENTIALS: /* @__PURE__ */ __name(() => MAX_SELECTED_CREDENTIALS, "MAX_SELECTED_CREDENTIALS"),
|
|
262
|
+
MAX_SHARE_CIPHERTEXT_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_CIPHERTEXT_BYTES, "MAX_SHARE_CIPHERTEXT_BYTES"),
|
|
263
|
+
MAX_SHARE_LINK_REQUEST_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_LINK_REQUEST_BYTES, "MAX_SHARE_LINK_REQUEST_BYTES"),
|
|
264
|
+
MAX_SHARE_RECOVERY_JWE_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_RECOVERY_JWE_BYTES, "MAX_SHARE_RECOVERY_JWE_BYTES"),
|
|
265
|
+
MAX_SHARE_SOURCE_URI_CHARS: /* @__PURE__ */ __name(() => MAX_SHARE_SOURCE_URI_CHARS, "MAX_SHARE_SOURCE_URI_CHARS"),
|
|
266
|
+
MAX_VP_MEMBERS: /* @__PURE__ */ __name(() => MAX_VP_MEMBERS, "MAX_VP_MEMBERS"),
|
|
252
267
|
ManagedCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshReceiptValidator, "ManagedCredentialRefreshReceiptValidator"),
|
|
253
268
|
ManagedCredentialRefreshServiceValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshServiceValidator, "ManagedCredentialRefreshServiceValidator"),
|
|
254
269
|
PaginatedAppStoreListingsValidator: /* @__PURE__ */ __name(() => PaginatedAppStoreListingsValidator, "PaginatedAppStoreListingsValidator"),
|
|
@@ -271,6 +286,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
271
286
|
PaginatedLCNProfileManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfileManagersValidator, "PaginatedLCNProfileManagersValidator"),
|
|
272
287
|
PaginatedLCNProfilesAndManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesAndManagersValidator, "PaginatedLCNProfilesAndManagersValidator"),
|
|
273
288
|
PaginatedLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesValidator, "PaginatedLCNProfilesValidator"),
|
|
289
|
+
PaginatedShareLinksValidator: /* @__PURE__ */ __name(() => PaginatedShareLinksValidator, "PaginatedShareLinksValidator"),
|
|
274
290
|
PaginatedSkillFrameworksValidator: /* @__PURE__ */ __name(() => PaginatedSkillFrameworksValidator, "PaginatedSkillFrameworksValidator"),
|
|
275
291
|
PaginatedSkillTreeValidator: /* @__PURE__ */ __name(() => PaginatedSkillTreeValidator, "PaginatedSkillTreeValidator"),
|
|
276
292
|
PaginatedVisibleLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedVisibleLCNProfilesValidator, "PaginatedVisibleLCNProfilesValidator"),
|
|
@@ -295,11 +311,20 @@ var require_types_cjs_development = __commonJS({
|
|
|
295
311
|
ReplaceSkillFrameworkSkillsInputValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsInputValidator, "ReplaceSkillFrameworkSkillsInputValidator"),
|
|
296
312
|
ReplaceSkillFrameworkSkillsResultValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsResultValidator, "ReplaceSkillFrameworkSkillsResultValidator"),
|
|
297
313
|
RequestLearnerContextEventValidator: /* @__PURE__ */ __name(() => RequestLearnerContextEventValidator, "RequestLearnerContextEventValidator"),
|
|
314
|
+
ResolveShareLinkInputValidator: /* @__PURE__ */ __name(() => ResolveShareLinkInputValidator, "ResolveShareLinkInputValidator"),
|
|
298
315
|
ResultDescriptionValidator: /* @__PURE__ */ __name(() => ResultDescriptionValidator, "ResultDescriptionValidator"),
|
|
299
316
|
ResultStatusTypeValidator: /* @__PURE__ */ __name(() => ResultStatusTypeValidator, "ResultStatusTypeValidator"),
|
|
300
317
|
ResultTypeValidator: /* @__PURE__ */ __name(() => ResultTypeValidator, "ResultTypeValidator"),
|
|
301
318
|
ResultValidator: /* @__PURE__ */ __name(() => ResultValidator, "ResultValidator"),
|
|
302
319
|
RubricCriterionValidator: /* @__PURE__ */ __name(() => RubricCriterionValidator, "RubricCriterionValidator"),
|
|
320
|
+
SHARE_AAD_PREFIX: /* @__PURE__ */ __name(() => SHARE_AAD_PREFIX, "SHARE_AAD_PREFIX"),
|
|
321
|
+
SHARE_CONTENT_KEY_BYTES: /* @__PURE__ */ __name(() => SHARE_CONTENT_KEY_BYTES, "SHARE_CONTENT_KEY_BYTES"),
|
|
322
|
+
SHARE_IV_BYTES: /* @__PURE__ */ __name(() => SHARE_IV_BYTES, "SHARE_IV_BYTES"),
|
|
323
|
+
SHARE_LINK_ID_BYTES: /* @__PURE__ */ __name(() => SHARE_LINK_ID_BYTES, "SHARE_LINK_ID_BYTES"),
|
|
324
|
+
SHARE_LINK_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_LINK_PROTOCOL, "SHARE_LINK_PROTOCOL"),
|
|
325
|
+
SHARE_RECOVERY_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_RECOVERY_PROTOCOL, "SHARE_RECOVERY_PROTOCOL"),
|
|
326
|
+
SHARE_TAG_BYTES: /* @__PURE__ */ __name(() => SHARE_TAG_BYTES, "SHARE_TAG_BYTES"),
|
|
327
|
+
SHARE_VIEW_RECEIPT_TTL_SECONDS: /* @__PURE__ */ __name(() => SHARE_VIEW_RECEIPT_TTL_SECONDS, "SHARE_VIEW_RECEIPT_TTL_SECONDS"),
|
|
303
328
|
SendAiSessionCredentialEventValidator: /* @__PURE__ */ __name(() => SendAiSessionCredentialEventValidator, "SendAiSessionCredentialEventValidator"),
|
|
304
329
|
SendBoostInputValidator: /* @__PURE__ */ __name(() => SendBoostInputValidator, "SendBoostInputValidator"),
|
|
305
330
|
SendBoostResponseValidator: /* @__PURE__ */ __name(() => SendBoostResponseValidator, "SendBoostResponseValidator"),
|
|
@@ -314,6 +339,32 @@ var require_types_cjs_development = __commonJS({
|
|
|
314
339
|
SentCredentialInfoValidator: /* @__PURE__ */ __name(() => SentCredentialInfoValidator, "SentCredentialInfoValidator"),
|
|
315
340
|
ServiceValidator: /* @__PURE__ */ __name(() => ServiceValidator, "ServiceValidator"),
|
|
316
341
|
SetPrimaryContactMethodValidator: /* @__PURE__ */ __name(() => SetPrimaryContactMethodValidator, "SetPrimaryContactMethodValidator"),
|
|
342
|
+
ShareCiphertextValidator: /* @__PURE__ */ __name(() => ShareCiphertextValidator, "ShareCiphertextValidator"),
|
|
343
|
+
ShareContentKeyValidator: /* @__PURE__ */ __name(() => ShareContentKeyValidator, "ShareContentKeyValidator"),
|
|
344
|
+
ShareContentStateValidator: /* @__PURE__ */ __name(() => ShareContentStateValidator, "ShareContentStateValidator"),
|
|
345
|
+
ShareEndorsementEntryValidator: /* @__PURE__ */ __name(() => ShareEndorsementEntryValidator, "ShareEndorsementEntryValidator"),
|
|
346
|
+
ShareEnvelopeValidator: /* @__PURE__ */ __name(() => ShareEnvelopeValidator, "ShareEnvelopeValidator"),
|
|
347
|
+
ShareIvValidator: /* @__PURE__ */ __name(() => ShareIvValidator, "ShareIvValidator"),
|
|
348
|
+
ShareLinkIdInputValidator: /* @__PURE__ */ __name(() => ShareLinkIdInputValidator, "ShareLinkIdInputValidator"),
|
|
349
|
+
ShareLinkIdValidator: /* @__PURE__ */ __name(() => ShareLinkIdValidator, "ShareLinkIdValidator"),
|
|
350
|
+
ShareLinkOperationKeyInputValidator: /* @__PURE__ */ __name(() => ShareLinkOperationKeyInputValidator, "ShareLinkOperationKeyInputValidator"),
|
|
351
|
+
ShareLinkOwnerCommitOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerCommitOutputValidator, "ShareLinkOwnerCommitOutputValidator"),
|
|
352
|
+
ShareLinkOwnerContentOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerContentOutputValidator, "ShareLinkOwnerContentOutputValidator"),
|
|
353
|
+
ShareLinkOwnerRecoveryOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerRecoveryOutputValidator, "ShareLinkOwnerRecoveryOutputValidator"),
|
|
354
|
+
ShareLinkOwnerStatusOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerStatusOutputValidator, "ShareLinkOwnerStatusOutputValidator"),
|
|
355
|
+
ShareLinkPublicContentViewValidator: /* @__PURE__ */ __name(() => ShareLinkPublicContentViewValidator, "ShareLinkPublicContentViewValidator"),
|
|
356
|
+
ShareLinkPublicStateValidator: /* @__PURE__ */ __name(() => ShareLinkPublicStateValidator, "ShareLinkPublicStateValidator"),
|
|
357
|
+
ShareLinkStatusValidator: /* @__PURE__ */ __name(() => ShareLinkStatusValidator, "ShareLinkStatusValidator"),
|
|
358
|
+
ShareLinkValidator: /* @__PURE__ */ __name(() => ShareLinkValidator, "ShareLinkValidator"),
|
|
359
|
+
ShareManifestPresentationValidator: /* @__PURE__ */ __name(() => ShareManifestPresentationValidator, "ShareManifestPresentationValidator"),
|
|
360
|
+
ShareOwnerRecoveryValidator: /* @__PURE__ */ __name(() => ShareOwnerRecoveryValidator, "ShareOwnerRecoveryValidator"),
|
|
361
|
+
SharePayloadValidator: /* @__PURE__ */ __name(() => SharePayloadValidator, "SharePayloadValidator"),
|
|
362
|
+
SharePayloadValidatorShape: /* @__PURE__ */ __name(() => SharePayloadValidatorShape, "SharePayloadValidatorShape"),
|
|
363
|
+
ShareRecoveryEndorsementValidator: /* @__PURE__ */ __name(() => ShareRecoveryEndorsementValidator, "ShareRecoveryEndorsementValidator"),
|
|
364
|
+
ShareRecoveryPlaintextValidator: /* @__PURE__ */ __name(() => ShareRecoveryPlaintextValidator, "ShareRecoveryPlaintextValidator"),
|
|
365
|
+
ShareRecoverySelectionValidator: /* @__PURE__ */ __name(() => ShareRecoverySelectionValidator, "ShareRecoverySelectionValidator"),
|
|
366
|
+
ShareSelectionEntryValidator: /* @__PURE__ */ __name(() => ShareSelectionEntryValidator, "ShareSelectionEntryValidator"),
|
|
367
|
+
ShareSharerValidator: /* @__PURE__ */ __name(() => ShareSharerValidator, "ShareSharerValidator"),
|
|
317
368
|
SkillDeletionStrategyValidator: /* @__PURE__ */ __name(() => SkillDeletionStrategyValidator, "SkillDeletionStrategyValidator"),
|
|
318
369
|
SkillFrameworkAdminInputValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminInputValidator, "SkillFrameworkAdminInputValidator"),
|
|
319
370
|
SkillFrameworkAdminsValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminsValidator, "SkillFrameworkAdminsValidator"),
|
|
@@ -345,6 +396,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
345
396
|
UnsignedVCValidator: /* @__PURE__ */ __name(() => UnsignedVCValidator, "UnsignedVCValidator"),
|
|
346
397
|
UnsignedVPValidator: /* @__PURE__ */ __name(() => UnsignedVPValidator, "UnsignedVPValidator"),
|
|
347
398
|
UpdateFrameworkInputValidator: /* @__PURE__ */ __name(() => UpdateFrameworkInputValidator, "UpdateFrameworkInputValidator"),
|
|
399
|
+
UpdateShareLinkInputValidator: /* @__PURE__ */ __name(() => UpdateShareLinkInputValidator, "UpdateShareLinkInputValidator"),
|
|
348
400
|
UpdateSkillInputValidator: /* @__PURE__ */ __name(() => UpdateSkillInputValidator, "UpdateSkillInputValidator"),
|
|
349
401
|
VC2EvidenceValidator: /* @__PURE__ */ __name(() => VC2EvidenceValidator, "VC2EvidenceValidator"),
|
|
350
402
|
VCValidator: /* @__PURE__ */ __name(() => VCValidator, "VCValidator"),
|
|
@@ -356,7 +408,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
356
408
|
VerificationStatusValidator: /* @__PURE__ */ __name(() => VerificationStatusValidator, "VerificationStatusValidator"),
|
|
357
409
|
appStoreActionValidator: /* @__PURE__ */ __name(() => appStoreActionValidator, "appStoreActionValidator"),
|
|
358
410
|
capgoUpdateActionValidator: /* @__PURE__ */ __name(() => capgoUpdateActionValidator, "capgoUpdateActionValidator"),
|
|
411
|
+
classifyShareManifest: /* @__PURE__ */ __name(() => classifyShareManifest, "classifyShareManifest"),
|
|
412
|
+
decodeBase64Url: /* @__PURE__ */ __name(() => decodeBase64Url, "decodeBase64Url"),
|
|
413
|
+
decodedBase64UrlByteLength: /* @__PURE__ */ __name(() => decodedBase64UrlByteLength, "decodedBase64UrlByteLength"),
|
|
359
414
|
dismissActionValidator: /* @__PURE__ */ __name(() => dismissActionValidator, "dismissActionValidator"),
|
|
415
|
+
encodeBase64Url: /* @__PURE__ */ __name(() => encodeBase64Url, "encodeBase64Url"),
|
|
416
|
+
escrowPinMismatchMessage: /* @__PURE__ */ __name(() => escrowPinMismatchMessage, "escrowPinMismatchMessage"),
|
|
360
417
|
externalLinkActionValidator: /* @__PURE__ */ __name(() => externalLinkActionValidator, "externalLinkActionValidator"),
|
|
361
418
|
inAppMessageActionStyleValidator: /* @__PURE__ */ __name(() => inAppMessageActionStyleValidator, "inAppMessageActionStyleValidator"),
|
|
362
419
|
inAppMessageActionTargetValidator: /* @__PURE__ */ __name(() => inAppMessageActionTargetValidator, "inAppMessageActionTargetValidator"),
|
|
@@ -371,10 +428,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
371
428
|
inAppMessageVersionSourceValidator: /* @__PURE__ */ __name(() => inAppMessageVersionSourceValidator, "inAppMessageVersionSourceValidator"),
|
|
372
429
|
inAppMessagesFlagValidator: /* @__PURE__ */ __name(() => inAppMessagesFlagValidator, "inAppMessagesFlagValidator"),
|
|
373
430
|
internalLinkActionValidator: /* @__PURE__ */ __name(() => internalLinkActionValidator, "internalLinkActionValidator"),
|
|
431
|
+
isCanonicalBase64Url: /* @__PURE__ */ __name(() => isCanonicalBase64Url, "isCanonicalBase64Url"),
|
|
374
432
|
isStoredCredentialEnvelope: /* @__PURE__ */ __name(() => isStoredCredentialEnvelope, "isStoredCredentialEnvelope"),
|
|
375
433
|
parseInAppMessagesFlag: /* @__PURE__ */ __name(() => parseInAppMessagesFlag, "parseInAppMessagesFlag"),
|
|
376
434
|
platformPredicateValidator: /* @__PURE__ */ __name(() => platformPredicateValidator, "platformPredicateValidator"),
|
|
377
435
|
rolePredicateValidator: /* @__PURE__ */ __name(() => rolePredicateValidator, "rolePredicateValidator"),
|
|
436
|
+
utf8ByteLength: /* @__PURE__ */ __name(() => utf8ByteLength, "utf8ByteLength"),
|
|
378
437
|
versionPredicateValidator: /* @__PURE__ */ __name(() => versionPredicateValidator, "versionPredicateValidator")
|
|
379
438
|
});
|
|
380
439
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -18011,6 +18070,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
18011
18070
|
__name2(this, "AuthSessionError");
|
|
18012
18071
|
}
|
|
18013
18072
|
};
|
|
18073
|
+
var ESCROW_PIN_LOCKED_MESSAGE2 = "Too many incorrect PIN attempts. You can still recover by waiting.";
|
|
18074
|
+
var ESCROW_PIN_UNAVAILABLE_MESSAGE2 = "PIN recovery is not available for this account.";
|
|
18075
|
+
var ESCROW_PIN_MISMATCH_PATTERN2 = /^Incorrect PIN\. (\d+) attempts left\.$/;
|
|
18076
|
+
var escrowPinMismatchMessage = /* @__PURE__ */ __name2((attemptsRemaining) => `Incorrect PIN. ${attemptsRemaining} attempts left.`, "escrowPinMismatchMessage");
|
|
18077
|
+
var EscrowPinStatusValidator = external_exports.object({
|
|
18078
|
+
state: external_exports.enum(["none", "enabled", "locked", "stale"]),
|
|
18079
|
+
enabled: external_exports.boolean(),
|
|
18080
|
+
attemptsRemaining: external_exports.number().int().nonnegative(),
|
|
18081
|
+
salt: external_exports.string().optional()
|
|
18082
|
+
});
|
|
18014
18083
|
var BITSTRING_STATUS_PURPOSES = ["revocation", "suspension"];
|
|
18015
18084
|
var DEFAULT_BITSTRING_STATUS_LIST_SIZE = 131072;
|
|
18016
18085
|
var BitstringStatusPurposeValidator = external_exports.enum(BITSTRING_STATUS_PURPOSES);
|
|
@@ -18125,6 +18194,458 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
18125
18194
|
const result = inAppMessagesFlagValidator.safeParse(raw);
|
|
18126
18195
|
return result.success ? result.data : EMPTY_IN_APP_MESSAGES_FLAG;
|
|
18127
18196
|
}, "parseInAppMessagesFlag");
|
|
18197
|
+
var SHARE_LINK_ID_BYTES = 16;
|
|
18198
|
+
var SHARE_CONTENT_KEY_BYTES = 32;
|
|
18199
|
+
var SHARE_IV_BYTES = 12;
|
|
18200
|
+
var SHARE_TAG_BYTES = 16;
|
|
18201
|
+
var MAX_SHARE_CIPHERTEXT_BYTES = 512 * 1024;
|
|
18202
|
+
var MAX_SHARE_RECOVERY_JWE_BYTES = 64 * 1024;
|
|
18203
|
+
var MAX_SELECTED_CREDENTIALS = 50;
|
|
18204
|
+
var MAX_ENDORSEMENTS = 200;
|
|
18205
|
+
var MAX_VP_MEMBERS = MAX_SELECTED_CREDENTIALS + MAX_ENDORSEMENTS;
|
|
18206
|
+
var MAX_SHARE_SOURCE_URI_CHARS = 2048;
|
|
18207
|
+
var MAX_SHARE_LINK_REQUEST_BYTES = 1024 * 1024;
|
|
18208
|
+
var SHARE_LINK_PROTOCOL = "lc-share/v1";
|
|
18209
|
+
var SHARE_RECOVERY_PROTOCOL = "lc-share-recovery/v1";
|
|
18210
|
+
var SHARE_AAD_PREFIX = "lc-share:v1";
|
|
18211
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
18212
|
+
var BASE64URL_LOOKUP = (() => {
|
|
18213
|
+
const table = new Int16Array(128).fill(-1);
|
|
18214
|
+
for (let index = 0; index < BASE64URL_ALPHABET.length; index += 1) {
|
|
18215
|
+
table[BASE64URL_ALPHABET.charCodeAt(index)] = index;
|
|
18216
|
+
}
|
|
18217
|
+
return table;
|
|
18218
|
+
})();
|
|
18219
|
+
var encodeBase64Url = /* @__PURE__ */ __name2((bytes) => {
|
|
18220
|
+
let output = "";
|
|
18221
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
18222
|
+
const first = bytes[index];
|
|
18223
|
+
const second = bytes[index + 1];
|
|
18224
|
+
const third = bytes[index + 2];
|
|
18225
|
+
output += BASE64URL_ALPHABET[first >> 2];
|
|
18226
|
+
output += BASE64URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
|
|
18227
|
+
if (second === void 0) break;
|
|
18228
|
+
output += BASE64URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
|
|
18229
|
+
if (third === void 0) break;
|
|
18230
|
+
output += BASE64URL_ALPHABET[third & 63];
|
|
18231
|
+
}
|
|
18232
|
+
return output;
|
|
18233
|
+
}, "encodeBase64Url");
|
|
18234
|
+
var decodeBase64Url = /* @__PURE__ */ __name2((value) => {
|
|
18235
|
+
if (typeof value !== "string") return null;
|
|
18236
|
+
const output = new Uint8Array(Math.floor(value.length * 6 / 8));
|
|
18237
|
+
let bits = 0;
|
|
18238
|
+
let bitCount = 0;
|
|
18239
|
+
let outputIndex = 0;
|
|
18240
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
18241
|
+
const code = value.charCodeAt(index);
|
|
18242
|
+
const digit = code < 128 ? BASE64URL_LOOKUP[code] : -1;
|
|
18243
|
+
if (digit < 0) return null;
|
|
18244
|
+
bits = bits << 6 | digit;
|
|
18245
|
+
bitCount += 6;
|
|
18246
|
+
if (bitCount >= 8) {
|
|
18247
|
+
bitCount -= 8;
|
|
18248
|
+
output[outputIndex] = bits >> bitCount & 255;
|
|
18249
|
+
outputIndex += 1;
|
|
18250
|
+
}
|
|
18251
|
+
}
|
|
18252
|
+
return output;
|
|
18253
|
+
}, "decodeBase64Url");
|
|
18254
|
+
var isCanonicalBase64Url = /* @__PURE__ */ __name2((value, expectedBytes) => {
|
|
18255
|
+
if (typeof value !== "string") return false;
|
|
18256
|
+
if (expectedBytes !== void 0 && value.length !== Math.ceil(expectedBytes * 4 / 3))
|
|
18257
|
+
return false;
|
|
18258
|
+
const decoded = decodeBase64Url(value);
|
|
18259
|
+
if (decoded === null) return false;
|
|
18260
|
+
if (expectedBytes !== void 0 && decoded.length !== expectedBytes) return false;
|
|
18261
|
+
return encodeBase64Url(decoded) === value;
|
|
18262
|
+
}, "isCanonicalBase64Url");
|
|
18263
|
+
var decodedBase64UrlByteLength = /* @__PURE__ */ __name2((value) => decodeBase64Url(value)?.length ?? null, "decodedBase64UrlByteLength");
|
|
18264
|
+
var utf8ByteLength = /* @__PURE__ */ __name2((value) => new TextEncoder().encode(value).length, "utf8ByteLength");
|
|
18265
|
+
var canonicalBase64Url = /* @__PURE__ */ __name2((bytes) => external_exports.string().refine((value) => isCanonicalBase64Url(value, bytes), {
|
|
18266
|
+
message: `must be canonical base64url for exactly ${bytes} bytes`
|
|
18267
|
+
}), "canonicalBase64Url");
|
|
18268
|
+
var ShareLinkIdValidator = canonicalBase64Url(SHARE_LINK_ID_BYTES);
|
|
18269
|
+
var ShareContentKeyValidator = canonicalBase64Url(SHARE_CONTENT_KEY_BYTES);
|
|
18270
|
+
var ShareIvValidator = canonicalBase64Url(SHARE_IV_BYTES);
|
|
18271
|
+
var ShareCiphertextValidator = external_exports.string().refine(
|
|
18272
|
+
(value) => {
|
|
18273
|
+
if (value.length > Math.ceil(MAX_SHARE_CIPHERTEXT_BYTES * 4 / 3)) return false;
|
|
18274
|
+
const decoded = decodeBase64Url(value);
|
|
18275
|
+
if (decoded === null) return false;
|
|
18276
|
+
if (encodeBase64Url(decoded) !== value) return false;
|
|
18277
|
+
return decoded.length >= SHARE_TAG_BYTES && decoded.length <= MAX_SHARE_CIPHERTEXT_BYTES;
|
|
18278
|
+
},
|
|
18279
|
+
{
|
|
18280
|
+
message: `must be canonical base64url decoding to ${SHARE_TAG_BYTES}..${MAX_SHARE_CIPHERTEXT_BYTES} bytes`
|
|
18281
|
+
}
|
|
18282
|
+
);
|
|
18283
|
+
var ShareEnvelopeValidator = external_exports.object({
|
|
18284
|
+
v: external_exports.literal(1),
|
|
18285
|
+
alg: external_exports.literal("A256GCM"),
|
|
18286
|
+
iv: ShareIvValidator,
|
|
18287
|
+
ct: ShareCiphertextValidator
|
|
18288
|
+
}).strict();
|
|
18289
|
+
var safeVersion = external_exports.number().int().min(1).max(2 ** 31 - 1);
|
|
18290
|
+
var safeCredentialIndex = external_exports.number().int().min(0).max(MAX_VP_MEMBERS - 1);
|
|
18291
|
+
var ShareSelectionEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex }).strict();
|
|
18292
|
+
var ShareEndorsementEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex, targetCredentialIndex: safeCredentialIndex }).strict();
|
|
18293
|
+
var ShareSharerValidator = external_exports.object({
|
|
18294
|
+
profileId: external_exports.string().min(1).max(128),
|
|
18295
|
+
displayName: external_exports.string().min(1).max(120),
|
|
18296
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18297
|
+
}).strict();
|
|
18298
|
+
var presentationShape = VPValidator.extend({
|
|
18299
|
+
verifiableCredential: VCValidator.array().min(1).max(MAX_VP_MEMBERS)
|
|
18300
|
+
});
|
|
18301
|
+
var hasBoundedPresentation = /* @__PURE__ */ __name2((value) => {
|
|
18302
|
+
let credentialCount = 0;
|
|
18303
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
18304
|
+
const visit = /* @__PURE__ */ __name2((node, depth) => {
|
|
18305
|
+
if (node === null || typeof node !== "object") return true;
|
|
18306
|
+
if (depth > 32 || ancestors.has(node)) return false;
|
|
18307
|
+
ancestors.add(node);
|
|
18308
|
+
try {
|
|
18309
|
+
for (const [key, child] of Object.entries(node)) {
|
|
18310
|
+
if (key === "verifiableCredential") {
|
|
18311
|
+
credentialCount += Array.isArray(child) ? child.length : 1;
|
|
18312
|
+
if (credentialCount > MAX_VP_MEMBERS) return false;
|
|
18313
|
+
}
|
|
18314
|
+
if (!visit(child, depth + 1)) return false;
|
|
18315
|
+
}
|
|
18316
|
+
return true;
|
|
18317
|
+
} finally {
|
|
18318
|
+
ancestors.delete(node);
|
|
18319
|
+
}
|
|
18320
|
+
}, "visit");
|
|
18321
|
+
return visit(value, 0);
|
|
18322
|
+
}, "hasBoundedPresentation");
|
|
18323
|
+
var ShareManifestPresentationValidator = external_exports.custom(
|
|
18324
|
+
(value) => hasBoundedPresentation(value) && presentationShape.safeParse(value).success,
|
|
18325
|
+
{ message: "invalid share presentation" }
|
|
18326
|
+
);
|
|
18327
|
+
var SharePayloadValidatorShape = external_exports.object({
|
|
18328
|
+
protocol: external_exports.literal(SHARE_LINK_PROTOCOL),
|
|
18329
|
+
shareId: ShareLinkIdValidator,
|
|
18330
|
+
contentVersion: safeVersion,
|
|
18331
|
+
createdAt: external_exports.iso.datetime(),
|
|
18332
|
+
sharer: ShareSharerValidator,
|
|
18333
|
+
presentation: ShareManifestPresentationValidator,
|
|
18334
|
+
selection: ShareSelectionEntryValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18335
|
+
endorsements: ShareEndorsementEntryValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18336
|
+
}).strict();
|
|
18337
|
+
var classificationFailure = /* @__PURE__ */ __name2((code, message, path = []) => ({ ok: false, code, message, path }), "classificationFailure");
|
|
18338
|
+
var readSignedClaimId = /* @__PURE__ */ __name2((member) => {
|
|
18339
|
+
if (member === null || typeof member !== "object") return void 0;
|
|
18340
|
+
const record2 = member;
|
|
18341
|
+
const claim = record2.credentialSubject ?? record2.endorsement;
|
|
18342
|
+
if (claim === null || typeof claim !== "object") return void 0;
|
|
18343
|
+
const claimRecord = claim;
|
|
18344
|
+
const nested = claimRecord.endorsement;
|
|
18345
|
+
const nestedId = nested !== null && typeof nested === "object" ? nested.id : void 0;
|
|
18346
|
+
return claimRecord.id ?? nestedId;
|
|
18347
|
+
}, "readSignedClaimId");
|
|
18348
|
+
var classifyShareManifest = /* @__PURE__ */ __name2((payload) => {
|
|
18349
|
+
const members = payload.presentation?.verifiableCredential;
|
|
18350
|
+
if (!Array.isArray(members) || members.length === 0) {
|
|
18351
|
+
return classificationFailure("NO_PRESENTATION_MEMBERS", "presentation has no credentials");
|
|
18352
|
+
}
|
|
18353
|
+
if (members.length > MAX_VP_MEMBERS) {
|
|
18354
|
+
return classificationFailure(
|
|
18355
|
+
"TOO_MANY_VP_MEMBERS",
|
|
18356
|
+
`presentation has ${members.length} members, over the ${MAX_VP_MEMBERS} bound`
|
|
18357
|
+
);
|
|
18358
|
+
}
|
|
18359
|
+
const selection = payload.selection;
|
|
18360
|
+
const endorsements = payload.endorsements ?? [];
|
|
18361
|
+
if (!Array.isArray(selection) || selection.length < 1 || selection.length > MAX_SELECTED_CREDENTIALS) {
|
|
18362
|
+
return classificationFailure("BAD_SELECTION", "selection must contain 1..50 entries");
|
|
18363
|
+
}
|
|
18364
|
+
if (!Array.isArray(endorsements) || endorsements.length > MAX_ENDORSEMENTS) {
|
|
18365
|
+
return classificationFailure(
|
|
18366
|
+
"BAD_ENDORSEMENTS",
|
|
18367
|
+
"endorsements must contain at most 200 entries"
|
|
18368
|
+
);
|
|
18369
|
+
}
|
|
18370
|
+
const selectedIndices = [];
|
|
18371
|
+
const selected = /* @__PURE__ */ new Set();
|
|
18372
|
+
for (let index = 0; index < selection.length; index += 1) {
|
|
18373
|
+
const entry = selection[index];
|
|
18374
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18375
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18376
|
+
return classificationFailure(
|
|
18377
|
+
"SELECTION_INDEX_OUT_OF_BOUNDS",
|
|
18378
|
+
`selection[${index}].credentialIndex is out of bounds`,
|
|
18379
|
+
["selection", index, "credentialIndex"]
|
|
18380
|
+
);
|
|
18381
|
+
}
|
|
18382
|
+
if (selected.has(credentialIndex)) {
|
|
18383
|
+
return classificationFailure(
|
|
18384
|
+
"SELECTION_INDEX_DUPLICATE",
|
|
18385
|
+
`selection[${index}].credentialIndex is duplicated`,
|
|
18386
|
+
["selection", index, "credentialIndex"]
|
|
18387
|
+
);
|
|
18388
|
+
}
|
|
18389
|
+
selected.add(credentialIndex);
|
|
18390
|
+
selectedIndices.push(credentialIndex);
|
|
18391
|
+
}
|
|
18392
|
+
const classified = new Set(selected);
|
|
18393
|
+
const endorsementIndices = [];
|
|
18394
|
+
for (let index = 0; index < endorsements.length; index += 1) {
|
|
18395
|
+
const entry = endorsements[index];
|
|
18396
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18397
|
+
const targetIndex = entry?.targetCredentialIndex;
|
|
18398
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18399
|
+
return classificationFailure(
|
|
18400
|
+
"ENDORSEMENT_INDEX_OUT_OF_BOUNDS",
|
|
18401
|
+
`endorsements[${index}].credentialIndex is out of bounds`,
|
|
18402
|
+
["endorsements", index, "credentialIndex"]
|
|
18403
|
+
);
|
|
18404
|
+
}
|
|
18405
|
+
if (classified.has(credentialIndex)) {
|
|
18406
|
+
return classificationFailure(
|
|
18407
|
+
"VP_MEMBER_CLASSIFIED_TWICE",
|
|
18408
|
+
`endorsements[${index}].credentialIndex was already classified`,
|
|
18409
|
+
["endorsements", index, "credentialIndex"]
|
|
18410
|
+
);
|
|
18411
|
+
}
|
|
18412
|
+
if (!selected.has(targetIndex)) {
|
|
18413
|
+
return classificationFailure(
|
|
18414
|
+
"ENDORSEMENT_TARGET_NOT_SELECTED",
|
|
18415
|
+
`endorsements[${index}].targetCredentialIndex must reference a selected credential`,
|
|
18416
|
+
["endorsements", index, "targetCredentialIndex"]
|
|
18417
|
+
);
|
|
18418
|
+
}
|
|
18419
|
+
const referenced = readSignedClaimId(members[credentialIndex]);
|
|
18420
|
+
const target = members[targetIndex];
|
|
18421
|
+
const targetId = target !== null && typeof target === "object" ? target.id : void 0;
|
|
18422
|
+
if (typeof referenced !== "string" || typeof targetId !== "string" || referenced !== targetId) {
|
|
18423
|
+
return classificationFailure(
|
|
18424
|
+
"ENDORSEMENT_TARGET_UNBOUND",
|
|
18425
|
+
`endorsements[${index}] claim does not reference the target credential id`,
|
|
18426
|
+
["endorsements", index]
|
|
18427
|
+
);
|
|
18428
|
+
}
|
|
18429
|
+
classified.add(credentialIndex);
|
|
18430
|
+
endorsementIndices.push(credentialIndex);
|
|
18431
|
+
}
|
|
18432
|
+
if (classified.size !== members.length) {
|
|
18433
|
+
return classificationFailure(
|
|
18434
|
+
"UNCLASSIFIED_VP_MEMBER",
|
|
18435
|
+
"every presentation member must be classified exactly once",
|
|
18436
|
+
["presentation", "verifiableCredential"]
|
|
18437
|
+
);
|
|
18438
|
+
}
|
|
18439
|
+
return {
|
|
18440
|
+
ok: true,
|
|
18441
|
+
classification: { selectedIndices, endorsementIndices, memberCount: members.length }
|
|
18442
|
+
};
|
|
18443
|
+
}, "classifyShareManifest");
|
|
18444
|
+
var SharePayloadValidator = SharePayloadValidatorShape.superRefine((payload, ctx) => {
|
|
18445
|
+
const result = classifyShareManifest(payload);
|
|
18446
|
+
if (!result.ok) {
|
|
18447
|
+
ctx.addIssue({
|
|
18448
|
+
code: "custom",
|
|
18449
|
+
message: result.message,
|
|
18450
|
+
path: result.path,
|
|
18451
|
+
params: { shareLinkCode: result.code }
|
|
18452
|
+
});
|
|
18453
|
+
}
|
|
18454
|
+
});
|
|
18455
|
+
var ShareRecoverySelectionValidator = external_exports.object({
|
|
18456
|
+
ref: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS),
|
|
18457
|
+
order: external_exports.number().int().min(0).max(MAX_SELECTED_CREDENTIALS - 1)
|
|
18458
|
+
}).strict();
|
|
18459
|
+
var ShareRecoveryEndorsementValidator = external_exports.object({ targetRef: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS) }).strict();
|
|
18460
|
+
var ShareRecoveryPlaintextValidator = external_exports.object({
|
|
18461
|
+
protocol: external_exports.literal(SHARE_RECOVERY_PROTOCOL),
|
|
18462
|
+
shareId: ShareLinkIdValidator,
|
|
18463
|
+
ownerProfileId: external_exports.string().min(1).max(128),
|
|
18464
|
+
createdAt: external_exports.iso.datetime(),
|
|
18465
|
+
latest: external_exports.object({ contentVersion: safeVersion, key: ShareContentKeyValidator }).strict(),
|
|
18466
|
+
selection: ShareRecoverySelectionValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18467
|
+
endorsements: ShareRecoveryEndorsementValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18468
|
+
}).strict().superRefine((recovery, ctx) => {
|
|
18469
|
+
const orders = /* @__PURE__ */ new Set();
|
|
18470
|
+
recovery.selection.forEach((entry, index) => {
|
|
18471
|
+
if (entry.order >= recovery.selection.length) {
|
|
18472
|
+
ctx.addIssue({
|
|
18473
|
+
code: "custom",
|
|
18474
|
+
message: "order must be within the selection bounds",
|
|
18475
|
+
path: ["selection", index, "order"]
|
|
18476
|
+
});
|
|
18477
|
+
}
|
|
18478
|
+
if (orders.has(entry.order)) {
|
|
18479
|
+
ctx.addIssue({
|
|
18480
|
+
code: "custom",
|
|
18481
|
+
message: "order must be unique within the selection",
|
|
18482
|
+
path: ["selection", index, "order"]
|
|
18483
|
+
});
|
|
18484
|
+
}
|
|
18485
|
+
orders.add(entry.order);
|
|
18486
|
+
});
|
|
18487
|
+
});
|
|
18488
|
+
var ShareOwnerRecoveryValidator = JWEValidator.refine(
|
|
18489
|
+
(value) => utf8ByteLength(JSON.stringify(value)) <= MAX_SHARE_RECOVERY_JWE_BYTES,
|
|
18490
|
+
{ message: `serialized recovery JWE must be at most ${MAX_SHARE_RECOVERY_JWE_BYTES} bytes` }
|
|
18491
|
+
);
|
|
18492
|
+
var ShareLinkStatusValidator = external_exports.enum(["pending", "active", "stopped"]);
|
|
18493
|
+
var ShareContentStateValidator = external_exports.enum(["staging", "finalized", "content_missing"]);
|
|
18494
|
+
var ShareLinkValidator = external_exports.object({
|
|
18495
|
+
id: ShareLinkIdValidator,
|
|
18496
|
+
title: external_exports.string().min(1).max(120),
|
|
18497
|
+
note: external_exports.string().max(500).optional(),
|
|
18498
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18499
|
+
version: safeVersion,
|
|
18500
|
+
contentVersion: safeVersion,
|
|
18501
|
+
status: ShareLinkStatusValidator,
|
|
18502
|
+
contentState: ShareContentStateValidator,
|
|
18503
|
+
createdAt: external_exports.iso.datetime(),
|
|
18504
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18505
|
+
expiresAt: external_exports.iso.datetime().nullable(),
|
|
18506
|
+
stoppedAt: external_exports.iso.datetime().nullable(),
|
|
18507
|
+
lastViewedAt: external_exports.iso.datetime().nullable(),
|
|
18508
|
+
viewCount: external_exports.number().int().min(0).optional(),
|
|
18509
|
+
passcodeProtected: external_exports.boolean(),
|
|
18510
|
+
notifyOnView: external_exports.boolean(),
|
|
18511
|
+
minorPolicy: external_exports.object({
|
|
18512
|
+
isMinor: external_exports.boolean().nullable(),
|
|
18513
|
+
policyResolved: external_exports.boolean(),
|
|
18514
|
+
defaultExpiryDays: external_exports.union([external_exports.literal(30), external_exports.literal(365)]),
|
|
18515
|
+
viewCountingEnabled: external_exports.boolean()
|
|
18516
|
+
}),
|
|
18517
|
+
/**
|
|
18518
|
+
* Guarded application/API URL for the content. Optional because owner APIs in
|
|
18519
|
+
* D1 have no public resolve/content endpoint yet; it must never be a
|
|
18520
|
+
* LearnCloud object URL or carry a fabricated decryption key.
|
|
18521
|
+
*/
|
|
18522
|
+
contentUrl: external_exports.string().optional()
|
|
18523
|
+
});
|
|
18524
|
+
var clientRequestId = external_exports.string().uuid();
|
|
18525
|
+
var CreateShareLinkInputValidator = external_exports.object({
|
|
18526
|
+
id: ShareLinkIdValidator,
|
|
18527
|
+
clientRequestId,
|
|
18528
|
+
title: external_exports.string().min(1).max(120),
|
|
18529
|
+
note: external_exports.string().max(500).optional(),
|
|
18530
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18531
|
+
passcode: external_exports.string().min(8).max(64).optional(),
|
|
18532
|
+
notifyOnView: external_exports.boolean().default(false),
|
|
18533
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18534
|
+
contentVersion: external_exports.literal(1),
|
|
18535
|
+
envelope: ShareEnvelopeValidator,
|
|
18536
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator
|
|
18537
|
+
}).strict();
|
|
18538
|
+
var UpdateShareLinkInputValidator = external_exports.object({
|
|
18539
|
+
id: ShareLinkIdValidator,
|
|
18540
|
+
expectedVersion: safeVersion,
|
|
18541
|
+
clientRequestId,
|
|
18542
|
+
title: external_exports.string().min(1).max(120).optional(),
|
|
18543
|
+
note: external_exports.string().max(500).nullable().optional(),
|
|
18544
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18545
|
+
/** Omitted preserves the current passcode, null removes it, and a string replaces it. */
|
|
18546
|
+
passcode: external_exports.string().min(8).max(64).nullable().optional(),
|
|
18547
|
+
notifyOnView: external_exports.boolean().optional(),
|
|
18548
|
+
contentVersion: safeVersion.optional(),
|
|
18549
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS).optional(),
|
|
18550
|
+
envelope: ShareEnvelopeValidator.optional(),
|
|
18551
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator.optional()
|
|
18552
|
+
}).strict().superRefine((value, ctx) => {
|
|
18553
|
+
const provided = [
|
|
18554
|
+
value.contentVersion,
|
|
18555
|
+
value.selectedCount,
|
|
18556
|
+
value.envelope,
|
|
18557
|
+
value.ownerEncryptedRecovery
|
|
18558
|
+
].filter((field) => field !== void 0).length;
|
|
18559
|
+
if (provided !== 0 && provided !== 4) {
|
|
18560
|
+
ctx.addIssue({
|
|
18561
|
+
code: "custom",
|
|
18562
|
+
message: "content replacement requires contentVersion, selectedCount, envelope and ownerEncryptedRecovery together"
|
|
18563
|
+
});
|
|
18564
|
+
}
|
|
18565
|
+
});
|
|
18566
|
+
var ShareLinkIdInputValidator = external_exports.object({ id: ShareLinkIdValidator }).strict();
|
|
18567
|
+
var ShareLinkOperationKeyInputValidator = external_exports.object({ id: ShareLinkIdValidator, operationId: external_exports.string().uuid() }).strict();
|
|
18568
|
+
var ShareLinkOwnerCommitOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18569
|
+
external_exports.object({ status: external_exports.literal("completed"), share: ShareLinkValidator }).strict(),
|
|
18570
|
+
external_exports.object({
|
|
18571
|
+
status: external_exports.literal("pending"),
|
|
18572
|
+
id: ShareLinkIdValidator,
|
|
18573
|
+
operationId: external_exports.string().uuid()
|
|
18574
|
+
}).strict()
|
|
18575
|
+
]);
|
|
18576
|
+
var ShareLinkOwnerStatusOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18577
|
+
external_exports.object({ status: external_exports.literal("found"), share: ShareLinkValidator }).strict(),
|
|
18578
|
+
external_exports.object({
|
|
18579
|
+
status: external_exports.literal("pending"),
|
|
18580
|
+
id: ShareLinkIdValidator,
|
|
18581
|
+
operationId: external_exports.string().uuid()
|
|
18582
|
+
}).strict(),
|
|
18583
|
+
external_exports.object({ status: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
18584
|
+
]);
|
|
18585
|
+
var ShareLinkOwnerRecoveryOutputValidator = external_exports.object({ recovery: ShareOwnerRecoveryValidator }).strict();
|
|
18586
|
+
var ShareLinkOwnerContentOutputValidator = external_exports.object({
|
|
18587
|
+
id: ShareLinkIdValidator,
|
|
18588
|
+
contentVersion: safeVersion,
|
|
18589
|
+
envelope: ShareEnvelopeValidator
|
|
18590
|
+
}).strict();
|
|
18591
|
+
var ResolveShareLinkInputValidator = external_exports.object({
|
|
18592
|
+
id: ShareLinkIdValidator,
|
|
18593
|
+
/** Sent only in a POST body; it must never be placed in a share URL. */
|
|
18594
|
+
passcode: external_exports.string().min(4).max(64).optional()
|
|
18595
|
+
}).strict();
|
|
18596
|
+
var ShareLinkPublicStateValidator = external_exports.discriminatedUnion("state", [
|
|
18597
|
+
external_exports.object({
|
|
18598
|
+
state: external_exports.literal("passcode_required"),
|
|
18599
|
+
id: ShareLinkIdValidator
|
|
18600
|
+
}).strict(),
|
|
18601
|
+
external_exports.object({ state: external_exports.literal("try_later"), id: ShareLinkIdValidator }).strict(),
|
|
18602
|
+
external_exports.object({
|
|
18603
|
+
state: external_exports.literal("active"),
|
|
18604
|
+
id: ShareLinkIdValidator,
|
|
18605
|
+
title: external_exports.string().max(120),
|
|
18606
|
+
note: external_exports.string().max(500).optional(),
|
|
18607
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18608
|
+
contentVersion: safeVersion,
|
|
18609
|
+
contentUrl: external_exports.string(),
|
|
18610
|
+
sharer: external_exports.object({
|
|
18611
|
+
displayName: external_exports.string().max(120),
|
|
18612
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18613
|
+
}),
|
|
18614
|
+
createdAt: external_exports.iso.datetime(),
|
|
18615
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18616
|
+
expiresAt: external_exports.iso.datetime().nullable()
|
|
18617
|
+
}).strict(),
|
|
18618
|
+
external_exports.object({
|
|
18619
|
+
state: external_exports.literal("expired"),
|
|
18620
|
+
id: ShareLinkIdValidator,
|
|
18621
|
+
expiresAt: external_exports.iso.datetime()
|
|
18622
|
+
}).strict(),
|
|
18623
|
+
external_exports.object({
|
|
18624
|
+
state: external_exports.literal("stopped"),
|
|
18625
|
+
id: ShareLinkIdValidator,
|
|
18626
|
+
stoppedAt: external_exports.iso.datetime()
|
|
18627
|
+
}).strict(),
|
|
18628
|
+
external_exports.object({ state: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
18629
|
+
]);
|
|
18630
|
+
var ListShareLinksInputValidator = external_exports.object({
|
|
18631
|
+
limit: external_exports.number().int().min(1).max(50).default(25),
|
|
18632
|
+
cursor: external_exports.string().max(512).optional()
|
|
18633
|
+
}).strict();
|
|
18634
|
+
var PaginatedShareLinksValidator = PaginationResponseValidator.extend({
|
|
18635
|
+
records: ShareLinkValidator.array()
|
|
18636
|
+
});
|
|
18637
|
+
var AcknowledgeViewInputValidator = external_exports.object({
|
|
18638
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18639
|
+
}).strict();
|
|
18640
|
+
var AcknowledgeViewOutputValidator = external_exports.object({ ok: external_exports.literal(true) }).strict();
|
|
18641
|
+
var SHARE_VIEW_RECEIPT_TTL_SECONDS = 10 * 60;
|
|
18642
|
+
var ShareLinkPublicContentViewValidator = external_exports.object({
|
|
18643
|
+
id: ShareLinkIdValidator,
|
|
18644
|
+
contentVersion: safeVersion,
|
|
18645
|
+
envelope: ShareEnvelopeValidator,
|
|
18646
|
+
contentUrl: external_exports.string(),
|
|
18647
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18648
|
+
}).strict();
|
|
18128
18649
|
}
|
|
18129
18650
|
});
|
|
18130
18651
|
|
|
@@ -18141,6 +18662,10 @@ var require_dist = __commonJS({
|
|
|
18141
18662
|
});
|
|
18142
18663
|
|
|
18143
18664
|
// src/api-client.ts
|
|
18665
|
+
var DID_CHALLENGE_REQUIRED_ERROR = "This operation requires the DID-challenge flow; use createSSSStrategy";
|
|
18666
|
+
var throwDidChallengeRequired = /* @__PURE__ */ __name(() => {
|
|
18667
|
+
throw new Error(DID_CHALLENGE_REQUIRED_ERROR);
|
|
18668
|
+
}, "throwDidChallengeRequired");
|
|
18144
18669
|
var SSSApiClient = class {
|
|
18145
18670
|
static {
|
|
18146
18671
|
__name(this, "SSSApiClient");
|
|
@@ -18153,20 +18678,9 @@ var SSSApiClient = class {
|
|
|
18153
18678
|
const token = await this.authProvider.getIdToken();
|
|
18154
18679
|
return {
|
|
18155
18680
|
"Content-Type": "application/json",
|
|
18156
|
-
|
|
18681
|
+
"X-Auth-Token": token
|
|
18157
18682
|
};
|
|
18158
18683
|
}
|
|
18159
|
-
async getContactMethodFromUser() {
|
|
18160
|
-
const user = await this.authProvider.getCurrentUser();
|
|
18161
|
-
if (!user) return null;
|
|
18162
|
-
if (user.email) {
|
|
18163
|
-
return { type: "email", value: user.email.toLowerCase() };
|
|
18164
|
-
}
|
|
18165
|
-
if (user.phone) {
|
|
18166
|
-
return { type: "phone", value: user.phone };
|
|
18167
|
-
}
|
|
18168
|
-
return null;
|
|
18169
|
-
}
|
|
18170
18684
|
async getAuthShare() {
|
|
18171
18685
|
const headers = await this.getAuthHeaders();
|
|
18172
18686
|
const providerType = this.authProvider.getProviderType();
|
|
@@ -18183,52 +18697,24 @@ var SSSApiClient = class {
|
|
|
18183
18697
|
}
|
|
18184
18698
|
return response.json();
|
|
18185
18699
|
}
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
|
|
18190
|
-
method: "PUT",
|
|
18191
|
-
headers,
|
|
18192
|
-
body: JSON.stringify({
|
|
18193
|
-
...input,
|
|
18194
|
-
providerType
|
|
18195
|
-
})
|
|
18196
|
-
});
|
|
18197
|
-
if (!response.ok) {
|
|
18198
|
-
throw new Error(`Failed to store auth share: ${response.statusText}`);
|
|
18199
|
-
}
|
|
18700
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18701
|
+
async storeAuthShare(_input) {
|
|
18702
|
+
throwDidChallengeRequired();
|
|
18200
18703
|
}
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery`, {
|
|
18205
|
-
method: "POST",
|
|
18206
|
-
headers,
|
|
18207
|
-
body: JSON.stringify({
|
|
18208
|
-
...input,
|
|
18209
|
-
providerType
|
|
18210
|
-
})
|
|
18211
|
-
});
|
|
18212
|
-
if (!response.ok) {
|
|
18213
|
-
throw new Error(`Failed to add recovery method: ${response.statusText}`);
|
|
18214
|
-
}
|
|
18704
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18705
|
+
async addRecoveryMethod(_input) {
|
|
18706
|
+
throwDidChallengeRequired();
|
|
18215
18707
|
}
|
|
18216
18708
|
async getRecoveryShare(type, credentialId) {
|
|
18217
18709
|
const token = await this.authProvider.getIdToken();
|
|
18218
18710
|
const providerType = this.authProvider.getProviderType();
|
|
18219
|
-
const params = new URLSearchParams({
|
|
18220
|
-
type,
|
|
18221
|
-
providerType,
|
|
18222
|
-
authToken: token
|
|
18223
|
-
});
|
|
18711
|
+
const params = new URLSearchParams({ type, providerType });
|
|
18224
18712
|
if (credentialId) {
|
|
18225
18713
|
params.append("credentialId", credentialId);
|
|
18226
18714
|
}
|
|
18227
18715
|
const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
|
|
18228
18716
|
method: "GET",
|
|
18229
|
-
headers: {
|
|
18230
|
-
"Content-Type": "application/json"
|
|
18231
|
-
}
|
|
18717
|
+
headers: { "Content-Type": "application/json", "X-Auth-Token": token }
|
|
18232
18718
|
});
|
|
18233
18719
|
if (!response.ok) {
|
|
18234
18720
|
if (response.status === 404) {
|
|
@@ -18238,81 +18724,81 @@ var SSSApiClient = class {
|
|
|
18238
18724
|
}
|
|
18239
18725
|
return response.json();
|
|
18240
18726
|
}
|
|
18241
|
-
async
|
|
18242
|
-
const
|
|
18243
|
-
const providerType = this.authProvider.getProviderType();
|
|
18244
|
-
const response = await fetch(`${this.serverUrl}/keys/migrate`, {
|
|
18727
|
+
async startIdentityRecovery(email) {
|
|
18728
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/start`, {
|
|
18245
18729
|
method: "POST",
|
|
18246
|
-
headers,
|
|
18247
|
-
body: JSON.stringify({
|
|
18730
|
+
headers: { "Content-Type": "application/json" },
|
|
18731
|
+
body: JSON.stringify({ email })
|
|
18248
18732
|
});
|
|
18249
18733
|
if (!response.ok) {
|
|
18250
|
-
throw new Error(`Failed to
|
|
18734
|
+
throw new Error(`Failed to send recovery code: ${response.statusText}`);
|
|
18251
18735
|
}
|
|
18252
18736
|
}
|
|
18253
|
-
async
|
|
18254
|
-
const
|
|
18255
|
-
const providerType = this.authProvider.getProviderType();
|
|
18256
|
-
let email = overrideEmail;
|
|
18257
|
-
if (!email) {
|
|
18258
|
-
const user = await this.authProvider.getCurrentUser();
|
|
18259
|
-
email = user?.email;
|
|
18260
|
-
}
|
|
18261
|
-
if (!email) {
|
|
18262
|
-
console.warn("Cannot send email backup share: no email address available");
|
|
18263
|
-
return;
|
|
18264
|
-
}
|
|
18265
|
-
const response = await fetch(`${this.serverUrl}/keys/email-backup`, {
|
|
18737
|
+
async verifyIdentityRecovery(email, code) {
|
|
18738
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/verify`, {
|
|
18266
18739
|
method: "POST",
|
|
18267
|
-
headers,
|
|
18268
|
-
body: JSON.stringify({
|
|
18269
|
-
providerType,
|
|
18270
|
-
emailShare,
|
|
18271
|
-
email
|
|
18272
|
-
})
|
|
18740
|
+
headers: { "Content-Type": "application/json" },
|
|
18741
|
+
body: JSON.stringify({ email, code })
|
|
18273
18742
|
});
|
|
18274
18743
|
if (!response.ok) {
|
|
18275
|
-
|
|
18744
|
+
throw new Error(`Failed to verify recovery code: ${response.statusText}`);
|
|
18276
18745
|
}
|
|
18746
|
+
return response.json();
|
|
18277
18747
|
}
|
|
18278
|
-
async
|
|
18279
|
-
const
|
|
18280
|
-
const providerType = this.authProvider.getProviderType();
|
|
18281
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/add`, {
|
|
18748
|
+
async useIdentityRecoverySession(recoverySessionToken, type, credentialId) {
|
|
18749
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/recover`, {
|
|
18282
18750
|
method: "POST",
|
|
18283
18751
|
headers: { "Content-Type": "application/json" },
|
|
18284
|
-
body: JSON.stringify({
|
|
18752
|
+
body: JSON.stringify({ recoverySessionToken, type, credentialId })
|
|
18285
18753
|
});
|
|
18286
18754
|
if (!response.ok) {
|
|
18287
|
-
|
|
18288
|
-
throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
|
|
18755
|
+
throw new Error(`Failed to retrieve recovery data: ${response.statusText}`);
|
|
18289
18756
|
}
|
|
18757
|
+
return response.json();
|
|
18290
18758
|
}
|
|
18291
|
-
async
|
|
18292
|
-
const
|
|
18293
|
-
const providerType = this.authProvider.getProviderType();
|
|
18294
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/verify`, {
|
|
18759
|
+
async completeIdentityRecoveryRebind(input) {
|
|
18760
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/rebind`, {
|
|
18295
18761
|
method: "POST",
|
|
18296
|
-
headers: {
|
|
18297
|
-
|
|
18762
|
+
headers: {
|
|
18763
|
+
"Content-Type": "application/json",
|
|
18764
|
+
Authorization: `Bearer ${input.didAuthVp}`,
|
|
18765
|
+
"X-Auth-Token": input.newAuthToken
|
|
18766
|
+
},
|
|
18767
|
+
body: JSON.stringify({
|
|
18768
|
+
recoverySessionToken: input.recoverySessionToken,
|
|
18769
|
+
providerType: input.providerType,
|
|
18770
|
+
primaryDid: input.primaryDid,
|
|
18771
|
+
authShare: input.authShare
|
|
18772
|
+
})
|
|
18298
18773
|
});
|
|
18299
18774
|
if (!response.ok) {
|
|
18300
|
-
|
|
18301
|
-
throw new Error(data?.message || `Failed to verify recovery email: ${response.statusText}`);
|
|
18775
|
+
throw new Error(`Failed to bind the new sign-in: ${response.statusText}`);
|
|
18302
18776
|
}
|
|
18303
18777
|
return response.json();
|
|
18304
18778
|
}
|
|
18779
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18780
|
+
async markMigrated() {
|
|
18781
|
+
throwDidChallengeRequired();
|
|
18782
|
+
}
|
|
18783
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18784
|
+
async activate() {
|
|
18785
|
+
throwDidChallengeRequired();
|
|
18786
|
+
}
|
|
18787
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18788
|
+
async sendEmailBackupShare(_emailShare, _overrideEmail) {
|
|
18789
|
+
throwDidChallengeRequired();
|
|
18790
|
+
}
|
|
18791
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18792
|
+
async addRecoveryEmail(_email) {
|
|
18793
|
+
throwDidChallengeRequired();
|
|
18794
|
+
}
|
|
18795
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18796
|
+
async verifyRecoveryEmail(_code) {
|
|
18797
|
+
return throwDidChallengeRequired();
|
|
18798
|
+
}
|
|
18799
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18305
18800
|
async deleteUserKey() {
|
|
18306
|
-
|
|
18307
|
-
const providerType = this.authProvider.getProviderType();
|
|
18308
|
-
const response = await fetch(`${this.serverUrl}/keys`, {
|
|
18309
|
-
method: "DELETE",
|
|
18310
|
-
headers,
|
|
18311
|
-
body: JSON.stringify({ providerType })
|
|
18312
|
-
});
|
|
18313
|
-
if (!response.ok) {
|
|
18314
|
-
throw new Error(`Failed to delete user key: ${response.statusText}`);
|
|
18315
|
-
}
|
|
18801
|
+
throwDidChallengeRequired();
|
|
18316
18802
|
}
|
|
18317
18803
|
};
|
|
18318
18804
|
|
|
@@ -18507,11 +18993,10 @@ async function getOrCreateMasterKey() {
|
|
|
18507
18993
|
(s) => s.get("master-key")
|
|
18508
18994
|
);
|
|
18509
18995
|
if (existing) return existing;
|
|
18510
|
-
const key = await crypto.subtle.generateKey(
|
|
18511
|
-
|
|
18512
|
-
|
|
18513
|
-
|
|
18514
|
-
);
|
|
18996
|
+
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
|
|
18997
|
+
"encrypt",
|
|
18998
|
+
"decrypt"
|
|
18999
|
+
]);
|
|
18515
19000
|
await tx(db, KEYS_STORE, "readwrite", (s) => s.put(key, "master-key"));
|
|
18516
19001
|
return key;
|
|
18517
19002
|
} finally {
|
|
@@ -18612,12 +19097,7 @@ __name(getShareVersion, "getShareVersion");
|
|
|
18612
19097
|
async function getDeviceShare(id = DEFAULT_DEVICE_SHARE_ID) {
|
|
18613
19098
|
const db = await openDB();
|
|
18614
19099
|
try {
|
|
18615
|
-
const raw = await tx(
|
|
18616
|
-
db,
|
|
18617
|
-
SHARES_STORE,
|
|
18618
|
-
"readonly",
|
|
18619
|
-
(s) => s.get(id)
|
|
18620
|
-
);
|
|
19100
|
+
const raw = await tx(db, SHARES_STORE, "readonly", (s) => s.get(id));
|
|
18621
19101
|
if (raw == null) {
|
|
18622
19102
|
return null;
|
|
18623
19103
|
}
|
|
@@ -18676,7 +19156,9 @@ async function listAllDeviceShares() {
|
|
|
18676
19156
|
try {
|
|
18677
19157
|
const share = await getDeviceShare(id);
|
|
18678
19158
|
if (!share) {
|
|
18679
|
-
console.warn(
|
|
19159
|
+
console.warn(
|
|
19160
|
+
`SSS Storage: removing orphaned entry that failed to decrypt: ${id}`
|
|
19161
|
+
);
|
|
18680
19162
|
await deleteDeviceShare(id);
|
|
18681
19163
|
continue;
|
|
18682
19164
|
}
|
|
@@ -18760,6 +19242,7 @@ function createAdaptiveStorage() {
|
|
|
18760
19242
|
getDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetDeviceShare(id) : getDeviceShare(id), "getDeviceShare"),
|
|
18761
19243
|
hasDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionHasDeviceShare(id) : hasDeviceShare(id), "hasDeviceShare"),
|
|
18762
19244
|
clearAllShares: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionClearAllShares(id) : clearAllShares(id), "clearAllShares"),
|
|
19245
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionDeleteDeviceShare(id) : deleteDeviceShare(id), "deleteDeviceShare"),
|
|
18763
19246
|
storeShareVersion: /* @__PURE__ */ __name((version, id) => isPublicComputerMode() ? sessionStoreShareVersion(version, id) : storeShareVersion(version, id), "storeShareVersion"),
|
|
18764
19247
|
getShareVersion: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetShareVersion(id) : getShareVersion(id), "getShareVersion")
|
|
18765
19248
|
};
|
|
@@ -19040,6 +19523,33 @@ function countWords(phrase) {
|
|
|
19040
19523
|
return phrase.trim().split(/\s+/).filter((w) => w.length > 0).length;
|
|
19041
19524
|
}
|
|
19042
19525
|
__name(countWords, "countWords");
|
|
19526
|
+
function randomIndex(max) {
|
|
19527
|
+
const limit = 4294967296 - 4294967296 % max;
|
|
19528
|
+
for (; ; ) {
|
|
19529
|
+
const value = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
19530
|
+
if (value === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
19531
|
+
if (value < limit) return value % max;
|
|
19532
|
+
}
|
|
19533
|
+
}
|
|
19534
|
+
__name(randomIndex, "randomIndex");
|
|
19535
|
+
async function buildRecoveryPhraseChallengeOptions(phrase, challengeWordIndices, optionCount = 6) {
|
|
19536
|
+
const words = phrase.trim().split(/\s+/);
|
|
19537
|
+
const phraseWords = new Set(words);
|
|
19538
|
+
const decoyPool = (await getWordlist()).filter((word) => !phraseWords.has(word));
|
|
19539
|
+
return challengeWordIndices.map((wordIndex) => {
|
|
19540
|
+
const answer = words[wordIndex];
|
|
19541
|
+
if (!answer) throw new Error("Recovery phrase challenge index is out of range");
|
|
19542
|
+
const options = /* @__PURE__ */ new Set([answer]);
|
|
19543
|
+
while (options.size < optionCount) options.add(decoyPool[randomIndex(decoyPool.length)]);
|
|
19544
|
+
const shuffled = [...options];
|
|
19545
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
19546
|
+
const j = randomIndex(i + 1);
|
|
19547
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
19548
|
+
}
|
|
19549
|
+
return shuffled;
|
|
19550
|
+
});
|
|
19551
|
+
}
|
|
19552
|
+
__name(buildRecoveryPhraseChallengeOptions, "buildRecoveryPhraseChallengeOptions");
|
|
19043
19553
|
|
|
19044
19554
|
// src/key-manager.ts
|
|
19045
19555
|
var SSSKeyManager = class {
|
|
@@ -19265,117 +19775,671 @@ function createSSSKeyManager(config) {
|
|
|
19265
19775
|
}
|
|
19266
19776
|
__name(createSSSKeyManager, "createSSSKeyManager");
|
|
19267
19777
|
|
|
19268
|
-
// src/
|
|
19269
|
-
var
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
|
|
19281
|
-
var
|
|
19282
|
-
|
|
19283
|
-
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
this.cause = cause;
|
|
19287
|
-
this.name = "AtomicUpdateError";
|
|
19778
|
+
// src/email-relay-crypto.ts
|
|
19779
|
+
var EMAIL_RELAY_ENVELOPE_VERSION = 1;
|
|
19780
|
+
var EMAIL_RELAY_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
19781
|
+
var encodeUtf8 = /* @__PURE__ */ __name((value) => {
|
|
19782
|
+
const bytes = new TextEncoder().encode(value);
|
|
19783
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
19784
|
+
copy.set(bytes);
|
|
19785
|
+
return copy;
|
|
19786
|
+
}, "encodeUtf8");
|
|
19787
|
+
var EMAIL_RELAY_INFO = encodeUtf8("learncard-email-relay-v1");
|
|
19788
|
+
var SIX_DIGIT_CODE_RANGE = 9e5;
|
|
19789
|
+
var UINT32_RANGE = 4294967296;
|
|
19790
|
+
var MAX_UNBIASED_UINT32 = Math.floor(UINT32_RANGE / SIX_DIGIT_CODE_RANGE) * SIX_DIGIT_CODE_RANGE;
|
|
19791
|
+
var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
19792
|
+
var requireString = /* @__PURE__ */ __name((record, key, options) => {
|
|
19793
|
+
const value = record[key];
|
|
19794
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
19795
|
+
throw new Error(`Invalid email relay ${key}`);
|
|
19288
19796
|
}
|
|
19289
|
-
|
|
19290
|
-
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
const
|
|
19295
|
-
const
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19797
|
+
return value;
|
|
19798
|
+
}, "requireString");
|
|
19799
|
+
var parseBranding = /* @__PURE__ */ __name((value) => {
|
|
19800
|
+
if (value === void 0) return void 0;
|
|
19801
|
+
if (!isRecord(value)) throw new Error("Invalid email relay branding");
|
|
19802
|
+
const branding = {};
|
|
19803
|
+
const keys = [
|
|
19804
|
+
"brandName",
|
|
19805
|
+
"logoUrl",
|
|
19806
|
+
"logoAlt",
|
|
19807
|
+
"primaryColor",
|
|
19808
|
+
"primaryTextColor",
|
|
19809
|
+
"supportEmail",
|
|
19810
|
+
"websiteUrl",
|
|
19811
|
+
"appUrl",
|
|
19812
|
+
"fromDomain",
|
|
19813
|
+
"copyrightHolder"
|
|
19302
19814
|
];
|
|
19303
|
-
for (const
|
|
19304
|
-
const
|
|
19305
|
-
if (
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
name,
|
|
19309
|
-
privateKey,
|
|
19310
|
-
reconstructed
|
|
19311
|
-
);
|
|
19815
|
+
for (const key of keys) {
|
|
19816
|
+
const field = value[key];
|
|
19817
|
+
if (field === void 0) continue;
|
|
19818
|
+
if (typeof field !== "string" || field.length > 2048) {
|
|
19819
|
+
throw new Error(`Invalid email relay branding.${key}`);
|
|
19312
19820
|
}
|
|
19821
|
+
branding[key] = field;
|
|
19313
19822
|
}
|
|
19314
|
-
return
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
__name(splitAndVerify, "splitAndVerify");
|
|
19321
|
-
async function atomicShareUpdate(privateKey, storage, options = {}) {
|
|
19322
|
-
let newShares = null;
|
|
19323
|
-
let deviceStored = false;
|
|
19324
|
-
try {
|
|
19325
|
-
const result = await splitAndVerify(privateKey);
|
|
19326
|
-
newShares = result.shares;
|
|
19327
|
-
} catch (e) {
|
|
19328
|
-
throw new AtomicUpdateError(
|
|
19329
|
-
"Failed to split and verify shares",
|
|
19330
|
-
"split",
|
|
19331
|
-
false,
|
|
19332
|
-
e instanceof Error ? e : void 0
|
|
19333
|
-
);
|
|
19823
|
+
return branding;
|
|
19824
|
+
}, "parseBranding");
|
|
19825
|
+
var parseEmailRelayEnvelope = /* @__PURE__ */ __name((value) => {
|
|
19826
|
+
if (!isRecord(value)) throw new Error("Invalid email relay envelope");
|
|
19827
|
+
if (value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19828
|
+
throw new Error("Unsupported email relay envelope version");
|
|
19334
19829
|
}
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
deviceStored = true;
|
|
19338
|
-
} catch (e) {
|
|
19339
|
-
throw new AtomicUpdateError(
|
|
19340
|
-
"Failed to store device share locally",
|
|
19341
|
-
"store_device",
|
|
19342
|
-
false,
|
|
19343
|
-
e instanceof Error ? e : void 0
|
|
19344
|
-
);
|
|
19830
|
+
if (value.algorithm !== EMAIL_RELAY_ALGORITHM) {
|
|
19831
|
+
throw new Error("Unsupported email relay algorithm");
|
|
19345
19832
|
}
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
}
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
);
|
|
19833
|
+
return {
|
|
19834
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19835
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
19836
|
+
keyId: requireString(value, "keyId", {
|
|
19837
|
+
max: 128,
|
|
19838
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
19839
|
+
}),
|
|
19840
|
+
ephemeralPublicKey: requireString(value, "ephemeralPublicKey", { max: 256 }),
|
|
19841
|
+
salt: requireString(value, "salt", { max: 128 }),
|
|
19842
|
+
iv: requireString(value, "iv", { max: 64 }),
|
|
19843
|
+
ciphertext: requireString(value, "ciphertext", { max: 16384 })
|
|
19844
|
+
};
|
|
19845
|
+
}, "parseEmailRelayEnvelope");
|
|
19846
|
+
var parseEmailRelayPlaintext = /* @__PURE__ */ __name((value) => {
|
|
19847
|
+
if (!isRecord(value) || value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19848
|
+
throw new Error("Invalid email relay plaintext");
|
|
19363
19849
|
}
|
|
19364
|
-
return
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19850
|
+
return {
|
|
19851
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19852
|
+
targetEmail: requireString(value, "targetEmail", {
|
|
19853
|
+
max: 320,
|
|
19854
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
19855
|
+
}).trim().toLowerCase(),
|
|
19856
|
+
recoveryKey: requireString(value, "recoveryKey", {
|
|
19857
|
+
min: 5,
|
|
19858
|
+
max: 4096,
|
|
19859
|
+
pattern: /^[0-9a-f]+$/i
|
|
19860
|
+
}),
|
|
19861
|
+
confirmationCode: requireString(value, "confirmationCode", {
|
|
19862
|
+
max: 6,
|
|
19863
|
+
pattern: /^\d{6}$/
|
|
19864
|
+
}),
|
|
19865
|
+
branding: parseBranding(value.branding)
|
|
19374
19866
|
};
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19867
|
+
}, "parseEmailRelayPlaintext");
|
|
19868
|
+
var getAad = /* @__PURE__ */ __name((envelope) => encodeUtf8(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
19869
|
+
var deriveAesKey = /* @__PURE__ */ __name(async (sharedSecret, salt, usage) => {
|
|
19870
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
19871
|
+
"deriveKey"
|
|
19872
|
+
]);
|
|
19873
|
+
return crypto.subtle.deriveKey(
|
|
19874
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: EMAIL_RELAY_INFO },
|
|
19875
|
+
keyMaterial,
|
|
19876
|
+
{ name: "AES-GCM", length: 256 },
|
|
19877
|
+
false,
|
|
19878
|
+
[usage]
|
|
19879
|
+
);
|
|
19880
|
+
}, "deriveAesKey");
|
|
19881
|
+
var generateEmailRelayConfirmationCode = /* @__PURE__ */ __name(() => {
|
|
19882
|
+
const random = new Uint32Array(1);
|
|
19883
|
+
let value;
|
|
19884
|
+
do {
|
|
19885
|
+
crypto.getRandomValues(random);
|
|
19886
|
+
value = random[0] ?? UINT32_RANGE;
|
|
19887
|
+
} while (value >= MAX_UNBIASED_UINT32);
|
|
19888
|
+
return String(1e5 + value % SIX_DIGIT_CODE_RANGE);
|
|
19889
|
+
}, "generateEmailRelayConfirmationCode");
|
|
19890
|
+
var encryptEmailRelayPayload = /* @__PURE__ */ __name(async (input, publicKeyBase64, keyId) => {
|
|
19891
|
+
const plaintext = parseEmailRelayPlaintext({
|
|
19892
|
+
...input,
|
|
19893
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION
|
|
19894
|
+
});
|
|
19895
|
+
const normalizedKeyId = keyId.trim();
|
|
19896
|
+
if (!publicKeyBase64.trim()) throw new Error("Email relay public key is not configured");
|
|
19897
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(normalizedKeyId)) {
|
|
19898
|
+
throw new Error("Email relay key ID is not configured");
|
|
19899
|
+
}
|
|
19900
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
19901
|
+
"spki",
|
|
19902
|
+
base64ToBuffer(publicKeyBase64.trim()).buffer,
|
|
19903
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19904
|
+
false,
|
|
19905
|
+
[]
|
|
19906
|
+
);
|
|
19907
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
19908
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19909
|
+
true,
|
|
19910
|
+
["deriveBits"]
|
|
19911
|
+
);
|
|
19912
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
19913
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
19914
|
+
ephemeralKeyPair.privateKey,
|
|
19915
|
+
256
|
|
19916
|
+
);
|
|
19917
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
19918
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
19919
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "encrypt");
|
|
19920
|
+
const envelopeMetadata = {
|
|
19921
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19922
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
19923
|
+
keyId: normalizedKeyId
|
|
19924
|
+
};
|
|
19925
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
19926
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelopeMetadata) },
|
|
19927
|
+
aesKey,
|
|
19928
|
+
encodeUtf8(JSON.stringify(plaintext))
|
|
19929
|
+
);
|
|
19930
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
19931
|
+
return {
|
|
19932
|
+
...envelopeMetadata,
|
|
19933
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
19934
|
+
salt: bufferToBase64(salt.buffer),
|
|
19935
|
+
iv: bufferToBase64(iv.buffer),
|
|
19936
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
19937
|
+
};
|
|
19938
|
+
}, "encryptEmailRelayPayload");
|
|
19939
|
+
var decryptEmailRelayPayload = /* @__PURE__ */ __name(async (value, privateKeyBase64) => {
|
|
19940
|
+
const envelope = parseEmailRelayEnvelope(value);
|
|
19941
|
+
const privateKey = await crypto.subtle.importKey(
|
|
19942
|
+
"pkcs8",
|
|
19943
|
+
base64ToBuffer(privateKeyBase64.trim()).buffer,
|
|
19944
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19945
|
+
false,
|
|
19946
|
+
["deriveBits"]
|
|
19947
|
+
);
|
|
19948
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
19949
|
+
"raw",
|
|
19950
|
+
base64ToBuffer(envelope.ephemeralPublicKey).buffer,
|
|
19951
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19952
|
+
false,
|
|
19953
|
+
[]
|
|
19954
|
+
);
|
|
19955
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
19956
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
19957
|
+
privateKey,
|
|
19958
|
+
256
|
|
19959
|
+
);
|
|
19960
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
19961
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
19962
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "decrypt");
|
|
19963
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
19964
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelope) },
|
|
19965
|
+
aesKey,
|
|
19966
|
+
base64ToBuffer(envelope.ciphertext)
|
|
19967
|
+
);
|
|
19968
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
19969
|
+
return parseEmailRelayPlaintext(parsed);
|
|
19970
|
+
}, "decryptEmailRelayPayload");
|
|
19971
|
+
|
|
19972
|
+
// src/escrow-crypto.ts
|
|
19973
|
+
var ESCROW_ENVELOPE_VERSION = 1;
|
|
19974
|
+
var ESCROW_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
19975
|
+
var ESCROW_BLOB_INFO = "learncard-escrow-blob-v1";
|
|
19976
|
+
var ESCROW_RELEASE_INFO = "learncard-escrow-release-v1";
|
|
19977
|
+
var ESCROW_CLIENT_KEY_ID = "client";
|
|
19978
|
+
var encodeUtf82 = /* @__PURE__ */ __name((value) => {
|
|
19979
|
+
const bytes = new TextEncoder().encode(value);
|
|
19980
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
19981
|
+
copy.set(bytes);
|
|
19982
|
+
return copy;
|
|
19983
|
+
}, "encodeUtf8");
|
|
19984
|
+
var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
19985
|
+
var requireString2 = /* @__PURE__ */ __name((record, key, options) => {
|
|
19986
|
+
const value = record[key];
|
|
19987
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
19988
|
+
throw new Error(`Invalid escrow ${key}`);
|
|
19989
|
+
}
|
|
19990
|
+
return value;
|
|
19991
|
+
}, "requireString");
|
|
19992
|
+
var parseEscrowEnvelope = /* @__PURE__ */ __name((value) => {
|
|
19993
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow envelope");
|
|
19994
|
+
if (value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
19995
|
+
throw new Error("Unsupported escrow envelope version");
|
|
19996
|
+
}
|
|
19997
|
+
if (value.algorithm !== ESCROW_ALGORITHM) {
|
|
19998
|
+
throw new Error("Unsupported escrow algorithm");
|
|
19999
|
+
}
|
|
20000
|
+
return {
|
|
20001
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20002
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20003
|
+
keyId: requireString2(value, "keyId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ }),
|
|
20004
|
+
ephemeralPublicKey: requireString2(value, "ephemeralPublicKey", { max: 256 }),
|
|
20005
|
+
salt: requireString2(value, "salt", { max: 128 }),
|
|
20006
|
+
iv: requireString2(value, "iv", { max: 64 }),
|
|
20007
|
+
ciphertext: requireString2(value, "ciphertext", { max: 16384 })
|
|
20008
|
+
};
|
|
20009
|
+
}, "parseEscrowEnvelope");
|
|
20010
|
+
var parseEscrowBlobPlaintext = /* @__PURE__ */ __name((value) => {
|
|
20011
|
+
if (!isRecord2(value) || value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
20012
|
+
throw new Error("Invalid escrow plaintext");
|
|
20013
|
+
}
|
|
20014
|
+
if (typeof value.shareVersion !== "number" || !Number.isInteger(value.shareVersion) || value.shareVersion <= 0) {
|
|
20015
|
+
throw new Error("Invalid escrow shareVersion");
|
|
20016
|
+
}
|
|
20017
|
+
return {
|
|
20018
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20019
|
+
recoveryShare: requireString2(value, "recoveryShare", {
|
|
20020
|
+
min: 5,
|
|
20021
|
+
max: 4096,
|
|
20022
|
+
pattern: /^[0-9a-f]+$/i
|
|
20023
|
+
}).toLowerCase(),
|
|
20024
|
+
did: requireString2(value, "did", { max: 2048, pattern: /^did:/ }),
|
|
20025
|
+
shareVersion: value.shareVersion,
|
|
20026
|
+
...value.pinVerifier !== void 0 ? {
|
|
20027
|
+
pinVerifier: requireString2(value, "pinVerifier", {
|
|
20028
|
+
min: 64,
|
|
20029
|
+
max: 64,
|
|
20030
|
+
pattern: /^[0-9a-f]{64}$/i
|
|
20031
|
+
}).toLowerCase()
|
|
20032
|
+
} : {}
|
|
20033
|
+
};
|
|
20034
|
+
}, "parseEscrowBlobPlaintext");
|
|
20035
|
+
var parseEscrowReleasePlaintext = /* @__PURE__ */ __name((value) => {
|
|
20036
|
+
const plaintext = parseEscrowBlobPlaintext(value);
|
|
20037
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow plaintext");
|
|
20038
|
+
return {
|
|
20039
|
+
...plaintext,
|
|
20040
|
+
holdId: requireString2(value, "holdId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ })
|
|
20041
|
+
};
|
|
20042
|
+
}, "parseEscrowReleasePlaintext");
|
|
20043
|
+
var getAad2 = /* @__PURE__ */ __name((envelope) => encodeUtf82(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
20044
|
+
var deriveAesKey2 = /* @__PURE__ */ __name(async (sharedSecret, salt, usage, info) => {
|
|
20045
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
20046
|
+
"deriveKey"
|
|
20047
|
+
]);
|
|
20048
|
+
return crypto.subtle.deriveKey(
|
|
20049
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: encodeUtf82(info) },
|
|
20050
|
+
keyMaterial,
|
|
20051
|
+
{ name: "AES-GCM", length: 256 },
|
|
20052
|
+
false,
|
|
20053
|
+
[usage]
|
|
20054
|
+
);
|
|
20055
|
+
}, "deriveAesKey");
|
|
20056
|
+
var generateEscrowKeyPair = /* @__PURE__ */ __name(async () => {
|
|
20057
|
+
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
|
|
20058
|
+
"deriveBits"
|
|
20059
|
+
]);
|
|
20060
|
+
const [publicKey, privateKey] = await Promise.all([
|
|
20061
|
+
crypto.subtle.exportKey("spki", keyPair.publicKey),
|
|
20062
|
+
crypto.subtle.exportKey("pkcs8", keyPair.privateKey)
|
|
20063
|
+
]);
|
|
20064
|
+
return { publicKey: bufferToBase64(publicKey), privateKey: bufferToBase64(privateKey) };
|
|
20065
|
+
}, "generateEscrowKeyPair");
|
|
20066
|
+
var encryptToRecipient = /* @__PURE__ */ __name(async (plaintextJson, spki, keyId, info) => {
|
|
20067
|
+
if (typeof spki !== "string" || !spki.trim()) {
|
|
20068
|
+
throw new Error("Escrow public key is not configured");
|
|
20069
|
+
}
|
|
20070
|
+
const recipientKeyId = requireString2({ keyId }, "keyId", {
|
|
20071
|
+
max: 128,
|
|
20072
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
20073
|
+
});
|
|
20074
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
20075
|
+
"spki",
|
|
20076
|
+
base64ToBuffer(spki.trim()),
|
|
20077
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20078
|
+
false,
|
|
20079
|
+
[]
|
|
20080
|
+
);
|
|
20081
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
20082
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20083
|
+
true,
|
|
20084
|
+
["deriveBits"]
|
|
20085
|
+
);
|
|
20086
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20087
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
20088
|
+
ephemeralKeyPair.privateKey,
|
|
20089
|
+
256
|
|
20090
|
+
);
|
|
20091
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
20092
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
20093
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "encrypt", info);
|
|
20094
|
+
const metadata = {
|
|
20095
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20096
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20097
|
+
keyId: recipientKeyId
|
|
20098
|
+
};
|
|
20099
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
20100
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(metadata) },
|
|
20101
|
+
aesKey,
|
|
20102
|
+
encodeUtf82(plaintextJson)
|
|
20103
|
+
);
|
|
20104
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
20105
|
+
return {
|
|
20106
|
+
...metadata,
|
|
20107
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
20108
|
+
salt: bufferToBase64(salt.buffer),
|
|
20109
|
+
iv: bufferToBase64(iv.buffer),
|
|
20110
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
20111
|
+
};
|
|
20112
|
+
}, "encryptToRecipient");
|
|
20113
|
+
var decryptFromEnvelope = /* @__PURE__ */ __name(async (value, pkcs8, info) => {
|
|
20114
|
+
const envelope = parseEscrowEnvelope(value);
|
|
20115
|
+
const privateKey = await crypto.subtle.importKey(
|
|
20116
|
+
"pkcs8",
|
|
20117
|
+
base64ToBuffer(pkcs8.trim()),
|
|
20118
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20119
|
+
false,
|
|
20120
|
+
["deriveBits"]
|
|
20121
|
+
);
|
|
20122
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
20123
|
+
"raw",
|
|
20124
|
+
base64ToBuffer(envelope.ephemeralPublicKey),
|
|
20125
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20126
|
+
false,
|
|
20127
|
+
[]
|
|
20128
|
+
);
|
|
20129
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20130
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
20131
|
+
privateKey,
|
|
20132
|
+
256
|
|
20133
|
+
);
|
|
20134
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
20135
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
20136
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "decrypt", info);
|
|
20137
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
20138
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(envelope) },
|
|
20139
|
+
aesKey,
|
|
20140
|
+
base64ToBuffer(envelope.ciphertext)
|
|
20141
|
+
);
|
|
20142
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
20143
|
+
return parsed;
|
|
20144
|
+
}, "decryptFromEnvelope");
|
|
20145
|
+
var encryptEscrowBlob = /* @__PURE__ */ __name(async (input, enclavePublicKeySpkiB64, keyId) => {
|
|
20146
|
+
const plaintext = parseEscrowBlobPlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20147
|
+
return encryptToRecipient(
|
|
20148
|
+
JSON.stringify(plaintext),
|
|
20149
|
+
enclavePublicKeySpkiB64,
|
|
20150
|
+
keyId,
|
|
20151
|
+
ESCROW_BLOB_INFO
|
|
20152
|
+
);
|
|
20153
|
+
}, "encryptEscrowBlob");
|
|
20154
|
+
var decryptEscrowBlob = /* @__PURE__ */ __name(async (envelope, enclavePrivateKeyPkcs8B64) => parseEscrowBlobPlaintext(
|
|
20155
|
+
await decryptFromEnvelope(envelope, enclavePrivateKeyPkcs8B64, ESCROW_BLOB_INFO)
|
|
20156
|
+
), "decryptEscrowBlob");
|
|
20157
|
+
var sealEscrowRelease = /* @__PURE__ */ __name(async (input, clientEphemeralPublicKeySpkiB64) => {
|
|
20158
|
+
const plaintext = parseEscrowReleasePlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20159
|
+
return encryptToRecipient(
|
|
20160
|
+
JSON.stringify(plaintext),
|
|
20161
|
+
clientEphemeralPublicKeySpkiB64,
|
|
20162
|
+
ESCROW_CLIENT_KEY_ID,
|
|
20163
|
+
ESCROW_RELEASE_INFO
|
|
20164
|
+
);
|
|
20165
|
+
}, "sealEscrowRelease");
|
|
20166
|
+
var openEscrowRelease = /* @__PURE__ */ __name(async (envelope, clientEphemeralPrivateKeyPkcs8B64) => parseEscrowReleasePlaintext(
|
|
20167
|
+
await decryptFromEnvelope(envelope, clientEphemeralPrivateKeyPkcs8B64, ESCROW_RELEASE_INFO)
|
|
20168
|
+
), "openEscrowRelease");
|
|
20169
|
+
|
|
20170
|
+
// src/escrow-attestation.ts
|
|
20171
|
+
var isRecord3 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
20172
|
+
var normalizePublicKey = /* @__PURE__ */ __name((value) => {
|
|
20173
|
+
const trimmed = value.trim();
|
|
20174
|
+
if (!trimmed || trimmed.length > 512 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(trimmed)) {
|
|
20175
|
+
throw new Error("Invalid escrow attestation public key");
|
|
20176
|
+
}
|
|
20177
|
+
return bufferToBase64(base64ToBuffer(trimmed).buffer);
|
|
20178
|
+
}, "normalizePublicKey");
|
|
20179
|
+
var verifyEnclaveAttestation = /* @__PURE__ */ __name(async (attestation, policy) => {
|
|
20180
|
+
const fields = ["mode", "keyId", "publicKey", "measurements", "document", "issuedAt"];
|
|
20181
|
+
if (!isRecord3(attestation) || Object.keys(attestation).some((key) => !fields.includes(key)) || attestation.mode !== "software" && attestation.mode !== "nitro" || typeof attestation.keyId !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(attestation.keyId) || typeof attestation.publicKey !== "string" || !isRecord3(attestation.measurements) || Object.entries(attestation.measurements).some(
|
|
20182
|
+
([key, value]) => !["imageSha384", "pcr0", "pcr1", "pcr2"].includes(key) || typeof value !== "string" || !value || value.length > 1024
|
|
20183
|
+
) || typeof attestation.document !== "string" || !attestation.document || attestation.document.length > 65536 || typeof attestation.issuedAt !== "string" || !Number.isFinite(Date.parse(attestation.issuedAt))) {
|
|
20184
|
+
throw new Error("Invalid escrow attestation");
|
|
20185
|
+
}
|
|
20186
|
+
const publicKey = normalizePublicKey(attestation.publicKey);
|
|
20187
|
+
await crypto.subtle.importKey(
|
|
20188
|
+
"spki",
|
|
20189
|
+
base64ToBuffer(publicKey),
|
|
20190
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20191
|
+
false,
|
|
20192
|
+
[]
|
|
20193
|
+
);
|
|
20194
|
+
if (policy.mode === "nitro") {
|
|
20195
|
+
throw new Error("Nitro attestation verification is not implemented yet");
|
|
20196
|
+
}
|
|
20197
|
+
if (attestation.mode !== policy.mode) throw new Error("Escrow attestation mode mismatch");
|
|
20198
|
+
if (!policy.pinnedPublicKeys.some((pin) => normalizePublicKey(pin) === publicKey)) {
|
|
20199
|
+
throw new Error("Escrow attestation public key is not trusted");
|
|
20200
|
+
}
|
|
20201
|
+
return { publicKey, keyId: attestation.keyId, mode: attestation.mode };
|
|
20202
|
+
}, "verifyEnclaveAttestation");
|
|
20203
|
+
|
|
20204
|
+
// src/types.ts
|
|
20205
|
+
var import_types = __toESM(require_dist());
|
|
20206
|
+
var EscrowPinUnavailableError = class extends Error {
|
|
20207
|
+
static {
|
|
20208
|
+
__name(this, "EscrowPinUnavailableError");
|
|
20209
|
+
}
|
|
20210
|
+
constructor() {
|
|
20211
|
+
super("PIN recovery is not available for this account.");
|
|
20212
|
+
this.name = "EscrowPinUnavailableError";
|
|
20213
|
+
}
|
|
20214
|
+
};
|
|
20215
|
+
var EscrowPinMismatchError = class extends Error {
|
|
20216
|
+
constructor(attemptsRemaining) {
|
|
20217
|
+
super("Incorrect PIN.");
|
|
20218
|
+
this.attemptsRemaining = attemptsRemaining;
|
|
20219
|
+
this.name = "EscrowPinMismatchError";
|
|
20220
|
+
}
|
|
20221
|
+
static {
|
|
20222
|
+
__name(this, "EscrowPinMismatchError");
|
|
20223
|
+
}
|
|
20224
|
+
};
|
|
20225
|
+
var EscrowPinThrottledError = class extends Error {
|
|
20226
|
+
static {
|
|
20227
|
+
__name(this, "EscrowPinThrottledError");
|
|
20228
|
+
}
|
|
20229
|
+
constructor() {
|
|
20230
|
+
super("Too many tries right now. Wait a minute and try again.");
|
|
20231
|
+
this.name = "EscrowPinThrottledError";
|
|
20232
|
+
}
|
|
20233
|
+
};
|
|
20234
|
+
var EscrowHoldRestartThrottledError = class extends Error {
|
|
20235
|
+
constructor(retryAfter) {
|
|
20236
|
+
super("A recovery request was started recently. Please wait before restarting.");
|
|
20237
|
+
this.retryAfter = retryAfter;
|
|
20238
|
+
this.name = "EscrowHoldRestartThrottledError";
|
|
20239
|
+
}
|
|
20240
|
+
static {
|
|
20241
|
+
__name(this, "EscrowHoldRestartThrottledError");
|
|
20242
|
+
}
|
|
20243
|
+
};
|
|
20244
|
+
var EscrowPinLockedError = class extends Error {
|
|
20245
|
+
static {
|
|
20246
|
+
__name(this, "EscrowPinLockedError");
|
|
20247
|
+
}
|
|
20248
|
+
constructor() {
|
|
20249
|
+
super("Too many incorrect PIN attempts. You can still recover by waiting.");
|
|
20250
|
+
this.name = "EscrowPinLockedError";
|
|
20251
|
+
}
|
|
20252
|
+
};
|
|
20253
|
+
|
|
20254
|
+
// src/escrow-pin.ts
|
|
20255
|
+
var PIN_MIN_LENGTH = 6;
|
|
20256
|
+
var PIN_MAX_LENGTH = 12;
|
|
20257
|
+
var ESCROW_PIN_MAX_ATTEMPTS = 10;
|
|
20258
|
+
var TRIVIAL_PINS = /* @__PURE__ */ new Set([
|
|
20259
|
+
"123456",
|
|
20260
|
+
"654321",
|
|
20261
|
+
"000000",
|
|
20262
|
+
"111111",
|
|
20263
|
+
"121212",
|
|
20264
|
+
"112233",
|
|
20265
|
+
"123123",
|
|
20266
|
+
"696969",
|
|
20267
|
+
"123321"
|
|
20268
|
+
]);
|
|
20269
|
+
var normalizePin = /* @__PURE__ */ __name((raw) => raw.trim().normalize("NFKD").replace(/\D/g, ""), "normalizePin");
|
|
20270
|
+
var validatePin = /* @__PURE__ */ __name((pin) => {
|
|
20271
|
+
const normalized = normalizePin(pin);
|
|
20272
|
+
if (normalized.length < PIN_MIN_LENGTH || normalized.length > PIN_MAX_LENGTH) {
|
|
20273
|
+
return { ok: false, reason: "length" };
|
|
20274
|
+
}
|
|
20275
|
+
if (/^(\d)\1+$/.test(normalized) || "0123456789".includes(normalized) || "9876543210".includes(normalized) || TRIVIAL_PINS.has(normalized)) {
|
|
20276
|
+
return { ok: false, reason: "trivial" };
|
|
20277
|
+
}
|
|
20278
|
+
return { ok: true };
|
|
20279
|
+
}, "validatePin");
|
|
20280
|
+
var generatePinSalt = /* @__PURE__ */ __name(() => bufferToBase64(crypto.getRandomValues(new Uint8Array(16)).buffer), "generatePinSalt");
|
|
20281
|
+
var derivePinProof = /* @__PURE__ */ __name(async (pin, pinSaltBase64) => {
|
|
20282
|
+
const salt = base64ToBuffer(pinSaltBase64);
|
|
20283
|
+
if (salt.length !== 16) throw new Error("Invalid escrow PIN salt length");
|
|
20284
|
+
const key = await deriveKeyFromPassword(normalizePin(pin), salt);
|
|
20285
|
+
try {
|
|
20286
|
+
return bytesToHex(key);
|
|
20287
|
+
} finally {
|
|
20288
|
+
key.fill(0);
|
|
20289
|
+
}
|
|
20290
|
+
}, "derivePinProof");
|
|
20291
|
+
var constantTimeEqualHex = /* @__PURE__ */ __name((a, b) => {
|
|
20292
|
+
if (a.length !== b.length || !/^(?:[0-9a-f]{2})+$/i.test(a) || !/^(?:[0-9a-f]{2})+$/i.test(b)) {
|
|
20293
|
+
return false;
|
|
20294
|
+
}
|
|
20295
|
+
const aBytes = hexToBytes(a);
|
|
20296
|
+
const bBytes = hexToBytes(b);
|
|
20297
|
+
let difference = 0;
|
|
20298
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
20299
|
+
difference |= aBytes[i] ^ bBytes[i];
|
|
20300
|
+
}
|
|
20301
|
+
return difference === 0;
|
|
20302
|
+
}, "constantTimeEqualHex");
|
|
20303
|
+
|
|
20304
|
+
// src/atomic-operations.ts
|
|
20305
|
+
var ShareVerificationError = class extends Error {
|
|
20306
|
+
constructor(message, combination, expected, got) {
|
|
20307
|
+
super(message);
|
|
20308
|
+
this.combination = combination;
|
|
20309
|
+
this.expected = expected;
|
|
20310
|
+
this.got = got;
|
|
20311
|
+
this.name = "ShareVerificationError";
|
|
20312
|
+
}
|
|
20313
|
+
static {
|
|
20314
|
+
__name(this, "ShareVerificationError");
|
|
20315
|
+
}
|
|
20316
|
+
};
|
|
20317
|
+
var AtomicUpdateError = class extends Error {
|
|
20318
|
+
constructor(message, phase, rolledBack, cause) {
|
|
20319
|
+
super(message);
|
|
20320
|
+
this.phase = phase;
|
|
20321
|
+
this.rolledBack = rolledBack;
|
|
20322
|
+
this.cause = cause;
|
|
20323
|
+
this.name = "AtomicUpdateError";
|
|
20324
|
+
}
|
|
20325
|
+
static {
|
|
20326
|
+
__name(this, "AtomicUpdateError");
|
|
20327
|
+
}
|
|
20328
|
+
};
|
|
20329
|
+
var ShareWriteRejectedError = class extends Error {
|
|
20330
|
+
static {
|
|
20331
|
+
__name(this, "ShareWriteRejectedError");
|
|
20332
|
+
}
|
|
20333
|
+
constructor(message) {
|
|
20334
|
+
super(message);
|
|
20335
|
+
this.name = "ShareWriteRejectedError";
|
|
20336
|
+
}
|
|
20337
|
+
};
|
|
20338
|
+
async function splitAndVerify(privateKey) {
|
|
20339
|
+
const shares = await splitPrivateKey(privateKey);
|
|
20340
|
+
const combinations = [
|
|
20341
|
+
["device+auth", shares.deviceShare, shares.authShare],
|
|
20342
|
+
["device+recovery", shares.deviceShare, shares.recoveryShare],
|
|
20343
|
+
["device+email", shares.deviceShare, shares.emailShare],
|
|
20344
|
+
["auth+recovery", shares.authShare, shares.recoveryShare],
|
|
20345
|
+
["auth+email", shares.authShare, shares.emailShare],
|
|
20346
|
+
["recovery+email", shares.recoveryShare, shares.emailShare]
|
|
20347
|
+
];
|
|
20348
|
+
for (const [name, share1, share2] of combinations) {
|
|
20349
|
+
const reconstructed = await reconstructFromShares([share1, share2]);
|
|
20350
|
+
if (reconstructed !== privateKey) {
|
|
20351
|
+
throw new ShareVerificationError(
|
|
20352
|
+
`Share verification failed for ${name}: reconstruction mismatch`,
|
|
20353
|
+
name,
|
|
20354
|
+
privateKey,
|
|
20355
|
+
reconstructed
|
|
20356
|
+
);
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20359
|
+
return {
|
|
20360
|
+
privateKey,
|
|
20361
|
+
shares,
|
|
20362
|
+
verified: true
|
|
20363
|
+
};
|
|
20364
|
+
}
|
|
20365
|
+
__name(splitAndVerify, "splitAndVerify");
|
|
20366
|
+
async function atomicShareUpdate(privateKey, storage, options = {}) {
|
|
20367
|
+
let newShares = null;
|
|
20368
|
+
let deviceStored = false;
|
|
20369
|
+
try {
|
|
20370
|
+
const result = await splitAndVerify(privateKey);
|
|
20371
|
+
newShares = result.shares;
|
|
20372
|
+
} catch (e) {
|
|
20373
|
+
throw new AtomicUpdateError(
|
|
20374
|
+
"Failed to split and verify shares",
|
|
20375
|
+
"split",
|
|
20376
|
+
false,
|
|
20377
|
+
e instanceof Error ? e : void 0
|
|
20378
|
+
);
|
|
20379
|
+
}
|
|
20380
|
+
try {
|
|
20381
|
+
await storage.storeDevice(newShares.deviceShare);
|
|
20382
|
+
deviceStored = true;
|
|
20383
|
+
} catch (e) {
|
|
20384
|
+
throw new AtomicUpdateError(
|
|
20385
|
+
"Failed to store device share locally",
|
|
20386
|
+
"store_device",
|
|
20387
|
+
false,
|
|
20388
|
+
e instanceof Error ? e : void 0
|
|
20389
|
+
);
|
|
20390
|
+
}
|
|
20391
|
+
try {
|
|
20392
|
+
await storage.storeAuth(newShares.authShare);
|
|
20393
|
+
} catch (e) {
|
|
20394
|
+
if (!(e instanceof ShareWriteRejectedError)) {
|
|
20395
|
+
throw new AtomicUpdateError(
|
|
20396
|
+
"Auth share write outcome unknown; device share preserved for reconciliation",
|
|
20397
|
+
"store_auth",
|
|
20398
|
+
false,
|
|
20399
|
+
e instanceof Error ? e : void 0
|
|
20400
|
+
);
|
|
20401
|
+
}
|
|
20402
|
+
let rolledBack = false;
|
|
20403
|
+
if (deviceStored) {
|
|
20404
|
+
try {
|
|
20405
|
+
if (options.previousDeviceShare !== void 0) {
|
|
20406
|
+
await storage.storeDevice(options.previousDeviceShare);
|
|
20407
|
+
options.onRollback?.("Server storage failed, restored previous device share");
|
|
20408
|
+
rolledBack = true;
|
|
20409
|
+
} else if (storage.clearDevice) {
|
|
20410
|
+
await storage.clearDevice();
|
|
20411
|
+
options.onRollback?.("Server storage failed, removed new device share");
|
|
20412
|
+
rolledBack = true;
|
|
20413
|
+
}
|
|
20414
|
+
} catch (rollbackError) {
|
|
20415
|
+
console.error(
|
|
20416
|
+
"CRITICAL: Rollback failed after auth share storage failure",
|
|
20417
|
+
rollbackError
|
|
20418
|
+
);
|
|
20419
|
+
}
|
|
20420
|
+
}
|
|
20421
|
+
throw new AtomicUpdateError(
|
|
20422
|
+
rolledBack ? "Failed to store auth share on server, rolled back device share" : "Failed to store auth share on server; rollback did not complete",
|
|
20423
|
+
"store_auth",
|
|
20424
|
+
rolledBack,
|
|
20425
|
+
e instanceof Error ? e : void 0
|
|
20426
|
+
);
|
|
20427
|
+
}
|
|
20428
|
+
return newShares;
|
|
20429
|
+
}
|
|
20430
|
+
__name(atomicShareUpdate, "atomicShareUpdate");
|
|
20431
|
+
async function verifyStoredShares(storage, expectedDid, didFromPrivateKey) {
|
|
20432
|
+
const result = {
|
|
20433
|
+
healthy: false,
|
|
20434
|
+
hasDeviceShare: false,
|
|
20435
|
+
hasAuthShare: false,
|
|
20436
|
+
didMatches: false,
|
|
20437
|
+
error: void 0
|
|
20438
|
+
};
|
|
20439
|
+
try {
|
|
20440
|
+
const deviceShare = await storage.getDevice?.();
|
|
20441
|
+
result.hasDeviceShare = !!deviceShare;
|
|
20442
|
+
if (!deviceShare) {
|
|
19379
20443
|
result.error = "No device share found";
|
|
19380
20444
|
return result;
|
|
19381
20445
|
}
|
|
@@ -19411,12 +20475,16 @@ async function atomicRecovery(recoveryShare, authShareData, storage, options = {
|
|
|
19411
20475
|
__name(atomicRecovery, "atomicRecovery");
|
|
19412
20476
|
|
|
19413
20477
|
// src/sss-strategy.ts
|
|
20478
|
+
var import_types3 = __toESM(require_dist());
|
|
19414
20479
|
var SSS_DB_NAME = "lcb-sss-keys";
|
|
20480
|
+
var DEFAULT_DEVICE_SHARE_ID2 = "sss-device-share";
|
|
20481
|
+
var MAX_RECONCILIATION_HISTORY = 5;
|
|
19415
20482
|
var defaultStorage = {
|
|
19416
20483
|
storeDeviceShare,
|
|
19417
20484
|
getDeviceShare,
|
|
19418
20485
|
hasDeviceShare,
|
|
19419
20486
|
clearAllShares,
|
|
20487
|
+
deleteDeviceShare,
|
|
19420
20488
|
storeShareVersion,
|
|
19421
20489
|
getShareVersion
|
|
19422
20490
|
};
|
|
@@ -19425,10 +20493,24 @@ var buildHeaders = /* @__PURE__ */ __name((token, didAuthVp, tenantId) => ({
|
|
|
19425
20493
|
...didAuthVp ? { Authorization: `Bearer ${didAuthVp}` } : {},
|
|
19426
20494
|
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
19427
20495
|
}), "buildHeaders");
|
|
20496
|
+
var SERVER_REQUEST_TIMEOUT_MS = 3e4;
|
|
20497
|
+
var requestTimeoutSignal = /* @__PURE__ */ __name((ms = SERVER_REQUEST_TIMEOUT_MS) => typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(ms) : void 0, "requestTimeoutSignal");
|
|
20498
|
+
var describeHttpStatus = /* @__PURE__ */ __name((response) => response.statusText ? `${response.status} ${response.statusText}` : `${response.status}`, "describeHttpStatus");
|
|
20499
|
+
var EscrowRequestError = class extends Error {
|
|
20500
|
+
constructor(status) {
|
|
20501
|
+
super("Escrow request failed");
|
|
20502
|
+
this.status = status;
|
|
20503
|
+
this.name = "EscrowRequestError";
|
|
20504
|
+
}
|
|
20505
|
+
static {
|
|
20506
|
+
__name(this, "EscrowRequestError");
|
|
20507
|
+
}
|
|
20508
|
+
};
|
|
19428
20509
|
var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, providerType, shareVersion, tenantId) => {
|
|
19429
20510
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19430
20511
|
method: "POST",
|
|
19431
20512
|
headers: buildHeaders(token, void 0, tenantId),
|
|
20513
|
+
signal: requestTimeoutSignal(),
|
|
19432
20514
|
body: JSON.stringify({
|
|
19433
20515
|
authToken: token,
|
|
19434
20516
|
providerType,
|
|
@@ -19437,42 +20519,103 @@ var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, provider
|
|
|
19437
20519
|
});
|
|
19438
20520
|
if (!response.ok) {
|
|
19439
20521
|
if (response.status === 404) return null;
|
|
19440
|
-
throw new Error(`Failed to fetch key status: ${response
|
|
20522
|
+
throw new Error(`Failed to fetch key status: ${describeHttpStatus(response)}`);
|
|
19441
20523
|
}
|
|
19442
20524
|
return response.json();
|
|
19443
20525
|
}, "fetchAuthShareRaw");
|
|
19444
|
-
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId) => {
|
|
20526
|
+
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId, expectedShareVersion) => {
|
|
19445
20527
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19446
20528
|
method: "PUT",
|
|
19447
20529
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20530
|
+
signal: requestTimeoutSignal(),
|
|
19448
20531
|
body: JSON.stringify({
|
|
19449
20532
|
authToken: token,
|
|
19450
20533
|
providerType,
|
|
19451
20534
|
authShare: { encryptedData: authShare, encryptedDek: "", iv: "" },
|
|
19452
20535
|
primaryDid,
|
|
19453
|
-
securityLevel: "basic"
|
|
20536
|
+
securityLevel: "basic",
|
|
20537
|
+
sssActivationState: "provisional",
|
|
20538
|
+
expectedShareVersion
|
|
19454
20539
|
})
|
|
19455
20540
|
});
|
|
19456
20541
|
if (!response.ok) {
|
|
19457
|
-
|
|
20542
|
+
const message = `Failed to store auth share: ${describeHttpStatus(response)}`;
|
|
20543
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20544
|
+
throw new ShareWriteRejectedError(message);
|
|
20545
|
+
}
|
|
20546
|
+
throw new Error(message);
|
|
19458
20547
|
}
|
|
19459
20548
|
const data = await response.json();
|
|
19460
|
-
return {
|
|
20549
|
+
return {
|
|
20550
|
+
shareVersion: data.shareVersion ?? 1,
|
|
20551
|
+
expectedShareVersionChecked: data.expectedShareVersionChecked === true
|
|
20552
|
+
};
|
|
19461
20553
|
}, "putAuthShare");
|
|
20554
|
+
var authShareToString = /* @__PURE__ */ __name((authShare) => {
|
|
20555
|
+
if (typeof authShare === "string") return authShare;
|
|
20556
|
+
if (typeof authShare === "object" && authShare !== null && "encryptedData" in authShare && typeof authShare.encryptedData === "string") {
|
|
20557
|
+
return authShare.encryptedData;
|
|
20558
|
+
}
|
|
20559
|
+
return null;
|
|
20560
|
+
}, "authShareToString");
|
|
20561
|
+
var requestFreshDidAuthVp = /* @__PURE__ */ __name(async (serverUrl, privateKey, did, signDidAuthVp, tenantId) => {
|
|
20562
|
+
const bootstrapVp = await signDidAuthVp(privateKey);
|
|
20563
|
+
const response = await fetch(`${serverUrl}/keys/challenge`, {
|
|
20564
|
+
method: "POST",
|
|
20565
|
+
headers: buildHeaders("", bootstrapVp, tenantId),
|
|
20566
|
+
signal: requestTimeoutSignal(),
|
|
20567
|
+
body: JSON.stringify({ did })
|
|
20568
|
+
});
|
|
20569
|
+
if (!response.ok) {
|
|
20570
|
+
throw new Error(`Failed to request DID challenge: ${describeHttpStatus(response)}`);
|
|
20571
|
+
}
|
|
20572
|
+
const data = await response.json();
|
|
20573
|
+
if (!data.challenge) throw new Error("DID challenge response was empty");
|
|
20574
|
+
return signDidAuthVp(privateKey, data.challenge);
|
|
20575
|
+
}, "requestFreshDidAuthVp");
|
|
19462
20576
|
var fetchRecoveryShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, type, credentialId, tenantId) => {
|
|
19463
|
-
const params = new URLSearchParams({ type, providerType
|
|
20577
|
+
const params = new URLSearchParams({ type, providerType });
|
|
19464
20578
|
if (credentialId) {
|
|
19465
20579
|
params.set("credentialId", credentialId);
|
|
19466
20580
|
}
|
|
19467
20581
|
const response = await fetch(`${serverUrl}/keys/recovery?${params}`, {
|
|
19468
20582
|
method: "GET",
|
|
19469
|
-
headers:
|
|
20583
|
+
headers: {
|
|
20584
|
+
"Content-Type": "application/json",
|
|
20585
|
+
"X-Auth-Token": token,
|
|
20586
|
+
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
20587
|
+
}
|
|
19470
20588
|
});
|
|
19471
20589
|
if (!response.ok) {
|
|
19472
20590
|
throw new Error(`No ${type} recovery share found`);
|
|
19473
20591
|
}
|
|
19474
20592
|
return response.json();
|
|
19475
20593
|
}, "fetchRecoveryShare");
|
|
20594
|
+
var IdentityRecoverySessionConsumedError = class extends Error {
|
|
20595
|
+
static {
|
|
20596
|
+
__name(this, "IdentityRecoverySessionConsumedError");
|
|
20597
|
+
}
|
|
20598
|
+
constructor(message) {
|
|
20599
|
+
super(message);
|
|
20600
|
+
this.name = "IdentityRecoverySessionConsumedError";
|
|
20601
|
+
}
|
|
20602
|
+
};
|
|
20603
|
+
var postJson = /* @__PURE__ */ __name(async (url, body, headers) => {
|
|
20604
|
+
const response = await fetch(url, {
|
|
20605
|
+
method: "POST",
|
|
20606
|
+
headers,
|
|
20607
|
+
body: JSON.stringify(body)
|
|
20608
|
+
});
|
|
20609
|
+
if (!response.ok) {
|
|
20610
|
+
const data = await response.json().catch(() => null);
|
|
20611
|
+
const message = data?.error?.message || data?.message || response.statusText;
|
|
20612
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20613
|
+
throw new ShareWriteRejectedError(message);
|
|
20614
|
+
}
|
|
20615
|
+
throw new Error(message);
|
|
20616
|
+
}
|
|
20617
|
+
return response.json();
|
|
20618
|
+
}, "postJson");
|
|
19476
20619
|
var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
19477
20620
|
const response = await fetch(`${serverUrl}/keys/recovery`, {
|
|
19478
20621
|
method: "POST",
|
|
@@ -19480,9 +20623,40 @@ var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, provide
|
|
|
19480
20623
|
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
19481
20624
|
});
|
|
19482
20625
|
if (!response.ok) {
|
|
19483
|
-
throw new Error(`Failed to add recovery method: ${response
|
|
20626
|
+
throw new Error(`Failed to add recovery method: ${describeHttpStatus(response)}`);
|
|
19484
20627
|
}
|
|
19485
20628
|
}, "postRecoveryMethod");
|
|
20629
|
+
var postRecoveryConfirmation = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
20630
|
+
const response = await fetch(`${serverUrl}/keys/recovery/confirm`, {
|
|
20631
|
+
method: "POST",
|
|
20632
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20633
|
+
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
20634
|
+
});
|
|
20635
|
+
if (!response.ok) {
|
|
20636
|
+
const data = await response.json().catch(() => null);
|
|
20637
|
+
const message = data?.error?.message || data?.message || `Failed to confirm recovery method`;
|
|
20638
|
+
throw new Error(message);
|
|
20639
|
+
}
|
|
20640
|
+
}, "postRecoveryConfirmation");
|
|
20641
|
+
var calculateShareChecksum = /* @__PURE__ */ __name(async (share) => {
|
|
20642
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(share));
|
|
20643
|
+
return bytesToHex(new Uint8Array(digest));
|
|
20644
|
+
}, "calculateShareChecksum");
|
|
20645
|
+
var RECOVERY_PHRASE_CHALLENGE_COUNT = 2;
|
|
20646
|
+
var selectRecoveryPhraseChallengeIndices = /* @__PURE__ */ __name((wordCount, challengeCount = RECOVERY_PHRASE_CHALLENGE_COUNT) => {
|
|
20647
|
+
if (wordCount < challengeCount) {
|
|
20648
|
+
throw new Error("Recovery phrase does not contain enough words for confirmation");
|
|
20649
|
+
}
|
|
20650
|
+
const indices = /* @__PURE__ */ new Set();
|
|
20651
|
+
const limit = 4294967296 - 4294967296 % wordCount;
|
|
20652
|
+
while (indices.size < challengeCount) {
|
|
20653
|
+
const randomValue = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
20654
|
+
if (randomValue === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
20655
|
+
if (randomValue >= limit) continue;
|
|
20656
|
+
indices.add(randomValue % wordCount);
|
|
20657
|
+
}
|
|
20658
|
+
return [...indices].sort((a, b) => a - b);
|
|
20659
|
+
}, "selectRecoveryPhraseChallengeIndices");
|
|
19486
20660
|
var VERSION_PREFIX_LEN = 4;
|
|
19487
20661
|
var formatVersionedEmailShare = /* @__PURE__ */ __name((emailShare, shareVersion) => shareVersion.toString(16).padStart(VERSION_PREFIX_LEN, "0") + emailShare, "formatVersionedEmailShare");
|
|
19488
20662
|
var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
@@ -19495,44 +20669,512 @@ var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
|
19495
20669
|
}
|
|
19496
20670
|
return { share: input, version: void 0 };
|
|
19497
20671
|
}, "parseVersionedEmailShare");
|
|
19498
|
-
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, shareVersion, tenantId) => {
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
}
|
|
19512
|
-
}, "sendEmailBackupShare");
|
|
19513
|
-
var sendEmailShareToRecoveryEmail = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, shareVersion, tenantId) => {
|
|
19514
|
-
const payload = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20672
|
+
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, escrowRelayPublicKey, escrowRelayKeyId, emailBranding, shareVersion, didAuthVp, tenantId) => {
|
|
20673
|
+
const recoveryKey = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20674
|
+
const confirmationCode = generateEmailRelayConfirmationCode();
|
|
20675
|
+
const relayPayload = await encryptEmailRelayPayload(
|
|
20676
|
+
{
|
|
20677
|
+
targetEmail: email,
|
|
20678
|
+
recoveryKey,
|
|
20679
|
+
confirmationCode,
|
|
20680
|
+
branding: emailBranding
|
|
20681
|
+
},
|
|
20682
|
+
escrowRelayPublicKey,
|
|
20683
|
+
escrowRelayKeyId
|
|
20684
|
+
);
|
|
19515
20685
|
const response = await fetch(`${serverUrl}/keys/email-backup`, {
|
|
19516
20686
|
method: "POST",
|
|
19517
|
-
headers: buildHeaders(token,
|
|
20687
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19518
20688
|
body: JSON.stringify({
|
|
19519
20689
|
authToken: token,
|
|
19520
20690
|
providerType,
|
|
19521
|
-
|
|
19522
|
-
|
|
20691
|
+
relayPayload,
|
|
20692
|
+
confirmationCode,
|
|
20693
|
+
email,
|
|
20694
|
+
shareVersion
|
|
19523
20695
|
})
|
|
19524
20696
|
});
|
|
19525
20697
|
if (!response.ok) {
|
|
19526
|
-
throw new Error(`Failed to send recovery share
|
|
20698
|
+
throw new Error(`Failed to send recovery share: ${describeHttpStatus(response)}`);
|
|
20699
|
+
}
|
|
20700
|
+
}, "sendEmailBackupShare");
|
|
20701
|
+
var rotationChain = Promise.resolve();
|
|
20702
|
+
var withRotationLock = /* @__PURE__ */ __name((operation) => {
|
|
20703
|
+
const run = rotationChain.then(operation, operation);
|
|
20704
|
+
rotationChain = run.catch(() => void 0);
|
|
20705
|
+
return run;
|
|
20706
|
+
}, "withRotationLock");
|
|
20707
|
+
var pendingShareId = /* @__PURE__ */ __name((storageId) => `sss-pending-share:${storageId ?? DEFAULT_DEVICE_SHARE_ID2}`, "pendingShareId");
|
|
20708
|
+
var PENDING_WRITE_EXPIRY_MS = 10 * 60 * 1e3;
|
|
20709
|
+
var MAX_PENDING_SHARE_CANDIDATES = 8;
|
|
20710
|
+
var readPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => {
|
|
20711
|
+
const raw = await storage.getDeviceShare(pendingShareId(storageId));
|
|
20712
|
+
if (!raw) return [];
|
|
20713
|
+
if (!raw.trimStart().startsWith("{")) return [{ share: raw, createdAt: 0 }];
|
|
20714
|
+
const data = JSON.parse(raw);
|
|
20715
|
+
if (!data || typeof data !== "object" || !("v" in data) || data.v !== 1 || !("candidates" in data) || !Array.isArray(data.candidates) || !data.candidates.every(
|
|
20716
|
+
(candidate) => candidate !== null && typeof candidate === "object" && "share" in candidate && typeof candidate.share === "string" && "createdAt" in candidate && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && candidate.createdAt >= 0
|
|
20717
|
+
))
|
|
20718
|
+
throw new Error("Invalid pending share candidates");
|
|
20719
|
+
return data.candidates;
|
|
20720
|
+
}, "readPendingShareCandidates");
|
|
20721
|
+
var clearPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => storage.clearAllShares(pendingShareId(storageId)), "clearPendingShareCandidates");
|
|
20722
|
+
var writePendingShareCandidates = /* @__PURE__ */ __name(async (storage, candidates, storageId) => {
|
|
20723
|
+
if (!candidates.length) return clearPendingShareCandidates(storage, storageId);
|
|
20724
|
+
await storage.storeDeviceShare(JSON.stringify({ v: 1, candidates }), pendingShareId(storageId));
|
|
20725
|
+
}, "writePendingShareCandidates");
|
|
20726
|
+
var addPendingShareCandidate = /* @__PURE__ */ __name(async (storage, candidate, storageId) => {
|
|
20727
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20728
|
+
if (candidates.length >= MAX_PENDING_SHARE_CANDIDATES) {
|
|
20729
|
+
throw new AtomicUpdateError(
|
|
20730
|
+
"Reconcile the pending share update before writing new shares",
|
|
20731
|
+
"store_device",
|
|
20732
|
+
false
|
|
20733
|
+
);
|
|
20734
|
+
}
|
|
20735
|
+
await writePendingShareCandidates(storage, [...candidates, candidate], storageId);
|
|
20736
|
+
}, "addPendingShareCandidate");
|
|
20737
|
+
var removePendingShareCandidate = /* @__PURE__ */ __name(async (storage, share, storageId) => {
|
|
20738
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20739
|
+
await writePendingShareCandidates(
|
|
20740
|
+
storage,
|
|
20741
|
+
candidates.filter((candidate) => candidate.share !== share),
|
|
20742
|
+
storageId
|
|
20743
|
+
);
|
|
20744
|
+
}, "removePendingShareCandidate");
|
|
20745
|
+
var resolvePendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId, fetchCurrent, expectedDid, didFromPrivateKey, assertActiveUser = () => {
|
|
20746
|
+
}, onPromoted, fetchHistorical) => {
|
|
20747
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20748
|
+
const snapshot = await fetchCurrent();
|
|
20749
|
+
assertActiveUser();
|
|
20750
|
+
const { authShare, currentVersion } = snapshot;
|
|
20751
|
+
if (authShare && (currentVersion === null || !Number.isInteger(currentVersion) || currentVersion < 1)) {
|
|
20752
|
+
throw new Error("Current auth share is missing a valid share version");
|
|
20753
|
+
}
|
|
20754
|
+
if (authShare && currentVersion !== null) {
|
|
20755
|
+
for (const candidate of candidates) {
|
|
20756
|
+
const health = await verifyStoredShares(
|
|
20757
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth") },
|
|
20758
|
+
expectedDid,
|
|
20759
|
+
didFromPrivateKey
|
|
20760
|
+
);
|
|
20761
|
+
assertActiveUser();
|
|
20762
|
+
if (!health.healthy) continue;
|
|
20763
|
+
const privateKey = await reconstructFromShares([candidate.share, authShare]);
|
|
20764
|
+
assertActiveUser();
|
|
20765
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20766
|
+
await storage.storeShareVersion(currentVersion, storageId);
|
|
20767
|
+
await removePendingShareCandidate(storage, candidate.share, storageId);
|
|
20768
|
+
assertActiveUser();
|
|
20769
|
+
onPromoted?.(snapshot);
|
|
20770
|
+
return { snapshot, privateKey };
|
|
20771
|
+
}
|
|
20772
|
+
}
|
|
20773
|
+
const matched = /* @__PURE__ */ new Set();
|
|
20774
|
+
const history = /* @__PURE__ */ new Map();
|
|
20775
|
+
let historyComplete = true;
|
|
20776
|
+
if (candidates.length && currentVersion !== null) {
|
|
20777
|
+
for (let version = currentVersion - 1; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
20778
|
+
if (!fetchHistorical) {
|
|
20779
|
+
historyComplete = false;
|
|
20780
|
+
break;
|
|
20781
|
+
}
|
|
20782
|
+
try {
|
|
20783
|
+
const share = await fetchHistorical(version);
|
|
20784
|
+
if (share) history.set(version, share);
|
|
20785
|
+
} catch {
|
|
20786
|
+
historyComplete = false;
|
|
20787
|
+
}
|
|
20788
|
+
assertActiveUser();
|
|
20789
|
+
}
|
|
20790
|
+
}
|
|
20791
|
+
const mainShare = await storage.getDeviceShare(storageId);
|
|
20792
|
+
let mainHealthy = false;
|
|
20793
|
+
const retainedShares = [...history.values(), ...authShare ? [authShare] : []];
|
|
20794
|
+
if (mainShare) {
|
|
20795
|
+
for (const share of retainedShares) {
|
|
20796
|
+
const health = await verifyStoredShares(
|
|
20797
|
+
{ getDevice: /* @__PURE__ */ __name(async () => mainShare, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20798
|
+
expectedDid,
|
|
20799
|
+
didFromPrivateKey
|
|
20800
|
+
);
|
|
20801
|
+
assertActiveUser();
|
|
20802
|
+
if (health.healthy) {
|
|
20803
|
+
mainHealthy = true;
|
|
20804
|
+
break;
|
|
20805
|
+
}
|
|
20806
|
+
}
|
|
20807
|
+
}
|
|
20808
|
+
let promoted;
|
|
20809
|
+
for (const candidate of candidates) {
|
|
20810
|
+
for (const [version, share] of history) {
|
|
20811
|
+
const health = await verifyStoredShares(
|
|
20812
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20813
|
+
expectedDid,
|
|
20814
|
+
didFromPrivateKey
|
|
20815
|
+
);
|
|
20816
|
+
assertActiveUser();
|
|
20817
|
+
if (!health.healthy) continue;
|
|
20818
|
+
matched.add(candidate.share);
|
|
20819
|
+
if (!mainHealthy && !promoted) {
|
|
20820
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20821
|
+
await storage.storeShareVersion(version, storageId);
|
|
20822
|
+
assertActiveUser();
|
|
20823
|
+
onPromoted?.({ currentVersion: version, authShare: share });
|
|
20824
|
+
promoted = candidate.share;
|
|
20825
|
+
}
|
|
20826
|
+
break;
|
|
20827
|
+
}
|
|
20828
|
+
}
|
|
20829
|
+
const live = candidates.filter(
|
|
20830
|
+
(candidate) => candidate.share !== promoted && (matched.has(candidate.share) || !historyComplete || candidate.createdAt !== 0 && Date.now() - candidate.createdAt <= PENDING_WRITE_EXPIRY_MS)
|
|
20831
|
+
);
|
|
20832
|
+
if (live.length !== candidates.length) {
|
|
20833
|
+
assertActiveUser();
|
|
20834
|
+
await writePendingShareCandidates(storage, live, storageId);
|
|
20835
|
+
}
|
|
20836
|
+
return { snapshot, privateKey: null };
|
|
20837
|
+
}, "resolvePendingShareCandidates");
|
|
20838
|
+
var activeShareUpdates = /* @__PURE__ */ new WeakMap();
|
|
20839
|
+
var shareUpdateStorageOwners = /* @__PURE__ */ new WeakMap();
|
|
20840
|
+
var withShareUpdateLock = /* @__PURE__ */ __name(async (storage, storageId, operation) => {
|
|
20841
|
+
const id = pendingShareId(storageId);
|
|
20842
|
+
const owner = shareUpdateStorageOwners.get(storage) ?? storage;
|
|
20843
|
+
const active = activeShareUpdates.get(owner) ?? /* @__PURE__ */ new Set();
|
|
20844
|
+
activeShareUpdates.set(owner, active);
|
|
20845
|
+
const busy = /* @__PURE__ */ __name(() => new AtomicUpdateError("A share update is already in progress", "store_device", false), "busy");
|
|
20846
|
+
if (active.has(id)) throw busy();
|
|
20847
|
+
active.add(id);
|
|
20848
|
+
try {
|
|
20849
|
+
if (typeof navigator !== "undefined" && navigator.locks) {
|
|
20850
|
+
return await navigator.locks.request(id, { ifAvailable: true }, async (lock) => {
|
|
20851
|
+
if (!lock) throw busy();
|
|
20852
|
+
return operation();
|
|
20853
|
+
});
|
|
20854
|
+
}
|
|
20855
|
+
return await operation();
|
|
20856
|
+
} finally {
|
|
20857
|
+
active.delete(id);
|
|
20858
|
+
}
|
|
20859
|
+
}, "withShareUpdateLock");
|
|
20860
|
+
var persistSharesWithoutLock = /* @__PURE__ */ __name(async (privateKey, serverUrl, token, providerType, primaryDid, storage, storageId, didFromPrivateKey, signDidAuthVp, tenantId, onPromoted, assertActiveUser = () => {
|
|
20861
|
+
}) => {
|
|
20862
|
+
if (primaryDid && didFromPrivateKey) {
|
|
20863
|
+
const derivedDid = await didFromPrivateKey(privateKey);
|
|
20864
|
+
if (derivedDid && derivedDid !== primaryDid) {
|
|
20865
|
+
throw new Error(
|
|
20866
|
+
"rotateShares: key does not match expected DID \u2014 refusing to overwrite server shares"
|
|
20867
|
+
);
|
|
20868
|
+
}
|
|
20869
|
+
}
|
|
20870
|
+
const { snapshot } = await resolvePendingShareCandidates(
|
|
20871
|
+
storage,
|
|
20872
|
+
storageId,
|
|
20873
|
+
async () => {
|
|
20874
|
+
const data = await fetchAuthShareRaw(
|
|
20875
|
+
serverUrl,
|
|
20876
|
+
token,
|
|
20877
|
+
providerType,
|
|
20878
|
+
void 0,
|
|
20879
|
+
tenantId
|
|
20880
|
+
);
|
|
20881
|
+
return {
|
|
20882
|
+
currentVersion: data?.shareVersion ?? null,
|
|
20883
|
+
authShare: authShareToString(data?.authShare)
|
|
20884
|
+
};
|
|
20885
|
+
},
|
|
20886
|
+
primaryDid,
|
|
20887
|
+
didFromPrivateKey ?? (async (key) => key === privateKey ? primaryDid : `${primaryDid}:mismatch`),
|
|
20888
|
+
assertActiveUser,
|
|
20889
|
+
onPromoted,
|
|
20890
|
+
async (version) => authShareToString(
|
|
20891
|
+
(await fetchAuthShareRaw(serverUrl, token, providerType, version, tenantId))?.authShare
|
|
20892
|
+
)
|
|
20893
|
+
);
|
|
20894
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(serverUrl, privateKey, primaryDid, signDidAuthVp, tenantId) : void 0;
|
|
20895
|
+
assertActiveUser();
|
|
20896
|
+
let shareVersion;
|
|
20897
|
+
let stagedShare = "";
|
|
20898
|
+
const shares = await atomicShareUpdate(privateKey, {
|
|
20899
|
+
storeDevice: /* @__PURE__ */ __name(async (share) => {
|
|
20900
|
+
assertActiveUser();
|
|
20901
|
+
stagedShare = share;
|
|
20902
|
+
await addPendingShareCandidate(storage, { share, createdAt: Date.now() }, storageId);
|
|
20903
|
+
}, "storeDevice"),
|
|
20904
|
+
clearDevice: /* @__PURE__ */ __name(() => removePendingShareCandidate(storage, stagedShare, storageId), "clearDevice"),
|
|
20905
|
+
storeAuth: /* @__PURE__ */ __name(async (share) => {
|
|
20906
|
+
assertActiveUser();
|
|
20907
|
+
const result = await putAuthShare(
|
|
20908
|
+
serverUrl,
|
|
20909
|
+
token,
|
|
20910
|
+
providerType,
|
|
20911
|
+
share,
|
|
20912
|
+
primaryDid,
|
|
20913
|
+
didAuthVp,
|
|
20914
|
+
tenantId,
|
|
20915
|
+
snapshot.currentVersion ?? 0
|
|
20916
|
+
);
|
|
20917
|
+
shareVersion = result.shareVersion;
|
|
20918
|
+
}, "storeAuth")
|
|
20919
|
+
});
|
|
20920
|
+
if (shareVersion === void 0) {
|
|
20921
|
+
throw new Error("Server did not return a share version");
|
|
19527
20922
|
}
|
|
19528
|
-
|
|
20923
|
+
await storage.storeDeviceShare(shares.deviceShare, storageId);
|
|
20924
|
+
await storage.storeShareVersion(shareVersion, storageId);
|
|
20925
|
+
await removePendingShareCandidate(storage, shares.deviceShare, storageId);
|
|
20926
|
+
assertActiveUser();
|
|
20927
|
+
return { shares, shareVersion };
|
|
20928
|
+
}, "persistSharesWithoutLock");
|
|
20929
|
+
var persistSharesAtomically = /* @__PURE__ */ __name((...args) => withShareUpdateLock(
|
|
20930
|
+
args[5],
|
|
20931
|
+
args[6],
|
|
20932
|
+
() => withRotationLock(() => persistSharesWithoutLock(...args))
|
|
20933
|
+
), "persistSharesAtomically");
|
|
19529
20934
|
function createSSSStrategy(config) {
|
|
19530
|
-
const {
|
|
20935
|
+
const {
|
|
20936
|
+
serverUrl,
|
|
20937
|
+
enableEmailBackupShare = false,
|
|
20938
|
+
escrowRelayPublicKey = "",
|
|
20939
|
+
escrowRelayKeyId = "",
|
|
20940
|
+
emailBranding,
|
|
20941
|
+
tenantId
|
|
20942
|
+
} = config;
|
|
19531
20943
|
const storage = config.storage || defaultStorage;
|
|
20944
|
+
let storageGeneration = 0;
|
|
20945
|
+
const localWrites = /* @__PURE__ */ new Set();
|
|
20946
|
+
const guardedStorage = /* @__PURE__ */ __name(() => {
|
|
20947
|
+
const generation = storageGeneration;
|
|
20948
|
+
const write = /* @__PURE__ */ __name((operation) => {
|
|
20949
|
+
if (generation !== storageGeneration) {
|
|
20950
|
+
return Promise.reject(new Error("This recovery request was cancelled."));
|
|
20951
|
+
}
|
|
20952
|
+
const pending = operation();
|
|
20953
|
+
localWrites.add(pending);
|
|
20954
|
+
return pending.finally(() => localWrites.delete(pending));
|
|
20955
|
+
}, "write");
|
|
20956
|
+
const guarded = {
|
|
20957
|
+
...storage,
|
|
20958
|
+
storeDeviceShare: /* @__PURE__ */ __name((share, id) => write(() => storage.storeDeviceShare(share, id)), "storeDeviceShare"),
|
|
20959
|
+
storeShareVersion: /* @__PURE__ */ __name((version, id) => write(() => storage.storeShareVersion(version, id)), "storeShareVersion"),
|
|
20960
|
+
clearAllShares: /* @__PURE__ */ __name((id) => write(() => storage.clearAllShares(id)), "clearAllShares"),
|
|
20961
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => write(() => storage.deleteDeviceShare(id)), "deleteDeviceShare")
|
|
20962
|
+
};
|
|
20963
|
+
shareUpdateStorageOwners.set(guarded, storage);
|
|
20964
|
+
return guarded;
|
|
20965
|
+
}, "guardedStorage");
|
|
19532
20966
|
let activeStorageId;
|
|
19533
20967
|
let lastEmailShare;
|
|
19534
20968
|
let lastShareVersion;
|
|
19535
|
-
let
|
|
20969
|
+
let pendingPhraseConfirmation;
|
|
20970
|
+
let pendingBackupConfirmation;
|
|
20971
|
+
let pendingIdentityRecovery;
|
|
20972
|
+
let lastServerSnapshot = null;
|
|
20973
|
+
let escrowEnrollmentInFlight;
|
|
20974
|
+
let unenrolledRotation;
|
|
20975
|
+
const escrowRequest = /* @__PURE__ */ __name(async (path, init, pinAttempt = false) => {
|
|
20976
|
+
const response = await fetch(`${serverUrl}/keys/escrow${path}`, {
|
|
20977
|
+
signal: requestTimeoutSignal(),
|
|
20978
|
+
...init
|
|
20979
|
+
});
|
|
20980
|
+
if (!response.ok) {
|
|
20981
|
+
if (!pinAttempt && path === "/recover" && response.status === 429) {
|
|
20982
|
+
const body = await response.json().catch(() => void 0);
|
|
20983
|
+
const message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : "";
|
|
20984
|
+
const retryAfter = /Try again after (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\./.exec(
|
|
20985
|
+
message
|
|
20986
|
+
)?.[1];
|
|
20987
|
+
throw new EscrowHoldRestartThrottledError(
|
|
20988
|
+
retryAfter && Number.isFinite(Date.parse(retryAfter)) ? retryAfter : void 0
|
|
20989
|
+
);
|
|
20990
|
+
}
|
|
20991
|
+
if (pinAttempt && response.status === 429) {
|
|
20992
|
+
const body = await response.json().catch(() => void 0);
|
|
20993
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_LOCKED_MESSAGE) {
|
|
20994
|
+
throw new EscrowPinLockedError();
|
|
20995
|
+
}
|
|
20996
|
+
throw new EscrowPinThrottledError();
|
|
20997
|
+
}
|
|
20998
|
+
if (pinAttempt && response.status === 403) {
|
|
20999
|
+
const body = await response.json().catch(() => void 0);
|
|
21000
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_UNAVAILABLE_MESSAGE) {
|
|
21001
|
+
throw new EscrowPinUnavailableError();
|
|
21002
|
+
}
|
|
21003
|
+
if (body && typeof body === "object" && "message" in body && typeof body.message === "string" && body.message.startsWith("Incorrect PIN.")) {
|
|
21004
|
+
const match = import_types3.ESCROW_PIN_MISMATCH_PATTERN.exec(body.message);
|
|
21005
|
+
const remaining = match ? Number(match[1]) : 0;
|
|
21006
|
+
throw new EscrowPinMismatchError(
|
|
21007
|
+
Number.isSafeInteger(remaining) && remaining >= 0 && remaining < ESCROW_PIN_MAX_ATTEMPTS ? remaining : 0
|
|
21008
|
+
);
|
|
21009
|
+
}
|
|
21010
|
+
}
|
|
21011
|
+
throw new EscrowRequestError(response.status);
|
|
21012
|
+
}
|
|
21013
|
+
try {
|
|
21014
|
+
return await response.json();
|
|
21015
|
+
} catch {
|
|
21016
|
+
throw new EscrowRequestError(response.status);
|
|
21017
|
+
}
|
|
21018
|
+
}, "escrowRequest");
|
|
21019
|
+
const fetchVerifiedEnclaveKey = /* @__PURE__ */ __name(async () => {
|
|
21020
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21021
|
+
const { attestation } = await escrowRequest("/attestation", {
|
|
21022
|
+
method: "GET",
|
|
21023
|
+
headers: buildHeaders("", void 0, tenantId)
|
|
21024
|
+
});
|
|
21025
|
+
return verifyEnclaveAttestation(attestation, config.escrow.attestation);
|
|
21026
|
+
}, "fetchVerifiedEnclaveKey");
|
|
21027
|
+
const enrollEscrow = /* @__PURE__ */ __name(async (token, providerType, privateKey, primaryDid, shares, shareVersion, signDidAuthVp, verifiedKey, pinMaterial, clearPin = false) => {
|
|
21028
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21029
|
+
if (!signDidAuthVp) throw new Error("DID proof signing is required for escrow enrollment");
|
|
21030
|
+
const { publicKey, keyId } = verifiedKey ?? await fetchVerifiedEnclaveKey();
|
|
21031
|
+
const envelope = await encryptEscrowBlob(
|
|
21032
|
+
{
|
|
21033
|
+
recoveryShare: shares.recoveryShare,
|
|
21034
|
+
did: primaryDid,
|
|
21035
|
+
shareVersion,
|
|
21036
|
+
...pinMaterial ? { pinVerifier: pinMaterial.pinVerifier } : {}
|
|
21037
|
+
},
|
|
21038
|
+
publicKey,
|
|
21039
|
+
keyId
|
|
21040
|
+
);
|
|
21041
|
+
const vp = await requestFreshDidAuthVp(
|
|
21042
|
+
serverUrl,
|
|
21043
|
+
privateKey,
|
|
21044
|
+
primaryDid,
|
|
21045
|
+
signDidAuthVp,
|
|
21046
|
+
tenantId
|
|
21047
|
+
);
|
|
21048
|
+
await escrowRequest("", {
|
|
21049
|
+
method: "POST",
|
|
21050
|
+
headers: buildHeaders(token, vp, tenantId),
|
|
21051
|
+
body: JSON.stringify({
|
|
21052
|
+
authToken: token,
|
|
21053
|
+
providerType,
|
|
21054
|
+
envelope,
|
|
21055
|
+
shareVersion,
|
|
21056
|
+
enclaveKeyId: keyId,
|
|
21057
|
+
...pinMaterial ? { pinSalt: pinMaterial.pinSalt } : {},
|
|
21058
|
+
...clearPin ? { clearPin: true } : {}
|
|
21059
|
+
})
|
|
21060
|
+
});
|
|
21061
|
+
}, "enrollEscrow");
|
|
21062
|
+
const tryEnrollEscrow = /* @__PURE__ */ __name(async (...args) => {
|
|
21063
|
+
if (!config.escrow?.enabled) return;
|
|
21064
|
+
try {
|
|
21065
|
+
await enrollEscrow(...args);
|
|
21066
|
+
} catch (err) {
|
|
21067
|
+
try {
|
|
21068
|
+
if (config.onEscrowError) config.onEscrowError(err);
|
|
21069
|
+
else console.warn("SSS: escrow enrollment failed");
|
|
21070
|
+
} catch {
|
|
21071
|
+
console.warn("SSS: escrow error reporting failed");
|
|
21072
|
+
}
|
|
21073
|
+
}
|
|
21074
|
+
}, "tryEnrollEscrow");
|
|
21075
|
+
const ensureEscrowEnrollmentUnguarded = /* @__PURE__ */ __name(async (self, params, storage2, forceRotate = false) => {
|
|
21076
|
+
if (!config.escrow?.enabled) return { enrolled: false, reason: "disabled" };
|
|
21077
|
+
const pin = params.options?.pin;
|
|
21078
|
+
if (pin !== void 0) {
|
|
21079
|
+
const validation = validatePin(pin);
|
|
21080
|
+
if (!validation.ok)
|
|
21081
|
+
throw new Error(
|
|
21082
|
+
validation.reason === "length" ? "PIN must contain 6\u201312 digits." : "Choose a PIN without trivial or sequential patterns."
|
|
21083
|
+
);
|
|
21084
|
+
}
|
|
21085
|
+
const status = await self.fetchServerKeyStatus(params.token, params.providerType);
|
|
21086
|
+
if (status.escrowOptedOut) return { enrolled: false, reason: "opted-out" };
|
|
21087
|
+
if (!forceRotate && pin === void 0 && status.shareVersion !== null && status.recoveryMethods.some(
|
|
21088
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21089
|
+
))
|
|
21090
|
+
return { enrolled: true, changed: false };
|
|
21091
|
+
if (!status.primaryDid) throw new Error("Cannot enroll escrow without a primary DID");
|
|
21092
|
+
if (!params.signDidAuthVp) {
|
|
21093
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21094
|
+
}
|
|
21095
|
+
if (!forceRotate && pin === void 0 && unenrolledRotation && unenrolledRotation.shareVersion === status.shareVersion && unenrolledRotation.primaryDid === status.primaryDid) {
|
|
21096
|
+
const retry = unenrolledRotation;
|
|
21097
|
+
await enrollEscrow(
|
|
21098
|
+
params.token,
|
|
21099
|
+
params.providerType,
|
|
21100
|
+
params.privateKey,
|
|
21101
|
+
retry.primaryDid,
|
|
21102
|
+
retry.shares,
|
|
21103
|
+
retry.shareVersion,
|
|
21104
|
+
params.signDidAuthVp,
|
|
21105
|
+
void 0,
|
|
21106
|
+
void 0,
|
|
21107
|
+
retry.clearPin
|
|
21108
|
+
);
|
|
21109
|
+
unenrolledRotation = void 0;
|
|
21110
|
+
return { enrolled: true, changed: true, shareVersion: retry.shareVersion };
|
|
21111
|
+
}
|
|
21112
|
+
const verifiedKey = await fetchVerifiedEnclaveKey();
|
|
21113
|
+
const pinSalt = pin !== void 0 ? generatePinSalt() : void 0;
|
|
21114
|
+
const pinMaterial = pin !== void 0 && pinSalt !== void 0 ? { pinSalt, pinVerifier: await derivePinProof(pin, pinSalt) } : void 0;
|
|
21115
|
+
const storageId = activeStorageId;
|
|
21116
|
+
const primaryDid = status.primaryDid;
|
|
21117
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
21118
|
+
storage2,
|
|
21119
|
+
storageId,
|
|
21120
|
+
() => persistSharesWithoutLock(
|
|
21121
|
+
params.privateKey,
|
|
21122
|
+
serverUrl,
|
|
21123
|
+
params.token,
|
|
21124
|
+
params.providerType,
|
|
21125
|
+
primaryDid,
|
|
21126
|
+
storage2,
|
|
21127
|
+
storageId,
|
|
21128
|
+
void 0,
|
|
21129
|
+
params.signDidAuthVp,
|
|
21130
|
+
tenantId
|
|
21131
|
+
)
|
|
21132
|
+
);
|
|
21133
|
+
lastEmailShare = shares.emailShare;
|
|
21134
|
+
lastShareVersion = shareVersion;
|
|
21135
|
+
lastServerSnapshot = {
|
|
21136
|
+
currentVersion: shareVersion,
|
|
21137
|
+
resolvedVersion: shareVersion,
|
|
21138
|
+
authShare: shares.authShare,
|
|
21139
|
+
primaryDid: status.primaryDid
|
|
21140
|
+
};
|
|
21141
|
+
unenrolledRotation = pinMaterial ? void 0 : { shares, shareVersion, primaryDid: status.primaryDid, clearPin: forceRotate };
|
|
21142
|
+
await enrollEscrow(
|
|
21143
|
+
params.token,
|
|
21144
|
+
params.providerType,
|
|
21145
|
+
params.privateKey,
|
|
21146
|
+
status.primaryDid,
|
|
21147
|
+
shares,
|
|
21148
|
+
shareVersion,
|
|
21149
|
+
params.signDidAuthVp,
|
|
21150
|
+
verifiedKey,
|
|
21151
|
+
pinMaterial,
|
|
21152
|
+
forceRotate
|
|
21153
|
+
);
|
|
21154
|
+
unenrolledRotation = void 0;
|
|
21155
|
+
return { enrolled: true, changed: true, shareVersion };
|
|
21156
|
+
}, "ensureEscrowEnrollmentUnguarded");
|
|
21157
|
+
const recordPromotion = /* @__PURE__ */ __name((primaryDid) => (snapshot) => {
|
|
21158
|
+
lastEmailShare = void 0;
|
|
21159
|
+
lastShareVersion = snapshot.currentVersion ?? void 0;
|
|
21160
|
+
lastServerSnapshot = {
|
|
21161
|
+
...snapshot,
|
|
21162
|
+
resolvedVersion: snapshot.currentVersion,
|
|
21163
|
+
primaryDid
|
|
21164
|
+
};
|
|
21165
|
+
}, "recordPromotion");
|
|
21166
|
+
const captureActiveUserGuard = /* @__PURE__ */ __name(() => {
|
|
21167
|
+
const storageId = activeStorageId;
|
|
21168
|
+
return () => {
|
|
21169
|
+
if (activeStorageId !== storageId) {
|
|
21170
|
+
throw new AtomicUpdateError(
|
|
21171
|
+
"Active account changed during share update",
|
|
21172
|
+
"verify_stored",
|
|
21173
|
+
false
|
|
21174
|
+
);
|
|
21175
|
+
}
|
|
21176
|
+
};
|
|
21177
|
+
}, "captureActiveUserGuard");
|
|
19536
21178
|
return {
|
|
19537
21179
|
name: "sss",
|
|
19538
21180
|
capabilities: {
|
|
@@ -19543,23 +21185,31 @@ function createSSSStrategy(config) {
|
|
|
19543
21185
|
},
|
|
19544
21186
|
// --- User scoping ---
|
|
19545
21187
|
setActiveUser(userId) {
|
|
21188
|
+
if (activeStorageId !== `sss-device-share:${userId}`) storageGeneration++;
|
|
19546
21189
|
activeStorageId = `sss-device-share:${userId}`;
|
|
21190
|
+
lastServerSnapshot = null;
|
|
21191
|
+
pendingPhraseConfirmation = void 0;
|
|
21192
|
+
pendingBackupConfirmation = void 0;
|
|
19547
21193
|
},
|
|
19548
21194
|
// --- Key lifecycle ---
|
|
19549
21195
|
async hasLocalKey() {
|
|
19550
21196
|
if (await storage.hasDeviceShare(activeStorageId)) return true;
|
|
21197
|
+
if ((await readPendingShareCandidates(storage, activeStorageId)).length) return true;
|
|
19551
21198
|
if (activeStorageId) {
|
|
19552
21199
|
return storage.hasDeviceShare();
|
|
19553
21200
|
}
|
|
19554
21201
|
return false;
|
|
19555
21202
|
},
|
|
19556
21203
|
async getLocalKey() {
|
|
19557
|
-
const
|
|
21204
|
+
const storage2 = guardedStorage();
|
|
21205
|
+
const scoped = await storage2.getDeviceShare(activeStorageId);
|
|
19558
21206
|
if (scoped) return scoped;
|
|
21207
|
+
const pending = await readPendingShareCandidates(storage2, activeStorageId);
|
|
21208
|
+
if (pending.length) return pending[pending.length - 1].share;
|
|
19559
21209
|
if (activeStorageId) {
|
|
19560
|
-
const legacy = await
|
|
21210
|
+
const legacy = await storage2.getDeviceShare();
|
|
19561
21211
|
if (legacy) {
|
|
19562
|
-
await
|
|
21212
|
+
await storage2.storeDeviceShare(legacy, activeStorageId);
|
|
19563
21213
|
return legacy;
|
|
19564
21214
|
}
|
|
19565
21215
|
}
|
|
@@ -19568,8 +21218,20 @@ function createSSSStrategy(config) {
|
|
|
19568
21218
|
async storeLocalKey(key) {
|
|
19569
21219
|
return storage.storeDeviceShare(key, activeStorageId);
|
|
19570
21220
|
},
|
|
19571
|
-
async clearLocalKeys() {
|
|
19572
|
-
|
|
21221
|
+
async clearLocalKeys(options) {
|
|
21222
|
+
const storageId = activeStorageId;
|
|
21223
|
+
if (options?.preservePending) {
|
|
21224
|
+
const storage2 = guardedStorage();
|
|
21225
|
+
return withShareUpdateLock(
|
|
21226
|
+
storage2,
|
|
21227
|
+
storageId,
|
|
21228
|
+
() => storage2.deleteDeviceShare(storageId)
|
|
21229
|
+
);
|
|
21230
|
+
}
|
|
21231
|
+
storageGeneration++;
|
|
21232
|
+
await Promise.allSettled(localWrites);
|
|
21233
|
+
await clearPendingShareCandidates(storage, storageId);
|
|
21234
|
+
return storage.clearAllShares(storageId);
|
|
19573
21235
|
},
|
|
19574
21236
|
async splitKey(privateKey) {
|
|
19575
21237
|
const { shares } = await splitAndVerify(privateKey);
|
|
@@ -19593,9 +21255,175 @@ function createSSSStrategy(config) {
|
|
|
19593
21255
|
);
|
|
19594
21256
|
return result.healthy;
|
|
19595
21257
|
},
|
|
21258
|
+
async getFreshDidAuthVp(privateKey, did, signDidAuthVp) {
|
|
21259
|
+
return requestFreshDidAuthVp(serverUrl, privateKey, did, signDidAuthVp, tenantId);
|
|
21260
|
+
},
|
|
21261
|
+
async atomicUpdateShares(params) {
|
|
21262
|
+
const storage2 = guardedStorage();
|
|
21263
|
+
const result = await persistSharesAtomically(
|
|
21264
|
+
params.privateKey,
|
|
21265
|
+
serverUrl,
|
|
21266
|
+
params.token,
|
|
21267
|
+
params.providerType,
|
|
21268
|
+
params.did,
|
|
21269
|
+
storage2,
|
|
21270
|
+
activeStorageId,
|
|
21271
|
+
void 0,
|
|
21272
|
+
params.signDidAuthVp,
|
|
21273
|
+
tenantId,
|
|
21274
|
+
recordPromotion(params.did),
|
|
21275
|
+
captureActiveUserGuard()
|
|
21276
|
+
);
|
|
21277
|
+
lastEmailShare = result.shares.emailShare;
|
|
21278
|
+
lastShareVersion = result.shareVersion;
|
|
21279
|
+
lastServerSnapshot = {
|
|
21280
|
+
currentVersion: result.shareVersion,
|
|
21281
|
+
resolvedVersion: result.shareVersion,
|
|
21282
|
+
authShare: result.shares.authShare,
|
|
21283
|
+
primaryDid: params.did
|
|
21284
|
+
};
|
|
21285
|
+
},
|
|
21286
|
+
async reconcileShares(params) {
|
|
21287
|
+
const storage2 = guardedStorage();
|
|
21288
|
+
const storageId = activeStorageId;
|
|
21289
|
+
const assertActiveUser = /* @__PURE__ */ __name(() => {
|
|
21290
|
+
if (activeStorageId !== storageId) {
|
|
21291
|
+
throw new AtomicUpdateError(
|
|
21292
|
+
"Active account changed during reconciliation",
|
|
21293
|
+
"verify_stored",
|
|
21294
|
+
false
|
|
21295
|
+
);
|
|
21296
|
+
}
|
|
21297
|
+
}, "assertActiveUser");
|
|
21298
|
+
return withShareUpdateLock(storage2, storageId, async () => {
|
|
21299
|
+
let deviceShare = await storage2.getDeviceShare(storageId);
|
|
21300
|
+
const pending = await readPendingShareCandidates(storage2, storageId);
|
|
21301
|
+
if (!deviceShare && !pending.length) return null;
|
|
21302
|
+
const resolution = await resolvePendingShareCandidates(
|
|
21303
|
+
storage2,
|
|
21304
|
+
storageId,
|
|
21305
|
+
async () => {
|
|
21306
|
+
const data = await fetchAuthShareRaw(
|
|
21307
|
+
serverUrl,
|
|
21308
|
+
params.token,
|
|
21309
|
+
params.providerType,
|
|
21310
|
+
void 0,
|
|
21311
|
+
tenantId
|
|
21312
|
+
);
|
|
21313
|
+
return {
|
|
21314
|
+
currentVersion: data?.shareVersion ?? null,
|
|
21315
|
+
authShare: authShareToString(data?.authShare)
|
|
21316
|
+
};
|
|
21317
|
+
},
|
|
21318
|
+
params.expectedDid,
|
|
21319
|
+
params.didFromPrivateKey,
|
|
21320
|
+
assertActiveUser,
|
|
21321
|
+
recordPromotion(params.expectedDid),
|
|
21322
|
+
async (version) => authShareToString(
|
|
21323
|
+
(await fetchAuthShareRaw(
|
|
21324
|
+
serverUrl,
|
|
21325
|
+
params.token,
|
|
21326
|
+
params.providerType,
|
|
21327
|
+
version,
|
|
21328
|
+
tenantId
|
|
21329
|
+
))?.authShare
|
|
21330
|
+
)
|
|
21331
|
+
);
|
|
21332
|
+
assertActiveUser();
|
|
21333
|
+
if (resolution.privateKey) {
|
|
21334
|
+
return { privateKey: resolution.privateKey, did: params.expectedDid };
|
|
21335
|
+
}
|
|
21336
|
+
const { currentVersion, authShare: currentAuthShare } = resolution.snapshot;
|
|
21337
|
+
if (currentVersion === null && !currentAuthShare) return null;
|
|
21338
|
+
deviceShare = await storage2.getDeviceShare(storageId);
|
|
21339
|
+
const localVersion = await storage2.getShareVersion(storageId);
|
|
21340
|
+
if (!deviceShare) return null;
|
|
21341
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
21342
|
+
const addCandidate = /* @__PURE__ */ __name((version, authShare) => {
|
|
21343
|
+
if (version !== null && authShare && !candidates.has(version)) {
|
|
21344
|
+
candidates.set(version, authShare);
|
|
21345
|
+
}
|
|
21346
|
+
}, "addCandidate");
|
|
21347
|
+
addCandidate(
|
|
21348
|
+
lastServerSnapshot?.resolvedVersion ?? null,
|
|
21349
|
+
lastServerSnapshot?.authShare ?? null
|
|
21350
|
+
);
|
|
21351
|
+
addCandidate(currentVersion, currentAuthShare);
|
|
21352
|
+
const versionsToTry = /* @__PURE__ */ new Set();
|
|
21353
|
+
if (localVersion !== null) versionsToTry.add(localVersion);
|
|
21354
|
+
if (currentVersion !== null) {
|
|
21355
|
+
for (let version = currentVersion; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
21356
|
+
versionsToTry.add(version);
|
|
21357
|
+
}
|
|
21358
|
+
}
|
|
21359
|
+
for (const version of versionsToTry) {
|
|
21360
|
+
if (candidates.has(version)) continue;
|
|
21361
|
+
const data = await fetchAuthShareRaw(
|
|
21362
|
+
serverUrl,
|
|
21363
|
+
params.token,
|
|
21364
|
+
params.providerType,
|
|
21365
|
+
version,
|
|
21366
|
+
tenantId
|
|
21367
|
+
);
|
|
21368
|
+
addCandidate(version, authShareToString(data?.authShare));
|
|
21369
|
+
}
|
|
21370
|
+
for (const [candidateVersion, authShare] of candidates) {
|
|
21371
|
+
const health = await verifyStoredShares(
|
|
21372
|
+
{
|
|
21373
|
+
getDevice: /* @__PURE__ */ __name(async () => deviceShare, "getDevice"),
|
|
21374
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
21375
|
+
},
|
|
21376
|
+
params.expectedDid,
|
|
21377
|
+
params.didFromPrivateKey
|
|
21378
|
+
);
|
|
21379
|
+
if (!health.healthy) continue;
|
|
21380
|
+
assertActiveUser();
|
|
21381
|
+
const privateKey = await reconstructFromShares([deviceShare, authShare]);
|
|
21382
|
+
if (candidateVersion === currentVersion) {
|
|
21383
|
+
if (localVersion !== currentVersion) {
|
|
21384
|
+
await storage2.storeShareVersion(candidateVersion, storageId);
|
|
21385
|
+
}
|
|
21386
|
+
assertActiveUser();
|
|
21387
|
+
lastServerSnapshot = {
|
|
21388
|
+
currentVersion,
|
|
21389
|
+
resolvedVersion: candidateVersion,
|
|
21390
|
+
authShare,
|
|
21391
|
+
primaryDid: params.expectedDid
|
|
21392
|
+
};
|
|
21393
|
+
return localVersion === currentVersion ? null : { privateKey, did: params.expectedDid };
|
|
21394
|
+
}
|
|
21395
|
+
const result = await persistSharesWithoutLock(
|
|
21396
|
+
privateKey,
|
|
21397
|
+
serverUrl,
|
|
21398
|
+
params.token,
|
|
21399
|
+
params.providerType,
|
|
21400
|
+
params.expectedDid,
|
|
21401
|
+
storage2,
|
|
21402
|
+
storageId,
|
|
21403
|
+
params.didFromPrivateKey,
|
|
21404
|
+
params.signDidAuthVp,
|
|
21405
|
+
tenantId,
|
|
21406
|
+
recordPromotion(params.expectedDid),
|
|
21407
|
+
assertActiveUser
|
|
21408
|
+
);
|
|
21409
|
+
assertActiveUser();
|
|
21410
|
+
lastEmailShare = result.shares.emailShare;
|
|
21411
|
+
lastShareVersion = result.shareVersion;
|
|
21412
|
+
lastServerSnapshot = {
|
|
21413
|
+
currentVersion: result.shareVersion,
|
|
21414
|
+
resolvedVersion: result.shareVersion,
|
|
21415
|
+
authShare: result.shares.authShare,
|
|
21416
|
+
primaryDid: params.expectedDid
|
|
21417
|
+
};
|
|
21418
|
+
return { privateKey, did: params.expectedDid };
|
|
21419
|
+
}
|
|
21420
|
+
return null;
|
|
21421
|
+
});
|
|
21422
|
+
},
|
|
19596
21423
|
// --- Server communication ---
|
|
19597
21424
|
async fetchServerKeyStatus(token, providerType) {
|
|
19598
|
-
const
|
|
21425
|
+
const storage2 = guardedStorage();
|
|
21426
|
+
const localVersion = await storage2.getShareVersion(activeStorageId);
|
|
19599
21427
|
const data = await fetchAuthShareRaw(
|
|
19600
21428
|
serverUrl,
|
|
19601
21429
|
token,
|
|
@@ -19604,7 +21432,7 @@ function createSSSStrategy(config) {
|
|
|
19604
21432
|
tenantId
|
|
19605
21433
|
);
|
|
19606
21434
|
if (!data) {
|
|
19607
|
-
|
|
21435
|
+
lastServerSnapshot = null;
|
|
19608
21436
|
return {
|
|
19609
21437
|
exists: false,
|
|
19610
21438
|
needsMigration: false,
|
|
@@ -19612,16 +21440,25 @@ function createSSSStrategy(config) {
|
|
|
19612
21440
|
recoveryMethods: [],
|
|
19613
21441
|
authShare: null,
|
|
19614
21442
|
shareVersion: null,
|
|
19615
|
-
maskedRecoveryEmail: null
|
|
21443
|
+
maskedRecoveryEmail: null,
|
|
21444
|
+
sssActivationState: null
|
|
19616
21445
|
};
|
|
19617
21446
|
}
|
|
19618
21447
|
const rawAuthShare = data.authShare;
|
|
19619
|
-
const authShareString =
|
|
21448
|
+
const authShareString = authShareToString(rawAuthShare);
|
|
19620
21449
|
const serverVersion = data.shareVersion ?? null;
|
|
19621
|
-
if (
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
21450
|
+
if (
|
|
21451
|
+
// Version 0 means "no auth share yet" and must never be pinned locally.
|
|
21452
|
+
serverVersion != null && serverVersion > 0 && localVersion == null && !(await readPendingShareCandidates(storage2, activeStorageId)).length
|
|
21453
|
+
) {
|
|
21454
|
+
storage2.storeShareVersion(serverVersion, activeStorageId).catch((e) => console.warn("SSS: failed to backfill local shareVersion", e));
|
|
21455
|
+
}
|
|
21456
|
+
lastServerSnapshot = {
|
|
21457
|
+
currentVersion: serverVersion,
|
|
21458
|
+
resolvedVersion: authShareString ? localVersion ?? serverVersion : null,
|
|
21459
|
+
authShare: authShareString,
|
|
21460
|
+
primaryDid: data.primaryDid || null
|
|
21461
|
+
};
|
|
19625
21462
|
return {
|
|
19626
21463
|
exists: !!rawAuthShare || !!data.keyProvider || !!data.primaryDid,
|
|
19627
21464
|
needsMigration: data.keyProvider === "web3auth",
|
|
@@ -19629,35 +21466,258 @@ function createSSSStrategy(config) {
|
|
|
19629
21466
|
recoveryMethods: data.recoveryMethods || [],
|
|
19630
21467
|
authShare: authShareString,
|
|
19631
21468
|
shareVersion: serverVersion,
|
|
19632
|
-
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null
|
|
21469
|
+
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null,
|
|
21470
|
+
escrowOptedOut: data.escrowOptedOut === true,
|
|
21471
|
+
escrowPin: data.escrowPin,
|
|
21472
|
+
sssActivationState: data.sssActivationState ?? "active"
|
|
19633
21473
|
};
|
|
19634
21474
|
},
|
|
19635
21475
|
async storeAuthShare(token, providerType, authShare, primaryDid, didAuthVp) {
|
|
19636
|
-
const
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
21476
|
+
const storage2 = guardedStorage();
|
|
21477
|
+
const { shareVersion } = await withRotationLock(
|
|
21478
|
+
() => putAuthShare(
|
|
21479
|
+
serverUrl,
|
|
21480
|
+
token,
|
|
21481
|
+
providerType,
|
|
21482
|
+
authShare,
|
|
21483
|
+
primaryDid,
|
|
21484
|
+
didAuthVp,
|
|
21485
|
+
tenantId
|
|
21486
|
+
)
|
|
19644
21487
|
);
|
|
19645
|
-
await
|
|
21488
|
+
await storage2.storeShareVersion(shareVersion, activeStorageId);
|
|
19646
21489
|
lastShareVersion = shareVersion;
|
|
19647
21490
|
},
|
|
19648
|
-
async markMigrated(token, providerType, didAuthVp) {
|
|
19649
|
-
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21491
|
+
async markMigrated(token, providerType, didAuthVp) {
|
|
21492
|
+
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21493
|
+
method: "POST",
|
|
21494
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
21495
|
+
body: JSON.stringify({ authToken: token, providerType })
|
|
21496
|
+
});
|
|
21497
|
+
if (!response.ok) {
|
|
21498
|
+
throw new Error(`Failed to mark migrated: ${describeHttpStatus(response)}`);
|
|
21499
|
+
}
|
|
21500
|
+
},
|
|
21501
|
+
async activate(token, providerType, didAuthVp) {
|
|
21502
|
+
const response = await fetch(`${serverUrl}/keys/activate`, {
|
|
19650
21503
|
method: "POST",
|
|
19651
21504
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19652
21505
|
body: JSON.stringify({ authToken: token, providerType })
|
|
19653
21506
|
});
|
|
19654
21507
|
if (!response.ok) {
|
|
19655
|
-
throw new Error(`Failed to
|
|
21508
|
+
throw new Error(`Failed to activate SSS key: ${describeHttpStatus(response)}`);
|
|
19656
21509
|
}
|
|
19657
21510
|
},
|
|
19658
21511
|
// --- Recovery execution ---
|
|
21512
|
+
async getEscrowEnrollmentState(params) {
|
|
21513
|
+
if (!config.escrow?.enabled) return { state: "disabled" };
|
|
21514
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21515
|
+
if (status.escrowOptedOut) return { state: "opted-out", escrowPin: status.escrowPin };
|
|
21516
|
+
const state = status.shareVersion !== null && status.recoveryMethods.some(
|
|
21517
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21518
|
+
) ? "enrolled" : "not-enrolled";
|
|
21519
|
+
return { state, escrowPin: status.escrowPin };
|
|
21520
|
+
},
|
|
21521
|
+
async disableEscrowRecovery(params) {
|
|
21522
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21523
|
+
if (!status.primaryDid) throw new Error("Cannot disable escrow without a primary DID");
|
|
21524
|
+
const vp = await requestFreshDidAuthVp(
|
|
21525
|
+
serverUrl,
|
|
21526
|
+
params.privateKey,
|
|
21527
|
+
status.primaryDid,
|
|
21528
|
+
params.signDidAuthVp,
|
|
21529
|
+
tenantId
|
|
21530
|
+
);
|
|
21531
|
+
await escrowRequest("", {
|
|
21532
|
+
method: "DELETE",
|
|
21533
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21534
|
+
body: JSON.stringify({
|
|
21535
|
+
authToken: params.token,
|
|
21536
|
+
providerType: params.providerType,
|
|
21537
|
+
optOut: true
|
|
21538
|
+
})
|
|
21539
|
+
});
|
|
21540
|
+
},
|
|
21541
|
+
async enableEscrowRecovery(params) {
|
|
21542
|
+
const storage2 = guardedStorage();
|
|
21543
|
+
return withRotationLock(async () => {
|
|
21544
|
+
if (!config.escrow?.enabled)
|
|
21545
|
+
return { enrolled: false, reason: "disabled" };
|
|
21546
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21547
|
+
if (!status.primaryDid)
|
|
21548
|
+
throw new Error("Cannot enable escrow without a primary DID");
|
|
21549
|
+
const vp = await requestFreshDidAuthVp(
|
|
21550
|
+
serverUrl,
|
|
21551
|
+
params.privateKey,
|
|
21552
|
+
status.primaryDid,
|
|
21553
|
+
params.signDidAuthVp,
|
|
21554
|
+
tenantId
|
|
21555
|
+
);
|
|
21556
|
+
await escrowRequest("/opt-in", {
|
|
21557
|
+
method: "POST",
|
|
21558
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21559
|
+
body: JSON.stringify({
|
|
21560
|
+
authToken: params.token,
|
|
21561
|
+
providerType: params.providerType
|
|
21562
|
+
})
|
|
21563
|
+
});
|
|
21564
|
+
return ensureEscrowEnrollmentUnguarded(this, params, storage2);
|
|
21565
|
+
});
|
|
21566
|
+
},
|
|
21567
|
+
async setEscrowPin({ pin, ...params }) {
|
|
21568
|
+
const storage2 = guardedStorage();
|
|
21569
|
+
const result = await withRotationLock(
|
|
21570
|
+
() => ensureEscrowEnrollmentUnguarded(this, { ...params, options: { pin } }, storage2)
|
|
21571
|
+
);
|
|
21572
|
+
if (!result.enrolled) {
|
|
21573
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21574
|
+
}
|
|
21575
|
+
},
|
|
21576
|
+
async clearEscrowPin(params) {
|
|
21577
|
+
const storage2 = guardedStorage();
|
|
21578
|
+
const result = await withRotationLock(
|
|
21579
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2, true)
|
|
21580
|
+
);
|
|
21581
|
+
if (!result.enrolled) {
|
|
21582
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21583
|
+
}
|
|
21584
|
+
},
|
|
21585
|
+
/** Enroll missing/stale escrow material using the standard atomic rotation. */
|
|
21586
|
+
async ensureEscrowEnrollment(params) {
|
|
21587
|
+
const storage2 = guardedStorage();
|
|
21588
|
+
if (params.options?.pin !== void 0)
|
|
21589
|
+
return withRotationLock(
|
|
21590
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21591
|
+
);
|
|
21592
|
+
if (escrowEnrollmentInFlight) return escrowEnrollmentInFlight;
|
|
21593
|
+
escrowEnrollmentInFlight = withRotationLock(
|
|
21594
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21595
|
+
);
|
|
21596
|
+
try {
|
|
21597
|
+
return await escrowEnrollmentInFlight;
|
|
21598
|
+
} finally {
|
|
21599
|
+
escrowEnrollmentInFlight = void 0;
|
|
21600
|
+
}
|
|
21601
|
+
},
|
|
21602
|
+
/** Start or explicitly restart a hold with fresh client-bound resume secrets. */
|
|
21603
|
+
async startEscrowRecovery(params) {
|
|
21604
|
+
const sessionProof = params.recoverySessionToken !== void 0;
|
|
21605
|
+
if (sessionProof ? params.token !== void 0 || params.providerType !== void 0 : !params.token || !params.providerType) {
|
|
21606
|
+
throw new Error("Provide exactly one identity proof");
|
|
21607
|
+
}
|
|
21608
|
+
const pair = await generateEscrowKeyPair();
|
|
21609
|
+
const result = await escrowRequest(
|
|
21610
|
+
"/recover",
|
|
21611
|
+
{
|
|
21612
|
+
method: "POST",
|
|
21613
|
+
headers: buildHeaders("", void 0, params.tenantId ?? tenantId),
|
|
21614
|
+
body: JSON.stringify({
|
|
21615
|
+
clientEphemeralPublicKey: pair.publicKey,
|
|
21616
|
+
...params.options?.restart ? { restart: true } : {},
|
|
21617
|
+
...params.options?.releasePolicy ? { releasePolicy: params.options.releasePolicy } : {},
|
|
21618
|
+
...sessionProof ? { recoverySessionToken: params.recoverySessionToken } : { authToken: params.token, providerType: params.providerType }
|
|
21619
|
+
})
|
|
21620
|
+
},
|
|
21621
|
+
params.options?.releasePolicy === "pin"
|
|
21622
|
+
);
|
|
21623
|
+
return { ...result, clientEphemeralPrivateKey: pair.privateKey };
|
|
21624
|
+
},
|
|
21625
|
+
/** Read hold status; secrets (auth or resume token) never travel in a URL. */
|
|
21626
|
+
async getEscrowRecoveryStatus(params) {
|
|
21627
|
+
const query = new URLSearchParams(
|
|
21628
|
+
"holdId" in params ? { holdId: params.holdId } : { providerType: params.providerType }
|
|
21629
|
+
);
|
|
21630
|
+
const secret = "holdId" in params ? params.resumeToken : params.token;
|
|
21631
|
+
const result = await escrowRequest(
|
|
21632
|
+
`/status?${query}`,
|
|
21633
|
+
{
|
|
21634
|
+
method: "GET",
|
|
21635
|
+
headers: {
|
|
21636
|
+
...buildHeaders("", void 0, tenantId),
|
|
21637
|
+
"X-Auth-Token": secret
|
|
21638
|
+
}
|
|
21639
|
+
}
|
|
21640
|
+
);
|
|
21641
|
+
return result.hold;
|
|
21642
|
+
},
|
|
21643
|
+
/** Cancel a pending hold with a fresh DID-owner proof. */
|
|
21644
|
+
async cancelEscrowRecovery(params) {
|
|
21645
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21646
|
+
if (!status.primaryDid) throw new Error("Cannot cancel escrow without a primary DID");
|
|
21647
|
+
const vp = await requestFreshDidAuthVp(
|
|
21648
|
+
serverUrl,
|
|
21649
|
+
params.privateKey,
|
|
21650
|
+
status.primaryDid,
|
|
21651
|
+
params.signDidAuthVp,
|
|
21652
|
+
tenantId
|
|
21653
|
+
);
|
|
21654
|
+
const result = await escrowRequest("/cancel", {
|
|
21655
|
+
method: "POST",
|
|
21656
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21657
|
+
body: JSON.stringify({
|
|
21658
|
+
authToken: params.token,
|
|
21659
|
+
providerType: params.providerType
|
|
21660
|
+
})
|
|
21661
|
+
});
|
|
21662
|
+
return { cancelled: result.cancelled };
|
|
21663
|
+
},
|
|
19659
21664
|
async executeRecovery(params) {
|
|
19660
|
-
const
|
|
21665
|
+
const storage2 = guardedStorage();
|
|
21666
|
+
const { token, providerType, input, didFromPrivateKey, signDidAuthVp } = params;
|
|
21667
|
+
if (input.method === "escrow" || input.method === "escrow-pin") {
|
|
21668
|
+
if (!didFromPrivateKey)
|
|
21669
|
+
throw new Error("DID verification is required for escrow recovery");
|
|
21670
|
+
const start = input.method === "escrow-pin" ? await this.startEscrowRecovery({
|
|
21671
|
+
token,
|
|
21672
|
+
providerType,
|
|
21673
|
+
options: { releasePolicy: "pin" }
|
|
21674
|
+
}) : input;
|
|
21675
|
+
let pinProof;
|
|
21676
|
+
if (input.method === "escrow-pin") {
|
|
21677
|
+
if (!("pinSalt" in start) || !start.pinSalt || !start.resumeToken)
|
|
21678
|
+
throw new Error("PIN recovery response is missing required material");
|
|
21679
|
+
pinProof = await derivePinProof(input.pin, start.pinSalt);
|
|
21680
|
+
}
|
|
21681
|
+
const material = await escrowRequest(
|
|
21682
|
+
"/complete",
|
|
21683
|
+
{
|
|
21684
|
+
method: "POST",
|
|
21685
|
+
headers: buildHeaders("", void 0, tenantId),
|
|
21686
|
+
body: JSON.stringify({
|
|
21687
|
+
holdId: start.holdId,
|
|
21688
|
+
resumeToken: start.resumeToken,
|
|
21689
|
+
...pinProof ? { pinProof } : {}
|
|
21690
|
+
})
|
|
21691
|
+
},
|
|
21692
|
+
input.method === "escrow-pin"
|
|
21693
|
+
);
|
|
21694
|
+
const plaintext = await openEscrowRelease(
|
|
21695
|
+
material.sealedShare,
|
|
21696
|
+
start.clientEphemeralPrivateKey
|
|
21697
|
+
);
|
|
21698
|
+
if (plaintext.holdId !== start.holdId || plaintext.did !== material.primaryDid || plaintext.shareVersion !== material.shareVersion) {
|
|
21699
|
+
throw new Error("Escrow recovery material does not match the request");
|
|
21700
|
+
}
|
|
21701
|
+
const authShare = material.authShare.encryptedData;
|
|
21702
|
+
const privateKey2 = await reconstructFromShares([
|
|
21703
|
+
plaintext.recoveryShare,
|
|
21704
|
+
authShare
|
|
21705
|
+
]);
|
|
21706
|
+
if (!material.primaryDid || await didFromPrivateKey(privateKey2) !== material.primaryDid) {
|
|
21707
|
+
throw new Error(
|
|
21708
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
21709
|
+
);
|
|
21710
|
+
}
|
|
21711
|
+
pendingIdentityRecovery = {
|
|
21712
|
+
privateKey: privateKey2,
|
|
21713
|
+
primaryDid: material.primaryDid,
|
|
21714
|
+
recoveryShare: plaintext.recoveryShare,
|
|
21715
|
+
authShare,
|
|
21716
|
+
rebindSessionToken: material.rebindSessionToken
|
|
21717
|
+
};
|
|
21718
|
+
if (!token) return { privateKey: privateKey2, did: material.primaryDid };
|
|
21719
|
+
return this.completeIdentityRecovery({ token, providerType, signDidAuthVp });
|
|
21720
|
+
}
|
|
19661
21721
|
let recoveryShare;
|
|
19662
21722
|
let recoveryShareVersion;
|
|
19663
21723
|
switch (input.method) {
|
|
@@ -19751,14 +21811,61 @@ function createSSSStrategy(config) {
|
|
|
19751
21811
|
);
|
|
19752
21812
|
}
|
|
19753
21813
|
}
|
|
19754
|
-
await
|
|
19755
|
-
|
|
19756
|
-
|
|
21814
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21815
|
+
privateKey,
|
|
21816
|
+
serverUrl,
|
|
21817
|
+
token,
|
|
21818
|
+
providerType,
|
|
21819
|
+
primaryDid,
|
|
21820
|
+
storage2,
|
|
21821
|
+
activeStorageId,
|
|
21822
|
+
didFromPrivateKey,
|
|
21823
|
+
signDidAuthVp,
|
|
21824
|
+
tenantId,
|
|
21825
|
+
recordPromotion(primaryDid),
|
|
21826
|
+
captureActiveUserGuard()
|
|
21827
|
+
);
|
|
21828
|
+
lastEmailShare = shares.emailShare;
|
|
21829
|
+
lastShareVersion = shareVersion;
|
|
21830
|
+
lastServerSnapshot = {
|
|
21831
|
+
currentVersion: shareVersion,
|
|
21832
|
+
resolvedVersion: shareVersion,
|
|
21833
|
+
authShare: shares.authShare,
|
|
21834
|
+
primaryDid
|
|
21835
|
+
};
|
|
21836
|
+
await tryEnrollEscrow(
|
|
21837
|
+
token,
|
|
21838
|
+
providerType,
|
|
21839
|
+
privateKey,
|
|
21840
|
+
primaryDid,
|
|
21841
|
+
shares,
|
|
21842
|
+
shareVersion,
|
|
21843
|
+
signDidAuthVp
|
|
21844
|
+
);
|
|
19757
21845
|
return { privateKey, did: primaryDid };
|
|
19758
21846
|
},
|
|
19759
21847
|
// --- Recovery setup ---
|
|
19760
21848
|
async setupRecoveryMethod(params) {
|
|
21849
|
+
const storage2 = guardedStorage();
|
|
19761
21850
|
const { token, providerType, privateKey, input, authUser, signDidAuthVp } = params;
|
|
21851
|
+
if (input.method === "escrow" && input.pin !== void 0) {
|
|
21852
|
+
if (!signDidAuthVp)
|
|
21853
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21854
|
+
const result = await this.enableEscrowRecovery({
|
|
21855
|
+
token,
|
|
21856
|
+
providerType,
|
|
21857
|
+
privateKey,
|
|
21858
|
+
signDidAuthVp,
|
|
21859
|
+
options: { pin: input.pin }
|
|
21860
|
+
});
|
|
21861
|
+
if (!result.enrolled || !result.changed)
|
|
21862
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21863
|
+
return { method: "escrow", shareVersion: result.shareVersion };
|
|
21864
|
+
}
|
|
21865
|
+
if (input.method === "escrow" && !config.escrow?.enabled) {
|
|
21866
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21867
|
+
}
|
|
21868
|
+
const escrowKey = input.method === "escrow" ? await fetchVerifiedEnclaveKey() : void 0;
|
|
19762
21869
|
let passkeyCredential;
|
|
19763
21870
|
if (input.method === "passkey") {
|
|
19764
21871
|
if (!isWebAuthnSupported()) {
|
|
@@ -19768,10 +21875,6 @@ function createSSSStrategy(config) {
|
|
|
19768
21875
|
const userName = authUser?.email || authUser?.phone || authUser?.id || "";
|
|
19769
21876
|
passkeyCredential = await createPasskeyCredential(userId, userName);
|
|
19770
21877
|
}
|
|
19771
|
-
const vpJwt = signDidAuthVp ? await signDidAuthVp(privateKey) : void 0;
|
|
19772
|
-
const { shares } = await splitAndVerify(privateKey);
|
|
19773
|
-
lastEmailShare = shares.emailShare;
|
|
19774
|
-
await storage.storeDeviceShare(shares.deviceShare, activeStorageId);
|
|
19775
21878
|
const serverData = await fetchAuthShareRaw(
|
|
19776
21879
|
serverUrl,
|
|
19777
21880
|
token,
|
|
@@ -19780,39 +21883,42 @@ function createSSSStrategy(config) {
|
|
|
19780
21883
|
tenantId
|
|
19781
21884
|
);
|
|
19782
21885
|
const primaryDid = serverData?.primaryDid || "";
|
|
19783
|
-
|
|
21886
|
+
if (!primaryDid) throw new Error("Cannot set up recovery without a primary DID");
|
|
21887
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21888
|
+
privateKey,
|
|
19784
21889
|
serverUrl,
|
|
19785
21890
|
token,
|
|
19786
21891
|
providerType,
|
|
19787
|
-
shares.authShare,
|
|
19788
21892
|
primaryDid,
|
|
19789
|
-
|
|
19790
|
-
|
|
21893
|
+
storage2,
|
|
21894
|
+
activeStorageId,
|
|
21895
|
+
void 0,
|
|
21896
|
+
signDidAuthVp,
|
|
21897
|
+
tenantId,
|
|
21898
|
+
recordPromotion(primaryDid),
|
|
21899
|
+
captureActiveUserGuard()
|
|
19791
21900
|
);
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
tenantId
|
|
19801
|
-
) : authUser?.email ? sendEmailBackupShare(
|
|
19802
|
-
serverUrl,
|
|
19803
|
-
token,
|
|
19804
|
-
providerType,
|
|
19805
|
-
lastEmailShare,
|
|
19806
|
-
authUser.email,
|
|
19807
|
-
shareVersion,
|
|
19808
|
-
tenantId
|
|
19809
|
-
) : Promise.resolve();
|
|
19810
|
-
resend.catch(
|
|
19811
|
-
(e) => console.warn("Email backup share re-send failed (non-fatal):", e)
|
|
19812
|
-
);
|
|
19813
|
-
lastEmailShare = void 0;
|
|
19814
|
-
}
|
|
21901
|
+
lastEmailShare = shares.emailShare;
|
|
21902
|
+
lastShareVersion = shareVersion;
|
|
21903
|
+
lastServerSnapshot = {
|
|
21904
|
+
currentVersion: shareVersion,
|
|
21905
|
+
resolvedVersion: shareVersion,
|
|
21906
|
+
authShare: shares.authShare,
|
|
21907
|
+
primaryDid
|
|
21908
|
+
};
|
|
19815
21909
|
switch (input.method) {
|
|
21910
|
+
case "escrow":
|
|
21911
|
+
await enrollEscrow(
|
|
21912
|
+
token,
|
|
21913
|
+
providerType,
|
|
21914
|
+
privateKey,
|
|
21915
|
+
primaryDid,
|
|
21916
|
+
shares,
|
|
21917
|
+
shareVersion,
|
|
21918
|
+
signDidAuthVp,
|
|
21919
|
+
escrowKey
|
|
21920
|
+
);
|
|
21921
|
+
return { method: "escrow", shareVersion };
|
|
19816
21922
|
case "passkey": {
|
|
19817
21923
|
const credential = passkeyCredential;
|
|
19818
21924
|
const encryptedShare = await encryptShareWithPasskey(
|
|
@@ -19832,9 +21938,38 @@ function createSSSStrategy(config) {
|
|
|
19832
21938
|
credentialId: credential.credentialId,
|
|
19833
21939
|
shareVersion
|
|
19834
21940
|
},
|
|
19835
|
-
|
|
21941
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21942
|
+
serverUrl,
|
|
21943
|
+
privateKey,
|
|
21944
|
+
primaryDid,
|
|
21945
|
+
signDidAuthVp,
|
|
21946
|
+
tenantId
|
|
21947
|
+
) : void 0,
|
|
19836
21948
|
tenantId
|
|
19837
21949
|
);
|
|
21950
|
+
await postRecoveryConfirmation(
|
|
21951
|
+
serverUrl,
|
|
21952
|
+
token,
|
|
21953
|
+
providerType,
|
|
21954
|
+
{ type: "passkey", credentialId: credential.credentialId },
|
|
21955
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21956
|
+
serverUrl,
|
|
21957
|
+
privateKey,
|
|
21958
|
+
primaryDid,
|
|
21959
|
+
signDidAuthVp,
|
|
21960
|
+
tenantId
|
|
21961
|
+
) : void 0,
|
|
21962
|
+
tenantId
|
|
21963
|
+
);
|
|
21964
|
+
await tryEnrollEscrow(
|
|
21965
|
+
token,
|
|
21966
|
+
providerType,
|
|
21967
|
+
privateKey,
|
|
21968
|
+
primaryDid,
|
|
21969
|
+
shares,
|
|
21970
|
+
shareVersion,
|
|
21971
|
+
signDidAuthVp
|
|
21972
|
+
);
|
|
19838
21973
|
return { method: "passkey", credentialId: credential.credentialId };
|
|
19839
21974
|
}
|
|
19840
21975
|
case "phrase": {
|
|
@@ -19847,10 +21982,38 @@ function createSSSStrategy(config) {
|
|
|
19847
21982
|
type: "phrase",
|
|
19848
21983
|
shareVersion
|
|
19849
21984
|
},
|
|
19850
|
-
|
|
21985
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21986
|
+
serverUrl,
|
|
21987
|
+
privateKey,
|
|
21988
|
+
primaryDid,
|
|
21989
|
+
signDidAuthVp,
|
|
21990
|
+
tenantId
|
|
21991
|
+
) : void 0,
|
|
19851
21992
|
tenantId
|
|
19852
21993
|
);
|
|
19853
|
-
|
|
21994
|
+
const challengeWordIndices = selectRecoveryPhraseChallengeIndices(
|
|
21995
|
+
phrase.split(/\s+/).length
|
|
21996
|
+
);
|
|
21997
|
+
const challengeWordOptions = await buildRecoveryPhraseChallengeOptions(
|
|
21998
|
+
phrase,
|
|
21999
|
+
challengeWordIndices
|
|
22000
|
+
);
|
|
22001
|
+
pendingPhraseConfirmation = { phrase, challengeWordIndices };
|
|
22002
|
+
await tryEnrollEscrow(
|
|
22003
|
+
token,
|
|
22004
|
+
providerType,
|
|
22005
|
+
privateKey,
|
|
22006
|
+
primaryDid,
|
|
22007
|
+
shares,
|
|
22008
|
+
shareVersion,
|
|
22009
|
+
signDidAuthVp
|
|
22010
|
+
);
|
|
22011
|
+
return {
|
|
22012
|
+
method: "phrase",
|
|
22013
|
+
phrase,
|
|
22014
|
+
challengeWordIndices,
|
|
22015
|
+
challengeWordOptions
|
|
22016
|
+
};
|
|
19854
22017
|
}
|
|
19855
22018
|
case "backup": {
|
|
19856
22019
|
const encrypted = await encryptWithPassword(
|
|
@@ -19862,6 +22025,7 @@ function createSSSStrategy(config) {
|
|
|
19862
22025
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19863
22026
|
primaryDid: input.did,
|
|
19864
22027
|
shareVersion,
|
|
22028
|
+
shareChecksum: await calculateShareChecksum(shares.recoveryShare),
|
|
19865
22029
|
encryptedShare: {
|
|
19866
22030
|
ciphertext: encrypted.ciphertext,
|
|
19867
22031
|
iv: encrypted.iv,
|
|
@@ -19877,36 +22041,121 @@ function createSSSStrategy(config) {
|
|
|
19877
22041
|
type: "backup",
|
|
19878
22042
|
shareVersion
|
|
19879
22043
|
},
|
|
19880
|
-
|
|
22044
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22045
|
+
serverUrl,
|
|
22046
|
+
privateKey,
|
|
22047
|
+
primaryDid,
|
|
22048
|
+
signDidAuthVp,
|
|
22049
|
+
tenantId
|
|
22050
|
+
) : void 0,
|
|
19881
22051
|
tenantId
|
|
19882
22052
|
);
|
|
22053
|
+
pendingBackupConfirmation = {
|
|
22054
|
+
backupFile,
|
|
22055
|
+
recoveryShare: shares.recoveryShare
|
|
22056
|
+
};
|
|
22057
|
+
await tryEnrollEscrow(
|
|
22058
|
+
token,
|
|
22059
|
+
providerType,
|
|
22060
|
+
privateKey,
|
|
22061
|
+
primaryDid,
|
|
22062
|
+
shares,
|
|
22063
|
+
shareVersion,
|
|
22064
|
+
signDidAuthVp
|
|
22065
|
+
);
|
|
19883
22066
|
return { method: "backup", backupFile };
|
|
19884
22067
|
}
|
|
19885
22068
|
case "email": {
|
|
19886
|
-
await
|
|
22069
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22070
|
+
serverUrl,
|
|
22071
|
+
privateKey,
|
|
22072
|
+
primaryDid,
|
|
22073
|
+
signDidAuthVp,
|
|
22074
|
+
tenantId
|
|
22075
|
+
) : void 0;
|
|
22076
|
+
await sendEmailBackupShare(
|
|
19887
22077
|
serverUrl,
|
|
19888
22078
|
token,
|
|
19889
22079
|
providerType,
|
|
19890
22080
|
shares.emailShare,
|
|
22081
|
+
input.email,
|
|
22082
|
+
escrowRelayPublicKey,
|
|
22083
|
+
escrowRelayKeyId,
|
|
22084
|
+
emailBranding,
|
|
19891
22085
|
shareVersion,
|
|
22086
|
+
didAuthVp,
|
|
19892
22087
|
tenantId
|
|
19893
22088
|
);
|
|
19894
|
-
await
|
|
19895
|
-
serverUrl,
|
|
22089
|
+
await tryEnrollEscrow(
|
|
19896
22090
|
token,
|
|
19897
22091
|
providerType,
|
|
19898
|
-
|
|
19899
|
-
|
|
19900
|
-
|
|
19901
|
-
|
|
19902
|
-
|
|
19903
|
-
tenantId
|
|
22092
|
+
privateKey,
|
|
22093
|
+
primaryDid,
|
|
22094
|
+
shares,
|
|
22095
|
+
shareVersion,
|
|
22096
|
+
signDidAuthVp
|
|
19904
22097
|
);
|
|
19905
|
-
hasRecoveryEmail = true;
|
|
19906
22098
|
return { method: "email" };
|
|
19907
22099
|
}
|
|
19908
22100
|
}
|
|
19909
22101
|
},
|
|
22102
|
+
async confirmRecoveryMethod(params) {
|
|
22103
|
+
const { token, providerType, privateKey, input, signDidAuthVp } = params;
|
|
22104
|
+
const primaryDid = lastServerSnapshot?.primaryDid;
|
|
22105
|
+
if (!primaryDid) throw new Error("Cannot confirm recovery without a primary DID");
|
|
22106
|
+
const confirmationBody = { type: input.method };
|
|
22107
|
+
if (input.method === "phrase") {
|
|
22108
|
+
const pending = pendingPhraseConfirmation;
|
|
22109
|
+
if (!pending) throw new Error("Generate a new recovery phrase to continue");
|
|
22110
|
+
const phraseWords = pending.phrase.trim().toLowerCase().split(/\s+/);
|
|
22111
|
+
const matches = input.challengeWords.length === pending.challengeWordIndices.length && pending.challengeWordIndices.every(
|
|
22112
|
+
(wordIndex, answerIndex) => input.challengeWords[answerIndex]?.trim().toLowerCase() === phraseWords[wordIndex]
|
|
22113
|
+
);
|
|
22114
|
+
if (!matches) {
|
|
22115
|
+
throw new Error("Those words do not match. Check your phrase and try again.");
|
|
22116
|
+
}
|
|
22117
|
+
} else if (input.method === "backup") {
|
|
22118
|
+
const pending = pendingBackupConfirmation;
|
|
22119
|
+
if (!pending) throw new Error("Generate a new backup file to continue");
|
|
22120
|
+
let suppliedBackup;
|
|
22121
|
+
let decryptedShare;
|
|
22122
|
+
try {
|
|
22123
|
+
suppliedBackup = JSON.parse(input.fileContents);
|
|
22124
|
+
decryptedShare = await decryptWithPassword(
|
|
22125
|
+
suppliedBackup.encryptedShare.ciphertext,
|
|
22126
|
+
suppliedBackup.encryptedShare.iv,
|
|
22127
|
+
suppliedBackup.encryptedShare.salt,
|
|
22128
|
+
input.password,
|
|
22129
|
+
suppliedBackup.encryptedShare.kdfParams
|
|
22130
|
+
);
|
|
22131
|
+
} catch {
|
|
22132
|
+
throw new Error("Incorrect password or corrupted data. Please try again.");
|
|
22133
|
+
}
|
|
22134
|
+
const checksum = await calculateShareChecksum(decryptedShare);
|
|
22135
|
+
if (suppliedBackup.shareChecksum !== checksum || pending.backupFile.shareChecksum !== checksum || decryptedShare !== pending.recoveryShare) {
|
|
22136
|
+
throw new Error("This backup file could not be verified. Please try again.");
|
|
22137
|
+
}
|
|
22138
|
+
} else {
|
|
22139
|
+
confirmationBody.code = input.code;
|
|
22140
|
+
}
|
|
22141
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22142
|
+
serverUrl,
|
|
22143
|
+
privateKey,
|
|
22144
|
+
primaryDid,
|
|
22145
|
+
signDidAuthVp,
|
|
22146
|
+
tenantId
|
|
22147
|
+
) : void 0;
|
|
22148
|
+
await postRecoveryConfirmation(
|
|
22149
|
+
serverUrl,
|
|
22150
|
+
token,
|
|
22151
|
+
providerType,
|
|
22152
|
+
confirmationBody,
|
|
22153
|
+
didAuthVp,
|
|
22154
|
+
tenantId
|
|
22155
|
+
);
|
|
22156
|
+
if (input.method === "phrase") pendingPhraseConfirmation = void 0;
|
|
22157
|
+
if (input.method === "backup") pendingBackupConfirmation = void 0;
|
|
22158
|
+
},
|
|
19910
22159
|
async getAvailableRecoveryMethods(token, providerType) {
|
|
19911
22160
|
try {
|
|
19912
22161
|
const serverData = await fetchAuthShareRaw(
|
|
@@ -19917,15 +22166,274 @@ function createSSSStrategy(config) {
|
|
|
19917
22166
|
tenantId
|
|
19918
22167
|
);
|
|
19919
22168
|
const methods = serverData?.recoveryMethods || [];
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
22169
|
+
const activationState = serverData?.sssActivationState ?? "active";
|
|
22170
|
+
return methods.filter((method) => {
|
|
22171
|
+
if (method.confirmedAt) return true;
|
|
22172
|
+
if (method.confirmationStatus) return false;
|
|
22173
|
+
return activationState === "active";
|
|
22174
|
+
});
|
|
19924
22175
|
} catch (e) {
|
|
19925
22176
|
console.error("Error getting recovery methods:", e);
|
|
19926
22177
|
return [];
|
|
19927
22178
|
}
|
|
19928
22179
|
},
|
|
22180
|
+
async startIdentityRecovery(email) {
|
|
22181
|
+
await postJson(
|
|
22182
|
+
`${serverUrl}/keys/recovery-session/start`,
|
|
22183
|
+
{ email },
|
|
22184
|
+
buildHeaders("", void 0, tenantId)
|
|
22185
|
+
);
|
|
22186
|
+
},
|
|
22187
|
+
async verifyIdentityRecovery(email, code) {
|
|
22188
|
+
const result = await postJson(
|
|
22189
|
+
`${serverUrl}/keys/recovery-session/verify`,
|
|
22190
|
+
{ email, code },
|
|
22191
|
+
buildHeaders("", void 0, tenantId)
|
|
22192
|
+
);
|
|
22193
|
+
return {
|
|
22194
|
+
...result,
|
|
22195
|
+
recoveryMethods: result.recoveryMethods.map((method) => ({
|
|
22196
|
+
...method,
|
|
22197
|
+
createdAt: new Date(method.createdAt)
|
|
22198
|
+
}))
|
|
22199
|
+
};
|
|
22200
|
+
},
|
|
22201
|
+
async prepareIdentityRecovery(params) {
|
|
22202
|
+
const { input } = params;
|
|
22203
|
+
if (input.method === "escrow") {
|
|
22204
|
+
throw new Error("Use executeRecovery to complete an escrow hold");
|
|
22205
|
+
}
|
|
22206
|
+
let recoveryShare;
|
|
22207
|
+
switch (input.method) {
|
|
22208
|
+
case "passkey":
|
|
22209
|
+
break;
|
|
22210
|
+
case "phrase":
|
|
22211
|
+
if (!await validateRecoveryPhrase(input.phrase)) {
|
|
22212
|
+
throw new Error("Invalid recovery phrase");
|
|
22213
|
+
}
|
|
22214
|
+
recoveryShare = await recoveryPhraseToShare(input.phrase);
|
|
22215
|
+
break;
|
|
22216
|
+
case "backup": {
|
|
22217
|
+
const backup = JSON.parse(input.fileContents);
|
|
22218
|
+
if (backup.version !== 1) throw new Error("Unsupported backup file version");
|
|
22219
|
+
recoveryShare = await decryptWithPassword(
|
|
22220
|
+
backup.encryptedShare.ciphertext,
|
|
22221
|
+
backup.encryptedShare.iv,
|
|
22222
|
+
backup.encryptedShare.salt,
|
|
22223
|
+
input.password,
|
|
22224
|
+
backup.encryptedShare.kdfParams
|
|
22225
|
+
);
|
|
22226
|
+
break;
|
|
22227
|
+
}
|
|
22228
|
+
case "email":
|
|
22229
|
+
recoveryShare = parseVersionedEmailShare(input.emailShare.trim()).share;
|
|
22230
|
+
break;
|
|
22231
|
+
}
|
|
22232
|
+
try {
|
|
22233
|
+
const material = await postJson(
|
|
22234
|
+
`${serverUrl}/keys/recovery-session/recover`,
|
|
22235
|
+
{
|
|
22236
|
+
recoverySessionToken: params.recoverySessionToken,
|
|
22237
|
+
type: input.method,
|
|
22238
|
+
...input.method === "passkey" ? { credentialId: input.credentialId } : {}
|
|
22239
|
+
},
|
|
22240
|
+
buildHeaders("", void 0, tenantId)
|
|
22241
|
+
);
|
|
22242
|
+
if (input.method === "passkey") {
|
|
22243
|
+
if (!material.encryptedShare) {
|
|
22244
|
+
throw new Error("No passkey recovery data found");
|
|
22245
|
+
}
|
|
22246
|
+
recoveryShare = await decryptShareWithPasskey({
|
|
22247
|
+
encryptedData: material.encryptedShare.encryptedData,
|
|
22248
|
+
iv: material.encryptedShare.iv,
|
|
22249
|
+
credentialId: input.credentialId
|
|
22250
|
+
});
|
|
22251
|
+
}
|
|
22252
|
+
if (!recoveryShare) throw new Error("Recovery data was empty");
|
|
22253
|
+
const authShare = material.authShare.encryptedData;
|
|
22254
|
+
let privateKey;
|
|
22255
|
+
try {
|
|
22256
|
+
privateKey = await reconstructFromShares([recoveryShare, authShare]);
|
|
22257
|
+
} catch {
|
|
22258
|
+
throw new Error(
|
|
22259
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22260
|
+
);
|
|
22261
|
+
}
|
|
22262
|
+
const derivedDid = await params.didFromPrivateKey(privateKey);
|
|
22263
|
+
if (!derivedDid || derivedDid !== material.primaryDid) {
|
|
22264
|
+
throw new Error(
|
|
22265
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22266
|
+
);
|
|
22267
|
+
}
|
|
22268
|
+
pendingIdentityRecovery = {
|
|
22269
|
+
privateKey,
|
|
22270
|
+
primaryDid: material.primaryDid,
|
|
22271
|
+
recoveryShare,
|
|
22272
|
+
authShare,
|
|
22273
|
+
rebindSessionToken: material.rebindSessionToken
|
|
22274
|
+
};
|
|
22275
|
+
return { privateKey, did: material.primaryDid };
|
|
22276
|
+
} catch (error) {
|
|
22277
|
+
const message = error instanceof Error ? error.message : "Identity recovery failed";
|
|
22278
|
+
throw new IdentityRecoverySessionConsumedError(message);
|
|
22279
|
+
}
|
|
22280
|
+
},
|
|
22281
|
+
hasPendingIdentityRecovery() {
|
|
22282
|
+
return Boolean(pendingIdentityRecovery);
|
|
22283
|
+
},
|
|
22284
|
+
cancelIdentityRecovery() {
|
|
22285
|
+
pendingIdentityRecovery = void 0;
|
|
22286
|
+
},
|
|
22287
|
+
async completeIdentityRecovery(params) {
|
|
22288
|
+
const storage2 = guardedStorage();
|
|
22289
|
+
const generation = storageGeneration;
|
|
22290
|
+
const pending = pendingIdentityRecovery;
|
|
22291
|
+
const signDidAuthVp = params.signDidAuthVp;
|
|
22292
|
+
if (!pending) throw new Error("No identity recovery is waiting for a new sign-in");
|
|
22293
|
+
if (!signDidAuthVp) {
|
|
22294
|
+
throw new Error("DID proof signing is required to bind a new sign-in");
|
|
22295
|
+
}
|
|
22296
|
+
const storageId = activeStorageId;
|
|
22297
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
22298
|
+
storage2,
|
|
22299
|
+
storageId,
|
|
22300
|
+
() => withRotationLock(async () => {
|
|
22301
|
+
const assertActive = /* @__PURE__ */ __name(() => {
|
|
22302
|
+
if (activeStorageId !== storageId || pendingIdentityRecovery !== pending || storageGeneration !== generation) {
|
|
22303
|
+
throw new Error("This recovery request was cancelled.");
|
|
22304
|
+
}
|
|
22305
|
+
}, "assertActive");
|
|
22306
|
+
assertActive();
|
|
22307
|
+
const candidates = await readPendingShareCandidates(storage2, storageId);
|
|
22308
|
+
if (pending.rebind && pending.rebind.storageId !== storageId) {
|
|
22309
|
+
throw new Error("Active account changed during identity recovery");
|
|
22310
|
+
}
|
|
22311
|
+
if (candidates.some(
|
|
22312
|
+
(candidate) => candidate.share !== pending.rebind?.shares.deviceShare
|
|
22313
|
+
)) {
|
|
22314
|
+
throw new Error(
|
|
22315
|
+
"Reconcile the pending share update before binding a sign-in"
|
|
22316
|
+
);
|
|
22317
|
+
}
|
|
22318
|
+
let shareVersion2;
|
|
22319
|
+
const retry = pending.rebind?.attempted ?? false;
|
|
22320
|
+
if (retry && pending.rebind) {
|
|
22321
|
+
const current = await fetchAuthShareRaw(
|
|
22322
|
+
serverUrl,
|
|
22323
|
+
params.token,
|
|
22324
|
+
params.providerType,
|
|
22325
|
+
void 0,
|
|
22326
|
+
tenantId
|
|
22327
|
+
);
|
|
22328
|
+
const authShare = authShareToString(current?.authShare);
|
|
22329
|
+
if (authShare && current?.shareVersion != null) {
|
|
22330
|
+
const health = await verifyStoredShares(
|
|
22331
|
+
{
|
|
22332
|
+
getDevice: /* @__PURE__ */ __name(async () => pending.rebind.shares.deviceShare, "getDevice"),
|
|
22333
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
22334
|
+
},
|
|
22335
|
+
pending.primaryDid,
|
|
22336
|
+
async (key) => key === pending.privateKey ? pending.primaryDid : ""
|
|
22337
|
+
);
|
|
22338
|
+
if (health.healthy) shareVersion2 = current.shareVersion;
|
|
22339
|
+
}
|
|
22340
|
+
}
|
|
22341
|
+
assertActive();
|
|
22342
|
+
if (shareVersion2 === void 0) {
|
|
22343
|
+
const didAuthVp = await requestFreshDidAuthVp(
|
|
22344
|
+
serverUrl,
|
|
22345
|
+
pending.privateKey,
|
|
22346
|
+
pending.primaryDid,
|
|
22347
|
+
signDidAuthVp,
|
|
22348
|
+
tenantId
|
|
22349
|
+
);
|
|
22350
|
+
assertActive();
|
|
22351
|
+
pending.rebind ??= {
|
|
22352
|
+
shares: (await splitAndVerify(pending.privateKey)).shares,
|
|
22353
|
+
storageId,
|
|
22354
|
+
attempted: false
|
|
22355
|
+
};
|
|
22356
|
+
const rebind = pending.rebind;
|
|
22357
|
+
if (!candidates.some(
|
|
22358
|
+
(candidate) => candidate.share === rebind.shares.deviceShare
|
|
22359
|
+
)) {
|
|
22360
|
+
await addPendingShareCandidate(
|
|
22361
|
+
storage2,
|
|
22362
|
+
{ share: rebind.shares.deviceShare, createdAt: Date.now() },
|
|
22363
|
+
storageId
|
|
22364
|
+
);
|
|
22365
|
+
}
|
|
22366
|
+
assertActive();
|
|
22367
|
+
rebind.attempted = true;
|
|
22368
|
+
try {
|
|
22369
|
+
const response = await postJson(
|
|
22370
|
+
`${serverUrl}/keys/recovery-session/rebind`,
|
|
22371
|
+
{
|
|
22372
|
+
recoverySessionToken: pending.rebindSessionToken,
|
|
22373
|
+
providerType: params.providerType,
|
|
22374
|
+
primaryDid: pending.primaryDid,
|
|
22375
|
+
authShare: {
|
|
22376
|
+
encryptedData: rebind.shares.authShare,
|
|
22377
|
+
encryptedDek: "",
|
|
22378
|
+
iv: ""
|
|
22379
|
+
}
|
|
22380
|
+
},
|
|
22381
|
+
{
|
|
22382
|
+
...buildHeaders("", didAuthVp, tenantId),
|
|
22383
|
+
"X-Auth-Token": params.token
|
|
22384
|
+
}
|
|
22385
|
+
);
|
|
22386
|
+
shareVersion2 = response.shareVersion;
|
|
22387
|
+
} catch (error) {
|
|
22388
|
+
if (error instanceof ShareWriteRejectedError && !retry) {
|
|
22389
|
+
await removePendingShareCandidate(
|
|
22390
|
+
storage2,
|
|
22391
|
+
rebind.shares.deviceShare,
|
|
22392
|
+
storageId
|
|
22393
|
+
);
|
|
22394
|
+
pending.rebind = void 0;
|
|
22395
|
+
}
|
|
22396
|
+
throw error;
|
|
22397
|
+
}
|
|
22398
|
+
}
|
|
22399
|
+
if (shareVersion2 === void 0 || !pending.rebind) {
|
|
22400
|
+
throw new Error("Server did not confirm the new share");
|
|
22401
|
+
}
|
|
22402
|
+
assertActive();
|
|
22403
|
+
const shares2 = pending.rebind.shares;
|
|
22404
|
+
await storage2.storeDeviceShare(shares2.deviceShare, storageId);
|
|
22405
|
+
await storage2.storeShareVersion(shareVersion2, storageId);
|
|
22406
|
+
await clearPendingShareCandidates(storage2, storageId);
|
|
22407
|
+
assertActive();
|
|
22408
|
+
lastEmailShare = shares2.emailShare;
|
|
22409
|
+
lastShareVersion = shareVersion2;
|
|
22410
|
+
lastServerSnapshot = {
|
|
22411
|
+
currentVersion: shareVersion2,
|
|
22412
|
+
resolvedVersion: shareVersion2,
|
|
22413
|
+
authShare: shares2.authShare,
|
|
22414
|
+
primaryDid: pending.primaryDid
|
|
22415
|
+
};
|
|
22416
|
+
pendingIdentityRecovery = void 0;
|
|
22417
|
+
return { shares: shares2, shareVersion: shareVersion2 };
|
|
22418
|
+
})
|
|
22419
|
+
);
|
|
22420
|
+
await tryEnrollEscrow(
|
|
22421
|
+
params.token,
|
|
22422
|
+
params.providerType,
|
|
22423
|
+
pending.privateKey,
|
|
22424
|
+
pending.primaryDid,
|
|
22425
|
+
shares,
|
|
22426
|
+
shareVersion,
|
|
22427
|
+
signDidAuthVp,
|
|
22428
|
+
void 0,
|
|
22429
|
+
void 0,
|
|
22430
|
+
true
|
|
22431
|
+
);
|
|
22432
|
+
if (activeStorageId !== storageId || storageGeneration !== generation) {
|
|
22433
|
+
throw new Error("This recovery request was cancelled.");
|
|
22434
|
+
}
|
|
22435
|
+
return { privateKey: pending.privateKey, did: pending.primaryDid };
|
|
22436
|
+
},
|
|
19929
22437
|
// --- Contact method management ---
|
|
19930
22438
|
async upgradeContactMethod(token, providerType, previousPhone, email, code) {
|
|
19931
22439
|
const res = await fetch(`${serverUrl}/keys/upgrade-contact-method`, {
|
|
@@ -19948,7 +22456,7 @@ function createSSSStrategy(config) {
|
|
|
19948
22456
|
return { customToken: data?.customToken };
|
|
19949
22457
|
},
|
|
19950
22458
|
// --- Email backup ---
|
|
19951
|
-
async sendEmailBackupShare(token, providerType, _privateKey, email) {
|
|
22459
|
+
async sendEmailBackupShare(token, providerType, _privateKey, email, didAuthVp) {
|
|
19952
22460
|
if (!enableEmailBackupShare) return;
|
|
19953
22461
|
if (!lastEmailShare) {
|
|
19954
22462
|
console.warn(
|
|
@@ -19956,29 +22464,22 @@ function createSSSStrategy(config) {
|
|
|
19956
22464
|
);
|
|
19957
22465
|
return;
|
|
19958
22466
|
}
|
|
19959
|
-
if (
|
|
19960
|
-
await sendEmailShareToRecoveryEmail(
|
|
19961
|
-
serverUrl,
|
|
19962
|
-
token,
|
|
19963
|
-
providerType,
|
|
19964
|
-
lastEmailShare,
|
|
19965
|
-
lastShareVersion,
|
|
19966
|
-
tenantId
|
|
19967
|
-
);
|
|
19968
|
-
} else {
|
|
19969
|
-
if (!email) {
|
|
19970
|
-
console.warn("Cannot send email backup share: no email address");
|
|
19971
|
-
return;
|
|
19972
|
-
}
|
|
22467
|
+
if (email) {
|
|
19973
22468
|
await sendEmailBackupShare(
|
|
19974
22469
|
serverUrl,
|
|
19975
22470
|
token,
|
|
19976
22471
|
providerType,
|
|
19977
22472
|
lastEmailShare,
|
|
19978
22473
|
email,
|
|
22474
|
+
escrowRelayPublicKey,
|
|
22475
|
+
escrowRelayKeyId,
|
|
22476
|
+
emailBranding,
|
|
19979
22477
|
lastShareVersion,
|
|
22478
|
+
didAuthVp,
|
|
19980
22479
|
tenantId
|
|
19981
22480
|
);
|
|
22481
|
+
} else {
|
|
22482
|
+
throw new Error("Enter a recovery email before sending a recovery key");
|
|
19982
22483
|
}
|
|
19983
22484
|
lastEmailShare = void 0;
|
|
19984
22485
|
lastShareVersion = void 0;
|
|
@@ -19995,6 +22496,8 @@ function createSSSStrategy(config) {
|
|
|
19995
22496
|
return [SSS_DB_NAME];
|
|
19996
22497
|
},
|
|
19997
22498
|
async cleanup() {
|
|
22499
|
+
storageGeneration++;
|
|
22500
|
+
await Promise.allSettled(localWrites);
|
|
19998
22501
|
}
|
|
19999
22502
|
};
|
|
20000
22503
|
}
|
|
@@ -20087,13 +22590,7 @@ var decryptShareFromTransfer = /* @__PURE__ */ __name(async (payload, recipientP
|
|
|
20087
22590
|
}, "decryptShareFromTransfer");
|
|
20088
22591
|
|
|
20089
22592
|
// src/qr-login.ts
|
|
20090
|
-
var buildHeaders2 = /* @__PURE__ */ __name((
|
|
20091
|
-
const headers = { "Content-Type": "application/json" };
|
|
20092
|
-
if (token) {
|
|
20093
|
-
headers.Authorization = `Bearer ${token}`;
|
|
20094
|
-
}
|
|
20095
|
-
return headers;
|
|
20096
|
-
}, "buildHeaders");
|
|
22593
|
+
var buildHeaders2 = /* @__PURE__ */ __name(() => ({ "Content-Type": "application/json" }), "buildHeaders");
|
|
20097
22594
|
var createQrLoginSession = /* @__PURE__ */ __name(async (config) => {
|
|
20098
22595
|
const ephemeralKeypair = await generateEphemeralKeypair();
|
|
20099
22596
|
const response = await fetch(`${config.serverUrl}/qr-login/session`, {
|
|
@@ -20161,10 +22658,14 @@ var pollUntilApproved = /* @__PURE__ */ __name(async (config, sessionId, ephemer
|
|
|
20161
22658
|
await new Promise((resolve, reject) => {
|
|
20162
22659
|
const timer = setTimeout(resolve, intervalMs);
|
|
20163
22660
|
if (options?.signal) {
|
|
20164
|
-
options.signal.addEventListener(
|
|
20165
|
-
|
|
20166
|
-
|
|
20167
|
-
|
|
22661
|
+
options.signal.addEventListener(
|
|
22662
|
+
"abort",
|
|
22663
|
+
() => {
|
|
22664
|
+
clearTimeout(timer);
|
|
22665
|
+
reject(new Error("QR login polling aborted"));
|
|
22666
|
+
},
|
|
22667
|
+
{ once: true }
|
|
22668
|
+
);
|
|
20168
22669
|
}
|
|
20169
22670
|
});
|
|
20170
22671
|
}
|
|
@@ -20216,43 +22717,68 @@ var notifyDevicesForQrSession = /* @__PURE__ */ __name(async (config, sessionId,
|
|
|
20216
22717
|
return { sent: false, deviceCount: 0 };
|
|
20217
22718
|
}
|
|
20218
22719
|
}, "notifyDevicesForQrSession");
|
|
20219
|
-
|
|
20220
|
-
// src/types.ts
|
|
20221
|
-
var import_types = __toESM(require_dist());
|
|
20222
22720
|
var export_AuthSessionError = import_types.AuthSessionError;
|
|
20223
22721
|
export {
|
|
20224
22722
|
AtomicUpdateError,
|
|
20225
22723
|
export_AuthSessionError as AuthSessionError,
|
|
20226
22724
|
DEFAULT_KDF_PARAMS,
|
|
22725
|
+
EMAIL_RELAY_ALGORITHM,
|
|
22726
|
+
EMAIL_RELAY_ENVELOPE_VERSION,
|
|
22727
|
+
ESCROW_ALGORITHM,
|
|
22728
|
+
ESCROW_BLOB_INFO,
|
|
22729
|
+
ESCROW_CLIENT_KEY_ID,
|
|
22730
|
+
ESCROW_ENVELOPE_VERSION,
|
|
22731
|
+
ESCROW_PIN_MAX_ATTEMPTS,
|
|
22732
|
+
ESCROW_RELEASE_INFO,
|
|
22733
|
+
EscrowHoldRestartThrottledError,
|
|
22734
|
+
EscrowPinLockedError,
|
|
22735
|
+
EscrowPinMismatchError,
|
|
22736
|
+
EscrowPinThrottledError,
|
|
22737
|
+
EscrowPinUnavailableError,
|
|
22738
|
+
EscrowRequestError,
|
|
22739
|
+
IdentityRecoverySessionConsumedError,
|
|
22740
|
+
PIN_MAX_LENGTH,
|
|
22741
|
+
PIN_MIN_LENGTH,
|
|
20227
22742
|
SSSApiClient,
|
|
20228
22743
|
SSSKeyManager,
|
|
20229
22744
|
SSS_THRESHOLD,
|
|
20230
22745
|
SSS_TOTAL_SHARES,
|
|
20231
22746
|
ShareVerificationError,
|
|
22747
|
+
ShareWriteRejectedError,
|
|
20232
22748
|
approveQrLoginSession,
|
|
20233
22749
|
atomicRecovery,
|
|
20234
22750
|
atomicShareUpdate,
|
|
20235
22751
|
base64ToBuffer,
|
|
20236
22752
|
bufferToBase64,
|
|
22753
|
+
buildRecoveryPhraseChallengeOptions,
|
|
20237
22754
|
bytesToHex,
|
|
20238
22755
|
clearAllShares,
|
|
22756
|
+
constantTimeEqualHex,
|
|
20239
22757
|
countWords,
|
|
20240
22758
|
createAdaptiveStorage,
|
|
20241
22759
|
createPasskeyCredential,
|
|
20242
22760
|
createQrLoginSession,
|
|
20243
22761
|
createSSSKeyManager,
|
|
20244
22762
|
createSSSStrategy,
|
|
22763
|
+
decryptEmailRelayPayload,
|
|
22764
|
+
decryptEscrowBlob,
|
|
20245
22765
|
decryptShareFromTransfer,
|
|
20246
22766
|
decryptShareWithPasskey,
|
|
20247
22767
|
decryptWithPassword,
|
|
20248
22768
|
deleteDeviceShare,
|
|
20249
22769
|
deriveKeyFromPasskey,
|
|
20250
22770
|
deriveKeyFromPassword,
|
|
22771
|
+
derivePinProof,
|
|
22772
|
+
encryptEmailRelayPayload,
|
|
22773
|
+
encryptEscrowBlob,
|
|
20251
22774
|
encryptShareForTransfer,
|
|
20252
22775
|
encryptShareWithPasskey,
|
|
20253
22776
|
encryptWithPassword,
|
|
20254
22777
|
generateEd25519PrivateKey,
|
|
22778
|
+
generateEmailRelayConfirmationCode,
|
|
20255
22779
|
generateEphemeralKeypair,
|
|
22780
|
+
generateEscrowKeyPair,
|
|
22781
|
+
generatePinSalt,
|
|
20256
22782
|
generateRecoveryPhrase,
|
|
20257
22783
|
getDeviceShare,
|
|
20258
22784
|
getQrLoginSessionInfo,
|
|
@@ -20263,18 +22789,28 @@ export {
|
|
|
20263
22789
|
isPublicComputerMode,
|
|
20264
22790
|
isWebAuthnSupported,
|
|
20265
22791
|
listAllDeviceShares,
|
|
22792
|
+
normalizePin,
|
|
20266
22793
|
notifyDevicesForQrSession,
|
|
22794
|
+
openEscrowRelease,
|
|
22795
|
+
parseEmailRelayEnvelope,
|
|
22796
|
+
parseEmailRelayPlaintext,
|
|
22797
|
+
parseEscrowBlobPlaintext,
|
|
22798
|
+
parseEscrowEnvelope,
|
|
22799
|
+
parseEscrowReleasePlaintext,
|
|
20267
22800
|
pollQrLoginSession,
|
|
20268
22801
|
pollUntilApproved,
|
|
20269
22802
|
reconstructFromShares,
|
|
20270
22803
|
reconstructPrivateKey,
|
|
20271
22804
|
recoveryPhraseToShare,
|
|
22805
|
+
sealEscrowRelease,
|
|
20272
22806
|
setPublicComputerMode,
|
|
20273
22807
|
shareToRecoveryPhrase,
|
|
20274
22808
|
splitAndVerify,
|
|
20275
22809
|
splitPrivateKey,
|
|
20276
22810
|
storeDeviceShare,
|
|
20277
22811
|
storeShareVersion,
|
|
22812
|
+
validatePin,
|
|
20278
22813
|
validateRecoveryPhrase,
|
|
22814
|
+
verifyEnclaveAttestation,
|
|
20279
22815
|
verifyStoredShares
|
|
20280
22816
|
};
|