@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
|
@@ -62,6 +62,8 @@ var require_types_cjs_development = __commonJS({
|
|
|
62
62
|
AchievementSubjectValidator: /* @__PURE__ */ __name(() => AchievementSubjectValidator, "AchievementSubjectValidator"),
|
|
63
63
|
AchievementTypeValidator: /* @__PURE__ */ __name(() => AchievementTypeValidator, "AchievementTypeValidator"),
|
|
64
64
|
AchievementValidator: /* @__PURE__ */ __name(() => AchievementValidator, "AchievementValidator"),
|
|
65
|
+
AcknowledgeViewInputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewInputValidator, "AcknowledgeViewInputValidator"),
|
|
66
|
+
AcknowledgeViewOutputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewOutputValidator, "AcknowledgeViewOutputValidator"),
|
|
65
67
|
AddTagInputValidator: /* @__PURE__ */ __name(() => AddTagInputValidator, "AddTagInputValidator"),
|
|
66
68
|
AddressValidator: /* @__PURE__ */ __name(() => AddressValidator, "AddressValidator"),
|
|
67
69
|
AgeRatingValidator: /* @__PURE__ */ __name(() => AgeRatingValidator, "AgeRatingValidator"),
|
|
@@ -139,6 +141,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
139
141
|
CreateContactMethodSessionValidator: /* @__PURE__ */ __name(() => CreateContactMethodSessionValidator, "CreateContactMethodSessionValidator"),
|
|
140
142
|
CreateManagedFrameworkBatchInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkBatchInputValidator, "CreateManagedFrameworkBatchInputValidator"),
|
|
141
143
|
CreateManagedFrameworkInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkInputValidator, "CreateManagedFrameworkInputValidator"),
|
|
144
|
+
CreateShareLinkInputValidator: /* @__PURE__ */ __name(() => CreateShareLinkInputValidator, "CreateShareLinkInputValidator"),
|
|
142
145
|
CreateSkillInputValidator: /* @__PURE__ */ __name(() => CreateSkillInputValidator, "CreateSkillInputValidator"),
|
|
143
146
|
CreateSkillsBatchInputValidator: /* @__PURE__ */ __name(() => CreateSkillsBatchInputValidator, "CreateSkillsBatchInputValidator"),
|
|
144
147
|
CredentialActivityEventTypeValidator: /* @__PURE__ */ __name(() => CredentialActivityEventTypeValidator, "CredentialActivityEventTypeValidator"),
|
|
@@ -170,10 +173,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
170
173
|
DeleteSkillInputValidator: /* @__PURE__ */ __name(() => DeleteSkillInputValidator, "DeleteSkillInputValidator"),
|
|
171
174
|
DidDocumentValidator: /* @__PURE__ */ __name(() => DidDocumentValidator, "DidDocumentValidator"),
|
|
172
175
|
EMPTY_IN_APP_MESSAGES_FLAG: /* @__PURE__ */ __name(() => EMPTY_IN_APP_MESSAGES_FLAG, "EMPTY_IN_APP_MESSAGES_FLAG"),
|
|
176
|
+
ESCROW_PIN_LOCKED_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_LOCKED_MESSAGE2, "ESCROW_PIN_LOCKED_MESSAGE"),
|
|
177
|
+
ESCROW_PIN_MISMATCH_PATTERN: /* @__PURE__ */ __name(() => ESCROW_PIN_MISMATCH_PATTERN2, "ESCROW_PIN_MISMATCH_PATTERN"),
|
|
178
|
+
ESCROW_PIN_UNAVAILABLE_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_UNAVAILABLE_MESSAGE2, "ESCROW_PIN_UNAVAILABLE_MESSAGE"),
|
|
173
179
|
EncryptedCredentialRecordValidator: /* @__PURE__ */ __name(() => EncryptedCredentialRecordValidator, "EncryptedCredentialRecordValidator"),
|
|
174
180
|
EncryptedRecordValidator: /* @__PURE__ */ __name(() => EncryptedRecordValidator, "EncryptedRecordValidator"),
|
|
175
181
|
EndorsementCredentialValidator: /* @__PURE__ */ __name(() => EndorsementCredentialValidator, "EndorsementCredentialValidator"),
|
|
176
182
|
EndorsementSubjectValidator: /* @__PURE__ */ __name(() => EndorsementSubjectValidator, "EndorsementSubjectValidator"),
|
|
183
|
+
EscrowPinStatusValidator: /* @__PURE__ */ __name(() => EscrowPinStatusValidator, "EscrowPinStatusValidator"),
|
|
177
184
|
EvidenceValidator: /* @__PURE__ */ __name(() => EvidenceValidator, "EvidenceValidator"),
|
|
178
185
|
FlatAuthGrantValidator: /* @__PURE__ */ __name(() => FlatAuthGrantValidator, "FlatAuthGrantValidator"),
|
|
179
186
|
FrameworkWithSkillsValidator: /* @__PURE__ */ __name(() => FrameworkWithSkillsValidator, "FrameworkWithSkillsValidator"),
|
|
@@ -255,6 +262,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
255
262
|
LaunchTypeValidator: /* @__PURE__ */ __name(() => LaunchTypeValidator, "LaunchTypeValidator"),
|
|
256
263
|
LearnCardRefreshAuthorizationValidator: /* @__PURE__ */ __name(() => LearnCardRefreshAuthorizationValidator, "LearnCardRefreshAuthorizationValidator"),
|
|
257
264
|
LinkProviderFrameworkInputValidator: /* @__PURE__ */ __name(() => LinkProviderFrameworkInputValidator, "LinkProviderFrameworkInputValidator"),
|
|
265
|
+
ListShareLinksInputValidator: /* @__PURE__ */ __name(() => ListShareLinksInputValidator, "ListShareLinksInputValidator"),
|
|
266
|
+
MAX_ENDORSEMENTS: /* @__PURE__ */ __name(() => MAX_ENDORSEMENTS, "MAX_ENDORSEMENTS"),
|
|
267
|
+
MAX_SELECTED_CREDENTIALS: /* @__PURE__ */ __name(() => MAX_SELECTED_CREDENTIALS, "MAX_SELECTED_CREDENTIALS"),
|
|
268
|
+
MAX_SHARE_CIPHERTEXT_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_CIPHERTEXT_BYTES, "MAX_SHARE_CIPHERTEXT_BYTES"),
|
|
269
|
+
MAX_SHARE_LINK_REQUEST_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_LINK_REQUEST_BYTES, "MAX_SHARE_LINK_REQUEST_BYTES"),
|
|
270
|
+
MAX_SHARE_RECOVERY_JWE_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_RECOVERY_JWE_BYTES, "MAX_SHARE_RECOVERY_JWE_BYTES"),
|
|
271
|
+
MAX_SHARE_SOURCE_URI_CHARS: /* @__PURE__ */ __name(() => MAX_SHARE_SOURCE_URI_CHARS, "MAX_SHARE_SOURCE_URI_CHARS"),
|
|
272
|
+
MAX_VP_MEMBERS: /* @__PURE__ */ __name(() => MAX_VP_MEMBERS, "MAX_VP_MEMBERS"),
|
|
258
273
|
ManagedCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshReceiptValidator, "ManagedCredentialRefreshReceiptValidator"),
|
|
259
274
|
ManagedCredentialRefreshServiceValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshServiceValidator, "ManagedCredentialRefreshServiceValidator"),
|
|
260
275
|
PaginatedAppStoreListingsValidator: /* @__PURE__ */ __name(() => PaginatedAppStoreListingsValidator, "PaginatedAppStoreListingsValidator"),
|
|
@@ -277,6 +292,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
277
292
|
PaginatedLCNProfileManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfileManagersValidator, "PaginatedLCNProfileManagersValidator"),
|
|
278
293
|
PaginatedLCNProfilesAndManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesAndManagersValidator, "PaginatedLCNProfilesAndManagersValidator"),
|
|
279
294
|
PaginatedLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesValidator, "PaginatedLCNProfilesValidator"),
|
|
295
|
+
PaginatedShareLinksValidator: /* @__PURE__ */ __name(() => PaginatedShareLinksValidator, "PaginatedShareLinksValidator"),
|
|
280
296
|
PaginatedSkillFrameworksValidator: /* @__PURE__ */ __name(() => PaginatedSkillFrameworksValidator, "PaginatedSkillFrameworksValidator"),
|
|
281
297
|
PaginatedSkillTreeValidator: /* @__PURE__ */ __name(() => PaginatedSkillTreeValidator, "PaginatedSkillTreeValidator"),
|
|
282
298
|
PaginatedVisibleLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedVisibleLCNProfilesValidator, "PaginatedVisibleLCNProfilesValidator"),
|
|
@@ -301,11 +317,20 @@ var require_types_cjs_development = __commonJS({
|
|
|
301
317
|
ReplaceSkillFrameworkSkillsInputValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsInputValidator, "ReplaceSkillFrameworkSkillsInputValidator"),
|
|
302
318
|
ReplaceSkillFrameworkSkillsResultValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsResultValidator, "ReplaceSkillFrameworkSkillsResultValidator"),
|
|
303
319
|
RequestLearnerContextEventValidator: /* @__PURE__ */ __name(() => RequestLearnerContextEventValidator, "RequestLearnerContextEventValidator"),
|
|
320
|
+
ResolveShareLinkInputValidator: /* @__PURE__ */ __name(() => ResolveShareLinkInputValidator, "ResolveShareLinkInputValidator"),
|
|
304
321
|
ResultDescriptionValidator: /* @__PURE__ */ __name(() => ResultDescriptionValidator, "ResultDescriptionValidator"),
|
|
305
322
|
ResultStatusTypeValidator: /* @__PURE__ */ __name(() => ResultStatusTypeValidator, "ResultStatusTypeValidator"),
|
|
306
323
|
ResultTypeValidator: /* @__PURE__ */ __name(() => ResultTypeValidator, "ResultTypeValidator"),
|
|
307
324
|
ResultValidator: /* @__PURE__ */ __name(() => ResultValidator, "ResultValidator"),
|
|
308
325
|
RubricCriterionValidator: /* @__PURE__ */ __name(() => RubricCriterionValidator, "RubricCriterionValidator"),
|
|
326
|
+
SHARE_AAD_PREFIX: /* @__PURE__ */ __name(() => SHARE_AAD_PREFIX, "SHARE_AAD_PREFIX"),
|
|
327
|
+
SHARE_CONTENT_KEY_BYTES: /* @__PURE__ */ __name(() => SHARE_CONTENT_KEY_BYTES, "SHARE_CONTENT_KEY_BYTES"),
|
|
328
|
+
SHARE_IV_BYTES: /* @__PURE__ */ __name(() => SHARE_IV_BYTES, "SHARE_IV_BYTES"),
|
|
329
|
+
SHARE_LINK_ID_BYTES: /* @__PURE__ */ __name(() => SHARE_LINK_ID_BYTES, "SHARE_LINK_ID_BYTES"),
|
|
330
|
+
SHARE_LINK_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_LINK_PROTOCOL, "SHARE_LINK_PROTOCOL"),
|
|
331
|
+
SHARE_RECOVERY_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_RECOVERY_PROTOCOL, "SHARE_RECOVERY_PROTOCOL"),
|
|
332
|
+
SHARE_TAG_BYTES: /* @__PURE__ */ __name(() => SHARE_TAG_BYTES, "SHARE_TAG_BYTES"),
|
|
333
|
+
SHARE_VIEW_RECEIPT_TTL_SECONDS: /* @__PURE__ */ __name(() => SHARE_VIEW_RECEIPT_TTL_SECONDS, "SHARE_VIEW_RECEIPT_TTL_SECONDS"),
|
|
309
334
|
SendAiSessionCredentialEventValidator: /* @__PURE__ */ __name(() => SendAiSessionCredentialEventValidator, "SendAiSessionCredentialEventValidator"),
|
|
310
335
|
SendBoostInputValidator: /* @__PURE__ */ __name(() => SendBoostInputValidator, "SendBoostInputValidator"),
|
|
311
336
|
SendBoostResponseValidator: /* @__PURE__ */ __name(() => SendBoostResponseValidator, "SendBoostResponseValidator"),
|
|
@@ -320,6 +345,32 @@ var require_types_cjs_development = __commonJS({
|
|
|
320
345
|
SentCredentialInfoValidator: /* @__PURE__ */ __name(() => SentCredentialInfoValidator, "SentCredentialInfoValidator"),
|
|
321
346
|
ServiceValidator: /* @__PURE__ */ __name(() => ServiceValidator, "ServiceValidator"),
|
|
322
347
|
SetPrimaryContactMethodValidator: /* @__PURE__ */ __name(() => SetPrimaryContactMethodValidator, "SetPrimaryContactMethodValidator"),
|
|
348
|
+
ShareCiphertextValidator: /* @__PURE__ */ __name(() => ShareCiphertextValidator, "ShareCiphertextValidator"),
|
|
349
|
+
ShareContentKeyValidator: /* @__PURE__ */ __name(() => ShareContentKeyValidator, "ShareContentKeyValidator"),
|
|
350
|
+
ShareContentStateValidator: /* @__PURE__ */ __name(() => ShareContentStateValidator, "ShareContentStateValidator"),
|
|
351
|
+
ShareEndorsementEntryValidator: /* @__PURE__ */ __name(() => ShareEndorsementEntryValidator, "ShareEndorsementEntryValidator"),
|
|
352
|
+
ShareEnvelopeValidator: /* @__PURE__ */ __name(() => ShareEnvelopeValidator, "ShareEnvelopeValidator"),
|
|
353
|
+
ShareIvValidator: /* @__PURE__ */ __name(() => ShareIvValidator, "ShareIvValidator"),
|
|
354
|
+
ShareLinkIdInputValidator: /* @__PURE__ */ __name(() => ShareLinkIdInputValidator, "ShareLinkIdInputValidator"),
|
|
355
|
+
ShareLinkIdValidator: /* @__PURE__ */ __name(() => ShareLinkIdValidator, "ShareLinkIdValidator"),
|
|
356
|
+
ShareLinkOperationKeyInputValidator: /* @__PURE__ */ __name(() => ShareLinkOperationKeyInputValidator, "ShareLinkOperationKeyInputValidator"),
|
|
357
|
+
ShareLinkOwnerCommitOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerCommitOutputValidator, "ShareLinkOwnerCommitOutputValidator"),
|
|
358
|
+
ShareLinkOwnerContentOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerContentOutputValidator, "ShareLinkOwnerContentOutputValidator"),
|
|
359
|
+
ShareLinkOwnerRecoveryOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerRecoveryOutputValidator, "ShareLinkOwnerRecoveryOutputValidator"),
|
|
360
|
+
ShareLinkOwnerStatusOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerStatusOutputValidator, "ShareLinkOwnerStatusOutputValidator"),
|
|
361
|
+
ShareLinkPublicContentViewValidator: /* @__PURE__ */ __name(() => ShareLinkPublicContentViewValidator, "ShareLinkPublicContentViewValidator"),
|
|
362
|
+
ShareLinkPublicStateValidator: /* @__PURE__ */ __name(() => ShareLinkPublicStateValidator, "ShareLinkPublicStateValidator"),
|
|
363
|
+
ShareLinkStatusValidator: /* @__PURE__ */ __name(() => ShareLinkStatusValidator, "ShareLinkStatusValidator"),
|
|
364
|
+
ShareLinkValidator: /* @__PURE__ */ __name(() => ShareLinkValidator, "ShareLinkValidator"),
|
|
365
|
+
ShareManifestPresentationValidator: /* @__PURE__ */ __name(() => ShareManifestPresentationValidator, "ShareManifestPresentationValidator"),
|
|
366
|
+
ShareOwnerRecoveryValidator: /* @__PURE__ */ __name(() => ShareOwnerRecoveryValidator, "ShareOwnerRecoveryValidator"),
|
|
367
|
+
SharePayloadValidator: /* @__PURE__ */ __name(() => SharePayloadValidator, "SharePayloadValidator"),
|
|
368
|
+
SharePayloadValidatorShape: /* @__PURE__ */ __name(() => SharePayloadValidatorShape, "SharePayloadValidatorShape"),
|
|
369
|
+
ShareRecoveryEndorsementValidator: /* @__PURE__ */ __name(() => ShareRecoveryEndorsementValidator, "ShareRecoveryEndorsementValidator"),
|
|
370
|
+
ShareRecoveryPlaintextValidator: /* @__PURE__ */ __name(() => ShareRecoveryPlaintextValidator, "ShareRecoveryPlaintextValidator"),
|
|
371
|
+
ShareRecoverySelectionValidator: /* @__PURE__ */ __name(() => ShareRecoverySelectionValidator, "ShareRecoverySelectionValidator"),
|
|
372
|
+
ShareSelectionEntryValidator: /* @__PURE__ */ __name(() => ShareSelectionEntryValidator, "ShareSelectionEntryValidator"),
|
|
373
|
+
ShareSharerValidator: /* @__PURE__ */ __name(() => ShareSharerValidator, "ShareSharerValidator"),
|
|
323
374
|
SkillDeletionStrategyValidator: /* @__PURE__ */ __name(() => SkillDeletionStrategyValidator, "SkillDeletionStrategyValidator"),
|
|
324
375
|
SkillFrameworkAdminInputValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminInputValidator, "SkillFrameworkAdminInputValidator"),
|
|
325
376
|
SkillFrameworkAdminsValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminsValidator, "SkillFrameworkAdminsValidator"),
|
|
@@ -351,6 +402,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
351
402
|
UnsignedVCValidator: /* @__PURE__ */ __name(() => UnsignedVCValidator, "UnsignedVCValidator"),
|
|
352
403
|
UnsignedVPValidator: /* @__PURE__ */ __name(() => UnsignedVPValidator, "UnsignedVPValidator"),
|
|
353
404
|
UpdateFrameworkInputValidator: /* @__PURE__ */ __name(() => UpdateFrameworkInputValidator, "UpdateFrameworkInputValidator"),
|
|
405
|
+
UpdateShareLinkInputValidator: /* @__PURE__ */ __name(() => UpdateShareLinkInputValidator, "UpdateShareLinkInputValidator"),
|
|
354
406
|
UpdateSkillInputValidator: /* @__PURE__ */ __name(() => UpdateSkillInputValidator, "UpdateSkillInputValidator"),
|
|
355
407
|
VC2EvidenceValidator: /* @__PURE__ */ __name(() => VC2EvidenceValidator, "VC2EvidenceValidator"),
|
|
356
408
|
VCValidator: /* @__PURE__ */ __name(() => VCValidator, "VCValidator"),
|
|
@@ -362,7 +414,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
362
414
|
VerificationStatusValidator: /* @__PURE__ */ __name(() => VerificationStatusValidator, "VerificationStatusValidator"),
|
|
363
415
|
appStoreActionValidator: /* @__PURE__ */ __name(() => appStoreActionValidator, "appStoreActionValidator"),
|
|
364
416
|
capgoUpdateActionValidator: /* @__PURE__ */ __name(() => capgoUpdateActionValidator, "capgoUpdateActionValidator"),
|
|
417
|
+
classifyShareManifest: /* @__PURE__ */ __name(() => classifyShareManifest, "classifyShareManifest"),
|
|
418
|
+
decodeBase64Url: /* @__PURE__ */ __name(() => decodeBase64Url, "decodeBase64Url"),
|
|
419
|
+
decodedBase64UrlByteLength: /* @__PURE__ */ __name(() => decodedBase64UrlByteLength, "decodedBase64UrlByteLength"),
|
|
365
420
|
dismissActionValidator: /* @__PURE__ */ __name(() => dismissActionValidator, "dismissActionValidator"),
|
|
421
|
+
encodeBase64Url: /* @__PURE__ */ __name(() => encodeBase64Url, "encodeBase64Url"),
|
|
422
|
+
escrowPinMismatchMessage: /* @__PURE__ */ __name(() => escrowPinMismatchMessage, "escrowPinMismatchMessage"),
|
|
366
423
|
externalLinkActionValidator: /* @__PURE__ */ __name(() => externalLinkActionValidator, "externalLinkActionValidator"),
|
|
367
424
|
inAppMessageActionStyleValidator: /* @__PURE__ */ __name(() => inAppMessageActionStyleValidator, "inAppMessageActionStyleValidator"),
|
|
368
425
|
inAppMessageActionTargetValidator: /* @__PURE__ */ __name(() => inAppMessageActionTargetValidator, "inAppMessageActionTargetValidator"),
|
|
@@ -377,10 +434,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
377
434
|
inAppMessageVersionSourceValidator: /* @__PURE__ */ __name(() => inAppMessageVersionSourceValidator, "inAppMessageVersionSourceValidator"),
|
|
378
435
|
inAppMessagesFlagValidator: /* @__PURE__ */ __name(() => inAppMessagesFlagValidator, "inAppMessagesFlagValidator"),
|
|
379
436
|
internalLinkActionValidator: /* @__PURE__ */ __name(() => internalLinkActionValidator, "internalLinkActionValidator"),
|
|
437
|
+
isCanonicalBase64Url: /* @__PURE__ */ __name(() => isCanonicalBase64Url, "isCanonicalBase64Url"),
|
|
380
438
|
isStoredCredentialEnvelope: /* @__PURE__ */ __name(() => isStoredCredentialEnvelope, "isStoredCredentialEnvelope"),
|
|
381
439
|
parseInAppMessagesFlag: /* @__PURE__ */ __name(() => parseInAppMessagesFlag, "parseInAppMessagesFlag"),
|
|
382
440
|
platformPredicateValidator: /* @__PURE__ */ __name(() => platformPredicateValidator, "platformPredicateValidator"),
|
|
383
441
|
rolePredicateValidator: /* @__PURE__ */ __name(() => rolePredicateValidator, "rolePredicateValidator"),
|
|
442
|
+
utf8ByteLength: /* @__PURE__ */ __name(() => utf8ByteLength, "utf8ByteLength"),
|
|
384
443
|
versionPredicateValidator: /* @__PURE__ */ __name(() => versionPredicateValidator, "versionPredicateValidator")
|
|
385
444
|
});
|
|
386
445
|
module2.exports = __toCommonJS2(index_exports2);
|
|
@@ -18017,6 +18076,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
18017
18076
|
__name2(this, "AuthSessionError");
|
|
18018
18077
|
}
|
|
18019
18078
|
};
|
|
18079
|
+
var ESCROW_PIN_LOCKED_MESSAGE2 = "Too many incorrect PIN attempts. You can still recover by waiting.";
|
|
18080
|
+
var ESCROW_PIN_UNAVAILABLE_MESSAGE2 = "PIN recovery is not available for this account.";
|
|
18081
|
+
var ESCROW_PIN_MISMATCH_PATTERN2 = /^Incorrect PIN\. (\d+) attempts left\.$/;
|
|
18082
|
+
var escrowPinMismatchMessage = /* @__PURE__ */ __name2((attemptsRemaining) => `Incorrect PIN. ${attemptsRemaining} attempts left.`, "escrowPinMismatchMessage");
|
|
18083
|
+
var EscrowPinStatusValidator = external_exports.object({
|
|
18084
|
+
state: external_exports.enum(["none", "enabled", "locked", "stale"]),
|
|
18085
|
+
enabled: external_exports.boolean(),
|
|
18086
|
+
attemptsRemaining: external_exports.number().int().nonnegative(),
|
|
18087
|
+
salt: external_exports.string().optional()
|
|
18088
|
+
});
|
|
18020
18089
|
var BITSTRING_STATUS_PURPOSES = ["revocation", "suspension"];
|
|
18021
18090
|
var DEFAULT_BITSTRING_STATUS_LIST_SIZE = 131072;
|
|
18022
18091
|
var BitstringStatusPurposeValidator = external_exports.enum(BITSTRING_STATUS_PURPOSES);
|
|
@@ -18131,6 +18200,458 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
18131
18200
|
const result = inAppMessagesFlagValidator.safeParse(raw);
|
|
18132
18201
|
return result.success ? result.data : EMPTY_IN_APP_MESSAGES_FLAG;
|
|
18133
18202
|
}, "parseInAppMessagesFlag");
|
|
18203
|
+
var SHARE_LINK_ID_BYTES = 16;
|
|
18204
|
+
var SHARE_CONTENT_KEY_BYTES = 32;
|
|
18205
|
+
var SHARE_IV_BYTES = 12;
|
|
18206
|
+
var SHARE_TAG_BYTES = 16;
|
|
18207
|
+
var MAX_SHARE_CIPHERTEXT_BYTES = 512 * 1024;
|
|
18208
|
+
var MAX_SHARE_RECOVERY_JWE_BYTES = 64 * 1024;
|
|
18209
|
+
var MAX_SELECTED_CREDENTIALS = 50;
|
|
18210
|
+
var MAX_ENDORSEMENTS = 200;
|
|
18211
|
+
var MAX_VP_MEMBERS = MAX_SELECTED_CREDENTIALS + MAX_ENDORSEMENTS;
|
|
18212
|
+
var MAX_SHARE_SOURCE_URI_CHARS = 2048;
|
|
18213
|
+
var MAX_SHARE_LINK_REQUEST_BYTES = 1024 * 1024;
|
|
18214
|
+
var SHARE_LINK_PROTOCOL = "lc-share/v1";
|
|
18215
|
+
var SHARE_RECOVERY_PROTOCOL = "lc-share-recovery/v1";
|
|
18216
|
+
var SHARE_AAD_PREFIX = "lc-share:v1";
|
|
18217
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
18218
|
+
var BASE64URL_LOOKUP = (() => {
|
|
18219
|
+
const table = new Int16Array(128).fill(-1);
|
|
18220
|
+
for (let index = 0; index < BASE64URL_ALPHABET.length; index += 1) {
|
|
18221
|
+
table[BASE64URL_ALPHABET.charCodeAt(index)] = index;
|
|
18222
|
+
}
|
|
18223
|
+
return table;
|
|
18224
|
+
})();
|
|
18225
|
+
var encodeBase64Url = /* @__PURE__ */ __name2((bytes) => {
|
|
18226
|
+
let output = "";
|
|
18227
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
18228
|
+
const first = bytes[index];
|
|
18229
|
+
const second = bytes[index + 1];
|
|
18230
|
+
const third = bytes[index + 2];
|
|
18231
|
+
output += BASE64URL_ALPHABET[first >> 2];
|
|
18232
|
+
output += BASE64URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
|
|
18233
|
+
if (second === void 0) break;
|
|
18234
|
+
output += BASE64URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
|
|
18235
|
+
if (third === void 0) break;
|
|
18236
|
+
output += BASE64URL_ALPHABET[third & 63];
|
|
18237
|
+
}
|
|
18238
|
+
return output;
|
|
18239
|
+
}, "encodeBase64Url");
|
|
18240
|
+
var decodeBase64Url = /* @__PURE__ */ __name2((value) => {
|
|
18241
|
+
if (typeof value !== "string") return null;
|
|
18242
|
+
const output = new Uint8Array(Math.floor(value.length * 6 / 8));
|
|
18243
|
+
let bits = 0;
|
|
18244
|
+
let bitCount = 0;
|
|
18245
|
+
let outputIndex = 0;
|
|
18246
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
18247
|
+
const code = value.charCodeAt(index);
|
|
18248
|
+
const digit = code < 128 ? BASE64URL_LOOKUP[code] : -1;
|
|
18249
|
+
if (digit < 0) return null;
|
|
18250
|
+
bits = bits << 6 | digit;
|
|
18251
|
+
bitCount += 6;
|
|
18252
|
+
if (bitCount >= 8) {
|
|
18253
|
+
bitCount -= 8;
|
|
18254
|
+
output[outputIndex] = bits >> bitCount & 255;
|
|
18255
|
+
outputIndex += 1;
|
|
18256
|
+
}
|
|
18257
|
+
}
|
|
18258
|
+
return output;
|
|
18259
|
+
}, "decodeBase64Url");
|
|
18260
|
+
var isCanonicalBase64Url = /* @__PURE__ */ __name2((value, expectedBytes) => {
|
|
18261
|
+
if (typeof value !== "string") return false;
|
|
18262
|
+
if (expectedBytes !== void 0 && value.length !== Math.ceil(expectedBytes * 4 / 3))
|
|
18263
|
+
return false;
|
|
18264
|
+
const decoded = decodeBase64Url(value);
|
|
18265
|
+
if (decoded === null) return false;
|
|
18266
|
+
if (expectedBytes !== void 0 && decoded.length !== expectedBytes) return false;
|
|
18267
|
+
return encodeBase64Url(decoded) === value;
|
|
18268
|
+
}, "isCanonicalBase64Url");
|
|
18269
|
+
var decodedBase64UrlByteLength = /* @__PURE__ */ __name2((value) => decodeBase64Url(value)?.length ?? null, "decodedBase64UrlByteLength");
|
|
18270
|
+
var utf8ByteLength = /* @__PURE__ */ __name2((value) => new TextEncoder().encode(value).length, "utf8ByteLength");
|
|
18271
|
+
var canonicalBase64Url = /* @__PURE__ */ __name2((bytes) => external_exports.string().refine((value) => isCanonicalBase64Url(value, bytes), {
|
|
18272
|
+
message: `must be canonical base64url for exactly ${bytes} bytes`
|
|
18273
|
+
}), "canonicalBase64Url");
|
|
18274
|
+
var ShareLinkIdValidator = canonicalBase64Url(SHARE_LINK_ID_BYTES);
|
|
18275
|
+
var ShareContentKeyValidator = canonicalBase64Url(SHARE_CONTENT_KEY_BYTES);
|
|
18276
|
+
var ShareIvValidator = canonicalBase64Url(SHARE_IV_BYTES);
|
|
18277
|
+
var ShareCiphertextValidator = external_exports.string().refine(
|
|
18278
|
+
(value) => {
|
|
18279
|
+
if (value.length > Math.ceil(MAX_SHARE_CIPHERTEXT_BYTES * 4 / 3)) return false;
|
|
18280
|
+
const decoded = decodeBase64Url(value);
|
|
18281
|
+
if (decoded === null) return false;
|
|
18282
|
+
if (encodeBase64Url(decoded) !== value) return false;
|
|
18283
|
+
return decoded.length >= SHARE_TAG_BYTES && decoded.length <= MAX_SHARE_CIPHERTEXT_BYTES;
|
|
18284
|
+
},
|
|
18285
|
+
{
|
|
18286
|
+
message: `must be canonical base64url decoding to ${SHARE_TAG_BYTES}..${MAX_SHARE_CIPHERTEXT_BYTES} bytes`
|
|
18287
|
+
}
|
|
18288
|
+
);
|
|
18289
|
+
var ShareEnvelopeValidator = external_exports.object({
|
|
18290
|
+
v: external_exports.literal(1),
|
|
18291
|
+
alg: external_exports.literal("A256GCM"),
|
|
18292
|
+
iv: ShareIvValidator,
|
|
18293
|
+
ct: ShareCiphertextValidator
|
|
18294
|
+
}).strict();
|
|
18295
|
+
var safeVersion = external_exports.number().int().min(1).max(2 ** 31 - 1);
|
|
18296
|
+
var safeCredentialIndex = external_exports.number().int().min(0).max(MAX_VP_MEMBERS - 1);
|
|
18297
|
+
var ShareSelectionEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex }).strict();
|
|
18298
|
+
var ShareEndorsementEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex, targetCredentialIndex: safeCredentialIndex }).strict();
|
|
18299
|
+
var ShareSharerValidator = external_exports.object({
|
|
18300
|
+
profileId: external_exports.string().min(1).max(128),
|
|
18301
|
+
displayName: external_exports.string().min(1).max(120),
|
|
18302
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18303
|
+
}).strict();
|
|
18304
|
+
var presentationShape = VPValidator.extend({
|
|
18305
|
+
verifiableCredential: VCValidator.array().min(1).max(MAX_VP_MEMBERS)
|
|
18306
|
+
});
|
|
18307
|
+
var hasBoundedPresentation = /* @__PURE__ */ __name2((value) => {
|
|
18308
|
+
let credentialCount = 0;
|
|
18309
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
18310
|
+
const visit = /* @__PURE__ */ __name2((node, depth) => {
|
|
18311
|
+
if (node === null || typeof node !== "object") return true;
|
|
18312
|
+
if (depth > 32 || ancestors.has(node)) return false;
|
|
18313
|
+
ancestors.add(node);
|
|
18314
|
+
try {
|
|
18315
|
+
for (const [key, child] of Object.entries(node)) {
|
|
18316
|
+
if (key === "verifiableCredential") {
|
|
18317
|
+
credentialCount += Array.isArray(child) ? child.length : 1;
|
|
18318
|
+
if (credentialCount > MAX_VP_MEMBERS) return false;
|
|
18319
|
+
}
|
|
18320
|
+
if (!visit(child, depth + 1)) return false;
|
|
18321
|
+
}
|
|
18322
|
+
return true;
|
|
18323
|
+
} finally {
|
|
18324
|
+
ancestors.delete(node);
|
|
18325
|
+
}
|
|
18326
|
+
}, "visit");
|
|
18327
|
+
return visit(value, 0);
|
|
18328
|
+
}, "hasBoundedPresentation");
|
|
18329
|
+
var ShareManifestPresentationValidator = external_exports.custom(
|
|
18330
|
+
(value) => hasBoundedPresentation(value) && presentationShape.safeParse(value).success,
|
|
18331
|
+
{ message: "invalid share presentation" }
|
|
18332
|
+
);
|
|
18333
|
+
var SharePayloadValidatorShape = external_exports.object({
|
|
18334
|
+
protocol: external_exports.literal(SHARE_LINK_PROTOCOL),
|
|
18335
|
+
shareId: ShareLinkIdValidator,
|
|
18336
|
+
contentVersion: safeVersion,
|
|
18337
|
+
createdAt: external_exports.iso.datetime(),
|
|
18338
|
+
sharer: ShareSharerValidator,
|
|
18339
|
+
presentation: ShareManifestPresentationValidator,
|
|
18340
|
+
selection: ShareSelectionEntryValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18341
|
+
endorsements: ShareEndorsementEntryValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18342
|
+
}).strict();
|
|
18343
|
+
var classificationFailure = /* @__PURE__ */ __name2((code, message, path = []) => ({ ok: false, code, message, path }), "classificationFailure");
|
|
18344
|
+
var readSignedClaimId = /* @__PURE__ */ __name2((member) => {
|
|
18345
|
+
if (member === null || typeof member !== "object") return void 0;
|
|
18346
|
+
const record2 = member;
|
|
18347
|
+
const claim = record2.credentialSubject ?? record2.endorsement;
|
|
18348
|
+
if (claim === null || typeof claim !== "object") return void 0;
|
|
18349
|
+
const claimRecord = claim;
|
|
18350
|
+
const nested = claimRecord.endorsement;
|
|
18351
|
+
const nestedId = nested !== null && typeof nested === "object" ? nested.id : void 0;
|
|
18352
|
+
return claimRecord.id ?? nestedId;
|
|
18353
|
+
}, "readSignedClaimId");
|
|
18354
|
+
var classifyShareManifest = /* @__PURE__ */ __name2((payload) => {
|
|
18355
|
+
const members = payload.presentation?.verifiableCredential;
|
|
18356
|
+
if (!Array.isArray(members) || members.length === 0) {
|
|
18357
|
+
return classificationFailure("NO_PRESENTATION_MEMBERS", "presentation has no credentials");
|
|
18358
|
+
}
|
|
18359
|
+
if (members.length > MAX_VP_MEMBERS) {
|
|
18360
|
+
return classificationFailure(
|
|
18361
|
+
"TOO_MANY_VP_MEMBERS",
|
|
18362
|
+
`presentation has ${members.length} members, over the ${MAX_VP_MEMBERS} bound`
|
|
18363
|
+
);
|
|
18364
|
+
}
|
|
18365
|
+
const selection = payload.selection;
|
|
18366
|
+
const endorsements = payload.endorsements ?? [];
|
|
18367
|
+
if (!Array.isArray(selection) || selection.length < 1 || selection.length > MAX_SELECTED_CREDENTIALS) {
|
|
18368
|
+
return classificationFailure("BAD_SELECTION", "selection must contain 1..50 entries");
|
|
18369
|
+
}
|
|
18370
|
+
if (!Array.isArray(endorsements) || endorsements.length > MAX_ENDORSEMENTS) {
|
|
18371
|
+
return classificationFailure(
|
|
18372
|
+
"BAD_ENDORSEMENTS",
|
|
18373
|
+
"endorsements must contain at most 200 entries"
|
|
18374
|
+
);
|
|
18375
|
+
}
|
|
18376
|
+
const selectedIndices = [];
|
|
18377
|
+
const selected = /* @__PURE__ */ new Set();
|
|
18378
|
+
for (let index = 0; index < selection.length; index += 1) {
|
|
18379
|
+
const entry = selection[index];
|
|
18380
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18381
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18382
|
+
return classificationFailure(
|
|
18383
|
+
"SELECTION_INDEX_OUT_OF_BOUNDS",
|
|
18384
|
+
`selection[${index}].credentialIndex is out of bounds`,
|
|
18385
|
+
["selection", index, "credentialIndex"]
|
|
18386
|
+
);
|
|
18387
|
+
}
|
|
18388
|
+
if (selected.has(credentialIndex)) {
|
|
18389
|
+
return classificationFailure(
|
|
18390
|
+
"SELECTION_INDEX_DUPLICATE",
|
|
18391
|
+
`selection[${index}].credentialIndex is duplicated`,
|
|
18392
|
+
["selection", index, "credentialIndex"]
|
|
18393
|
+
);
|
|
18394
|
+
}
|
|
18395
|
+
selected.add(credentialIndex);
|
|
18396
|
+
selectedIndices.push(credentialIndex);
|
|
18397
|
+
}
|
|
18398
|
+
const classified = new Set(selected);
|
|
18399
|
+
const endorsementIndices = [];
|
|
18400
|
+
for (let index = 0; index < endorsements.length; index += 1) {
|
|
18401
|
+
const entry = endorsements[index];
|
|
18402
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18403
|
+
const targetIndex = entry?.targetCredentialIndex;
|
|
18404
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18405
|
+
return classificationFailure(
|
|
18406
|
+
"ENDORSEMENT_INDEX_OUT_OF_BOUNDS",
|
|
18407
|
+
`endorsements[${index}].credentialIndex is out of bounds`,
|
|
18408
|
+
["endorsements", index, "credentialIndex"]
|
|
18409
|
+
);
|
|
18410
|
+
}
|
|
18411
|
+
if (classified.has(credentialIndex)) {
|
|
18412
|
+
return classificationFailure(
|
|
18413
|
+
"VP_MEMBER_CLASSIFIED_TWICE",
|
|
18414
|
+
`endorsements[${index}].credentialIndex was already classified`,
|
|
18415
|
+
["endorsements", index, "credentialIndex"]
|
|
18416
|
+
);
|
|
18417
|
+
}
|
|
18418
|
+
if (!selected.has(targetIndex)) {
|
|
18419
|
+
return classificationFailure(
|
|
18420
|
+
"ENDORSEMENT_TARGET_NOT_SELECTED",
|
|
18421
|
+
`endorsements[${index}].targetCredentialIndex must reference a selected credential`,
|
|
18422
|
+
["endorsements", index, "targetCredentialIndex"]
|
|
18423
|
+
);
|
|
18424
|
+
}
|
|
18425
|
+
const referenced = readSignedClaimId(members[credentialIndex]);
|
|
18426
|
+
const target = members[targetIndex];
|
|
18427
|
+
const targetId = target !== null && typeof target === "object" ? target.id : void 0;
|
|
18428
|
+
if (typeof referenced !== "string" || typeof targetId !== "string" || referenced !== targetId) {
|
|
18429
|
+
return classificationFailure(
|
|
18430
|
+
"ENDORSEMENT_TARGET_UNBOUND",
|
|
18431
|
+
`endorsements[${index}] claim does not reference the target credential id`,
|
|
18432
|
+
["endorsements", index]
|
|
18433
|
+
);
|
|
18434
|
+
}
|
|
18435
|
+
classified.add(credentialIndex);
|
|
18436
|
+
endorsementIndices.push(credentialIndex);
|
|
18437
|
+
}
|
|
18438
|
+
if (classified.size !== members.length) {
|
|
18439
|
+
return classificationFailure(
|
|
18440
|
+
"UNCLASSIFIED_VP_MEMBER",
|
|
18441
|
+
"every presentation member must be classified exactly once",
|
|
18442
|
+
["presentation", "verifiableCredential"]
|
|
18443
|
+
);
|
|
18444
|
+
}
|
|
18445
|
+
return {
|
|
18446
|
+
ok: true,
|
|
18447
|
+
classification: { selectedIndices, endorsementIndices, memberCount: members.length }
|
|
18448
|
+
};
|
|
18449
|
+
}, "classifyShareManifest");
|
|
18450
|
+
var SharePayloadValidator = SharePayloadValidatorShape.superRefine((payload, ctx) => {
|
|
18451
|
+
const result = classifyShareManifest(payload);
|
|
18452
|
+
if (!result.ok) {
|
|
18453
|
+
ctx.addIssue({
|
|
18454
|
+
code: "custom",
|
|
18455
|
+
message: result.message,
|
|
18456
|
+
path: result.path,
|
|
18457
|
+
params: { shareLinkCode: result.code }
|
|
18458
|
+
});
|
|
18459
|
+
}
|
|
18460
|
+
});
|
|
18461
|
+
var ShareRecoverySelectionValidator = external_exports.object({
|
|
18462
|
+
ref: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS),
|
|
18463
|
+
order: external_exports.number().int().min(0).max(MAX_SELECTED_CREDENTIALS - 1)
|
|
18464
|
+
}).strict();
|
|
18465
|
+
var ShareRecoveryEndorsementValidator = external_exports.object({ targetRef: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS) }).strict();
|
|
18466
|
+
var ShareRecoveryPlaintextValidator = external_exports.object({
|
|
18467
|
+
protocol: external_exports.literal(SHARE_RECOVERY_PROTOCOL),
|
|
18468
|
+
shareId: ShareLinkIdValidator,
|
|
18469
|
+
ownerProfileId: external_exports.string().min(1).max(128),
|
|
18470
|
+
createdAt: external_exports.iso.datetime(),
|
|
18471
|
+
latest: external_exports.object({ contentVersion: safeVersion, key: ShareContentKeyValidator }).strict(),
|
|
18472
|
+
selection: ShareRecoverySelectionValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18473
|
+
endorsements: ShareRecoveryEndorsementValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18474
|
+
}).strict().superRefine((recovery, ctx) => {
|
|
18475
|
+
const orders = /* @__PURE__ */ new Set();
|
|
18476
|
+
recovery.selection.forEach((entry, index) => {
|
|
18477
|
+
if (entry.order >= recovery.selection.length) {
|
|
18478
|
+
ctx.addIssue({
|
|
18479
|
+
code: "custom",
|
|
18480
|
+
message: "order must be within the selection bounds",
|
|
18481
|
+
path: ["selection", index, "order"]
|
|
18482
|
+
});
|
|
18483
|
+
}
|
|
18484
|
+
if (orders.has(entry.order)) {
|
|
18485
|
+
ctx.addIssue({
|
|
18486
|
+
code: "custom",
|
|
18487
|
+
message: "order must be unique within the selection",
|
|
18488
|
+
path: ["selection", index, "order"]
|
|
18489
|
+
});
|
|
18490
|
+
}
|
|
18491
|
+
orders.add(entry.order);
|
|
18492
|
+
});
|
|
18493
|
+
});
|
|
18494
|
+
var ShareOwnerRecoveryValidator = JWEValidator.refine(
|
|
18495
|
+
(value) => utf8ByteLength(JSON.stringify(value)) <= MAX_SHARE_RECOVERY_JWE_BYTES,
|
|
18496
|
+
{ message: `serialized recovery JWE must be at most ${MAX_SHARE_RECOVERY_JWE_BYTES} bytes` }
|
|
18497
|
+
);
|
|
18498
|
+
var ShareLinkStatusValidator = external_exports.enum(["pending", "active", "stopped"]);
|
|
18499
|
+
var ShareContentStateValidator = external_exports.enum(["staging", "finalized", "content_missing"]);
|
|
18500
|
+
var ShareLinkValidator = external_exports.object({
|
|
18501
|
+
id: ShareLinkIdValidator,
|
|
18502
|
+
title: external_exports.string().min(1).max(120),
|
|
18503
|
+
note: external_exports.string().max(500).optional(),
|
|
18504
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18505
|
+
version: safeVersion,
|
|
18506
|
+
contentVersion: safeVersion,
|
|
18507
|
+
status: ShareLinkStatusValidator,
|
|
18508
|
+
contentState: ShareContentStateValidator,
|
|
18509
|
+
createdAt: external_exports.iso.datetime(),
|
|
18510
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18511
|
+
expiresAt: external_exports.iso.datetime().nullable(),
|
|
18512
|
+
stoppedAt: external_exports.iso.datetime().nullable(),
|
|
18513
|
+
lastViewedAt: external_exports.iso.datetime().nullable(),
|
|
18514
|
+
viewCount: external_exports.number().int().min(0).optional(),
|
|
18515
|
+
passcodeProtected: external_exports.boolean(),
|
|
18516
|
+
notifyOnView: external_exports.boolean(),
|
|
18517
|
+
minorPolicy: external_exports.object({
|
|
18518
|
+
isMinor: external_exports.boolean().nullable(),
|
|
18519
|
+
policyResolved: external_exports.boolean(),
|
|
18520
|
+
defaultExpiryDays: external_exports.union([external_exports.literal(30), external_exports.literal(365)]),
|
|
18521
|
+
viewCountingEnabled: external_exports.boolean()
|
|
18522
|
+
}),
|
|
18523
|
+
/**
|
|
18524
|
+
* Guarded application/API URL for the content. Optional because owner APIs in
|
|
18525
|
+
* D1 have no public resolve/content endpoint yet; it must never be a
|
|
18526
|
+
* LearnCloud object URL or carry a fabricated decryption key.
|
|
18527
|
+
*/
|
|
18528
|
+
contentUrl: external_exports.string().optional()
|
|
18529
|
+
});
|
|
18530
|
+
var clientRequestId = external_exports.string().uuid();
|
|
18531
|
+
var CreateShareLinkInputValidator = external_exports.object({
|
|
18532
|
+
id: ShareLinkIdValidator,
|
|
18533
|
+
clientRequestId,
|
|
18534
|
+
title: external_exports.string().min(1).max(120),
|
|
18535
|
+
note: external_exports.string().max(500).optional(),
|
|
18536
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18537
|
+
passcode: external_exports.string().min(8).max(64).optional(),
|
|
18538
|
+
notifyOnView: external_exports.boolean().default(false),
|
|
18539
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18540
|
+
contentVersion: external_exports.literal(1),
|
|
18541
|
+
envelope: ShareEnvelopeValidator,
|
|
18542
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator
|
|
18543
|
+
}).strict();
|
|
18544
|
+
var UpdateShareLinkInputValidator = external_exports.object({
|
|
18545
|
+
id: ShareLinkIdValidator,
|
|
18546
|
+
expectedVersion: safeVersion,
|
|
18547
|
+
clientRequestId,
|
|
18548
|
+
title: external_exports.string().min(1).max(120).optional(),
|
|
18549
|
+
note: external_exports.string().max(500).nullable().optional(),
|
|
18550
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18551
|
+
/** Omitted preserves the current passcode, null removes it, and a string replaces it. */
|
|
18552
|
+
passcode: external_exports.string().min(8).max(64).nullable().optional(),
|
|
18553
|
+
notifyOnView: external_exports.boolean().optional(),
|
|
18554
|
+
contentVersion: safeVersion.optional(),
|
|
18555
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS).optional(),
|
|
18556
|
+
envelope: ShareEnvelopeValidator.optional(),
|
|
18557
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator.optional()
|
|
18558
|
+
}).strict().superRefine((value, ctx) => {
|
|
18559
|
+
const provided = [
|
|
18560
|
+
value.contentVersion,
|
|
18561
|
+
value.selectedCount,
|
|
18562
|
+
value.envelope,
|
|
18563
|
+
value.ownerEncryptedRecovery
|
|
18564
|
+
].filter((field) => field !== void 0).length;
|
|
18565
|
+
if (provided !== 0 && provided !== 4) {
|
|
18566
|
+
ctx.addIssue({
|
|
18567
|
+
code: "custom",
|
|
18568
|
+
message: "content replacement requires contentVersion, selectedCount, envelope and ownerEncryptedRecovery together"
|
|
18569
|
+
});
|
|
18570
|
+
}
|
|
18571
|
+
});
|
|
18572
|
+
var ShareLinkIdInputValidator = external_exports.object({ id: ShareLinkIdValidator }).strict();
|
|
18573
|
+
var ShareLinkOperationKeyInputValidator = external_exports.object({ id: ShareLinkIdValidator, operationId: external_exports.string().uuid() }).strict();
|
|
18574
|
+
var ShareLinkOwnerCommitOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18575
|
+
external_exports.object({ status: external_exports.literal("completed"), share: ShareLinkValidator }).strict(),
|
|
18576
|
+
external_exports.object({
|
|
18577
|
+
status: external_exports.literal("pending"),
|
|
18578
|
+
id: ShareLinkIdValidator,
|
|
18579
|
+
operationId: external_exports.string().uuid()
|
|
18580
|
+
}).strict()
|
|
18581
|
+
]);
|
|
18582
|
+
var ShareLinkOwnerStatusOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18583
|
+
external_exports.object({ status: external_exports.literal("found"), share: ShareLinkValidator }).strict(),
|
|
18584
|
+
external_exports.object({
|
|
18585
|
+
status: external_exports.literal("pending"),
|
|
18586
|
+
id: ShareLinkIdValidator,
|
|
18587
|
+
operationId: external_exports.string().uuid()
|
|
18588
|
+
}).strict(),
|
|
18589
|
+
external_exports.object({ status: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
18590
|
+
]);
|
|
18591
|
+
var ShareLinkOwnerRecoveryOutputValidator = external_exports.object({ recovery: ShareOwnerRecoveryValidator }).strict();
|
|
18592
|
+
var ShareLinkOwnerContentOutputValidator = external_exports.object({
|
|
18593
|
+
id: ShareLinkIdValidator,
|
|
18594
|
+
contentVersion: safeVersion,
|
|
18595
|
+
envelope: ShareEnvelopeValidator
|
|
18596
|
+
}).strict();
|
|
18597
|
+
var ResolveShareLinkInputValidator = external_exports.object({
|
|
18598
|
+
id: ShareLinkIdValidator,
|
|
18599
|
+
/** Sent only in a POST body; it must never be placed in a share URL. */
|
|
18600
|
+
passcode: external_exports.string().min(4).max(64).optional()
|
|
18601
|
+
}).strict();
|
|
18602
|
+
var ShareLinkPublicStateValidator = external_exports.discriminatedUnion("state", [
|
|
18603
|
+
external_exports.object({
|
|
18604
|
+
state: external_exports.literal("passcode_required"),
|
|
18605
|
+
id: ShareLinkIdValidator
|
|
18606
|
+
}).strict(),
|
|
18607
|
+
external_exports.object({ state: external_exports.literal("try_later"), id: ShareLinkIdValidator }).strict(),
|
|
18608
|
+
external_exports.object({
|
|
18609
|
+
state: external_exports.literal("active"),
|
|
18610
|
+
id: ShareLinkIdValidator,
|
|
18611
|
+
title: external_exports.string().max(120),
|
|
18612
|
+
note: external_exports.string().max(500).optional(),
|
|
18613
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18614
|
+
contentVersion: safeVersion,
|
|
18615
|
+
contentUrl: external_exports.string(),
|
|
18616
|
+
sharer: external_exports.object({
|
|
18617
|
+
displayName: external_exports.string().max(120),
|
|
18618
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18619
|
+
}),
|
|
18620
|
+
createdAt: external_exports.iso.datetime(),
|
|
18621
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18622
|
+
expiresAt: external_exports.iso.datetime().nullable()
|
|
18623
|
+
}).strict(),
|
|
18624
|
+
external_exports.object({
|
|
18625
|
+
state: external_exports.literal("expired"),
|
|
18626
|
+
id: ShareLinkIdValidator,
|
|
18627
|
+
expiresAt: external_exports.iso.datetime()
|
|
18628
|
+
}).strict(),
|
|
18629
|
+
external_exports.object({
|
|
18630
|
+
state: external_exports.literal("stopped"),
|
|
18631
|
+
id: ShareLinkIdValidator,
|
|
18632
|
+
stoppedAt: external_exports.iso.datetime()
|
|
18633
|
+
}).strict(),
|
|
18634
|
+
external_exports.object({ state: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
18635
|
+
]);
|
|
18636
|
+
var ListShareLinksInputValidator = external_exports.object({
|
|
18637
|
+
limit: external_exports.number().int().min(1).max(50).default(25),
|
|
18638
|
+
cursor: external_exports.string().max(512).optional()
|
|
18639
|
+
}).strict();
|
|
18640
|
+
var PaginatedShareLinksValidator = PaginationResponseValidator.extend({
|
|
18641
|
+
records: ShareLinkValidator.array()
|
|
18642
|
+
});
|
|
18643
|
+
var AcknowledgeViewInputValidator = external_exports.object({
|
|
18644
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18645
|
+
}).strict();
|
|
18646
|
+
var AcknowledgeViewOutputValidator = external_exports.object({ ok: external_exports.literal(true) }).strict();
|
|
18647
|
+
var SHARE_VIEW_RECEIPT_TTL_SECONDS = 10 * 60;
|
|
18648
|
+
var ShareLinkPublicContentViewValidator = external_exports.object({
|
|
18649
|
+
id: ShareLinkIdValidator,
|
|
18650
|
+
contentVersion: safeVersion,
|
|
18651
|
+
envelope: ShareEnvelopeValidator,
|
|
18652
|
+
contentUrl: external_exports.string(),
|
|
18653
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18654
|
+
}).strict();
|
|
18134
18655
|
}
|
|
18135
18656
|
});
|
|
18136
18657
|
|
|
@@ -18152,35 +18673,63 @@ __export(index_exports, {
|
|
|
18152
18673
|
AtomicUpdateError: () => AtomicUpdateError,
|
|
18153
18674
|
AuthSessionError: () => import_types.AuthSessionError,
|
|
18154
18675
|
DEFAULT_KDF_PARAMS: () => DEFAULT_KDF_PARAMS,
|
|
18676
|
+
EMAIL_RELAY_ALGORITHM: () => EMAIL_RELAY_ALGORITHM,
|
|
18677
|
+
EMAIL_RELAY_ENVELOPE_VERSION: () => EMAIL_RELAY_ENVELOPE_VERSION,
|
|
18678
|
+
ESCROW_ALGORITHM: () => ESCROW_ALGORITHM,
|
|
18679
|
+
ESCROW_BLOB_INFO: () => ESCROW_BLOB_INFO,
|
|
18680
|
+
ESCROW_CLIENT_KEY_ID: () => ESCROW_CLIENT_KEY_ID,
|
|
18681
|
+
ESCROW_ENVELOPE_VERSION: () => ESCROW_ENVELOPE_VERSION,
|
|
18682
|
+
ESCROW_PIN_MAX_ATTEMPTS: () => ESCROW_PIN_MAX_ATTEMPTS,
|
|
18683
|
+
ESCROW_RELEASE_INFO: () => ESCROW_RELEASE_INFO,
|
|
18684
|
+
EscrowHoldRestartThrottledError: () => EscrowHoldRestartThrottledError,
|
|
18685
|
+
EscrowPinLockedError: () => EscrowPinLockedError,
|
|
18686
|
+
EscrowPinMismatchError: () => EscrowPinMismatchError,
|
|
18687
|
+
EscrowPinThrottledError: () => EscrowPinThrottledError,
|
|
18688
|
+
EscrowPinUnavailableError: () => EscrowPinUnavailableError,
|
|
18689
|
+
EscrowRequestError: () => EscrowRequestError,
|
|
18690
|
+
IdentityRecoverySessionConsumedError: () => IdentityRecoverySessionConsumedError,
|
|
18691
|
+
PIN_MAX_LENGTH: () => PIN_MAX_LENGTH,
|
|
18692
|
+
PIN_MIN_LENGTH: () => PIN_MIN_LENGTH,
|
|
18155
18693
|
SSSApiClient: () => SSSApiClient,
|
|
18156
18694
|
SSSKeyManager: () => SSSKeyManager,
|
|
18157
18695
|
SSS_THRESHOLD: () => SSS_THRESHOLD,
|
|
18158
18696
|
SSS_TOTAL_SHARES: () => SSS_TOTAL_SHARES,
|
|
18159
18697
|
ShareVerificationError: () => ShareVerificationError,
|
|
18698
|
+
ShareWriteRejectedError: () => ShareWriteRejectedError,
|
|
18160
18699
|
approveQrLoginSession: () => approveQrLoginSession,
|
|
18161
18700
|
atomicRecovery: () => atomicRecovery,
|
|
18162
18701
|
atomicShareUpdate: () => atomicShareUpdate,
|
|
18163
18702
|
base64ToBuffer: () => base64ToBuffer,
|
|
18164
18703
|
bufferToBase64: () => bufferToBase64,
|
|
18704
|
+
buildRecoveryPhraseChallengeOptions: () => buildRecoveryPhraseChallengeOptions,
|
|
18165
18705
|
bytesToHex: () => bytesToHex,
|
|
18166
18706
|
clearAllShares: () => clearAllShares,
|
|
18707
|
+
constantTimeEqualHex: () => constantTimeEqualHex,
|
|
18167
18708
|
countWords: () => countWords,
|
|
18168
18709
|
createAdaptiveStorage: () => createAdaptiveStorage,
|
|
18169
18710
|
createPasskeyCredential: () => createPasskeyCredential,
|
|
18170
18711
|
createQrLoginSession: () => createQrLoginSession,
|
|
18171
18712
|
createSSSKeyManager: () => createSSSKeyManager,
|
|
18172
18713
|
createSSSStrategy: () => createSSSStrategy,
|
|
18714
|
+
decryptEmailRelayPayload: () => decryptEmailRelayPayload,
|
|
18715
|
+
decryptEscrowBlob: () => decryptEscrowBlob,
|
|
18173
18716
|
decryptShareFromTransfer: () => decryptShareFromTransfer,
|
|
18174
18717
|
decryptShareWithPasskey: () => decryptShareWithPasskey,
|
|
18175
18718
|
decryptWithPassword: () => decryptWithPassword,
|
|
18176
18719
|
deleteDeviceShare: () => deleteDeviceShare,
|
|
18177
18720
|
deriveKeyFromPasskey: () => deriveKeyFromPasskey,
|
|
18178
18721
|
deriveKeyFromPassword: () => deriveKeyFromPassword,
|
|
18722
|
+
derivePinProof: () => derivePinProof,
|
|
18723
|
+
encryptEmailRelayPayload: () => encryptEmailRelayPayload,
|
|
18724
|
+
encryptEscrowBlob: () => encryptEscrowBlob,
|
|
18179
18725
|
encryptShareForTransfer: () => encryptShareForTransfer,
|
|
18180
18726
|
encryptShareWithPasskey: () => encryptShareWithPasskey,
|
|
18181
18727
|
encryptWithPassword: () => encryptWithPassword,
|
|
18182
18728
|
generateEd25519PrivateKey: () => generateEd25519PrivateKey,
|
|
18729
|
+
generateEmailRelayConfirmationCode: () => generateEmailRelayConfirmationCode,
|
|
18183
18730
|
generateEphemeralKeypair: () => generateEphemeralKeypair,
|
|
18731
|
+
generateEscrowKeyPair: () => generateEscrowKeyPair,
|
|
18732
|
+
generatePinSalt: () => generatePinSalt,
|
|
18184
18733
|
generateRecoveryPhrase: () => generateRecoveryPhrase,
|
|
18185
18734
|
getDeviceShare: () => getDeviceShare,
|
|
18186
18735
|
getQrLoginSessionInfo: () => getQrLoginSessionInfo,
|
|
@@ -18191,24 +18740,38 @@ __export(index_exports, {
|
|
|
18191
18740
|
isPublicComputerMode: () => isPublicComputerMode,
|
|
18192
18741
|
isWebAuthnSupported: () => isWebAuthnSupported,
|
|
18193
18742
|
listAllDeviceShares: () => listAllDeviceShares,
|
|
18743
|
+
normalizePin: () => normalizePin,
|
|
18194
18744
|
notifyDevicesForQrSession: () => notifyDevicesForQrSession,
|
|
18745
|
+
openEscrowRelease: () => openEscrowRelease,
|
|
18746
|
+
parseEmailRelayEnvelope: () => parseEmailRelayEnvelope,
|
|
18747
|
+
parseEmailRelayPlaintext: () => parseEmailRelayPlaintext,
|
|
18748
|
+
parseEscrowBlobPlaintext: () => parseEscrowBlobPlaintext,
|
|
18749
|
+
parseEscrowEnvelope: () => parseEscrowEnvelope,
|
|
18750
|
+
parseEscrowReleasePlaintext: () => parseEscrowReleasePlaintext,
|
|
18195
18751
|
pollQrLoginSession: () => pollQrLoginSession,
|
|
18196
18752
|
pollUntilApproved: () => pollUntilApproved,
|
|
18197
18753
|
reconstructFromShares: () => reconstructFromShares,
|
|
18198
18754
|
reconstructPrivateKey: () => reconstructPrivateKey,
|
|
18199
18755
|
recoveryPhraseToShare: () => recoveryPhraseToShare,
|
|
18756
|
+
sealEscrowRelease: () => sealEscrowRelease,
|
|
18200
18757
|
setPublicComputerMode: () => setPublicComputerMode,
|
|
18201
18758
|
shareToRecoveryPhrase: () => shareToRecoveryPhrase,
|
|
18202
18759
|
splitAndVerify: () => splitAndVerify,
|
|
18203
18760
|
splitPrivateKey: () => splitPrivateKey,
|
|
18204
18761
|
storeDeviceShare: () => storeDeviceShare,
|
|
18205
18762
|
storeShareVersion: () => storeShareVersion,
|
|
18763
|
+
validatePin: () => validatePin,
|
|
18206
18764
|
validateRecoveryPhrase: () => validateRecoveryPhrase,
|
|
18765
|
+
verifyEnclaveAttestation: () => verifyEnclaveAttestation,
|
|
18207
18766
|
verifyStoredShares: () => verifyStoredShares
|
|
18208
18767
|
});
|
|
18209
18768
|
module.exports = __toCommonJS(index_exports);
|
|
18210
18769
|
|
|
18211
18770
|
// src/api-client.ts
|
|
18771
|
+
var DID_CHALLENGE_REQUIRED_ERROR = "This operation requires the DID-challenge flow; use createSSSStrategy";
|
|
18772
|
+
var throwDidChallengeRequired = /* @__PURE__ */ __name(() => {
|
|
18773
|
+
throw new Error(DID_CHALLENGE_REQUIRED_ERROR);
|
|
18774
|
+
}, "throwDidChallengeRequired");
|
|
18212
18775
|
var SSSApiClient = class {
|
|
18213
18776
|
static {
|
|
18214
18777
|
__name(this, "SSSApiClient");
|
|
@@ -18221,20 +18784,9 @@ var SSSApiClient = class {
|
|
|
18221
18784
|
const token = await this.authProvider.getIdToken();
|
|
18222
18785
|
return {
|
|
18223
18786
|
"Content-Type": "application/json",
|
|
18224
|
-
|
|
18787
|
+
"X-Auth-Token": token
|
|
18225
18788
|
};
|
|
18226
18789
|
}
|
|
18227
|
-
async getContactMethodFromUser() {
|
|
18228
|
-
const user = await this.authProvider.getCurrentUser();
|
|
18229
|
-
if (!user) return null;
|
|
18230
|
-
if (user.email) {
|
|
18231
|
-
return { type: "email", value: user.email.toLowerCase() };
|
|
18232
|
-
}
|
|
18233
|
-
if (user.phone) {
|
|
18234
|
-
return { type: "phone", value: user.phone };
|
|
18235
|
-
}
|
|
18236
|
-
return null;
|
|
18237
|
-
}
|
|
18238
18790
|
async getAuthShare() {
|
|
18239
18791
|
const headers = await this.getAuthHeaders();
|
|
18240
18792
|
const providerType = this.authProvider.getProviderType();
|
|
@@ -18251,52 +18803,24 @@ var SSSApiClient = class {
|
|
|
18251
18803
|
}
|
|
18252
18804
|
return response.json();
|
|
18253
18805
|
}
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
|
|
18258
|
-
method: "PUT",
|
|
18259
|
-
headers,
|
|
18260
|
-
body: JSON.stringify({
|
|
18261
|
-
...input,
|
|
18262
|
-
providerType
|
|
18263
|
-
})
|
|
18264
|
-
});
|
|
18265
|
-
if (!response.ok) {
|
|
18266
|
-
throw new Error(`Failed to store auth share: ${response.statusText}`);
|
|
18267
|
-
}
|
|
18806
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18807
|
+
async storeAuthShare(_input) {
|
|
18808
|
+
throwDidChallengeRequired();
|
|
18268
18809
|
}
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery`, {
|
|
18273
|
-
method: "POST",
|
|
18274
|
-
headers,
|
|
18275
|
-
body: JSON.stringify({
|
|
18276
|
-
...input,
|
|
18277
|
-
providerType
|
|
18278
|
-
})
|
|
18279
|
-
});
|
|
18280
|
-
if (!response.ok) {
|
|
18281
|
-
throw new Error(`Failed to add recovery method: ${response.statusText}`);
|
|
18282
|
-
}
|
|
18810
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18811
|
+
async addRecoveryMethod(_input) {
|
|
18812
|
+
throwDidChallengeRequired();
|
|
18283
18813
|
}
|
|
18284
18814
|
async getRecoveryShare(type, credentialId) {
|
|
18285
18815
|
const token = await this.authProvider.getIdToken();
|
|
18286
18816
|
const providerType = this.authProvider.getProviderType();
|
|
18287
|
-
const params = new URLSearchParams({
|
|
18288
|
-
type,
|
|
18289
|
-
providerType,
|
|
18290
|
-
authToken: token
|
|
18291
|
-
});
|
|
18817
|
+
const params = new URLSearchParams({ type, providerType });
|
|
18292
18818
|
if (credentialId) {
|
|
18293
18819
|
params.append("credentialId", credentialId);
|
|
18294
18820
|
}
|
|
18295
18821
|
const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
|
|
18296
18822
|
method: "GET",
|
|
18297
|
-
headers: {
|
|
18298
|
-
"Content-Type": "application/json"
|
|
18299
|
-
}
|
|
18823
|
+
headers: { "Content-Type": "application/json", "X-Auth-Token": token }
|
|
18300
18824
|
});
|
|
18301
18825
|
if (!response.ok) {
|
|
18302
18826
|
if (response.status === 404) {
|
|
@@ -18306,81 +18830,81 @@ var SSSApiClient = class {
|
|
|
18306
18830
|
}
|
|
18307
18831
|
return response.json();
|
|
18308
18832
|
}
|
|
18309
|
-
async
|
|
18310
|
-
const
|
|
18311
|
-
const providerType = this.authProvider.getProviderType();
|
|
18312
|
-
const response = await fetch(`${this.serverUrl}/keys/migrate`, {
|
|
18833
|
+
async startIdentityRecovery(email) {
|
|
18834
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/start`, {
|
|
18313
18835
|
method: "POST",
|
|
18314
|
-
headers,
|
|
18315
|
-
body: JSON.stringify({
|
|
18836
|
+
headers: { "Content-Type": "application/json" },
|
|
18837
|
+
body: JSON.stringify({ email })
|
|
18316
18838
|
});
|
|
18317
18839
|
if (!response.ok) {
|
|
18318
|
-
throw new Error(`Failed to
|
|
18840
|
+
throw new Error(`Failed to send recovery code: ${response.statusText}`);
|
|
18319
18841
|
}
|
|
18320
18842
|
}
|
|
18321
|
-
async
|
|
18322
|
-
const
|
|
18323
|
-
const providerType = this.authProvider.getProviderType();
|
|
18324
|
-
let email = overrideEmail;
|
|
18325
|
-
if (!email) {
|
|
18326
|
-
const user = await this.authProvider.getCurrentUser();
|
|
18327
|
-
email = user?.email;
|
|
18328
|
-
}
|
|
18329
|
-
if (!email) {
|
|
18330
|
-
console.warn("Cannot send email backup share: no email address available");
|
|
18331
|
-
return;
|
|
18332
|
-
}
|
|
18333
|
-
const response = await fetch(`${this.serverUrl}/keys/email-backup`, {
|
|
18843
|
+
async verifyIdentityRecovery(email, code) {
|
|
18844
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/verify`, {
|
|
18334
18845
|
method: "POST",
|
|
18335
|
-
headers,
|
|
18336
|
-
body: JSON.stringify({
|
|
18337
|
-
providerType,
|
|
18338
|
-
emailShare,
|
|
18339
|
-
email
|
|
18340
|
-
})
|
|
18846
|
+
headers: { "Content-Type": "application/json" },
|
|
18847
|
+
body: JSON.stringify({ email, code })
|
|
18341
18848
|
});
|
|
18342
18849
|
if (!response.ok) {
|
|
18343
|
-
|
|
18850
|
+
throw new Error(`Failed to verify recovery code: ${response.statusText}`);
|
|
18344
18851
|
}
|
|
18852
|
+
return response.json();
|
|
18345
18853
|
}
|
|
18346
|
-
async
|
|
18347
|
-
const
|
|
18348
|
-
const providerType = this.authProvider.getProviderType();
|
|
18349
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/add`, {
|
|
18854
|
+
async useIdentityRecoverySession(recoverySessionToken, type, credentialId) {
|
|
18855
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/recover`, {
|
|
18350
18856
|
method: "POST",
|
|
18351
18857
|
headers: { "Content-Type": "application/json" },
|
|
18352
|
-
body: JSON.stringify({
|
|
18858
|
+
body: JSON.stringify({ recoverySessionToken, type, credentialId })
|
|
18353
18859
|
});
|
|
18354
18860
|
if (!response.ok) {
|
|
18355
|
-
|
|
18356
|
-
throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
|
|
18861
|
+
throw new Error(`Failed to retrieve recovery data: ${response.statusText}`);
|
|
18357
18862
|
}
|
|
18863
|
+
return response.json();
|
|
18358
18864
|
}
|
|
18359
|
-
async
|
|
18360
|
-
const
|
|
18361
|
-
const providerType = this.authProvider.getProviderType();
|
|
18362
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/verify`, {
|
|
18865
|
+
async completeIdentityRecoveryRebind(input) {
|
|
18866
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/rebind`, {
|
|
18363
18867
|
method: "POST",
|
|
18364
|
-
headers: {
|
|
18365
|
-
|
|
18868
|
+
headers: {
|
|
18869
|
+
"Content-Type": "application/json",
|
|
18870
|
+
Authorization: `Bearer ${input.didAuthVp}`,
|
|
18871
|
+
"X-Auth-Token": input.newAuthToken
|
|
18872
|
+
},
|
|
18873
|
+
body: JSON.stringify({
|
|
18874
|
+
recoverySessionToken: input.recoverySessionToken,
|
|
18875
|
+
providerType: input.providerType,
|
|
18876
|
+
primaryDid: input.primaryDid,
|
|
18877
|
+
authShare: input.authShare
|
|
18878
|
+
})
|
|
18366
18879
|
});
|
|
18367
18880
|
if (!response.ok) {
|
|
18368
|
-
|
|
18369
|
-
throw new Error(data?.message || `Failed to verify recovery email: ${response.statusText}`);
|
|
18881
|
+
throw new Error(`Failed to bind the new sign-in: ${response.statusText}`);
|
|
18370
18882
|
}
|
|
18371
18883
|
return response.json();
|
|
18372
18884
|
}
|
|
18885
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18886
|
+
async markMigrated() {
|
|
18887
|
+
throwDidChallengeRequired();
|
|
18888
|
+
}
|
|
18889
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18890
|
+
async activate() {
|
|
18891
|
+
throwDidChallengeRequired();
|
|
18892
|
+
}
|
|
18893
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18894
|
+
async sendEmailBackupShare(_emailShare, _overrideEmail) {
|
|
18895
|
+
throwDidChallengeRequired();
|
|
18896
|
+
}
|
|
18897
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18898
|
+
async addRecoveryEmail(_email) {
|
|
18899
|
+
throwDidChallengeRequired();
|
|
18900
|
+
}
|
|
18901
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18902
|
+
async verifyRecoveryEmail(_code) {
|
|
18903
|
+
return throwDidChallengeRequired();
|
|
18904
|
+
}
|
|
18905
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18373
18906
|
async deleteUserKey() {
|
|
18374
|
-
|
|
18375
|
-
const providerType = this.authProvider.getProviderType();
|
|
18376
|
-
const response = await fetch(`${this.serverUrl}/keys`, {
|
|
18377
|
-
method: "DELETE",
|
|
18378
|
-
headers,
|
|
18379
|
-
body: JSON.stringify({ providerType })
|
|
18380
|
-
});
|
|
18381
|
-
if (!response.ok) {
|
|
18382
|
-
throw new Error(`Failed to delete user key: ${response.statusText}`);
|
|
18383
|
-
}
|
|
18907
|
+
throwDidChallengeRequired();
|
|
18384
18908
|
}
|
|
18385
18909
|
};
|
|
18386
18910
|
|
|
@@ -18575,11 +19099,10 @@ async function getOrCreateMasterKey() {
|
|
|
18575
19099
|
(s) => s.get("master-key")
|
|
18576
19100
|
);
|
|
18577
19101
|
if (existing) return existing;
|
|
18578
|
-
const key = await crypto.subtle.generateKey(
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
);
|
|
19102
|
+
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
|
|
19103
|
+
"encrypt",
|
|
19104
|
+
"decrypt"
|
|
19105
|
+
]);
|
|
18583
19106
|
await tx(db, KEYS_STORE, "readwrite", (s) => s.put(key, "master-key"));
|
|
18584
19107
|
return key;
|
|
18585
19108
|
} finally {
|
|
@@ -18680,12 +19203,7 @@ __name(getShareVersion, "getShareVersion");
|
|
|
18680
19203
|
async function getDeviceShare(id = DEFAULT_DEVICE_SHARE_ID) {
|
|
18681
19204
|
const db = await openDB();
|
|
18682
19205
|
try {
|
|
18683
|
-
const raw = await tx(
|
|
18684
|
-
db,
|
|
18685
|
-
SHARES_STORE,
|
|
18686
|
-
"readonly",
|
|
18687
|
-
(s) => s.get(id)
|
|
18688
|
-
);
|
|
19206
|
+
const raw = await tx(db, SHARES_STORE, "readonly", (s) => s.get(id));
|
|
18689
19207
|
if (raw == null) {
|
|
18690
19208
|
return null;
|
|
18691
19209
|
}
|
|
@@ -18744,7 +19262,9 @@ async function listAllDeviceShares() {
|
|
|
18744
19262
|
try {
|
|
18745
19263
|
const share = await getDeviceShare(id);
|
|
18746
19264
|
if (!share) {
|
|
18747
|
-
console.warn(
|
|
19265
|
+
console.warn(
|
|
19266
|
+
`SSS Storage: removing orphaned entry that failed to decrypt: ${id}`
|
|
19267
|
+
);
|
|
18748
19268
|
await deleteDeviceShare(id);
|
|
18749
19269
|
continue;
|
|
18750
19270
|
}
|
|
@@ -18828,6 +19348,7 @@ function createAdaptiveStorage() {
|
|
|
18828
19348
|
getDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetDeviceShare(id) : getDeviceShare(id), "getDeviceShare"),
|
|
18829
19349
|
hasDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionHasDeviceShare(id) : hasDeviceShare(id), "hasDeviceShare"),
|
|
18830
19350
|
clearAllShares: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionClearAllShares(id) : clearAllShares(id), "clearAllShares"),
|
|
19351
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionDeleteDeviceShare(id) : deleteDeviceShare(id), "deleteDeviceShare"),
|
|
18831
19352
|
storeShareVersion: /* @__PURE__ */ __name((version, id) => isPublicComputerMode() ? sessionStoreShareVersion(version, id) : storeShareVersion(version, id), "storeShareVersion"),
|
|
18832
19353
|
getShareVersion: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetShareVersion(id) : getShareVersion(id), "getShareVersion")
|
|
18833
19354
|
};
|
|
@@ -19108,6 +19629,33 @@ function countWords(phrase) {
|
|
|
19108
19629
|
return phrase.trim().split(/\s+/).filter((w) => w.length > 0).length;
|
|
19109
19630
|
}
|
|
19110
19631
|
__name(countWords, "countWords");
|
|
19632
|
+
function randomIndex(max) {
|
|
19633
|
+
const limit = 4294967296 - 4294967296 % max;
|
|
19634
|
+
for (; ; ) {
|
|
19635
|
+
const value = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
19636
|
+
if (value === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
19637
|
+
if (value < limit) return value % max;
|
|
19638
|
+
}
|
|
19639
|
+
}
|
|
19640
|
+
__name(randomIndex, "randomIndex");
|
|
19641
|
+
async function buildRecoveryPhraseChallengeOptions(phrase, challengeWordIndices, optionCount = 6) {
|
|
19642
|
+
const words = phrase.trim().split(/\s+/);
|
|
19643
|
+
const phraseWords = new Set(words);
|
|
19644
|
+
const decoyPool = (await getWordlist()).filter((word) => !phraseWords.has(word));
|
|
19645
|
+
return challengeWordIndices.map((wordIndex) => {
|
|
19646
|
+
const answer = words[wordIndex];
|
|
19647
|
+
if (!answer) throw new Error("Recovery phrase challenge index is out of range");
|
|
19648
|
+
const options = /* @__PURE__ */ new Set([answer]);
|
|
19649
|
+
while (options.size < optionCount) options.add(decoyPool[randomIndex(decoyPool.length)]);
|
|
19650
|
+
const shuffled = [...options];
|
|
19651
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
19652
|
+
const j = randomIndex(i + 1);
|
|
19653
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
19654
|
+
}
|
|
19655
|
+
return shuffled;
|
|
19656
|
+
});
|
|
19657
|
+
}
|
|
19658
|
+
__name(buildRecoveryPhraseChallengeOptions, "buildRecoveryPhraseChallengeOptions");
|
|
19111
19659
|
|
|
19112
19660
|
// src/key-manager.ts
|
|
19113
19661
|
var SSSKeyManager = class {
|
|
@@ -19333,117 +19881,671 @@ function createSSSKeyManager(config) {
|
|
|
19333
19881
|
}
|
|
19334
19882
|
__name(createSSSKeyManager, "createSSSKeyManager");
|
|
19335
19883
|
|
|
19336
|
-
// src/
|
|
19337
|
-
var
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
var
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
this.cause = cause;
|
|
19355
|
-
this.name = "AtomicUpdateError";
|
|
19884
|
+
// src/email-relay-crypto.ts
|
|
19885
|
+
var EMAIL_RELAY_ENVELOPE_VERSION = 1;
|
|
19886
|
+
var EMAIL_RELAY_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
19887
|
+
var encodeUtf8 = /* @__PURE__ */ __name((value) => {
|
|
19888
|
+
const bytes = new TextEncoder().encode(value);
|
|
19889
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
19890
|
+
copy.set(bytes);
|
|
19891
|
+
return copy;
|
|
19892
|
+
}, "encodeUtf8");
|
|
19893
|
+
var EMAIL_RELAY_INFO = encodeUtf8("learncard-email-relay-v1");
|
|
19894
|
+
var SIX_DIGIT_CODE_RANGE = 9e5;
|
|
19895
|
+
var UINT32_RANGE = 4294967296;
|
|
19896
|
+
var MAX_UNBIASED_UINT32 = Math.floor(UINT32_RANGE / SIX_DIGIT_CODE_RANGE) * SIX_DIGIT_CODE_RANGE;
|
|
19897
|
+
var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
19898
|
+
var requireString = /* @__PURE__ */ __name((record, key, options) => {
|
|
19899
|
+
const value = record[key];
|
|
19900
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
19901
|
+
throw new Error(`Invalid email relay ${key}`);
|
|
19356
19902
|
}
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
const
|
|
19363
|
-
const
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19903
|
+
return value;
|
|
19904
|
+
}, "requireString");
|
|
19905
|
+
var parseBranding = /* @__PURE__ */ __name((value) => {
|
|
19906
|
+
if (value === void 0) return void 0;
|
|
19907
|
+
if (!isRecord(value)) throw new Error("Invalid email relay branding");
|
|
19908
|
+
const branding = {};
|
|
19909
|
+
const keys = [
|
|
19910
|
+
"brandName",
|
|
19911
|
+
"logoUrl",
|
|
19912
|
+
"logoAlt",
|
|
19913
|
+
"primaryColor",
|
|
19914
|
+
"primaryTextColor",
|
|
19915
|
+
"supportEmail",
|
|
19916
|
+
"websiteUrl",
|
|
19917
|
+
"appUrl",
|
|
19918
|
+
"fromDomain",
|
|
19919
|
+
"copyrightHolder"
|
|
19370
19920
|
];
|
|
19371
|
-
for (const
|
|
19372
|
-
const
|
|
19373
|
-
if (
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
name,
|
|
19377
|
-
privateKey,
|
|
19378
|
-
reconstructed
|
|
19379
|
-
);
|
|
19921
|
+
for (const key of keys) {
|
|
19922
|
+
const field = value[key];
|
|
19923
|
+
if (field === void 0) continue;
|
|
19924
|
+
if (typeof field !== "string" || field.length > 2048) {
|
|
19925
|
+
throw new Error(`Invalid email relay branding.${key}`);
|
|
19380
19926
|
}
|
|
19927
|
+
branding[key] = field;
|
|
19381
19928
|
}
|
|
19382
|
-
return
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
__name(splitAndVerify, "splitAndVerify");
|
|
19389
|
-
async function atomicShareUpdate(privateKey, storage, options = {}) {
|
|
19390
|
-
let newShares = null;
|
|
19391
|
-
let deviceStored = false;
|
|
19392
|
-
try {
|
|
19393
|
-
const result = await splitAndVerify(privateKey);
|
|
19394
|
-
newShares = result.shares;
|
|
19395
|
-
} catch (e) {
|
|
19396
|
-
throw new AtomicUpdateError(
|
|
19397
|
-
"Failed to split and verify shares",
|
|
19398
|
-
"split",
|
|
19399
|
-
false,
|
|
19400
|
-
e instanceof Error ? e : void 0
|
|
19401
|
-
);
|
|
19929
|
+
return branding;
|
|
19930
|
+
}, "parseBranding");
|
|
19931
|
+
var parseEmailRelayEnvelope = /* @__PURE__ */ __name((value) => {
|
|
19932
|
+
if (!isRecord(value)) throw new Error("Invalid email relay envelope");
|
|
19933
|
+
if (value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19934
|
+
throw new Error("Unsupported email relay envelope version");
|
|
19402
19935
|
}
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
deviceStored = true;
|
|
19406
|
-
} catch (e) {
|
|
19407
|
-
throw new AtomicUpdateError(
|
|
19408
|
-
"Failed to store device share locally",
|
|
19409
|
-
"store_device",
|
|
19410
|
-
false,
|
|
19411
|
-
e instanceof Error ? e : void 0
|
|
19412
|
-
);
|
|
19936
|
+
if (value.algorithm !== EMAIL_RELAY_ALGORITHM) {
|
|
19937
|
+
throw new Error("Unsupported email relay algorithm");
|
|
19413
19938
|
}
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
}
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
);
|
|
19939
|
+
return {
|
|
19940
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19941
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
19942
|
+
keyId: requireString(value, "keyId", {
|
|
19943
|
+
max: 128,
|
|
19944
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
19945
|
+
}),
|
|
19946
|
+
ephemeralPublicKey: requireString(value, "ephemeralPublicKey", { max: 256 }),
|
|
19947
|
+
salt: requireString(value, "salt", { max: 128 }),
|
|
19948
|
+
iv: requireString(value, "iv", { max: 64 }),
|
|
19949
|
+
ciphertext: requireString(value, "ciphertext", { max: 16384 })
|
|
19950
|
+
};
|
|
19951
|
+
}, "parseEmailRelayEnvelope");
|
|
19952
|
+
var parseEmailRelayPlaintext = /* @__PURE__ */ __name((value) => {
|
|
19953
|
+
if (!isRecord(value) || value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19954
|
+
throw new Error("Invalid email relay plaintext");
|
|
19431
19955
|
}
|
|
19432
|
-
return
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
19440
|
-
|
|
19441
|
-
|
|
19956
|
+
return {
|
|
19957
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19958
|
+
targetEmail: requireString(value, "targetEmail", {
|
|
19959
|
+
max: 320,
|
|
19960
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
19961
|
+
}).trim().toLowerCase(),
|
|
19962
|
+
recoveryKey: requireString(value, "recoveryKey", {
|
|
19963
|
+
min: 5,
|
|
19964
|
+
max: 4096,
|
|
19965
|
+
pattern: /^[0-9a-f]+$/i
|
|
19966
|
+
}),
|
|
19967
|
+
confirmationCode: requireString(value, "confirmationCode", {
|
|
19968
|
+
max: 6,
|
|
19969
|
+
pattern: /^\d{6}$/
|
|
19970
|
+
}),
|
|
19971
|
+
branding: parseBranding(value.branding)
|
|
19442
19972
|
};
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19973
|
+
}, "parseEmailRelayPlaintext");
|
|
19974
|
+
var getAad = /* @__PURE__ */ __name((envelope) => encodeUtf8(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
19975
|
+
var deriveAesKey = /* @__PURE__ */ __name(async (sharedSecret, salt, usage) => {
|
|
19976
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
19977
|
+
"deriveKey"
|
|
19978
|
+
]);
|
|
19979
|
+
return crypto.subtle.deriveKey(
|
|
19980
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: EMAIL_RELAY_INFO },
|
|
19981
|
+
keyMaterial,
|
|
19982
|
+
{ name: "AES-GCM", length: 256 },
|
|
19983
|
+
false,
|
|
19984
|
+
[usage]
|
|
19985
|
+
);
|
|
19986
|
+
}, "deriveAesKey");
|
|
19987
|
+
var generateEmailRelayConfirmationCode = /* @__PURE__ */ __name(() => {
|
|
19988
|
+
const random = new Uint32Array(1);
|
|
19989
|
+
let value;
|
|
19990
|
+
do {
|
|
19991
|
+
crypto.getRandomValues(random);
|
|
19992
|
+
value = random[0] ?? UINT32_RANGE;
|
|
19993
|
+
} while (value >= MAX_UNBIASED_UINT32);
|
|
19994
|
+
return String(1e5 + value % SIX_DIGIT_CODE_RANGE);
|
|
19995
|
+
}, "generateEmailRelayConfirmationCode");
|
|
19996
|
+
var encryptEmailRelayPayload = /* @__PURE__ */ __name(async (input, publicKeyBase64, keyId) => {
|
|
19997
|
+
const plaintext = parseEmailRelayPlaintext({
|
|
19998
|
+
...input,
|
|
19999
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION
|
|
20000
|
+
});
|
|
20001
|
+
const normalizedKeyId = keyId.trim();
|
|
20002
|
+
if (!publicKeyBase64.trim()) throw new Error("Email relay public key is not configured");
|
|
20003
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(normalizedKeyId)) {
|
|
20004
|
+
throw new Error("Email relay key ID is not configured");
|
|
20005
|
+
}
|
|
20006
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
20007
|
+
"spki",
|
|
20008
|
+
base64ToBuffer(publicKeyBase64.trim()).buffer,
|
|
20009
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20010
|
+
false,
|
|
20011
|
+
[]
|
|
20012
|
+
);
|
|
20013
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
20014
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20015
|
+
true,
|
|
20016
|
+
["deriveBits"]
|
|
20017
|
+
);
|
|
20018
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20019
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
20020
|
+
ephemeralKeyPair.privateKey,
|
|
20021
|
+
256
|
|
20022
|
+
);
|
|
20023
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
20024
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
20025
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "encrypt");
|
|
20026
|
+
const envelopeMetadata = {
|
|
20027
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
20028
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
20029
|
+
keyId: normalizedKeyId
|
|
20030
|
+
};
|
|
20031
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
20032
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelopeMetadata) },
|
|
20033
|
+
aesKey,
|
|
20034
|
+
encodeUtf8(JSON.stringify(plaintext))
|
|
20035
|
+
);
|
|
20036
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
20037
|
+
return {
|
|
20038
|
+
...envelopeMetadata,
|
|
20039
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
20040
|
+
salt: bufferToBase64(salt.buffer),
|
|
20041
|
+
iv: bufferToBase64(iv.buffer),
|
|
20042
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
20043
|
+
};
|
|
20044
|
+
}, "encryptEmailRelayPayload");
|
|
20045
|
+
var decryptEmailRelayPayload = /* @__PURE__ */ __name(async (value, privateKeyBase64) => {
|
|
20046
|
+
const envelope = parseEmailRelayEnvelope(value);
|
|
20047
|
+
const privateKey = await crypto.subtle.importKey(
|
|
20048
|
+
"pkcs8",
|
|
20049
|
+
base64ToBuffer(privateKeyBase64.trim()).buffer,
|
|
20050
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20051
|
+
false,
|
|
20052
|
+
["deriveBits"]
|
|
20053
|
+
);
|
|
20054
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
20055
|
+
"raw",
|
|
20056
|
+
base64ToBuffer(envelope.ephemeralPublicKey).buffer,
|
|
20057
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20058
|
+
false,
|
|
20059
|
+
[]
|
|
20060
|
+
);
|
|
20061
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20062
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
20063
|
+
privateKey,
|
|
20064
|
+
256
|
|
20065
|
+
);
|
|
20066
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
20067
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
20068
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "decrypt");
|
|
20069
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
20070
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelope) },
|
|
20071
|
+
aesKey,
|
|
20072
|
+
base64ToBuffer(envelope.ciphertext)
|
|
20073
|
+
);
|
|
20074
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
20075
|
+
return parseEmailRelayPlaintext(parsed);
|
|
20076
|
+
}, "decryptEmailRelayPayload");
|
|
20077
|
+
|
|
20078
|
+
// src/escrow-crypto.ts
|
|
20079
|
+
var ESCROW_ENVELOPE_VERSION = 1;
|
|
20080
|
+
var ESCROW_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
20081
|
+
var ESCROW_BLOB_INFO = "learncard-escrow-blob-v1";
|
|
20082
|
+
var ESCROW_RELEASE_INFO = "learncard-escrow-release-v1";
|
|
20083
|
+
var ESCROW_CLIENT_KEY_ID = "client";
|
|
20084
|
+
var encodeUtf82 = /* @__PURE__ */ __name((value) => {
|
|
20085
|
+
const bytes = new TextEncoder().encode(value);
|
|
20086
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
20087
|
+
copy.set(bytes);
|
|
20088
|
+
return copy;
|
|
20089
|
+
}, "encodeUtf8");
|
|
20090
|
+
var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
20091
|
+
var requireString2 = /* @__PURE__ */ __name((record, key, options) => {
|
|
20092
|
+
const value = record[key];
|
|
20093
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
20094
|
+
throw new Error(`Invalid escrow ${key}`);
|
|
20095
|
+
}
|
|
20096
|
+
return value;
|
|
20097
|
+
}, "requireString");
|
|
20098
|
+
var parseEscrowEnvelope = /* @__PURE__ */ __name((value) => {
|
|
20099
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow envelope");
|
|
20100
|
+
if (value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
20101
|
+
throw new Error("Unsupported escrow envelope version");
|
|
20102
|
+
}
|
|
20103
|
+
if (value.algorithm !== ESCROW_ALGORITHM) {
|
|
20104
|
+
throw new Error("Unsupported escrow algorithm");
|
|
20105
|
+
}
|
|
20106
|
+
return {
|
|
20107
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20108
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20109
|
+
keyId: requireString2(value, "keyId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ }),
|
|
20110
|
+
ephemeralPublicKey: requireString2(value, "ephemeralPublicKey", { max: 256 }),
|
|
20111
|
+
salt: requireString2(value, "salt", { max: 128 }),
|
|
20112
|
+
iv: requireString2(value, "iv", { max: 64 }),
|
|
20113
|
+
ciphertext: requireString2(value, "ciphertext", { max: 16384 })
|
|
20114
|
+
};
|
|
20115
|
+
}, "parseEscrowEnvelope");
|
|
20116
|
+
var parseEscrowBlobPlaintext = /* @__PURE__ */ __name((value) => {
|
|
20117
|
+
if (!isRecord2(value) || value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
20118
|
+
throw new Error("Invalid escrow plaintext");
|
|
20119
|
+
}
|
|
20120
|
+
if (typeof value.shareVersion !== "number" || !Number.isInteger(value.shareVersion) || value.shareVersion <= 0) {
|
|
20121
|
+
throw new Error("Invalid escrow shareVersion");
|
|
20122
|
+
}
|
|
20123
|
+
return {
|
|
20124
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20125
|
+
recoveryShare: requireString2(value, "recoveryShare", {
|
|
20126
|
+
min: 5,
|
|
20127
|
+
max: 4096,
|
|
20128
|
+
pattern: /^[0-9a-f]+$/i
|
|
20129
|
+
}).toLowerCase(),
|
|
20130
|
+
did: requireString2(value, "did", { max: 2048, pattern: /^did:/ }),
|
|
20131
|
+
shareVersion: value.shareVersion,
|
|
20132
|
+
...value.pinVerifier !== void 0 ? {
|
|
20133
|
+
pinVerifier: requireString2(value, "pinVerifier", {
|
|
20134
|
+
min: 64,
|
|
20135
|
+
max: 64,
|
|
20136
|
+
pattern: /^[0-9a-f]{64}$/i
|
|
20137
|
+
}).toLowerCase()
|
|
20138
|
+
} : {}
|
|
20139
|
+
};
|
|
20140
|
+
}, "parseEscrowBlobPlaintext");
|
|
20141
|
+
var parseEscrowReleasePlaintext = /* @__PURE__ */ __name((value) => {
|
|
20142
|
+
const plaintext = parseEscrowBlobPlaintext(value);
|
|
20143
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow plaintext");
|
|
20144
|
+
return {
|
|
20145
|
+
...plaintext,
|
|
20146
|
+
holdId: requireString2(value, "holdId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ })
|
|
20147
|
+
};
|
|
20148
|
+
}, "parseEscrowReleasePlaintext");
|
|
20149
|
+
var getAad2 = /* @__PURE__ */ __name((envelope) => encodeUtf82(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
20150
|
+
var deriveAesKey2 = /* @__PURE__ */ __name(async (sharedSecret, salt, usage, info) => {
|
|
20151
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
20152
|
+
"deriveKey"
|
|
20153
|
+
]);
|
|
20154
|
+
return crypto.subtle.deriveKey(
|
|
20155
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: encodeUtf82(info) },
|
|
20156
|
+
keyMaterial,
|
|
20157
|
+
{ name: "AES-GCM", length: 256 },
|
|
20158
|
+
false,
|
|
20159
|
+
[usage]
|
|
20160
|
+
);
|
|
20161
|
+
}, "deriveAesKey");
|
|
20162
|
+
var generateEscrowKeyPair = /* @__PURE__ */ __name(async () => {
|
|
20163
|
+
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
|
|
20164
|
+
"deriveBits"
|
|
20165
|
+
]);
|
|
20166
|
+
const [publicKey, privateKey] = await Promise.all([
|
|
20167
|
+
crypto.subtle.exportKey("spki", keyPair.publicKey),
|
|
20168
|
+
crypto.subtle.exportKey("pkcs8", keyPair.privateKey)
|
|
20169
|
+
]);
|
|
20170
|
+
return { publicKey: bufferToBase64(publicKey), privateKey: bufferToBase64(privateKey) };
|
|
20171
|
+
}, "generateEscrowKeyPair");
|
|
20172
|
+
var encryptToRecipient = /* @__PURE__ */ __name(async (plaintextJson, spki, keyId, info) => {
|
|
20173
|
+
if (typeof spki !== "string" || !spki.trim()) {
|
|
20174
|
+
throw new Error("Escrow public key is not configured");
|
|
20175
|
+
}
|
|
20176
|
+
const recipientKeyId = requireString2({ keyId }, "keyId", {
|
|
20177
|
+
max: 128,
|
|
20178
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
20179
|
+
});
|
|
20180
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
20181
|
+
"spki",
|
|
20182
|
+
base64ToBuffer(spki.trim()),
|
|
20183
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20184
|
+
false,
|
|
20185
|
+
[]
|
|
20186
|
+
);
|
|
20187
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
20188
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20189
|
+
true,
|
|
20190
|
+
["deriveBits"]
|
|
20191
|
+
);
|
|
20192
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20193
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
20194
|
+
ephemeralKeyPair.privateKey,
|
|
20195
|
+
256
|
|
20196
|
+
);
|
|
20197
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
20198
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
20199
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "encrypt", info);
|
|
20200
|
+
const metadata = {
|
|
20201
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20202
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20203
|
+
keyId: recipientKeyId
|
|
20204
|
+
};
|
|
20205
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
20206
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(metadata) },
|
|
20207
|
+
aesKey,
|
|
20208
|
+
encodeUtf82(plaintextJson)
|
|
20209
|
+
);
|
|
20210
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
20211
|
+
return {
|
|
20212
|
+
...metadata,
|
|
20213
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
20214
|
+
salt: bufferToBase64(salt.buffer),
|
|
20215
|
+
iv: bufferToBase64(iv.buffer),
|
|
20216
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
20217
|
+
};
|
|
20218
|
+
}, "encryptToRecipient");
|
|
20219
|
+
var decryptFromEnvelope = /* @__PURE__ */ __name(async (value, pkcs8, info) => {
|
|
20220
|
+
const envelope = parseEscrowEnvelope(value);
|
|
20221
|
+
const privateKey = await crypto.subtle.importKey(
|
|
20222
|
+
"pkcs8",
|
|
20223
|
+
base64ToBuffer(pkcs8.trim()),
|
|
20224
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20225
|
+
false,
|
|
20226
|
+
["deriveBits"]
|
|
20227
|
+
);
|
|
20228
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
20229
|
+
"raw",
|
|
20230
|
+
base64ToBuffer(envelope.ephemeralPublicKey),
|
|
20231
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20232
|
+
false,
|
|
20233
|
+
[]
|
|
20234
|
+
);
|
|
20235
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20236
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
20237
|
+
privateKey,
|
|
20238
|
+
256
|
|
20239
|
+
);
|
|
20240
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
20241
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
20242
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "decrypt", info);
|
|
20243
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
20244
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(envelope) },
|
|
20245
|
+
aesKey,
|
|
20246
|
+
base64ToBuffer(envelope.ciphertext)
|
|
20247
|
+
);
|
|
20248
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
20249
|
+
return parsed;
|
|
20250
|
+
}, "decryptFromEnvelope");
|
|
20251
|
+
var encryptEscrowBlob = /* @__PURE__ */ __name(async (input, enclavePublicKeySpkiB64, keyId) => {
|
|
20252
|
+
const plaintext = parseEscrowBlobPlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20253
|
+
return encryptToRecipient(
|
|
20254
|
+
JSON.stringify(plaintext),
|
|
20255
|
+
enclavePublicKeySpkiB64,
|
|
20256
|
+
keyId,
|
|
20257
|
+
ESCROW_BLOB_INFO
|
|
20258
|
+
);
|
|
20259
|
+
}, "encryptEscrowBlob");
|
|
20260
|
+
var decryptEscrowBlob = /* @__PURE__ */ __name(async (envelope, enclavePrivateKeyPkcs8B64) => parseEscrowBlobPlaintext(
|
|
20261
|
+
await decryptFromEnvelope(envelope, enclavePrivateKeyPkcs8B64, ESCROW_BLOB_INFO)
|
|
20262
|
+
), "decryptEscrowBlob");
|
|
20263
|
+
var sealEscrowRelease = /* @__PURE__ */ __name(async (input, clientEphemeralPublicKeySpkiB64) => {
|
|
20264
|
+
const plaintext = parseEscrowReleasePlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20265
|
+
return encryptToRecipient(
|
|
20266
|
+
JSON.stringify(plaintext),
|
|
20267
|
+
clientEphemeralPublicKeySpkiB64,
|
|
20268
|
+
ESCROW_CLIENT_KEY_ID,
|
|
20269
|
+
ESCROW_RELEASE_INFO
|
|
20270
|
+
);
|
|
20271
|
+
}, "sealEscrowRelease");
|
|
20272
|
+
var openEscrowRelease = /* @__PURE__ */ __name(async (envelope, clientEphemeralPrivateKeyPkcs8B64) => parseEscrowReleasePlaintext(
|
|
20273
|
+
await decryptFromEnvelope(envelope, clientEphemeralPrivateKeyPkcs8B64, ESCROW_RELEASE_INFO)
|
|
20274
|
+
), "openEscrowRelease");
|
|
20275
|
+
|
|
20276
|
+
// src/escrow-attestation.ts
|
|
20277
|
+
var isRecord3 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
20278
|
+
var normalizePublicKey = /* @__PURE__ */ __name((value) => {
|
|
20279
|
+
const trimmed = value.trim();
|
|
20280
|
+
if (!trimmed || trimmed.length > 512 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(trimmed)) {
|
|
20281
|
+
throw new Error("Invalid escrow attestation public key");
|
|
20282
|
+
}
|
|
20283
|
+
return bufferToBase64(base64ToBuffer(trimmed).buffer);
|
|
20284
|
+
}, "normalizePublicKey");
|
|
20285
|
+
var verifyEnclaveAttestation = /* @__PURE__ */ __name(async (attestation, policy) => {
|
|
20286
|
+
const fields = ["mode", "keyId", "publicKey", "measurements", "document", "issuedAt"];
|
|
20287
|
+
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(
|
|
20288
|
+
([key, value]) => !["imageSha384", "pcr0", "pcr1", "pcr2"].includes(key) || typeof value !== "string" || !value || value.length > 1024
|
|
20289
|
+
) || typeof attestation.document !== "string" || !attestation.document || attestation.document.length > 65536 || typeof attestation.issuedAt !== "string" || !Number.isFinite(Date.parse(attestation.issuedAt))) {
|
|
20290
|
+
throw new Error("Invalid escrow attestation");
|
|
20291
|
+
}
|
|
20292
|
+
const publicKey = normalizePublicKey(attestation.publicKey);
|
|
20293
|
+
await crypto.subtle.importKey(
|
|
20294
|
+
"spki",
|
|
20295
|
+
base64ToBuffer(publicKey),
|
|
20296
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20297
|
+
false,
|
|
20298
|
+
[]
|
|
20299
|
+
);
|
|
20300
|
+
if (policy.mode === "nitro") {
|
|
20301
|
+
throw new Error("Nitro attestation verification is not implemented yet");
|
|
20302
|
+
}
|
|
20303
|
+
if (attestation.mode !== policy.mode) throw new Error("Escrow attestation mode mismatch");
|
|
20304
|
+
if (!policy.pinnedPublicKeys.some((pin) => normalizePublicKey(pin) === publicKey)) {
|
|
20305
|
+
throw new Error("Escrow attestation public key is not trusted");
|
|
20306
|
+
}
|
|
20307
|
+
return { publicKey, keyId: attestation.keyId, mode: attestation.mode };
|
|
20308
|
+
}, "verifyEnclaveAttestation");
|
|
20309
|
+
|
|
20310
|
+
// src/types.ts
|
|
20311
|
+
var import_types = __toESM(require_dist());
|
|
20312
|
+
var EscrowPinUnavailableError = class extends Error {
|
|
20313
|
+
static {
|
|
20314
|
+
__name(this, "EscrowPinUnavailableError");
|
|
20315
|
+
}
|
|
20316
|
+
constructor() {
|
|
20317
|
+
super("PIN recovery is not available for this account.");
|
|
20318
|
+
this.name = "EscrowPinUnavailableError";
|
|
20319
|
+
}
|
|
20320
|
+
};
|
|
20321
|
+
var EscrowPinMismatchError = class extends Error {
|
|
20322
|
+
constructor(attemptsRemaining) {
|
|
20323
|
+
super("Incorrect PIN.");
|
|
20324
|
+
this.attemptsRemaining = attemptsRemaining;
|
|
20325
|
+
this.name = "EscrowPinMismatchError";
|
|
20326
|
+
}
|
|
20327
|
+
static {
|
|
20328
|
+
__name(this, "EscrowPinMismatchError");
|
|
20329
|
+
}
|
|
20330
|
+
};
|
|
20331
|
+
var EscrowPinThrottledError = class extends Error {
|
|
20332
|
+
static {
|
|
20333
|
+
__name(this, "EscrowPinThrottledError");
|
|
20334
|
+
}
|
|
20335
|
+
constructor() {
|
|
20336
|
+
super("Too many tries right now. Wait a minute and try again.");
|
|
20337
|
+
this.name = "EscrowPinThrottledError";
|
|
20338
|
+
}
|
|
20339
|
+
};
|
|
20340
|
+
var EscrowHoldRestartThrottledError = class extends Error {
|
|
20341
|
+
constructor(retryAfter) {
|
|
20342
|
+
super("A recovery request was started recently. Please wait before restarting.");
|
|
20343
|
+
this.retryAfter = retryAfter;
|
|
20344
|
+
this.name = "EscrowHoldRestartThrottledError";
|
|
20345
|
+
}
|
|
20346
|
+
static {
|
|
20347
|
+
__name(this, "EscrowHoldRestartThrottledError");
|
|
20348
|
+
}
|
|
20349
|
+
};
|
|
20350
|
+
var EscrowPinLockedError = class extends Error {
|
|
20351
|
+
static {
|
|
20352
|
+
__name(this, "EscrowPinLockedError");
|
|
20353
|
+
}
|
|
20354
|
+
constructor() {
|
|
20355
|
+
super("Too many incorrect PIN attempts. You can still recover by waiting.");
|
|
20356
|
+
this.name = "EscrowPinLockedError";
|
|
20357
|
+
}
|
|
20358
|
+
};
|
|
20359
|
+
|
|
20360
|
+
// src/escrow-pin.ts
|
|
20361
|
+
var PIN_MIN_LENGTH = 6;
|
|
20362
|
+
var PIN_MAX_LENGTH = 12;
|
|
20363
|
+
var ESCROW_PIN_MAX_ATTEMPTS = 10;
|
|
20364
|
+
var TRIVIAL_PINS = /* @__PURE__ */ new Set([
|
|
20365
|
+
"123456",
|
|
20366
|
+
"654321",
|
|
20367
|
+
"000000",
|
|
20368
|
+
"111111",
|
|
20369
|
+
"121212",
|
|
20370
|
+
"112233",
|
|
20371
|
+
"123123",
|
|
20372
|
+
"696969",
|
|
20373
|
+
"123321"
|
|
20374
|
+
]);
|
|
20375
|
+
var normalizePin = /* @__PURE__ */ __name((raw) => raw.trim().normalize("NFKD").replace(/\D/g, ""), "normalizePin");
|
|
20376
|
+
var validatePin = /* @__PURE__ */ __name((pin) => {
|
|
20377
|
+
const normalized = normalizePin(pin);
|
|
20378
|
+
if (normalized.length < PIN_MIN_LENGTH || normalized.length > PIN_MAX_LENGTH) {
|
|
20379
|
+
return { ok: false, reason: "length" };
|
|
20380
|
+
}
|
|
20381
|
+
if (/^(\d)\1+$/.test(normalized) || "0123456789".includes(normalized) || "9876543210".includes(normalized) || TRIVIAL_PINS.has(normalized)) {
|
|
20382
|
+
return { ok: false, reason: "trivial" };
|
|
20383
|
+
}
|
|
20384
|
+
return { ok: true };
|
|
20385
|
+
}, "validatePin");
|
|
20386
|
+
var generatePinSalt = /* @__PURE__ */ __name(() => bufferToBase64(crypto.getRandomValues(new Uint8Array(16)).buffer), "generatePinSalt");
|
|
20387
|
+
var derivePinProof = /* @__PURE__ */ __name(async (pin, pinSaltBase64) => {
|
|
20388
|
+
const salt = base64ToBuffer(pinSaltBase64);
|
|
20389
|
+
if (salt.length !== 16) throw new Error("Invalid escrow PIN salt length");
|
|
20390
|
+
const key = await deriveKeyFromPassword(normalizePin(pin), salt);
|
|
20391
|
+
try {
|
|
20392
|
+
return bytesToHex(key);
|
|
20393
|
+
} finally {
|
|
20394
|
+
key.fill(0);
|
|
20395
|
+
}
|
|
20396
|
+
}, "derivePinProof");
|
|
20397
|
+
var constantTimeEqualHex = /* @__PURE__ */ __name((a, b) => {
|
|
20398
|
+
if (a.length !== b.length || !/^(?:[0-9a-f]{2})+$/i.test(a) || !/^(?:[0-9a-f]{2})+$/i.test(b)) {
|
|
20399
|
+
return false;
|
|
20400
|
+
}
|
|
20401
|
+
const aBytes = hexToBytes(a);
|
|
20402
|
+
const bBytes = hexToBytes(b);
|
|
20403
|
+
let difference = 0;
|
|
20404
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
20405
|
+
difference |= aBytes[i] ^ bBytes[i];
|
|
20406
|
+
}
|
|
20407
|
+
return difference === 0;
|
|
20408
|
+
}, "constantTimeEqualHex");
|
|
20409
|
+
|
|
20410
|
+
// src/atomic-operations.ts
|
|
20411
|
+
var ShareVerificationError = class extends Error {
|
|
20412
|
+
constructor(message, combination, expected, got) {
|
|
20413
|
+
super(message);
|
|
20414
|
+
this.combination = combination;
|
|
20415
|
+
this.expected = expected;
|
|
20416
|
+
this.got = got;
|
|
20417
|
+
this.name = "ShareVerificationError";
|
|
20418
|
+
}
|
|
20419
|
+
static {
|
|
20420
|
+
__name(this, "ShareVerificationError");
|
|
20421
|
+
}
|
|
20422
|
+
};
|
|
20423
|
+
var AtomicUpdateError = class extends Error {
|
|
20424
|
+
constructor(message, phase, rolledBack, cause) {
|
|
20425
|
+
super(message);
|
|
20426
|
+
this.phase = phase;
|
|
20427
|
+
this.rolledBack = rolledBack;
|
|
20428
|
+
this.cause = cause;
|
|
20429
|
+
this.name = "AtomicUpdateError";
|
|
20430
|
+
}
|
|
20431
|
+
static {
|
|
20432
|
+
__name(this, "AtomicUpdateError");
|
|
20433
|
+
}
|
|
20434
|
+
};
|
|
20435
|
+
var ShareWriteRejectedError = class extends Error {
|
|
20436
|
+
static {
|
|
20437
|
+
__name(this, "ShareWriteRejectedError");
|
|
20438
|
+
}
|
|
20439
|
+
constructor(message) {
|
|
20440
|
+
super(message);
|
|
20441
|
+
this.name = "ShareWriteRejectedError";
|
|
20442
|
+
}
|
|
20443
|
+
};
|
|
20444
|
+
async function splitAndVerify(privateKey) {
|
|
20445
|
+
const shares = await splitPrivateKey(privateKey);
|
|
20446
|
+
const combinations = [
|
|
20447
|
+
["device+auth", shares.deviceShare, shares.authShare],
|
|
20448
|
+
["device+recovery", shares.deviceShare, shares.recoveryShare],
|
|
20449
|
+
["device+email", shares.deviceShare, shares.emailShare],
|
|
20450
|
+
["auth+recovery", shares.authShare, shares.recoveryShare],
|
|
20451
|
+
["auth+email", shares.authShare, shares.emailShare],
|
|
20452
|
+
["recovery+email", shares.recoveryShare, shares.emailShare]
|
|
20453
|
+
];
|
|
20454
|
+
for (const [name, share1, share2] of combinations) {
|
|
20455
|
+
const reconstructed = await reconstructFromShares([share1, share2]);
|
|
20456
|
+
if (reconstructed !== privateKey) {
|
|
20457
|
+
throw new ShareVerificationError(
|
|
20458
|
+
`Share verification failed for ${name}: reconstruction mismatch`,
|
|
20459
|
+
name,
|
|
20460
|
+
privateKey,
|
|
20461
|
+
reconstructed
|
|
20462
|
+
);
|
|
20463
|
+
}
|
|
20464
|
+
}
|
|
20465
|
+
return {
|
|
20466
|
+
privateKey,
|
|
20467
|
+
shares,
|
|
20468
|
+
verified: true
|
|
20469
|
+
};
|
|
20470
|
+
}
|
|
20471
|
+
__name(splitAndVerify, "splitAndVerify");
|
|
20472
|
+
async function atomicShareUpdate(privateKey, storage, options = {}) {
|
|
20473
|
+
let newShares = null;
|
|
20474
|
+
let deviceStored = false;
|
|
20475
|
+
try {
|
|
20476
|
+
const result = await splitAndVerify(privateKey);
|
|
20477
|
+
newShares = result.shares;
|
|
20478
|
+
} catch (e) {
|
|
20479
|
+
throw new AtomicUpdateError(
|
|
20480
|
+
"Failed to split and verify shares",
|
|
20481
|
+
"split",
|
|
20482
|
+
false,
|
|
20483
|
+
e instanceof Error ? e : void 0
|
|
20484
|
+
);
|
|
20485
|
+
}
|
|
20486
|
+
try {
|
|
20487
|
+
await storage.storeDevice(newShares.deviceShare);
|
|
20488
|
+
deviceStored = true;
|
|
20489
|
+
} catch (e) {
|
|
20490
|
+
throw new AtomicUpdateError(
|
|
20491
|
+
"Failed to store device share locally",
|
|
20492
|
+
"store_device",
|
|
20493
|
+
false,
|
|
20494
|
+
e instanceof Error ? e : void 0
|
|
20495
|
+
);
|
|
20496
|
+
}
|
|
20497
|
+
try {
|
|
20498
|
+
await storage.storeAuth(newShares.authShare);
|
|
20499
|
+
} catch (e) {
|
|
20500
|
+
if (!(e instanceof ShareWriteRejectedError)) {
|
|
20501
|
+
throw new AtomicUpdateError(
|
|
20502
|
+
"Auth share write outcome unknown; device share preserved for reconciliation",
|
|
20503
|
+
"store_auth",
|
|
20504
|
+
false,
|
|
20505
|
+
e instanceof Error ? e : void 0
|
|
20506
|
+
);
|
|
20507
|
+
}
|
|
20508
|
+
let rolledBack = false;
|
|
20509
|
+
if (deviceStored) {
|
|
20510
|
+
try {
|
|
20511
|
+
if (options.previousDeviceShare !== void 0) {
|
|
20512
|
+
await storage.storeDevice(options.previousDeviceShare);
|
|
20513
|
+
options.onRollback?.("Server storage failed, restored previous device share");
|
|
20514
|
+
rolledBack = true;
|
|
20515
|
+
} else if (storage.clearDevice) {
|
|
20516
|
+
await storage.clearDevice();
|
|
20517
|
+
options.onRollback?.("Server storage failed, removed new device share");
|
|
20518
|
+
rolledBack = true;
|
|
20519
|
+
}
|
|
20520
|
+
} catch (rollbackError) {
|
|
20521
|
+
console.error(
|
|
20522
|
+
"CRITICAL: Rollback failed after auth share storage failure",
|
|
20523
|
+
rollbackError
|
|
20524
|
+
);
|
|
20525
|
+
}
|
|
20526
|
+
}
|
|
20527
|
+
throw new AtomicUpdateError(
|
|
20528
|
+
rolledBack ? "Failed to store auth share on server, rolled back device share" : "Failed to store auth share on server; rollback did not complete",
|
|
20529
|
+
"store_auth",
|
|
20530
|
+
rolledBack,
|
|
20531
|
+
e instanceof Error ? e : void 0
|
|
20532
|
+
);
|
|
20533
|
+
}
|
|
20534
|
+
return newShares;
|
|
20535
|
+
}
|
|
20536
|
+
__name(atomicShareUpdate, "atomicShareUpdate");
|
|
20537
|
+
async function verifyStoredShares(storage, expectedDid, didFromPrivateKey) {
|
|
20538
|
+
const result = {
|
|
20539
|
+
healthy: false,
|
|
20540
|
+
hasDeviceShare: false,
|
|
20541
|
+
hasAuthShare: false,
|
|
20542
|
+
didMatches: false,
|
|
20543
|
+
error: void 0
|
|
20544
|
+
};
|
|
20545
|
+
try {
|
|
20546
|
+
const deviceShare = await storage.getDevice?.();
|
|
20547
|
+
result.hasDeviceShare = !!deviceShare;
|
|
20548
|
+
if (!deviceShare) {
|
|
19447
20549
|
result.error = "No device share found";
|
|
19448
20550
|
return result;
|
|
19449
20551
|
}
|
|
@@ -19479,12 +20581,16 @@ async function atomicRecovery(recoveryShare, authShareData, storage, options = {
|
|
|
19479
20581
|
__name(atomicRecovery, "atomicRecovery");
|
|
19480
20582
|
|
|
19481
20583
|
// src/sss-strategy.ts
|
|
20584
|
+
var import_types3 = __toESM(require_dist());
|
|
19482
20585
|
var SSS_DB_NAME = "lcb-sss-keys";
|
|
20586
|
+
var DEFAULT_DEVICE_SHARE_ID2 = "sss-device-share";
|
|
20587
|
+
var MAX_RECONCILIATION_HISTORY = 5;
|
|
19483
20588
|
var defaultStorage = {
|
|
19484
20589
|
storeDeviceShare,
|
|
19485
20590
|
getDeviceShare,
|
|
19486
20591
|
hasDeviceShare,
|
|
19487
20592
|
clearAllShares,
|
|
20593
|
+
deleteDeviceShare,
|
|
19488
20594
|
storeShareVersion,
|
|
19489
20595
|
getShareVersion
|
|
19490
20596
|
};
|
|
@@ -19493,10 +20599,24 @@ var buildHeaders = /* @__PURE__ */ __name((token, didAuthVp, tenantId) => ({
|
|
|
19493
20599
|
...didAuthVp ? { Authorization: `Bearer ${didAuthVp}` } : {},
|
|
19494
20600
|
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
19495
20601
|
}), "buildHeaders");
|
|
20602
|
+
var SERVER_REQUEST_TIMEOUT_MS = 3e4;
|
|
20603
|
+
var requestTimeoutSignal = /* @__PURE__ */ __name((ms = SERVER_REQUEST_TIMEOUT_MS) => typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(ms) : void 0, "requestTimeoutSignal");
|
|
20604
|
+
var describeHttpStatus = /* @__PURE__ */ __name((response) => response.statusText ? `${response.status} ${response.statusText}` : `${response.status}`, "describeHttpStatus");
|
|
20605
|
+
var EscrowRequestError = class extends Error {
|
|
20606
|
+
constructor(status) {
|
|
20607
|
+
super("Escrow request failed");
|
|
20608
|
+
this.status = status;
|
|
20609
|
+
this.name = "EscrowRequestError";
|
|
20610
|
+
}
|
|
20611
|
+
static {
|
|
20612
|
+
__name(this, "EscrowRequestError");
|
|
20613
|
+
}
|
|
20614
|
+
};
|
|
19496
20615
|
var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, providerType, shareVersion, tenantId) => {
|
|
19497
20616
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19498
20617
|
method: "POST",
|
|
19499
20618
|
headers: buildHeaders(token, void 0, tenantId),
|
|
20619
|
+
signal: requestTimeoutSignal(),
|
|
19500
20620
|
body: JSON.stringify({
|
|
19501
20621
|
authToken: token,
|
|
19502
20622
|
providerType,
|
|
@@ -19505,42 +20625,103 @@ var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, provider
|
|
|
19505
20625
|
});
|
|
19506
20626
|
if (!response.ok) {
|
|
19507
20627
|
if (response.status === 404) return null;
|
|
19508
|
-
throw new Error(`Failed to fetch key status: ${response
|
|
20628
|
+
throw new Error(`Failed to fetch key status: ${describeHttpStatus(response)}`);
|
|
19509
20629
|
}
|
|
19510
20630
|
return response.json();
|
|
19511
20631
|
}, "fetchAuthShareRaw");
|
|
19512
|
-
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId) => {
|
|
20632
|
+
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId, expectedShareVersion) => {
|
|
19513
20633
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19514
20634
|
method: "PUT",
|
|
19515
20635
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20636
|
+
signal: requestTimeoutSignal(),
|
|
19516
20637
|
body: JSON.stringify({
|
|
19517
20638
|
authToken: token,
|
|
19518
20639
|
providerType,
|
|
19519
20640
|
authShare: { encryptedData: authShare, encryptedDek: "", iv: "" },
|
|
19520
20641
|
primaryDid,
|
|
19521
|
-
securityLevel: "basic"
|
|
20642
|
+
securityLevel: "basic",
|
|
20643
|
+
sssActivationState: "provisional",
|
|
20644
|
+
expectedShareVersion
|
|
19522
20645
|
})
|
|
19523
20646
|
});
|
|
19524
20647
|
if (!response.ok) {
|
|
19525
|
-
|
|
20648
|
+
const message = `Failed to store auth share: ${describeHttpStatus(response)}`;
|
|
20649
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20650
|
+
throw new ShareWriteRejectedError(message);
|
|
20651
|
+
}
|
|
20652
|
+
throw new Error(message);
|
|
19526
20653
|
}
|
|
19527
20654
|
const data = await response.json();
|
|
19528
|
-
return {
|
|
20655
|
+
return {
|
|
20656
|
+
shareVersion: data.shareVersion ?? 1,
|
|
20657
|
+
expectedShareVersionChecked: data.expectedShareVersionChecked === true
|
|
20658
|
+
};
|
|
19529
20659
|
}, "putAuthShare");
|
|
20660
|
+
var authShareToString = /* @__PURE__ */ __name((authShare) => {
|
|
20661
|
+
if (typeof authShare === "string") return authShare;
|
|
20662
|
+
if (typeof authShare === "object" && authShare !== null && "encryptedData" in authShare && typeof authShare.encryptedData === "string") {
|
|
20663
|
+
return authShare.encryptedData;
|
|
20664
|
+
}
|
|
20665
|
+
return null;
|
|
20666
|
+
}, "authShareToString");
|
|
20667
|
+
var requestFreshDidAuthVp = /* @__PURE__ */ __name(async (serverUrl, privateKey, did, signDidAuthVp, tenantId) => {
|
|
20668
|
+
const bootstrapVp = await signDidAuthVp(privateKey);
|
|
20669
|
+
const response = await fetch(`${serverUrl}/keys/challenge`, {
|
|
20670
|
+
method: "POST",
|
|
20671
|
+
headers: buildHeaders("", bootstrapVp, tenantId),
|
|
20672
|
+
signal: requestTimeoutSignal(),
|
|
20673
|
+
body: JSON.stringify({ did })
|
|
20674
|
+
});
|
|
20675
|
+
if (!response.ok) {
|
|
20676
|
+
throw new Error(`Failed to request DID challenge: ${describeHttpStatus(response)}`);
|
|
20677
|
+
}
|
|
20678
|
+
const data = await response.json();
|
|
20679
|
+
if (!data.challenge) throw new Error("DID challenge response was empty");
|
|
20680
|
+
return signDidAuthVp(privateKey, data.challenge);
|
|
20681
|
+
}, "requestFreshDidAuthVp");
|
|
19530
20682
|
var fetchRecoveryShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, type, credentialId, tenantId) => {
|
|
19531
|
-
const params = new URLSearchParams({ type, providerType
|
|
20683
|
+
const params = new URLSearchParams({ type, providerType });
|
|
19532
20684
|
if (credentialId) {
|
|
19533
20685
|
params.set("credentialId", credentialId);
|
|
19534
20686
|
}
|
|
19535
20687
|
const response = await fetch(`${serverUrl}/keys/recovery?${params}`, {
|
|
19536
20688
|
method: "GET",
|
|
19537
|
-
headers:
|
|
20689
|
+
headers: {
|
|
20690
|
+
"Content-Type": "application/json",
|
|
20691
|
+
"X-Auth-Token": token,
|
|
20692
|
+
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
20693
|
+
}
|
|
19538
20694
|
});
|
|
19539
20695
|
if (!response.ok) {
|
|
19540
20696
|
throw new Error(`No ${type} recovery share found`);
|
|
19541
20697
|
}
|
|
19542
20698
|
return response.json();
|
|
19543
20699
|
}, "fetchRecoveryShare");
|
|
20700
|
+
var IdentityRecoverySessionConsumedError = class extends Error {
|
|
20701
|
+
static {
|
|
20702
|
+
__name(this, "IdentityRecoverySessionConsumedError");
|
|
20703
|
+
}
|
|
20704
|
+
constructor(message) {
|
|
20705
|
+
super(message);
|
|
20706
|
+
this.name = "IdentityRecoverySessionConsumedError";
|
|
20707
|
+
}
|
|
20708
|
+
};
|
|
20709
|
+
var postJson = /* @__PURE__ */ __name(async (url, body, headers) => {
|
|
20710
|
+
const response = await fetch(url, {
|
|
20711
|
+
method: "POST",
|
|
20712
|
+
headers,
|
|
20713
|
+
body: JSON.stringify(body)
|
|
20714
|
+
});
|
|
20715
|
+
if (!response.ok) {
|
|
20716
|
+
const data = await response.json().catch(() => null);
|
|
20717
|
+
const message = data?.error?.message || data?.message || response.statusText;
|
|
20718
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20719
|
+
throw new ShareWriteRejectedError(message);
|
|
20720
|
+
}
|
|
20721
|
+
throw new Error(message);
|
|
20722
|
+
}
|
|
20723
|
+
return response.json();
|
|
20724
|
+
}, "postJson");
|
|
19544
20725
|
var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
19545
20726
|
const response = await fetch(`${serverUrl}/keys/recovery`, {
|
|
19546
20727
|
method: "POST",
|
|
@@ -19548,9 +20729,40 @@ var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, provide
|
|
|
19548
20729
|
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
19549
20730
|
});
|
|
19550
20731
|
if (!response.ok) {
|
|
19551
|
-
throw new Error(`Failed to add recovery method: ${response
|
|
20732
|
+
throw new Error(`Failed to add recovery method: ${describeHttpStatus(response)}`);
|
|
19552
20733
|
}
|
|
19553
20734
|
}, "postRecoveryMethod");
|
|
20735
|
+
var postRecoveryConfirmation = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
20736
|
+
const response = await fetch(`${serverUrl}/keys/recovery/confirm`, {
|
|
20737
|
+
method: "POST",
|
|
20738
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20739
|
+
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
20740
|
+
});
|
|
20741
|
+
if (!response.ok) {
|
|
20742
|
+
const data = await response.json().catch(() => null);
|
|
20743
|
+
const message = data?.error?.message || data?.message || `Failed to confirm recovery method`;
|
|
20744
|
+
throw new Error(message);
|
|
20745
|
+
}
|
|
20746
|
+
}, "postRecoveryConfirmation");
|
|
20747
|
+
var calculateShareChecksum = /* @__PURE__ */ __name(async (share) => {
|
|
20748
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(share));
|
|
20749
|
+
return bytesToHex(new Uint8Array(digest));
|
|
20750
|
+
}, "calculateShareChecksum");
|
|
20751
|
+
var RECOVERY_PHRASE_CHALLENGE_COUNT = 2;
|
|
20752
|
+
var selectRecoveryPhraseChallengeIndices = /* @__PURE__ */ __name((wordCount, challengeCount = RECOVERY_PHRASE_CHALLENGE_COUNT) => {
|
|
20753
|
+
if (wordCount < challengeCount) {
|
|
20754
|
+
throw new Error("Recovery phrase does not contain enough words for confirmation");
|
|
20755
|
+
}
|
|
20756
|
+
const indices = /* @__PURE__ */ new Set();
|
|
20757
|
+
const limit = 4294967296 - 4294967296 % wordCount;
|
|
20758
|
+
while (indices.size < challengeCount) {
|
|
20759
|
+
const randomValue = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
20760
|
+
if (randomValue === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
20761
|
+
if (randomValue >= limit) continue;
|
|
20762
|
+
indices.add(randomValue % wordCount);
|
|
20763
|
+
}
|
|
20764
|
+
return [...indices].sort((a, b) => a - b);
|
|
20765
|
+
}, "selectRecoveryPhraseChallengeIndices");
|
|
19554
20766
|
var VERSION_PREFIX_LEN = 4;
|
|
19555
20767
|
var formatVersionedEmailShare = /* @__PURE__ */ __name((emailShare, shareVersion) => shareVersion.toString(16).padStart(VERSION_PREFIX_LEN, "0") + emailShare, "formatVersionedEmailShare");
|
|
19556
20768
|
var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
@@ -19563,44 +20775,512 @@ var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
|
19563
20775
|
}
|
|
19564
20776
|
return { share: input, version: void 0 };
|
|
19565
20777
|
}, "parseVersionedEmailShare");
|
|
19566
|
-
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, shareVersion, tenantId) => {
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
}
|
|
19580
|
-
}, "sendEmailBackupShare");
|
|
19581
|
-
var sendEmailShareToRecoveryEmail = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, shareVersion, tenantId) => {
|
|
19582
|
-
const payload = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20778
|
+
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, escrowRelayPublicKey, escrowRelayKeyId, emailBranding, shareVersion, didAuthVp, tenantId) => {
|
|
20779
|
+
const recoveryKey = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20780
|
+
const confirmationCode = generateEmailRelayConfirmationCode();
|
|
20781
|
+
const relayPayload = await encryptEmailRelayPayload(
|
|
20782
|
+
{
|
|
20783
|
+
targetEmail: email,
|
|
20784
|
+
recoveryKey,
|
|
20785
|
+
confirmationCode,
|
|
20786
|
+
branding: emailBranding
|
|
20787
|
+
},
|
|
20788
|
+
escrowRelayPublicKey,
|
|
20789
|
+
escrowRelayKeyId
|
|
20790
|
+
);
|
|
19583
20791
|
const response = await fetch(`${serverUrl}/keys/email-backup`, {
|
|
19584
20792
|
method: "POST",
|
|
19585
|
-
headers: buildHeaders(token,
|
|
20793
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19586
20794
|
body: JSON.stringify({
|
|
19587
20795
|
authToken: token,
|
|
19588
20796
|
providerType,
|
|
19589
|
-
|
|
19590
|
-
|
|
20797
|
+
relayPayload,
|
|
20798
|
+
confirmationCode,
|
|
20799
|
+
email,
|
|
20800
|
+
shareVersion
|
|
19591
20801
|
})
|
|
19592
20802
|
});
|
|
19593
20803
|
if (!response.ok) {
|
|
19594
|
-
throw new Error(`Failed to send recovery share
|
|
20804
|
+
throw new Error(`Failed to send recovery share: ${describeHttpStatus(response)}`);
|
|
20805
|
+
}
|
|
20806
|
+
}, "sendEmailBackupShare");
|
|
20807
|
+
var rotationChain = Promise.resolve();
|
|
20808
|
+
var withRotationLock = /* @__PURE__ */ __name((operation) => {
|
|
20809
|
+
const run = rotationChain.then(operation, operation);
|
|
20810
|
+
rotationChain = run.catch(() => void 0);
|
|
20811
|
+
return run;
|
|
20812
|
+
}, "withRotationLock");
|
|
20813
|
+
var pendingShareId = /* @__PURE__ */ __name((storageId) => `sss-pending-share:${storageId ?? DEFAULT_DEVICE_SHARE_ID2}`, "pendingShareId");
|
|
20814
|
+
var PENDING_WRITE_EXPIRY_MS = 10 * 60 * 1e3;
|
|
20815
|
+
var MAX_PENDING_SHARE_CANDIDATES = 8;
|
|
20816
|
+
var readPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => {
|
|
20817
|
+
const raw = await storage.getDeviceShare(pendingShareId(storageId));
|
|
20818
|
+
if (!raw) return [];
|
|
20819
|
+
if (!raw.trimStart().startsWith("{")) return [{ share: raw, createdAt: 0 }];
|
|
20820
|
+
const data = JSON.parse(raw);
|
|
20821
|
+
if (!data || typeof data !== "object" || !("v" in data) || data.v !== 1 || !("candidates" in data) || !Array.isArray(data.candidates) || !data.candidates.every(
|
|
20822
|
+
(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
|
|
20823
|
+
))
|
|
20824
|
+
throw new Error("Invalid pending share candidates");
|
|
20825
|
+
return data.candidates;
|
|
20826
|
+
}, "readPendingShareCandidates");
|
|
20827
|
+
var clearPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => storage.clearAllShares(pendingShareId(storageId)), "clearPendingShareCandidates");
|
|
20828
|
+
var writePendingShareCandidates = /* @__PURE__ */ __name(async (storage, candidates, storageId) => {
|
|
20829
|
+
if (!candidates.length) return clearPendingShareCandidates(storage, storageId);
|
|
20830
|
+
await storage.storeDeviceShare(JSON.stringify({ v: 1, candidates }), pendingShareId(storageId));
|
|
20831
|
+
}, "writePendingShareCandidates");
|
|
20832
|
+
var addPendingShareCandidate = /* @__PURE__ */ __name(async (storage, candidate, storageId) => {
|
|
20833
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20834
|
+
if (candidates.length >= MAX_PENDING_SHARE_CANDIDATES) {
|
|
20835
|
+
throw new AtomicUpdateError(
|
|
20836
|
+
"Reconcile the pending share update before writing new shares",
|
|
20837
|
+
"store_device",
|
|
20838
|
+
false
|
|
20839
|
+
);
|
|
20840
|
+
}
|
|
20841
|
+
await writePendingShareCandidates(storage, [...candidates, candidate], storageId);
|
|
20842
|
+
}, "addPendingShareCandidate");
|
|
20843
|
+
var removePendingShareCandidate = /* @__PURE__ */ __name(async (storage, share, storageId) => {
|
|
20844
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20845
|
+
await writePendingShareCandidates(
|
|
20846
|
+
storage,
|
|
20847
|
+
candidates.filter((candidate) => candidate.share !== share),
|
|
20848
|
+
storageId
|
|
20849
|
+
);
|
|
20850
|
+
}, "removePendingShareCandidate");
|
|
20851
|
+
var resolvePendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId, fetchCurrent, expectedDid, didFromPrivateKey, assertActiveUser = () => {
|
|
20852
|
+
}, onPromoted, fetchHistorical) => {
|
|
20853
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20854
|
+
const snapshot = await fetchCurrent();
|
|
20855
|
+
assertActiveUser();
|
|
20856
|
+
const { authShare, currentVersion } = snapshot;
|
|
20857
|
+
if (authShare && (currentVersion === null || !Number.isInteger(currentVersion) || currentVersion < 1)) {
|
|
20858
|
+
throw new Error("Current auth share is missing a valid share version");
|
|
19595
20859
|
}
|
|
19596
|
-
|
|
20860
|
+
if (authShare && currentVersion !== null) {
|
|
20861
|
+
for (const candidate of candidates) {
|
|
20862
|
+
const health = await verifyStoredShares(
|
|
20863
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth") },
|
|
20864
|
+
expectedDid,
|
|
20865
|
+
didFromPrivateKey
|
|
20866
|
+
);
|
|
20867
|
+
assertActiveUser();
|
|
20868
|
+
if (!health.healthy) continue;
|
|
20869
|
+
const privateKey = await reconstructFromShares([candidate.share, authShare]);
|
|
20870
|
+
assertActiveUser();
|
|
20871
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20872
|
+
await storage.storeShareVersion(currentVersion, storageId);
|
|
20873
|
+
await removePendingShareCandidate(storage, candidate.share, storageId);
|
|
20874
|
+
assertActiveUser();
|
|
20875
|
+
onPromoted?.(snapshot);
|
|
20876
|
+
return { snapshot, privateKey };
|
|
20877
|
+
}
|
|
20878
|
+
}
|
|
20879
|
+
const matched = /* @__PURE__ */ new Set();
|
|
20880
|
+
const history = /* @__PURE__ */ new Map();
|
|
20881
|
+
let historyComplete = true;
|
|
20882
|
+
if (candidates.length && currentVersion !== null) {
|
|
20883
|
+
for (let version = currentVersion - 1; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
20884
|
+
if (!fetchHistorical) {
|
|
20885
|
+
historyComplete = false;
|
|
20886
|
+
break;
|
|
20887
|
+
}
|
|
20888
|
+
try {
|
|
20889
|
+
const share = await fetchHistorical(version);
|
|
20890
|
+
if (share) history.set(version, share);
|
|
20891
|
+
} catch {
|
|
20892
|
+
historyComplete = false;
|
|
20893
|
+
}
|
|
20894
|
+
assertActiveUser();
|
|
20895
|
+
}
|
|
20896
|
+
}
|
|
20897
|
+
const mainShare = await storage.getDeviceShare(storageId);
|
|
20898
|
+
let mainHealthy = false;
|
|
20899
|
+
const retainedShares = [...history.values(), ...authShare ? [authShare] : []];
|
|
20900
|
+
if (mainShare) {
|
|
20901
|
+
for (const share of retainedShares) {
|
|
20902
|
+
const health = await verifyStoredShares(
|
|
20903
|
+
{ getDevice: /* @__PURE__ */ __name(async () => mainShare, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20904
|
+
expectedDid,
|
|
20905
|
+
didFromPrivateKey
|
|
20906
|
+
);
|
|
20907
|
+
assertActiveUser();
|
|
20908
|
+
if (health.healthy) {
|
|
20909
|
+
mainHealthy = true;
|
|
20910
|
+
break;
|
|
20911
|
+
}
|
|
20912
|
+
}
|
|
20913
|
+
}
|
|
20914
|
+
let promoted;
|
|
20915
|
+
for (const candidate of candidates) {
|
|
20916
|
+
for (const [version, share] of history) {
|
|
20917
|
+
const health = await verifyStoredShares(
|
|
20918
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20919
|
+
expectedDid,
|
|
20920
|
+
didFromPrivateKey
|
|
20921
|
+
);
|
|
20922
|
+
assertActiveUser();
|
|
20923
|
+
if (!health.healthy) continue;
|
|
20924
|
+
matched.add(candidate.share);
|
|
20925
|
+
if (!mainHealthy && !promoted) {
|
|
20926
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20927
|
+
await storage.storeShareVersion(version, storageId);
|
|
20928
|
+
assertActiveUser();
|
|
20929
|
+
onPromoted?.({ currentVersion: version, authShare: share });
|
|
20930
|
+
promoted = candidate.share;
|
|
20931
|
+
}
|
|
20932
|
+
break;
|
|
20933
|
+
}
|
|
20934
|
+
}
|
|
20935
|
+
const live = candidates.filter(
|
|
20936
|
+
(candidate) => candidate.share !== promoted && (matched.has(candidate.share) || !historyComplete || candidate.createdAt !== 0 && Date.now() - candidate.createdAt <= PENDING_WRITE_EXPIRY_MS)
|
|
20937
|
+
);
|
|
20938
|
+
if (live.length !== candidates.length) {
|
|
20939
|
+
assertActiveUser();
|
|
20940
|
+
await writePendingShareCandidates(storage, live, storageId);
|
|
20941
|
+
}
|
|
20942
|
+
return { snapshot, privateKey: null };
|
|
20943
|
+
}, "resolvePendingShareCandidates");
|
|
20944
|
+
var activeShareUpdates = /* @__PURE__ */ new WeakMap();
|
|
20945
|
+
var shareUpdateStorageOwners = /* @__PURE__ */ new WeakMap();
|
|
20946
|
+
var withShareUpdateLock = /* @__PURE__ */ __name(async (storage, storageId, operation) => {
|
|
20947
|
+
const id = pendingShareId(storageId);
|
|
20948
|
+
const owner = shareUpdateStorageOwners.get(storage) ?? storage;
|
|
20949
|
+
const active = activeShareUpdates.get(owner) ?? /* @__PURE__ */ new Set();
|
|
20950
|
+
activeShareUpdates.set(owner, active);
|
|
20951
|
+
const busy = /* @__PURE__ */ __name(() => new AtomicUpdateError("A share update is already in progress", "store_device", false), "busy");
|
|
20952
|
+
if (active.has(id)) throw busy();
|
|
20953
|
+
active.add(id);
|
|
20954
|
+
try {
|
|
20955
|
+
if (typeof navigator !== "undefined" && navigator.locks) {
|
|
20956
|
+
return await navigator.locks.request(id, { ifAvailable: true }, async (lock) => {
|
|
20957
|
+
if (!lock) throw busy();
|
|
20958
|
+
return operation();
|
|
20959
|
+
});
|
|
20960
|
+
}
|
|
20961
|
+
return await operation();
|
|
20962
|
+
} finally {
|
|
20963
|
+
active.delete(id);
|
|
20964
|
+
}
|
|
20965
|
+
}, "withShareUpdateLock");
|
|
20966
|
+
var persistSharesWithoutLock = /* @__PURE__ */ __name(async (privateKey, serverUrl, token, providerType, primaryDid, storage, storageId, didFromPrivateKey, signDidAuthVp, tenantId, onPromoted, assertActiveUser = () => {
|
|
20967
|
+
}) => {
|
|
20968
|
+
if (primaryDid && didFromPrivateKey) {
|
|
20969
|
+
const derivedDid = await didFromPrivateKey(privateKey);
|
|
20970
|
+
if (derivedDid && derivedDid !== primaryDid) {
|
|
20971
|
+
throw new Error(
|
|
20972
|
+
"rotateShares: key does not match expected DID \u2014 refusing to overwrite server shares"
|
|
20973
|
+
);
|
|
20974
|
+
}
|
|
20975
|
+
}
|
|
20976
|
+
const { snapshot } = await resolvePendingShareCandidates(
|
|
20977
|
+
storage,
|
|
20978
|
+
storageId,
|
|
20979
|
+
async () => {
|
|
20980
|
+
const data = await fetchAuthShareRaw(
|
|
20981
|
+
serverUrl,
|
|
20982
|
+
token,
|
|
20983
|
+
providerType,
|
|
20984
|
+
void 0,
|
|
20985
|
+
tenantId
|
|
20986
|
+
);
|
|
20987
|
+
return {
|
|
20988
|
+
currentVersion: data?.shareVersion ?? null,
|
|
20989
|
+
authShare: authShareToString(data?.authShare)
|
|
20990
|
+
};
|
|
20991
|
+
},
|
|
20992
|
+
primaryDid,
|
|
20993
|
+
didFromPrivateKey ?? (async (key) => key === privateKey ? primaryDid : `${primaryDid}:mismatch`),
|
|
20994
|
+
assertActiveUser,
|
|
20995
|
+
onPromoted,
|
|
20996
|
+
async (version) => authShareToString(
|
|
20997
|
+
(await fetchAuthShareRaw(serverUrl, token, providerType, version, tenantId))?.authShare
|
|
20998
|
+
)
|
|
20999
|
+
);
|
|
21000
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(serverUrl, privateKey, primaryDid, signDidAuthVp, tenantId) : void 0;
|
|
21001
|
+
assertActiveUser();
|
|
21002
|
+
let shareVersion;
|
|
21003
|
+
let stagedShare = "";
|
|
21004
|
+
const shares = await atomicShareUpdate(privateKey, {
|
|
21005
|
+
storeDevice: /* @__PURE__ */ __name(async (share) => {
|
|
21006
|
+
assertActiveUser();
|
|
21007
|
+
stagedShare = share;
|
|
21008
|
+
await addPendingShareCandidate(storage, { share, createdAt: Date.now() }, storageId);
|
|
21009
|
+
}, "storeDevice"),
|
|
21010
|
+
clearDevice: /* @__PURE__ */ __name(() => removePendingShareCandidate(storage, stagedShare, storageId), "clearDevice"),
|
|
21011
|
+
storeAuth: /* @__PURE__ */ __name(async (share) => {
|
|
21012
|
+
assertActiveUser();
|
|
21013
|
+
const result = await putAuthShare(
|
|
21014
|
+
serverUrl,
|
|
21015
|
+
token,
|
|
21016
|
+
providerType,
|
|
21017
|
+
share,
|
|
21018
|
+
primaryDid,
|
|
21019
|
+
didAuthVp,
|
|
21020
|
+
tenantId,
|
|
21021
|
+
snapshot.currentVersion ?? 0
|
|
21022
|
+
);
|
|
21023
|
+
shareVersion = result.shareVersion;
|
|
21024
|
+
}, "storeAuth")
|
|
21025
|
+
});
|
|
21026
|
+
if (shareVersion === void 0) {
|
|
21027
|
+
throw new Error("Server did not return a share version");
|
|
21028
|
+
}
|
|
21029
|
+
await storage.storeDeviceShare(shares.deviceShare, storageId);
|
|
21030
|
+
await storage.storeShareVersion(shareVersion, storageId);
|
|
21031
|
+
await removePendingShareCandidate(storage, shares.deviceShare, storageId);
|
|
21032
|
+
assertActiveUser();
|
|
21033
|
+
return { shares, shareVersion };
|
|
21034
|
+
}, "persistSharesWithoutLock");
|
|
21035
|
+
var persistSharesAtomically = /* @__PURE__ */ __name((...args) => withShareUpdateLock(
|
|
21036
|
+
args[5],
|
|
21037
|
+
args[6],
|
|
21038
|
+
() => withRotationLock(() => persistSharesWithoutLock(...args))
|
|
21039
|
+
), "persistSharesAtomically");
|
|
19597
21040
|
function createSSSStrategy(config) {
|
|
19598
|
-
const {
|
|
21041
|
+
const {
|
|
21042
|
+
serverUrl,
|
|
21043
|
+
enableEmailBackupShare = false,
|
|
21044
|
+
escrowRelayPublicKey = "",
|
|
21045
|
+
escrowRelayKeyId = "",
|
|
21046
|
+
emailBranding,
|
|
21047
|
+
tenantId
|
|
21048
|
+
} = config;
|
|
19599
21049
|
const storage = config.storage || defaultStorage;
|
|
21050
|
+
let storageGeneration = 0;
|
|
21051
|
+
const localWrites = /* @__PURE__ */ new Set();
|
|
21052
|
+
const guardedStorage = /* @__PURE__ */ __name(() => {
|
|
21053
|
+
const generation = storageGeneration;
|
|
21054
|
+
const write = /* @__PURE__ */ __name((operation) => {
|
|
21055
|
+
if (generation !== storageGeneration) {
|
|
21056
|
+
return Promise.reject(new Error("This recovery request was cancelled."));
|
|
21057
|
+
}
|
|
21058
|
+
const pending = operation();
|
|
21059
|
+
localWrites.add(pending);
|
|
21060
|
+
return pending.finally(() => localWrites.delete(pending));
|
|
21061
|
+
}, "write");
|
|
21062
|
+
const guarded = {
|
|
21063
|
+
...storage,
|
|
21064
|
+
storeDeviceShare: /* @__PURE__ */ __name((share, id) => write(() => storage.storeDeviceShare(share, id)), "storeDeviceShare"),
|
|
21065
|
+
storeShareVersion: /* @__PURE__ */ __name((version, id) => write(() => storage.storeShareVersion(version, id)), "storeShareVersion"),
|
|
21066
|
+
clearAllShares: /* @__PURE__ */ __name((id) => write(() => storage.clearAllShares(id)), "clearAllShares"),
|
|
21067
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => write(() => storage.deleteDeviceShare(id)), "deleteDeviceShare")
|
|
21068
|
+
};
|
|
21069
|
+
shareUpdateStorageOwners.set(guarded, storage);
|
|
21070
|
+
return guarded;
|
|
21071
|
+
}, "guardedStorage");
|
|
19600
21072
|
let activeStorageId;
|
|
19601
21073
|
let lastEmailShare;
|
|
19602
21074
|
let lastShareVersion;
|
|
19603
|
-
let
|
|
21075
|
+
let pendingPhraseConfirmation;
|
|
21076
|
+
let pendingBackupConfirmation;
|
|
21077
|
+
let pendingIdentityRecovery;
|
|
21078
|
+
let lastServerSnapshot = null;
|
|
21079
|
+
let escrowEnrollmentInFlight;
|
|
21080
|
+
let unenrolledRotation;
|
|
21081
|
+
const escrowRequest = /* @__PURE__ */ __name(async (path, init, pinAttempt = false) => {
|
|
21082
|
+
const response = await fetch(`${serverUrl}/keys/escrow${path}`, {
|
|
21083
|
+
signal: requestTimeoutSignal(),
|
|
21084
|
+
...init
|
|
21085
|
+
});
|
|
21086
|
+
if (!response.ok) {
|
|
21087
|
+
if (!pinAttempt && path === "/recover" && response.status === 429) {
|
|
21088
|
+
const body = await response.json().catch(() => void 0);
|
|
21089
|
+
const message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : "";
|
|
21090
|
+
const retryAfter = /Try again after (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\./.exec(
|
|
21091
|
+
message
|
|
21092
|
+
)?.[1];
|
|
21093
|
+
throw new EscrowHoldRestartThrottledError(
|
|
21094
|
+
retryAfter && Number.isFinite(Date.parse(retryAfter)) ? retryAfter : void 0
|
|
21095
|
+
);
|
|
21096
|
+
}
|
|
21097
|
+
if (pinAttempt && response.status === 429) {
|
|
21098
|
+
const body = await response.json().catch(() => void 0);
|
|
21099
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_LOCKED_MESSAGE) {
|
|
21100
|
+
throw new EscrowPinLockedError();
|
|
21101
|
+
}
|
|
21102
|
+
throw new EscrowPinThrottledError();
|
|
21103
|
+
}
|
|
21104
|
+
if (pinAttempt && response.status === 403) {
|
|
21105
|
+
const body = await response.json().catch(() => void 0);
|
|
21106
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_UNAVAILABLE_MESSAGE) {
|
|
21107
|
+
throw new EscrowPinUnavailableError();
|
|
21108
|
+
}
|
|
21109
|
+
if (body && typeof body === "object" && "message" in body && typeof body.message === "string" && body.message.startsWith("Incorrect PIN.")) {
|
|
21110
|
+
const match = import_types3.ESCROW_PIN_MISMATCH_PATTERN.exec(body.message);
|
|
21111
|
+
const remaining = match ? Number(match[1]) : 0;
|
|
21112
|
+
throw new EscrowPinMismatchError(
|
|
21113
|
+
Number.isSafeInteger(remaining) && remaining >= 0 && remaining < ESCROW_PIN_MAX_ATTEMPTS ? remaining : 0
|
|
21114
|
+
);
|
|
21115
|
+
}
|
|
21116
|
+
}
|
|
21117
|
+
throw new EscrowRequestError(response.status);
|
|
21118
|
+
}
|
|
21119
|
+
try {
|
|
21120
|
+
return await response.json();
|
|
21121
|
+
} catch {
|
|
21122
|
+
throw new EscrowRequestError(response.status);
|
|
21123
|
+
}
|
|
21124
|
+
}, "escrowRequest");
|
|
21125
|
+
const fetchVerifiedEnclaveKey = /* @__PURE__ */ __name(async () => {
|
|
21126
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21127
|
+
const { attestation } = await escrowRequest("/attestation", {
|
|
21128
|
+
method: "GET",
|
|
21129
|
+
headers: buildHeaders("", void 0, tenantId)
|
|
21130
|
+
});
|
|
21131
|
+
return verifyEnclaveAttestation(attestation, config.escrow.attestation);
|
|
21132
|
+
}, "fetchVerifiedEnclaveKey");
|
|
21133
|
+
const enrollEscrow = /* @__PURE__ */ __name(async (token, providerType, privateKey, primaryDid, shares, shareVersion, signDidAuthVp, verifiedKey, pinMaterial, clearPin = false) => {
|
|
21134
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21135
|
+
if (!signDidAuthVp) throw new Error("DID proof signing is required for escrow enrollment");
|
|
21136
|
+
const { publicKey, keyId } = verifiedKey ?? await fetchVerifiedEnclaveKey();
|
|
21137
|
+
const envelope = await encryptEscrowBlob(
|
|
21138
|
+
{
|
|
21139
|
+
recoveryShare: shares.recoveryShare,
|
|
21140
|
+
did: primaryDid,
|
|
21141
|
+
shareVersion,
|
|
21142
|
+
...pinMaterial ? { pinVerifier: pinMaterial.pinVerifier } : {}
|
|
21143
|
+
},
|
|
21144
|
+
publicKey,
|
|
21145
|
+
keyId
|
|
21146
|
+
);
|
|
21147
|
+
const vp = await requestFreshDidAuthVp(
|
|
21148
|
+
serverUrl,
|
|
21149
|
+
privateKey,
|
|
21150
|
+
primaryDid,
|
|
21151
|
+
signDidAuthVp,
|
|
21152
|
+
tenantId
|
|
21153
|
+
);
|
|
21154
|
+
await escrowRequest("", {
|
|
21155
|
+
method: "POST",
|
|
21156
|
+
headers: buildHeaders(token, vp, tenantId),
|
|
21157
|
+
body: JSON.stringify({
|
|
21158
|
+
authToken: token,
|
|
21159
|
+
providerType,
|
|
21160
|
+
envelope,
|
|
21161
|
+
shareVersion,
|
|
21162
|
+
enclaveKeyId: keyId,
|
|
21163
|
+
...pinMaterial ? { pinSalt: pinMaterial.pinSalt } : {},
|
|
21164
|
+
...clearPin ? { clearPin: true } : {}
|
|
21165
|
+
})
|
|
21166
|
+
});
|
|
21167
|
+
}, "enrollEscrow");
|
|
21168
|
+
const tryEnrollEscrow = /* @__PURE__ */ __name(async (...args) => {
|
|
21169
|
+
if (!config.escrow?.enabled) return;
|
|
21170
|
+
try {
|
|
21171
|
+
await enrollEscrow(...args);
|
|
21172
|
+
} catch (err) {
|
|
21173
|
+
try {
|
|
21174
|
+
if (config.onEscrowError) config.onEscrowError(err);
|
|
21175
|
+
else console.warn("SSS: escrow enrollment failed");
|
|
21176
|
+
} catch {
|
|
21177
|
+
console.warn("SSS: escrow error reporting failed");
|
|
21178
|
+
}
|
|
21179
|
+
}
|
|
21180
|
+
}, "tryEnrollEscrow");
|
|
21181
|
+
const ensureEscrowEnrollmentUnguarded = /* @__PURE__ */ __name(async (self, params, storage2, forceRotate = false) => {
|
|
21182
|
+
if (!config.escrow?.enabled) return { enrolled: false, reason: "disabled" };
|
|
21183
|
+
const pin = params.options?.pin;
|
|
21184
|
+
if (pin !== void 0) {
|
|
21185
|
+
const validation = validatePin(pin);
|
|
21186
|
+
if (!validation.ok)
|
|
21187
|
+
throw new Error(
|
|
21188
|
+
validation.reason === "length" ? "PIN must contain 6\u201312 digits." : "Choose a PIN without trivial or sequential patterns."
|
|
21189
|
+
);
|
|
21190
|
+
}
|
|
21191
|
+
const status = await self.fetchServerKeyStatus(params.token, params.providerType);
|
|
21192
|
+
if (status.escrowOptedOut) return { enrolled: false, reason: "opted-out" };
|
|
21193
|
+
if (!forceRotate && pin === void 0 && status.shareVersion !== null && status.recoveryMethods.some(
|
|
21194
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21195
|
+
))
|
|
21196
|
+
return { enrolled: true, changed: false };
|
|
21197
|
+
if (!status.primaryDid) throw new Error("Cannot enroll escrow without a primary DID");
|
|
21198
|
+
if (!params.signDidAuthVp) {
|
|
21199
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21200
|
+
}
|
|
21201
|
+
if (!forceRotate && pin === void 0 && unenrolledRotation && unenrolledRotation.shareVersion === status.shareVersion && unenrolledRotation.primaryDid === status.primaryDid) {
|
|
21202
|
+
const retry = unenrolledRotation;
|
|
21203
|
+
await enrollEscrow(
|
|
21204
|
+
params.token,
|
|
21205
|
+
params.providerType,
|
|
21206
|
+
params.privateKey,
|
|
21207
|
+
retry.primaryDid,
|
|
21208
|
+
retry.shares,
|
|
21209
|
+
retry.shareVersion,
|
|
21210
|
+
params.signDidAuthVp,
|
|
21211
|
+
void 0,
|
|
21212
|
+
void 0,
|
|
21213
|
+
retry.clearPin
|
|
21214
|
+
);
|
|
21215
|
+
unenrolledRotation = void 0;
|
|
21216
|
+
return { enrolled: true, changed: true, shareVersion: retry.shareVersion };
|
|
21217
|
+
}
|
|
21218
|
+
const verifiedKey = await fetchVerifiedEnclaveKey();
|
|
21219
|
+
const pinSalt = pin !== void 0 ? generatePinSalt() : void 0;
|
|
21220
|
+
const pinMaterial = pin !== void 0 && pinSalt !== void 0 ? { pinSalt, pinVerifier: await derivePinProof(pin, pinSalt) } : void 0;
|
|
21221
|
+
const storageId = activeStorageId;
|
|
21222
|
+
const primaryDid = status.primaryDid;
|
|
21223
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
21224
|
+
storage2,
|
|
21225
|
+
storageId,
|
|
21226
|
+
() => persistSharesWithoutLock(
|
|
21227
|
+
params.privateKey,
|
|
21228
|
+
serverUrl,
|
|
21229
|
+
params.token,
|
|
21230
|
+
params.providerType,
|
|
21231
|
+
primaryDid,
|
|
21232
|
+
storage2,
|
|
21233
|
+
storageId,
|
|
21234
|
+
void 0,
|
|
21235
|
+
params.signDidAuthVp,
|
|
21236
|
+
tenantId
|
|
21237
|
+
)
|
|
21238
|
+
);
|
|
21239
|
+
lastEmailShare = shares.emailShare;
|
|
21240
|
+
lastShareVersion = shareVersion;
|
|
21241
|
+
lastServerSnapshot = {
|
|
21242
|
+
currentVersion: shareVersion,
|
|
21243
|
+
resolvedVersion: shareVersion,
|
|
21244
|
+
authShare: shares.authShare,
|
|
21245
|
+
primaryDid: status.primaryDid
|
|
21246
|
+
};
|
|
21247
|
+
unenrolledRotation = pinMaterial ? void 0 : { shares, shareVersion, primaryDid: status.primaryDid, clearPin: forceRotate };
|
|
21248
|
+
await enrollEscrow(
|
|
21249
|
+
params.token,
|
|
21250
|
+
params.providerType,
|
|
21251
|
+
params.privateKey,
|
|
21252
|
+
status.primaryDid,
|
|
21253
|
+
shares,
|
|
21254
|
+
shareVersion,
|
|
21255
|
+
params.signDidAuthVp,
|
|
21256
|
+
verifiedKey,
|
|
21257
|
+
pinMaterial,
|
|
21258
|
+
forceRotate
|
|
21259
|
+
);
|
|
21260
|
+
unenrolledRotation = void 0;
|
|
21261
|
+
return { enrolled: true, changed: true, shareVersion };
|
|
21262
|
+
}, "ensureEscrowEnrollmentUnguarded");
|
|
21263
|
+
const recordPromotion = /* @__PURE__ */ __name((primaryDid) => (snapshot) => {
|
|
21264
|
+
lastEmailShare = void 0;
|
|
21265
|
+
lastShareVersion = snapshot.currentVersion ?? void 0;
|
|
21266
|
+
lastServerSnapshot = {
|
|
21267
|
+
...snapshot,
|
|
21268
|
+
resolvedVersion: snapshot.currentVersion,
|
|
21269
|
+
primaryDid
|
|
21270
|
+
};
|
|
21271
|
+
}, "recordPromotion");
|
|
21272
|
+
const captureActiveUserGuard = /* @__PURE__ */ __name(() => {
|
|
21273
|
+
const storageId = activeStorageId;
|
|
21274
|
+
return () => {
|
|
21275
|
+
if (activeStorageId !== storageId) {
|
|
21276
|
+
throw new AtomicUpdateError(
|
|
21277
|
+
"Active account changed during share update",
|
|
21278
|
+
"verify_stored",
|
|
21279
|
+
false
|
|
21280
|
+
);
|
|
21281
|
+
}
|
|
21282
|
+
};
|
|
21283
|
+
}, "captureActiveUserGuard");
|
|
19604
21284
|
return {
|
|
19605
21285
|
name: "sss",
|
|
19606
21286
|
capabilities: {
|
|
@@ -19611,23 +21291,31 @@ function createSSSStrategy(config) {
|
|
|
19611
21291
|
},
|
|
19612
21292
|
// --- User scoping ---
|
|
19613
21293
|
setActiveUser(userId) {
|
|
21294
|
+
if (activeStorageId !== `sss-device-share:${userId}`) storageGeneration++;
|
|
19614
21295
|
activeStorageId = `sss-device-share:${userId}`;
|
|
21296
|
+
lastServerSnapshot = null;
|
|
21297
|
+
pendingPhraseConfirmation = void 0;
|
|
21298
|
+
pendingBackupConfirmation = void 0;
|
|
19615
21299
|
},
|
|
19616
21300
|
// --- Key lifecycle ---
|
|
19617
21301
|
async hasLocalKey() {
|
|
19618
21302
|
if (await storage.hasDeviceShare(activeStorageId)) return true;
|
|
21303
|
+
if ((await readPendingShareCandidates(storage, activeStorageId)).length) return true;
|
|
19619
21304
|
if (activeStorageId) {
|
|
19620
21305
|
return storage.hasDeviceShare();
|
|
19621
21306
|
}
|
|
19622
21307
|
return false;
|
|
19623
21308
|
},
|
|
19624
21309
|
async getLocalKey() {
|
|
19625
|
-
const
|
|
21310
|
+
const storage2 = guardedStorage();
|
|
21311
|
+
const scoped = await storage2.getDeviceShare(activeStorageId);
|
|
19626
21312
|
if (scoped) return scoped;
|
|
21313
|
+
const pending = await readPendingShareCandidates(storage2, activeStorageId);
|
|
21314
|
+
if (pending.length) return pending[pending.length - 1].share;
|
|
19627
21315
|
if (activeStorageId) {
|
|
19628
|
-
const legacy = await
|
|
21316
|
+
const legacy = await storage2.getDeviceShare();
|
|
19629
21317
|
if (legacy) {
|
|
19630
|
-
await
|
|
21318
|
+
await storage2.storeDeviceShare(legacy, activeStorageId);
|
|
19631
21319
|
return legacy;
|
|
19632
21320
|
}
|
|
19633
21321
|
}
|
|
@@ -19636,8 +21324,20 @@ function createSSSStrategy(config) {
|
|
|
19636
21324
|
async storeLocalKey(key) {
|
|
19637
21325
|
return storage.storeDeviceShare(key, activeStorageId);
|
|
19638
21326
|
},
|
|
19639
|
-
async clearLocalKeys() {
|
|
19640
|
-
|
|
21327
|
+
async clearLocalKeys(options) {
|
|
21328
|
+
const storageId = activeStorageId;
|
|
21329
|
+
if (options?.preservePending) {
|
|
21330
|
+
const storage2 = guardedStorage();
|
|
21331
|
+
return withShareUpdateLock(
|
|
21332
|
+
storage2,
|
|
21333
|
+
storageId,
|
|
21334
|
+
() => storage2.deleteDeviceShare(storageId)
|
|
21335
|
+
);
|
|
21336
|
+
}
|
|
21337
|
+
storageGeneration++;
|
|
21338
|
+
await Promise.allSettled(localWrites);
|
|
21339
|
+
await clearPendingShareCandidates(storage, storageId);
|
|
21340
|
+
return storage.clearAllShares(storageId);
|
|
19641
21341
|
},
|
|
19642
21342
|
async splitKey(privateKey) {
|
|
19643
21343
|
const { shares } = await splitAndVerify(privateKey);
|
|
@@ -19661,9 +21361,175 @@ function createSSSStrategy(config) {
|
|
|
19661
21361
|
);
|
|
19662
21362
|
return result.healthy;
|
|
19663
21363
|
},
|
|
21364
|
+
async getFreshDidAuthVp(privateKey, did, signDidAuthVp) {
|
|
21365
|
+
return requestFreshDidAuthVp(serverUrl, privateKey, did, signDidAuthVp, tenantId);
|
|
21366
|
+
},
|
|
21367
|
+
async atomicUpdateShares(params) {
|
|
21368
|
+
const storage2 = guardedStorage();
|
|
21369
|
+
const result = await persistSharesAtomically(
|
|
21370
|
+
params.privateKey,
|
|
21371
|
+
serverUrl,
|
|
21372
|
+
params.token,
|
|
21373
|
+
params.providerType,
|
|
21374
|
+
params.did,
|
|
21375
|
+
storage2,
|
|
21376
|
+
activeStorageId,
|
|
21377
|
+
void 0,
|
|
21378
|
+
params.signDidAuthVp,
|
|
21379
|
+
tenantId,
|
|
21380
|
+
recordPromotion(params.did),
|
|
21381
|
+
captureActiveUserGuard()
|
|
21382
|
+
);
|
|
21383
|
+
lastEmailShare = result.shares.emailShare;
|
|
21384
|
+
lastShareVersion = result.shareVersion;
|
|
21385
|
+
lastServerSnapshot = {
|
|
21386
|
+
currentVersion: result.shareVersion,
|
|
21387
|
+
resolvedVersion: result.shareVersion,
|
|
21388
|
+
authShare: result.shares.authShare,
|
|
21389
|
+
primaryDid: params.did
|
|
21390
|
+
};
|
|
21391
|
+
},
|
|
21392
|
+
async reconcileShares(params) {
|
|
21393
|
+
const storage2 = guardedStorage();
|
|
21394
|
+
const storageId = activeStorageId;
|
|
21395
|
+
const assertActiveUser = /* @__PURE__ */ __name(() => {
|
|
21396
|
+
if (activeStorageId !== storageId) {
|
|
21397
|
+
throw new AtomicUpdateError(
|
|
21398
|
+
"Active account changed during reconciliation",
|
|
21399
|
+
"verify_stored",
|
|
21400
|
+
false
|
|
21401
|
+
);
|
|
21402
|
+
}
|
|
21403
|
+
}, "assertActiveUser");
|
|
21404
|
+
return withShareUpdateLock(storage2, storageId, async () => {
|
|
21405
|
+
let deviceShare = await storage2.getDeviceShare(storageId);
|
|
21406
|
+
const pending = await readPendingShareCandidates(storage2, storageId);
|
|
21407
|
+
if (!deviceShare && !pending.length) return null;
|
|
21408
|
+
const resolution = await resolvePendingShareCandidates(
|
|
21409
|
+
storage2,
|
|
21410
|
+
storageId,
|
|
21411
|
+
async () => {
|
|
21412
|
+
const data = await fetchAuthShareRaw(
|
|
21413
|
+
serverUrl,
|
|
21414
|
+
params.token,
|
|
21415
|
+
params.providerType,
|
|
21416
|
+
void 0,
|
|
21417
|
+
tenantId
|
|
21418
|
+
);
|
|
21419
|
+
return {
|
|
21420
|
+
currentVersion: data?.shareVersion ?? null,
|
|
21421
|
+
authShare: authShareToString(data?.authShare)
|
|
21422
|
+
};
|
|
21423
|
+
},
|
|
21424
|
+
params.expectedDid,
|
|
21425
|
+
params.didFromPrivateKey,
|
|
21426
|
+
assertActiveUser,
|
|
21427
|
+
recordPromotion(params.expectedDid),
|
|
21428
|
+
async (version) => authShareToString(
|
|
21429
|
+
(await fetchAuthShareRaw(
|
|
21430
|
+
serverUrl,
|
|
21431
|
+
params.token,
|
|
21432
|
+
params.providerType,
|
|
21433
|
+
version,
|
|
21434
|
+
tenantId
|
|
21435
|
+
))?.authShare
|
|
21436
|
+
)
|
|
21437
|
+
);
|
|
21438
|
+
assertActiveUser();
|
|
21439
|
+
if (resolution.privateKey) {
|
|
21440
|
+
return { privateKey: resolution.privateKey, did: params.expectedDid };
|
|
21441
|
+
}
|
|
21442
|
+
const { currentVersion, authShare: currentAuthShare } = resolution.snapshot;
|
|
21443
|
+
if (currentVersion === null && !currentAuthShare) return null;
|
|
21444
|
+
deviceShare = await storage2.getDeviceShare(storageId);
|
|
21445
|
+
const localVersion = await storage2.getShareVersion(storageId);
|
|
21446
|
+
if (!deviceShare) return null;
|
|
21447
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
21448
|
+
const addCandidate = /* @__PURE__ */ __name((version, authShare) => {
|
|
21449
|
+
if (version !== null && authShare && !candidates.has(version)) {
|
|
21450
|
+
candidates.set(version, authShare);
|
|
21451
|
+
}
|
|
21452
|
+
}, "addCandidate");
|
|
21453
|
+
addCandidate(
|
|
21454
|
+
lastServerSnapshot?.resolvedVersion ?? null,
|
|
21455
|
+
lastServerSnapshot?.authShare ?? null
|
|
21456
|
+
);
|
|
21457
|
+
addCandidate(currentVersion, currentAuthShare);
|
|
21458
|
+
const versionsToTry = /* @__PURE__ */ new Set();
|
|
21459
|
+
if (localVersion !== null) versionsToTry.add(localVersion);
|
|
21460
|
+
if (currentVersion !== null) {
|
|
21461
|
+
for (let version = currentVersion; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
21462
|
+
versionsToTry.add(version);
|
|
21463
|
+
}
|
|
21464
|
+
}
|
|
21465
|
+
for (const version of versionsToTry) {
|
|
21466
|
+
if (candidates.has(version)) continue;
|
|
21467
|
+
const data = await fetchAuthShareRaw(
|
|
21468
|
+
serverUrl,
|
|
21469
|
+
params.token,
|
|
21470
|
+
params.providerType,
|
|
21471
|
+
version,
|
|
21472
|
+
tenantId
|
|
21473
|
+
);
|
|
21474
|
+
addCandidate(version, authShareToString(data?.authShare));
|
|
21475
|
+
}
|
|
21476
|
+
for (const [candidateVersion, authShare] of candidates) {
|
|
21477
|
+
const health = await verifyStoredShares(
|
|
21478
|
+
{
|
|
21479
|
+
getDevice: /* @__PURE__ */ __name(async () => deviceShare, "getDevice"),
|
|
21480
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
21481
|
+
},
|
|
21482
|
+
params.expectedDid,
|
|
21483
|
+
params.didFromPrivateKey
|
|
21484
|
+
);
|
|
21485
|
+
if (!health.healthy) continue;
|
|
21486
|
+
assertActiveUser();
|
|
21487
|
+
const privateKey = await reconstructFromShares([deviceShare, authShare]);
|
|
21488
|
+
if (candidateVersion === currentVersion) {
|
|
21489
|
+
if (localVersion !== currentVersion) {
|
|
21490
|
+
await storage2.storeShareVersion(candidateVersion, storageId);
|
|
21491
|
+
}
|
|
21492
|
+
assertActiveUser();
|
|
21493
|
+
lastServerSnapshot = {
|
|
21494
|
+
currentVersion,
|
|
21495
|
+
resolvedVersion: candidateVersion,
|
|
21496
|
+
authShare,
|
|
21497
|
+
primaryDid: params.expectedDid
|
|
21498
|
+
};
|
|
21499
|
+
return localVersion === currentVersion ? null : { privateKey, did: params.expectedDid };
|
|
21500
|
+
}
|
|
21501
|
+
const result = await persistSharesWithoutLock(
|
|
21502
|
+
privateKey,
|
|
21503
|
+
serverUrl,
|
|
21504
|
+
params.token,
|
|
21505
|
+
params.providerType,
|
|
21506
|
+
params.expectedDid,
|
|
21507
|
+
storage2,
|
|
21508
|
+
storageId,
|
|
21509
|
+
params.didFromPrivateKey,
|
|
21510
|
+
params.signDidAuthVp,
|
|
21511
|
+
tenantId,
|
|
21512
|
+
recordPromotion(params.expectedDid),
|
|
21513
|
+
assertActiveUser
|
|
21514
|
+
);
|
|
21515
|
+
assertActiveUser();
|
|
21516
|
+
lastEmailShare = result.shares.emailShare;
|
|
21517
|
+
lastShareVersion = result.shareVersion;
|
|
21518
|
+
lastServerSnapshot = {
|
|
21519
|
+
currentVersion: result.shareVersion,
|
|
21520
|
+
resolvedVersion: result.shareVersion,
|
|
21521
|
+
authShare: result.shares.authShare,
|
|
21522
|
+
primaryDid: params.expectedDid
|
|
21523
|
+
};
|
|
21524
|
+
return { privateKey, did: params.expectedDid };
|
|
21525
|
+
}
|
|
21526
|
+
return null;
|
|
21527
|
+
});
|
|
21528
|
+
},
|
|
19664
21529
|
// --- Server communication ---
|
|
19665
21530
|
async fetchServerKeyStatus(token, providerType) {
|
|
19666
|
-
const
|
|
21531
|
+
const storage2 = guardedStorage();
|
|
21532
|
+
const localVersion = await storage2.getShareVersion(activeStorageId);
|
|
19667
21533
|
const data = await fetchAuthShareRaw(
|
|
19668
21534
|
serverUrl,
|
|
19669
21535
|
token,
|
|
@@ -19672,7 +21538,7 @@ function createSSSStrategy(config) {
|
|
|
19672
21538
|
tenantId
|
|
19673
21539
|
);
|
|
19674
21540
|
if (!data) {
|
|
19675
|
-
|
|
21541
|
+
lastServerSnapshot = null;
|
|
19676
21542
|
return {
|
|
19677
21543
|
exists: false,
|
|
19678
21544
|
needsMigration: false,
|
|
@@ -19680,16 +21546,25 @@ function createSSSStrategy(config) {
|
|
|
19680
21546
|
recoveryMethods: [],
|
|
19681
21547
|
authShare: null,
|
|
19682
21548
|
shareVersion: null,
|
|
19683
|
-
maskedRecoveryEmail: null
|
|
21549
|
+
maskedRecoveryEmail: null,
|
|
21550
|
+
sssActivationState: null
|
|
19684
21551
|
};
|
|
19685
21552
|
}
|
|
19686
21553
|
const rawAuthShare = data.authShare;
|
|
19687
|
-
const authShareString =
|
|
21554
|
+
const authShareString = authShareToString(rawAuthShare);
|
|
19688
21555
|
const serverVersion = data.shareVersion ?? null;
|
|
19689
|
-
if (
|
|
19690
|
-
|
|
19691
|
-
|
|
19692
|
-
|
|
21556
|
+
if (
|
|
21557
|
+
// Version 0 means "no auth share yet" and must never be pinned locally.
|
|
21558
|
+
serverVersion != null && serverVersion > 0 && localVersion == null && !(await readPendingShareCandidates(storage2, activeStorageId)).length
|
|
21559
|
+
) {
|
|
21560
|
+
storage2.storeShareVersion(serverVersion, activeStorageId).catch((e) => console.warn("SSS: failed to backfill local shareVersion", e));
|
|
21561
|
+
}
|
|
21562
|
+
lastServerSnapshot = {
|
|
21563
|
+
currentVersion: serverVersion,
|
|
21564
|
+
resolvedVersion: authShareString ? localVersion ?? serverVersion : null,
|
|
21565
|
+
authShare: authShareString,
|
|
21566
|
+
primaryDid: data.primaryDid || null
|
|
21567
|
+
};
|
|
19693
21568
|
return {
|
|
19694
21569
|
exists: !!rawAuthShare || !!data.keyProvider || !!data.primaryDid,
|
|
19695
21570
|
needsMigration: data.keyProvider === "web3auth",
|
|
@@ -19697,35 +21572,258 @@ function createSSSStrategy(config) {
|
|
|
19697
21572
|
recoveryMethods: data.recoveryMethods || [],
|
|
19698
21573
|
authShare: authShareString,
|
|
19699
21574
|
shareVersion: serverVersion,
|
|
19700
|
-
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null
|
|
21575
|
+
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null,
|
|
21576
|
+
escrowOptedOut: data.escrowOptedOut === true,
|
|
21577
|
+
escrowPin: data.escrowPin,
|
|
21578
|
+
sssActivationState: data.sssActivationState ?? "active"
|
|
19701
21579
|
};
|
|
19702
21580
|
},
|
|
19703
21581
|
async storeAuthShare(token, providerType, authShare, primaryDid, didAuthVp) {
|
|
19704
|
-
const
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
21582
|
+
const storage2 = guardedStorage();
|
|
21583
|
+
const { shareVersion } = await withRotationLock(
|
|
21584
|
+
() => putAuthShare(
|
|
21585
|
+
serverUrl,
|
|
21586
|
+
token,
|
|
21587
|
+
providerType,
|
|
21588
|
+
authShare,
|
|
21589
|
+
primaryDid,
|
|
21590
|
+
didAuthVp,
|
|
21591
|
+
tenantId
|
|
21592
|
+
)
|
|
19712
21593
|
);
|
|
19713
|
-
await
|
|
21594
|
+
await storage2.storeShareVersion(shareVersion, activeStorageId);
|
|
19714
21595
|
lastShareVersion = shareVersion;
|
|
19715
21596
|
},
|
|
19716
|
-
async markMigrated(token, providerType, didAuthVp) {
|
|
19717
|
-
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21597
|
+
async markMigrated(token, providerType, didAuthVp) {
|
|
21598
|
+
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21599
|
+
method: "POST",
|
|
21600
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
21601
|
+
body: JSON.stringify({ authToken: token, providerType })
|
|
21602
|
+
});
|
|
21603
|
+
if (!response.ok) {
|
|
21604
|
+
throw new Error(`Failed to mark migrated: ${describeHttpStatus(response)}`);
|
|
21605
|
+
}
|
|
21606
|
+
},
|
|
21607
|
+
async activate(token, providerType, didAuthVp) {
|
|
21608
|
+
const response = await fetch(`${serverUrl}/keys/activate`, {
|
|
19718
21609
|
method: "POST",
|
|
19719
21610
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19720
21611
|
body: JSON.stringify({ authToken: token, providerType })
|
|
19721
21612
|
});
|
|
19722
21613
|
if (!response.ok) {
|
|
19723
|
-
throw new Error(`Failed to
|
|
21614
|
+
throw new Error(`Failed to activate SSS key: ${describeHttpStatus(response)}`);
|
|
19724
21615
|
}
|
|
19725
21616
|
},
|
|
19726
21617
|
// --- Recovery execution ---
|
|
21618
|
+
async getEscrowEnrollmentState(params) {
|
|
21619
|
+
if (!config.escrow?.enabled) return { state: "disabled" };
|
|
21620
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21621
|
+
if (status.escrowOptedOut) return { state: "opted-out", escrowPin: status.escrowPin };
|
|
21622
|
+
const state = status.shareVersion !== null && status.recoveryMethods.some(
|
|
21623
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21624
|
+
) ? "enrolled" : "not-enrolled";
|
|
21625
|
+
return { state, escrowPin: status.escrowPin };
|
|
21626
|
+
},
|
|
21627
|
+
async disableEscrowRecovery(params) {
|
|
21628
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21629
|
+
if (!status.primaryDid) throw new Error("Cannot disable escrow without a primary DID");
|
|
21630
|
+
const vp = await requestFreshDidAuthVp(
|
|
21631
|
+
serverUrl,
|
|
21632
|
+
params.privateKey,
|
|
21633
|
+
status.primaryDid,
|
|
21634
|
+
params.signDidAuthVp,
|
|
21635
|
+
tenantId
|
|
21636
|
+
);
|
|
21637
|
+
await escrowRequest("", {
|
|
21638
|
+
method: "DELETE",
|
|
21639
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21640
|
+
body: JSON.stringify({
|
|
21641
|
+
authToken: params.token,
|
|
21642
|
+
providerType: params.providerType,
|
|
21643
|
+
optOut: true
|
|
21644
|
+
})
|
|
21645
|
+
});
|
|
21646
|
+
},
|
|
21647
|
+
async enableEscrowRecovery(params) {
|
|
21648
|
+
const storage2 = guardedStorage();
|
|
21649
|
+
return withRotationLock(async () => {
|
|
21650
|
+
if (!config.escrow?.enabled)
|
|
21651
|
+
return { enrolled: false, reason: "disabled" };
|
|
21652
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21653
|
+
if (!status.primaryDid)
|
|
21654
|
+
throw new Error("Cannot enable escrow without a primary DID");
|
|
21655
|
+
const vp = await requestFreshDidAuthVp(
|
|
21656
|
+
serverUrl,
|
|
21657
|
+
params.privateKey,
|
|
21658
|
+
status.primaryDid,
|
|
21659
|
+
params.signDidAuthVp,
|
|
21660
|
+
tenantId
|
|
21661
|
+
);
|
|
21662
|
+
await escrowRequest("/opt-in", {
|
|
21663
|
+
method: "POST",
|
|
21664
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21665
|
+
body: JSON.stringify({
|
|
21666
|
+
authToken: params.token,
|
|
21667
|
+
providerType: params.providerType
|
|
21668
|
+
})
|
|
21669
|
+
});
|
|
21670
|
+
return ensureEscrowEnrollmentUnguarded(this, params, storage2);
|
|
21671
|
+
});
|
|
21672
|
+
},
|
|
21673
|
+
async setEscrowPin({ pin, ...params }) {
|
|
21674
|
+
const storage2 = guardedStorage();
|
|
21675
|
+
const result = await withRotationLock(
|
|
21676
|
+
() => ensureEscrowEnrollmentUnguarded(this, { ...params, options: { pin } }, storage2)
|
|
21677
|
+
);
|
|
21678
|
+
if (!result.enrolled) {
|
|
21679
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21680
|
+
}
|
|
21681
|
+
},
|
|
21682
|
+
async clearEscrowPin(params) {
|
|
21683
|
+
const storage2 = guardedStorage();
|
|
21684
|
+
const result = await withRotationLock(
|
|
21685
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2, true)
|
|
21686
|
+
);
|
|
21687
|
+
if (!result.enrolled) {
|
|
21688
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21689
|
+
}
|
|
21690
|
+
},
|
|
21691
|
+
/** Enroll missing/stale escrow material using the standard atomic rotation. */
|
|
21692
|
+
async ensureEscrowEnrollment(params) {
|
|
21693
|
+
const storage2 = guardedStorage();
|
|
21694
|
+
if (params.options?.pin !== void 0)
|
|
21695
|
+
return withRotationLock(
|
|
21696
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21697
|
+
);
|
|
21698
|
+
if (escrowEnrollmentInFlight) return escrowEnrollmentInFlight;
|
|
21699
|
+
escrowEnrollmentInFlight = withRotationLock(
|
|
21700
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21701
|
+
);
|
|
21702
|
+
try {
|
|
21703
|
+
return await escrowEnrollmentInFlight;
|
|
21704
|
+
} finally {
|
|
21705
|
+
escrowEnrollmentInFlight = void 0;
|
|
21706
|
+
}
|
|
21707
|
+
},
|
|
21708
|
+
/** Start or explicitly restart a hold with fresh client-bound resume secrets. */
|
|
21709
|
+
async startEscrowRecovery(params) {
|
|
21710
|
+
const sessionProof = params.recoverySessionToken !== void 0;
|
|
21711
|
+
if (sessionProof ? params.token !== void 0 || params.providerType !== void 0 : !params.token || !params.providerType) {
|
|
21712
|
+
throw new Error("Provide exactly one identity proof");
|
|
21713
|
+
}
|
|
21714
|
+
const pair = await generateEscrowKeyPair();
|
|
21715
|
+
const result = await escrowRequest(
|
|
21716
|
+
"/recover",
|
|
21717
|
+
{
|
|
21718
|
+
method: "POST",
|
|
21719
|
+
headers: buildHeaders("", void 0, params.tenantId ?? tenantId),
|
|
21720
|
+
body: JSON.stringify({
|
|
21721
|
+
clientEphemeralPublicKey: pair.publicKey,
|
|
21722
|
+
...params.options?.restart ? { restart: true } : {},
|
|
21723
|
+
...params.options?.releasePolicy ? { releasePolicy: params.options.releasePolicy } : {},
|
|
21724
|
+
...sessionProof ? { recoverySessionToken: params.recoverySessionToken } : { authToken: params.token, providerType: params.providerType }
|
|
21725
|
+
})
|
|
21726
|
+
},
|
|
21727
|
+
params.options?.releasePolicy === "pin"
|
|
21728
|
+
);
|
|
21729
|
+
return { ...result, clientEphemeralPrivateKey: pair.privateKey };
|
|
21730
|
+
},
|
|
21731
|
+
/** Read hold status; secrets (auth or resume token) never travel in a URL. */
|
|
21732
|
+
async getEscrowRecoveryStatus(params) {
|
|
21733
|
+
const query = new URLSearchParams(
|
|
21734
|
+
"holdId" in params ? { holdId: params.holdId } : { providerType: params.providerType }
|
|
21735
|
+
);
|
|
21736
|
+
const secret = "holdId" in params ? params.resumeToken : params.token;
|
|
21737
|
+
const result = await escrowRequest(
|
|
21738
|
+
`/status?${query}`,
|
|
21739
|
+
{
|
|
21740
|
+
method: "GET",
|
|
21741
|
+
headers: {
|
|
21742
|
+
...buildHeaders("", void 0, tenantId),
|
|
21743
|
+
"X-Auth-Token": secret
|
|
21744
|
+
}
|
|
21745
|
+
}
|
|
21746
|
+
);
|
|
21747
|
+
return result.hold;
|
|
21748
|
+
},
|
|
21749
|
+
/** Cancel a pending hold with a fresh DID-owner proof. */
|
|
21750
|
+
async cancelEscrowRecovery(params) {
|
|
21751
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21752
|
+
if (!status.primaryDid) throw new Error("Cannot cancel escrow without a primary DID");
|
|
21753
|
+
const vp = await requestFreshDidAuthVp(
|
|
21754
|
+
serverUrl,
|
|
21755
|
+
params.privateKey,
|
|
21756
|
+
status.primaryDid,
|
|
21757
|
+
params.signDidAuthVp,
|
|
21758
|
+
tenantId
|
|
21759
|
+
);
|
|
21760
|
+
const result = await escrowRequest("/cancel", {
|
|
21761
|
+
method: "POST",
|
|
21762
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21763
|
+
body: JSON.stringify({
|
|
21764
|
+
authToken: params.token,
|
|
21765
|
+
providerType: params.providerType
|
|
21766
|
+
})
|
|
21767
|
+
});
|
|
21768
|
+
return { cancelled: result.cancelled };
|
|
21769
|
+
},
|
|
19727
21770
|
async executeRecovery(params) {
|
|
19728
|
-
const
|
|
21771
|
+
const storage2 = guardedStorage();
|
|
21772
|
+
const { token, providerType, input, didFromPrivateKey, signDidAuthVp } = params;
|
|
21773
|
+
if (input.method === "escrow" || input.method === "escrow-pin") {
|
|
21774
|
+
if (!didFromPrivateKey)
|
|
21775
|
+
throw new Error("DID verification is required for escrow recovery");
|
|
21776
|
+
const start = input.method === "escrow-pin" ? await this.startEscrowRecovery({
|
|
21777
|
+
token,
|
|
21778
|
+
providerType,
|
|
21779
|
+
options: { releasePolicy: "pin" }
|
|
21780
|
+
}) : input;
|
|
21781
|
+
let pinProof;
|
|
21782
|
+
if (input.method === "escrow-pin") {
|
|
21783
|
+
if (!("pinSalt" in start) || !start.pinSalt || !start.resumeToken)
|
|
21784
|
+
throw new Error("PIN recovery response is missing required material");
|
|
21785
|
+
pinProof = await derivePinProof(input.pin, start.pinSalt);
|
|
21786
|
+
}
|
|
21787
|
+
const material = await escrowRequest(
|
|
21788
|
+
"/complete",
|
|
21789
|
+
{
|
|
21790
|
+
method: "POST",
|
|
21791
|
+
headers: buildHeaders("", void 0, tenantId),
|
|
21792
|
+
body: JSON.stringify({
|
|
21793
|
+
holdId: start.holdId,
|
|
21794
|
+
resumeToken: start.resumeToken,
|
|
21795
|
+
...pinProof ? { pinProof } : {}
|
|
21796
|
+
})
|
|
21797
|
+
},
|
|
21798
|
+
input.method === "escrow-pin"
|
|
21799
|
+
);
|
|
21800
|
+
const plaintext = await openEscrowRelease(
|
|
21801
|
+
material.sealedShare,
|
|
21802
|
+
start.clientEphemeralPrivateKey
|
|
21803
|
+
);
|
|
21804
|
+
if (plaintext.holdId !== start.holdId || plaintext.did !== material.primaryDid || plaintext.shareVersion !== material.shareVersion) {
|
|
21805
|
+
throw new Error("Escrow recovery material does not match the request");
|
|
21806
|
+
}
|
|
21807
|
+
const authShare = material.authShare.encryptedData;
|
|
21808
|
+
const privateKey2 = await reconstructFromShares([
|
|
21809
|
+
plaintext.recoveryShare,
|
|
21810
|
+
authShare
|
|
21811
|
+
]);
|
|
21812
|
+
if (!material.primaryDid || await didFromPrivateKey(privateKey2) !== material.primaryDid) {
|
|
21813
|
+
throw new Error(
|
|
21814
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
21815
|
+
);
|
|
21816
|
+
}
|
|
21817
|
+
pendingIdentityRecovery = {
|
|
21818
|
+
privateKey: privateKey2,
|
|
21819
|
+
primaryDid: material.primaryDid,
|
|
21820
|
+
recoveryShare: plaintext.recoveryShare,
|
|
21821
|
+
authShare,
|
|
21822
|
+
rebindSessionToken: material.rebindSessionToken
|
|
21823
|
+
};
|
|
21824
|
+
if (!token) return { privateKey: privateKey2, did: material.primaryDid };
|
|
21825
|
+
return this.completeIdentityRecovery({ token, providerType, signDidAuthVp });
|
|
21826
|
+
}
|
|
19729
21827
|
let recoveryShare;
|
|
19730
21828
|
let recoveryShareVersion;
|
|
19731
21829
|
switch (input.method) {
|
|
@@ -19819,14 +21917,61 @@ function createSSSStrategy(config) {
|
|
|
19819
21917
|
);
|
|
19820
21918
|
}
|
|
19821
21919
|
}
|
|
19822
|
-
await
|
|
19823
|
-
|
|
19824
|
-
|
|
21920
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21921
|
+
privateKey,
|
|
21922
|
+
serverUrl,
|
|
21923
|
+
token,
|
|
21924
|
+
providerType,
|
|
21925
|
+
primaryDid,
|
|
21926
|
+
storage2,
|
|
21927
|
+
activeStorageId,
|
|
21928
|
+
didFromPrivateKey,
|
|
21929
|
+
signDidAuthVp,
|
|
21930
|
+
tenantId,
|
|
21931
|
+
recordPromotion(primaryDid),
|
|
21932
|
+
captureActiveUserGuard()
|
|
21933
|
+
);
|
|
21934
|
+
lastEmailShare = shares.emailShare;
|
|
21935
|
+
lastShareVersion = shareVersion;
|
|
21936
|
+
lastServerSnapshot = {
|
|
21937
|
+
currentVersion: shareVersion,
|
|
21938
|
+
resolvedVersion: shareVersion,
|
|
21939
|
+
authShare: shares.authShare,
|
|
21940
|
+
primaryDid
|
|
21941
|
+
};
|
|
21942
|
+
await tryEnrollEscrow(
|
|
21943
|
+
token,
|
|
21944
|
+
providerType,
|
|
21945
|
+
privateKey,
|
|
21946
|
+
primaryDid,
|
|
21947
|
+
shares,
|
|
21948
|
+
shareVersion,
|
|
21949
|
+
signDidAuthVp
|
|
21950
|
+
);
|
|
19825
21951
|
return { privateKey, did: primaryDid };
|
|
19826
21952
|
},
|
|
19827
21953
|
// --- Recovery setup ---
|
|
19828
21954
|
async setupRecoveryMethod(params) {
|
|
21955
|
+
const storage2 = guardedStorage();
|
|
19829
21956
|
const { token, providerType, privateKey, input, authUser, signDidAuthVp } = params;
|
|
21957
|
+
if (input.method === "escrow" && input.pin !== void 0) {
|
|
21958
|
+
if (!signDidAuthVp)
|
|
21959
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21960
|
+
const result = await this.enableEscrowRecovery({
|
|
21961
|
+
token,
|
|
21962
|
+
providerType,
|
|
21963
|
+
privateKey,
|
|
21964
|
+
signDidAuthVp,
|
|
21965
|
+
options: { pin: input.pin }
|
|
21966
|
+
});
|
|
21967
|
+
if (!result.enrolled || !result.changed)
|
|
21968
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21969
|
+
return { method: "escrow", shareVersion: result.shareVersion };
|
|
21970
|
+
}
|
|
21971
|
+
if (input.method === "escrow" && !config.escrow?.enabled) {
|
|
21972
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21973
|
+
}
|
|
21974
|
+
const escrowKey = input.method === "escrow" ? await fetchVerifiedEnclaveKey() : void 0;
|
|
19830
21975
|
let passkeyCredential;
|
|
19831
21976
|
if (input.method === "passkey") {
|
|
19832
21977
|
if (!isWebAuthnSupported()) {
|
|
@@ -19836,10 +21981,6 @@ function createSSSStrategy(config) {
|
|
|
19836
21981
|
const userName = authUser?.email || authUser?.phone || authUser?.id || "";
|
|
19837
21982
|
passkeyCredential = await createPasskeyCredential(userId, userName);
|
|
19838
21983
|
}
|
|
19839
|
-
const vpJwt = signDidAuthVp ? await signDidAuthVp(privateKey) : void 0;
|
|
19840
|
-
const { shares } = await splitAndVerify(privateKey);
|
|
19841
|
-
lastEmailShare = shares.emailShare;
|
|
19842
|
-
await storage.storeDeviceShare(shares.deviceShare, activeStorageId);
|
|
19843
21984
|
const serverData = await fetchAuthShareRaw(
|
|
19844
21985
|
serverUrl,
|
|
19845
21986
|
token,
|
|
@@ -19848,39 +21989,42 @@ function createSSSStrategy(config) {
|
|
|
19848
21989
|
tenantId
|
|
19849
21990
|
);
|
|
19850
21991
|
const primaryDid = serverData?.primaryDid || "";
|
|
19851
|
-
|
|
21992
|
+
if (!primaryDid) throw new Error("Cannot set up recovery without a primary DID");
|
|
21993
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21994
|
+
privateKey,
|
|
19852
21995
|
serverUrl,
|
|
19853
21996
|
token,
|
|
19854
21997
|
providerType,
|
|
19855
|
-
shares.authShare,
|
|
19856
21998
|
primaryDid,
|
|
19857
|
-
|
|
19858
|
-
|
|
21999
|
+
storage2,
|
|
22000
|
+
activeStorageId,
|
|
22001
|
+
void 0,
|
|
22002
|
+
signDidAuthVp,
|
|
22003
|
+
tenantId,
|
|
22004
|
+
recordPromotion(primaryDid),
|
|
22005
|
+
captureActiveUserGuard()
|
|
19859
22006
|
);
|
|
19860
|
-
|
|
19861
|
-
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
|
|
19865
|
-
|
|
19866
|
-
|
|
19867
|
-
|
|
19868
|
-
tenantId
|
|
19869
|
-
) : authUser?.email ? sendEmailBackupShare(
|
|
19870
|
-
serverUrl,
|
|
19871
|
-
token,
|
|
19872
|
-
providerType,
|
|
19873
|
-
lastEmailShare,
|
|
19874
|
-
authUser.email,
|
|
19875
|
-
shareVersion,
|
|
19876
|
-
tenantId
|
|
19877
|
-
) : Promise.resolve();
|
|
19878
|
-
resend.catch(
|
|
19879
|
-
(e) => console.warn("Email backup share re-send failed (non-fatal):", e)
|
|
19880
|
-
);
|
|
19881
|
-
lastEmailShare = void 0;
|
|
19882
|
-
}
|
|
22007
|
+
lastEmailShare = shares.emailShare;
|
|
22008
|
+
lastShareVersion = shareVersion;
|
|
22009
|
+
lastServerSnapshot = {
|
|
22010
|
+
currentVersion: shareVersion,
|
|
22011
|
+
resolvedVersion: shareVersion,
|
|
22012
|
+
authShare: shares.authShare,
|
|
22013
|
+
primaryDid
|
|
22014
|
+
};
|
|
19883
22015
|
switch (input.method) {
|
|
22016
|
+
case "escrow":
|
|
22017
|
+
await enrollEscrow(
|
|
22018
|
+
token,
|
|
22019
|
+
providerType,
|
|
22020
|
+
privateKey,
|
|
22021
|
+
primaryDid,
|
|
22022
|
+
shares,
|
|
22023
|
+
shareVersion,
|
|
22024
|
+
signDidAuthVp,
|
|
22025
|
+
escrowKey
|
|
22026
|
+
);
|
|
22027
|
+
return { method: "escrow", shareVersion };
|
|
19884
22028
|
case "passkey": {
|
|
19885
22029
|
const credential = passkeyCredential;
|
|
19886
22030
|
const encryptedShare = await encryptShareWithPasskey(
|
|
@@ -19900,9 +22044,38 @@ function createSSSStrategy(config) {
|
|
|
19900
22044
|
credentialId: credential.credentialId,
|
|
19901
22045
|
shareVersion
|
|
19902
22046
|
},
|
|
19903
|
-
|
|
22047
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22048
|
+
serverUrl,
|
|
22049
|
+
privateKey,
|
|
22050
|
+
primaryDid,
|
|
22051
|
+
signDidAuthVp,
|
|
22052
|
+
tenantId
|
|
22053
|
+
) : void 0,
|
|
19904
22054
|
tenantId
|
|
19905
22055
|
);
|
|
22056
|
+
await postRecoveryConfirmation(
|
|
22057
|
+
serverUrl,
|
|
22058
|
+
token,
|
|
22059
|
+
providerType,
|
|
22060
|
+
{ type: "passkey", credentialId: credential.credentialId },
|
|
22061
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22062
|
+
serverUrl,
|
|
22063
|
+
privateKey,
|
|
22064
|
+
primaryDid,
|
|
22065
|
+
signDidAuthVp,
|
|
22066
|
+
tenantId
|
|
22067
|
+
) : void 0,
|
|
22068
|
+
tenantId
|
|
22069
|
+
);
|
|
22070
|
+
await tryEnrollEscrow(
|
|
22071
|
+
token,
|
|
22072
|
+
providerType,
|
|
22073
|
+
privateKey,
|
|
22074
|
+
primaryDid,
|
|
22075
|
+
shares,
|
|
22076
|
+
shareVersion,
|
|
22077
|
+
signDidAuthVp
|
|
22078
|
+
);
|
|
19906
22079
|
return { method: "passkey", credentialId: credential.credentialId };
|
|
19907
22080
|
}
|
|
19908
22081
|
case "phrase": {
|
|
@@ -19915,10 +22088,38 @@ function createSSSStrategy(config) {
|
|
|
19915
22088
|
type: "phrase",
|
|
19916
22089
|
shareVersion
|
|
19917
22090
|
},
|
|
19918
|
-
|
|
22091
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22092
|
+
serverUrl,
|
|
22093
|
+
privateKey,
|
|
22094
|
+
primaryDid,
|
|
22095
|
+
signDidAuthVp,
|
|
22096
|
+
tenantId
|
|
22097
|
+
) : void 0,
|
|
19919
22098
|
tenantId
|
|
19920
22099
|
);
|
|
19921
|
-
|
|
22100
|
+
const challengeWordIndices = selectRecoveryPhraseChallengeIndices(
|
|
22101
|
+
phrase.split(/\s+/).length
|
|
22102
|
+
);
|
|
22103
|
+
const challengeWordOptions = await buildRecoveryPhraseChallengeOptions(
|
|
22104
|
+
phrase,
|
|
22105
|
+
challengeWordIndices
|
|
22106
|
+
);
|
|
22107
|
+
pendingPhraseConfirmation = { phrase, challengeWordIndices };
|
|
22108
|
+
await tryEnrollEscrow(
|
|
22109
|
+
token,
|
|
22110
|
+
providerType,
|
|
22111
|
+
privateKey,
|
|
22112
|
+
primaryDid,
|
|
22113
|
+
shares,
|
|
22114
|
+
shareVersion,
|
|
22115
|
+
signDidAuthVp
|
|
22116
|
+
);
|
|
22117
|
+
return {
|
|
22118
|
+
method: "phrase",
|
|
22119
|
+
phrase,
|
|
22120
|
+
challengeWordIndices,
|
|
22121
|
+
challengeWordOptions
|
|
22122
|
+
};
|
|
19922
22123
|
}
|
|
19923
22124
|
case "backup": {
|
|
19924
22125
|
const encrypted = await encryptWithPassword(
|
|
@@ -19930,6 +22131,7 @@ function createSSSStrategy(config) {
|
|
|
19930
22131
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19931
22132
|
primaryDid: input.did,
|
|
19932
22133
|
shareVersion,
|
|
22134
|
+
shareChecksum: await calculateShareChecksum(shares.recoveryShare),
|
|
19933
22135
|
encryptedShare: {
|
|
19934
22136
|
ciphertext: encrypted.ciphertext,
|
|
19935
22137
|
iv: encrypted.iv,
|
|
@@ -19945,36 +22147,121 @@ function createSSSStrategy(config) {
|
|
|
19945
22147
|
type: "backup",
|
|
19946
22148
|
shareVersion
|
|
19947
22149
|
},
|
|
19948
|
-
|
|
22150
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22151
|
+
serverUrl,
|
|
22152
|
+
privateKey,
|
|
22153
|
+
primaryDid,
|
|
22154
|
+
signDidAuthVp,
|
|
22155
|
+
tenantId
|
|
22156
|
+
) : void 0,
|
|
19949
22157
|
tenantId
|
|
19950
22158
|
);
|
|
22159
|
+
pendingBackupConfirmation = {
|
|
22160
|
+
backupFile,
|
|
22161
|
+
recoveryShare: shares.recoveryShare
|
|
22162
|
+
};
|
|
22163
|
+
await tryEnrollEscrow(
|
|
22164
|
+
token,
|
|
22165
|
+
providerType,
|
|
22166
|
+
privateKey,
|
|
22167
|
+
primaryDid,
|
|
22168
|
+
shares,
|
|
22169
|
+
shareVersion,
|
|
22170
|
+
signDidAuthVp
|
|
22171
|
+
);
|
|
19951
22172
|
return { method: "backup", backupFile };
|
|
19952
22173
|
}
|
|
19953
22174
|
case "email": {
|
|
19954
|
-
await
|
|
22175
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22176
|
+
serverUrl,
|
|
22177
|
+
privateKey,
|
|
22178
|
+
primaryDid,
|
|
22179
|
+
signDidAuthVp,
|
|
22180
|
+
tenantId
|
|
22181
|
+
) : void 0;
|
|
22182
|
+
await sendEmailBackupShare(
|
|
19955
22183
|
serverUrl,
|
|
19956
22184
|
token,
|
|
19957
22185
|
providerType,
|
|
19958
22186
|
shares.emailShare,
|
|
22187
|
+
input.email,
|
|
22188
|
+
escrowRelayPublicKey,
|
|
22189
|
+
escrowRelayKeyId,
|
|
22190
|
+
emailBranding,
|
|
19959
22191
|
shareVersion,
|
|
22192
|
+
didAuthVp,
|
|
19960
22193
|
tenantId
|
|
19961
22194
|
);
|
|
19962
|
-
await
|
|
19963
|
-
serverUrl,
|
|
22195
|
+
await tryEnrollEscrow(
|
|
19964
22196
|
token,
|
|
19965
22197
|
providerType,
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
tenantId
|
|
22198
|
+
privateKey,
|
|
22199
|
+
primaryDid,
|
|
22200
|
+
shares,
|
|
22201
|
+
shareVersion,
|
|
22202
|
+
signDidAuthVp
|
|
19972
22203
|
);
|
|
19973
|
-
hasRecoveryEmail = true;
|
|
19974
22204
|
return { method: "email" };
|
|
19975
22205
|
}
|
|
19976
22206
|
}
|
|
19977
22207
|
},
|
|
22208
|
+
async confirmRecoveryMethod(params) {
|
|
22209
|
+
const { token, providerType, privateKey, input, signDidAuthVp } = params;
|
|
22210
|
+
const primaryDid = lastServerSnapshot?.primaryDid;
|
|
22211
|
+
if (!primaryDid) throw new Error("Cannot confirm recovery without a primary DID");
|
|
22212
|
+
const confirmationBody = { type: input.method };
|
|
22213
|
+
if (input.method === "phrase") {
|
|
22214
|
+
const pending = pendingPhraseConfirmation;
|
|
22215
|
+
if (!pending) throw new Error("Generate a new recovery phrase to continue");
|
|
22216
|
+
const phraseWords = pending.phrase.trim().toLowerCase().split(/\s+/);
|
|
22217
|
+
const matches = input.challengeWords.length === pending.challengeWordIndices.length && pending.challengeWordIndices.every(
|
|
22218
|
+
(wordIndex, answerIndex) => input.challengeWords[answerIndex]?.trim().toLowerCase() === phraseWords[wordIndex]
|
|
22219
|
+
);
|
|
22220
|
+
if (!matches) {
|
|
22221
|
+
throw new Error("Those words do not match. Check your phrase and try again.");
|
|
22222
|
+
}
|
|
22223
|
+
} else if (input.method === "backup") {
|
|
22224
|
+
const pending = pendingBackupConfirmation;
|
|
22225
|
+
if (!pending) throw new Error("Generate a new backup file to continue");
|
|
22226
|
+
let suppliedBackup;
|
|
22227
|
+
let decryptedShare;
|
|
22228
|
+
try {
|
|
22229
|
+
suppliedBackup = JSON.parse(input.fileContents);
|
|
22230
|
+
decryptedShare = await decryptWithPassword(
|
|
22231
|
+
suppliedBackup.encryptedShare.ciphertext,
|
|
22232
|
+
suppliedBackup.encryptedShare.iv,
|
|
22233
|
+
suppliedBackup.encryptedShare.salt,
|
|
22234
|
+
input.password,
|
|
22235
|
+
suppliedBackup.encryptedShare.kdfParams
|
|
22236
|
+
);
|
|
22237
|
+
} catch {
|
|
22238
|
+
throw new Error("Incorrect password or corrupted data. Please try again.");
|
|
22239
|
+
}
|
|
22240
|
+
const checksum = await calculateShareChecksum(decryptedShare);
|
|
22241
|
+
if (suppliedBackup.shareChecksum !== checksum || pending.backupFile.shareChecksum !== checksum || decryptedShare !== pending.recoveryShare) {
|
|
22242
|
+
throw new Error("This backup file could not be verified. Please try again.");
|
|
22243
|
+
}
|
|
22244
|
+
} else {
|
|
22245
|
+
confirmationBody.code = input.code;
|
|
22246
|
+
}
|
|
22247
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22248
|
+
serverUrl,
|
|
22249
|
+
privateKey,
|
|
22250
|
+
primaryDid,
|
|
22251
|
+
signDidAuthVp,
|
|
22252
|
+
tenantId
|
|
22253
|
+
) : void 0;
|
|
22254
|
+
await postRecoveryConfirmation(
|
|
22255
|
+
serverUrl,
|
|
22256
|
+
token,
|
|
22257
|
+
providerType,
|
|
22258
|
+
confirmationBody,
|
|
22259
|
+
didAuthVp,
|
|
22260
|
+
tenantId
|
|
22261
|
+
);
|
|
22262
|
+
if (input.method === "phrase") pendingPhraseConfirmation = void 0;
|
|
22263
|
+
if (input.method === "backup") pendingBackupConfirmation = void 0;
|
|
22264
|
+
},
|
|
19978
22265
|
async getAvailableRecoveryMethods(token, providerType) {
|
|
19979
22266
|
try {
|
|
19980
22267
|
const serverData = await fetchAuthShareRaw(
|
|
@@ -19985,15 +22272,274 @@ function createSSSStrategy(config) {
|
|
|
19985
22272
|
tenantId
|
|
19986
22273
|
);
|
|
19987
22274
|
const methods = serverData?.recoveryMethods || [];
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
|
|
19991
|
-
|
|
22275
|
+
const activationState = serverData?.sssActivationState ?? "active";
|
|
22276
|
+
return methods.filter((method) => {
|
|
22277
|
+
if (method.confirmedAt) return true;
|
|
22278
|
+
if (method.confirmationStatus) return false;
|
|
22279
|
+
return activationState === "active";
|
|
22280
|
+
});
|
|
19992
22281
|
} catch (e) {
|
|
19993
22282
|
console.error("Error getting recovery methods:", e);
|
|
19994
22283
|
return [];
|
|
19995
22284
|
}
|
|
19996
22285
|
},
|
|
22286
|
+
async startIdentityRecovery(email) {
|
|
22287
|
+
await postJson(
|
|
22288
|
+
`${serverUrl}/keys/recovery-session/start`,
|
|
22289
|
+
{ email },
|
|
22290
|
+
buildHeaders("", void 0, tenantId)
|
|
22291
|
+
);
|
|
22292
|
+
},
|
|
22293
|
+
async verifyIdentityRecovery(email, code) {
|
|
22294
|
+
const result = await postJson(
|
|
22295
|
+
`${serverUrl}/keys/recovery-session/verify`,
|
|
22296
|
+
{ email, code },
|
|
22297
|
+
buildHeaders("", void 0, tenantId)
|
|
22298
|
+
);
|
|
22299
|
+
return {
|
|
22300
|
+
...result,
|
|
22301
|
+
recoveryMethods: result.recoveryMethods.map((method) => ({
|
|
22302
|
+
...method,
|
|
22303
|
+
createdAt: new Date(method.createdAt)
|
|
22304
|
+
}))
|
|
22305
|
+
};
|
|
22306
|
+
},
|
|
22307
|
+
async prepareIdentityRecovery(params) {
|
|
22308
|
+
const { input } = params;
|
|
22309
|
+
if (input.method === "escrow") {
|
|
22310
|
+
throw new Error("Use executeRecovery to complete an escrow hold");
|
|
22311
|
+
}
|
|
22312
|
+
let recoveryShare;
|
|
22313
|
+
switch (input.method) {
|
|
22314
|
+
case "passkey":
|
|
22315
|
+
break;
|
|
22316
|
+
case "phrase":
|
|
22317
|
+
if (!await validateRecoveryPhrase(input.phrase)) {
|
|
22318
|
+
throw new Error("Invalid recovery phrase");
|
|
22319
|
+
}
|
|
22320
|
+
recoveryShare = await recoveryPhraseToShare(input.phrase);
|
|
22321
|
+
break;
|
|
22322
|
+
case "backup": {
|
|
22323
|
+
const backup = JSON.parse(input.fileContents);
|
|
22324
|
+
if (backup.version !== 1) throw new Error("Unsupported backup file version");
|
|
22325
|
+
recoveryShare = await decryptWithPassword(
|
|
22326
|
+
backup.encryptedShare.ciphertext,
|
|
22327
|
+
backup.encryptedShare.iv,
|
|
22328
|
+
backup.encryptedShare.salt,
|
|
22329
|
+
input.password,
|
|
22330
|
+
backup.encryptedShare.kdfParams
|
|
22331
|
+
);
|
|
22332
|
+
break;
|
|
22333
|
+
}
|
|
22334
|
+
case "email":
|
|
22335
|
+
recoveryShare = parseVersionedEmailShare(input.emailShare.trim()).share;
|
|
22336
|
+
break;
|
|
22337
|
+
}
|
|
22338
|
+
try {
|
|
22339
|
+
const material = await postJson(
|
|
22340
|
+
`${serverUrl}/keys/recovery-session/recover`,
|
|
22341
|
+
{
|
|
22342
|
+
recoverySessionToken: params.recoverySessionToken,
|
|
22343
|
+
type: input.method,
|
|
22344
|
+
...input.method === "passkey" ? { credentialId: input.credentialId } : {}
|
|
22345
|
+
},
|
|
22346
|
+
buildHeaders("", void 0, tenantId)
|
|
22347
|
+
);
|
|
22348
|
+
if (input.method === "passkey") {
|
|
22349
|
+
if (!material.encryptedShare) {
|
|
22350
|
+
throw new Error("No passkey recovery data found");
|
|
22351
|
+
}
|
|
22352
|
+
recoveryShare = await decryptShareWithPasskey({
|
|
22353
|
+
encryptedData: material.encryptedShare.encryptedData,
|
|
22354
|
+
iv: material.encryptedShare.iv,
|
|
22355
|
+
credentialId: input.credentialId
|
|
22356
|
+
});
|
|
22357
|
+
}
|
|
22358
|
+
if (!recoveryShare) throw new Error("Recovery data was empty");
|
|
22359
|
+
const authShare = material.authShare.encryptedData;
|
|
22360
|
+
let privateKey;
|
|
22361
|
+
try {
|
|
22362
|
+
privateKey = await reconstructFromShares([recoveryShare, authShare]);
|
|
22363
|
+
} catch {
|
|
22364
|
+
throw new Error(
|
|
22365
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22366
|
+
);
|
|
22367
|
+
}
|
|
22368
|
+
const derivedDid = await params.didFromPrivateKey(privateKey);
|
|
22369
|
+
if (!derivedDid || derivedDid !== material.primaryDid) {
|
|
22370
|
+
throw new Error(
|
|
22371
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22372
|
+
);
|
|
22373
|
+
}
|
|
22374
|
+
pendingIdentityRecovery = {
|
|
22375
|
+
privateKey,
|
|
22376
|
+
primaryDid: material.primaryDid,
|
|
22377
|
+
recoveryShare,
|
|
22378
|
+
authShare,
|
|
22379
|
+
rebindSessionToken: material.rebindSessionToken
|
|
22380
|
+
};
|
|
22381
|
+
return { privateKey, did: material.primaryDid };
|
|
22382
|
+
} catch (error) {
|
|
22383
|
+
const message = error instanceof Error ? error.message : "Identity recovery failed";
|
|
22384
|
+
throw new IdentityRecoverySessionConsumedError(message);
|
|
22385
|
+
}
|
|
22386
|
+
},
|
|
22387
|
+
hasPendingIdentityRecovery() {
|
|
22388
|
+
return Boolean(pendingIdentityRecovery);
|
|
22389
|
+
},
|
|
22390
|
+
cancelIdentityRecovery() {
|
|
22391
|
+
pendingIdentityRecovery = void 0;
|
|
22392
|
+
},
|
|
22393
|
+
async completeIdentityRecovery(params) {
|
|
22394
|
+
const storage2 = guardedStorage();
|
|
22395
|
+
const generation = storageGeneration;
|
|
22396
|
+
const pending = pendingIdentityRecovery;
|
|
22397
|
+
const signDidAuthVp = params.signDidAuthVp;
|
|
22398
|
+
if (!pending) throw new Error("No identity recovery is waiting for a new sign-in");
|
|
22399
|
+
if (!signDidAuthVp) {
|
|
22400
|
+
throw new Error("DID proof signing is required to bind a new sign-in");
|
|
22401
|
+
}
|
|
22402
|
+
const storageId = activeStorageId;
|
|
22403
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
22404
|
+
storage2,
|
|
22405
|
+
storageId,
|
|
22406
|
+
() => withRotationLock(async () => {
|
|
22407
|
+
const assertActive = /* @__PURE__ */ __name(() => {
|
|
22408
|
+
if (activeStorageId !== storageId || pendingIdentityRecovery !== pending || storageGeneration !== generation) {
|
|
22409
|
+
throw new Error("This recovery request was cancelled.");
|
|
22410
|
+
}
|
|
22411
|
+
}, "assertActive");
|
|
22412
|
+
assertActive();
|
|
22413
|
+
const candidates = await readPendingShareCandidates(storage2, storageId);
|
|
22414
|
+
if (pending.rebind && pending.rebind.storageId !== storageId) {
|
|
22415
|
+
throw new Error("Active account changed during identity recovery");
|
|
22416
|
+
}
|
|
22417
|
+
if (candidates.some(
|
|
22418
|
+
(candidate) => candidate.share !== pending.rebind?.shares.deviceShare
|
|
22419
|
+
)) {
|
|
22420
|
+
throw new Error(
|
|
22421
|
+
"Reconcile the pending share update before binding a sign-in"
|
|
22422
|
+
);
|
|
22423
|
+
}
|
|
22424
|
+
let shareVersion2;
|
|
22425
|
+
const retry = pending.rebind?.attempted ?? false;
|
|
22426
|
+
if (retry && pending.rebind) {
|
|
22427
|
+
const current = await fetchAuthShareRaw(
|
|
22428
|
+
serverUrl,
|
|
22429
|
+
params.token,
|
|
22430
|
+
params.providerType,
|
|
22431
|
+
void 0,
|
|
22432
|
+
tenantId
|
|
22433
|
+
);
|
|
22434
|
+
const authShare = authShareToString(current?.authShare);
|
|
22435
|
+
if (authShare && current?.shareVersion != null) {
|
|
22436
|
+
const health = await verifyStoredShares(
|
|
22437
|
+
{
|
|
22438
|
+
getDevice: /* @__PURE__ */ __name(async () => pending.rebind.shares.deviceShare, "getDevice"),
|
|
22439
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
22440
|
+
},
|
|
22441
|
+
pending.primaryDid,
|
|
22442
|
+
async (key) => key === pending.privateKey ? pending.primaryDid : ""
|
|
22443
|
+
);
|
|
22444
|
+
if (health.healthy) shareVersion2 = current.shareVersion;
|
|
22445
|
+
}
|
|
22446
|
+
}
|
|
22447
|
+
assertActive();
|
|
22448
|
+
if (shareVersion2 === void 0) {
|
|
22449
|
+
const didAuthVp = await requestFreshDidAuthVp(
|
|
22450
|
+
serverUrl,
|
|
22451
|
+
pending.privateKey,
|
|
22452
|
+
pending.primaryDid,
|
|
22453
|
+
signDidAuthVp,
|
|
22454
|
+
tenantId
|
|
22455
|
+
);
|
|
22456
|
+
assertActive();
|
|
22457
|
+
pending.rebind ??= {
|
|
22458
|
+
shares: (await splitAndVerify(pending.privateKey)).shares,
|
|
22459
|
+
storageId,
|
|
22460
|
+
attempted: false
|
|
22461
|
+
};
|
|
22462
|
+
const rebind = pending.rebind;
|
|
22463
|
+
if (!candidates.some(
|
|
22464
|
+
(candidate) => candidate.share === rebind.shares.deviceShare
|
|
22465
|
+
)) {
|
|
22466
|
+
await addPendingShareCandidate(
|
|
22467
|
+
storage2,
|
|
22468
|
+
{ share: rebind.shares.deviceShare, createdAt: Date.now() },
|
|
22469
|
+
storageId
|
|
22470
|
+
);
|
|
22471
|
+
}
|
|
22472
|
+
assertActive();
|
|
22473
|
+
rebind.attempted = true;
|
|
22474
|
+
try {
|
|
22475
|
+
const response = await postJson(
|
|
22476
|
+
`${serverUrl}/keys/recovery-session/rebind`,
|
|
22477
|
+
{
|
|
22478
|
+
recoverySessionToken: pending.rebindSessionToken,
|
|
22479
|
+
providerType: params.providerType,
|
|
22480
|
+
primaryDid: pending.primaryDid,
|
|
22481
|
+
authShare: {
|
|
22482
|
+
encryptedData: rebind.shares.authShare,
|
|
22483
|
+
encryptedDek: "",
|
|
22484
|
+
iv: ""
|
|
22485
|
+
}
|
|
22486
|
+
},
|
|
22487
|
+
{
|
|
22488
|
+
...buildHeaders("", didAuthVp, tenantId),
|
|
22489
|
+
"X-Auth-Token": params.token
|
|
22490
|
+
}
|
|
22491
|
+
);
|
|
22492
|
+
shareVersion2 = response.shareVersion;
|
|
22493
|
+
} catch (error) {
|
|
22494
|
+
if (error instanceof ShareWriteRejectedError && !retry) {
|
|
22495
|
+
await removePendingShareCandidate(
|
|
22496
|
+
storage2,
|
|
22497
|
+
rebind.shares.deviceShare,
|
|
22498
|
+
storageId
|
|
22499
|
+
);
|
|
22500
|
+
pending.rebind = void 0;
|
|
22501
|
+
}
|
|
22502
|
+
throw error;
|
|
22503
|
+
}
|
|
22504
|
+
}
|
|
22505
|
+
if (shareVersion2 === void 0 || !pending.rebind) {
|
|
22506
|
+
throw new Error("Server did not confirm the new share");
|
|
22507
|
+
}
|
|
22508
|
+
assertActive();
|
|
22509
|
+
const shares2 = pending.rebind.shares;
|
|
22510
|
+
await storage2.storeDeviceShare(shares2.deviceShare, storageId);
|
|
22511
|
+
await storage2.storeShareVersion(shareVersion2, storageId);
|
|
22512
|
+
await clearPendingShareCandidates(storage2, storageId);
|
|
22513
|
+
assertActive();
|
|
22514
|
+
lastEmailShare = shares2.emailShare;
|
|
22515
|
+
lastShareVersion = shareVersion2;
|
|
22516
|
+
lastServerSnapshot = {
|
|
22517
|
+
currentVersion: shareVersion2,
|
|
22518
|
+
resolvedVersion: shareVersion2,
|
|
22519
|
+
authShare: shares2.authShare,
|
|
22520
|
+
primaryDid: pending.primaryDid
|
|
22521
|
+
};
|
|
22522
|
+
pendingIdentityRecovery = void 0;
|
|
22523
|
+
return { shares: shares2, shareVersion: shareVersion2 };
|
|
22524
|
+
})
|
|
22525
|
+
);
|
|
22526
|
+
await tryEnrollEscrow(
|
|
22527
|
+
params.token,
|
|
22528
|
+
params.providerType,
|
|
22529
|
+
pending.privateKey,
|
|
22530
|
+
pending.primaryDid,
|
|
22531
|
+
shares,
|
|
22532
|
+
shareVersion,
|
|
22533
|
+
signDidAuthVp,
|
|
22534
|
+
void 0,
|
|
22535
|
+
void 0,
|
|
22536
|
+
true
|
|
22537
|
+
);
|
|
22538
|
+
if (activeStorageId !== storageId || storageGeneration !== generation) {
|
|
22539
|
+
throw new Error("This recovery request was cancelled.");
|
|
22540
|
+
}
|
|
22541
|
+
return { privateKey: pending.privateKey, did: pending.primaryDid };
|
|
22542
|
+
},
|
|
19997
22543
|
// --- Contact method management ---
|
|
19998
22544
|
async upgradeContactMethod(token, providerType, previousPhone, email, code) {
|
|
19999
22545
|
const res = await fetch(`${serverUrl}/keys/upgrade-contact-method`, {
|
|
@@ -20016,7 +22562,7 @@ function createSSSStrategy(config) {
|
|
|
20016
22562
|
return { customToken: data?.customToken };
|
|
20017
22563
|
},
|
|
20018
22564
|
// --- Email backup ---
|
|
20019
|
-
async sendEmailBackupShare(token, providerType, _privateKey, email) {
|
|
22565
|
+
async sendEmailBackupShare(token, providerType, _privateKey, email, didAuthVp) {
|
|
20020
22566
|
if (!enableEmailBackupShare) return;
|
|
20021
22567
|
if (!lastEmailShare) {
|
|
20022
22568
|
console.warn(
|
|
@@ -20024,29 +22570,22 @@ function createSSSStrategy(config) {
|
|
|
20024
22570
|
);
|
|
20025
22571
|
return;
|
|
20026
22572
|
}
|
|
20027
|
-
if (
|
|
20028
|
-
await sendEmailShareToRecoveryEmail(
|
|
20029
|
-
serverUrl,
|
|
20030
|
-
token,
|
|
20031
|
-
providerType,
|
|
20032
|
-
lastEmailShare,
|
|
20033
|
-
lastShareVersion,
|
|
20034
|
-
tenantId
|
|
20035
|
-
);
|
|
20036
|
-
} else {
|
|
20037
|
-
if (!email) {
|
|
20038
|
-
console.warn("Cannot send email backup share: no email address");
|
|
20039
|
-
return;
|
|
20040
|
-
}
|
|
22573
|
+
if (email) {
|
|
20041
22574
|
await sendEmailBackupShare(
|
|
20042
22575
|
serverUrl,
|
|
20043
22576
|
token,
|
|
20044
22577
|
providerType,
|
|
20045
22578
|
lastEmailShare,
|
|
20046
22579
|
email,
|
|
22580
|
+
escrowRelayPublicKey,
|
|
22581
|
+
escrowRelayKeyId,
|
|
22582
|
+
emailBranding,
|
|
20047
22583
|
lastShareVersion,
|
|
22584
|
+
didAuthVp,
|
|
20048
22585
|
tenantId
|
|
20049
22586
|
);
|
|
22587
|
+
} else {
|
|
22588
|
+
throw new Error("Enter a recovery email before sending a recovery key");
|
|
20050
22589
|
}
|
|
20051
22590
|
lastEmailShare = void 0;
|
|
20052
22591
|
lastShareVersion = void 0;
|
|
@@ -20063,6 +22602,8 @@ function createSSSStrategy(config) {
|
|
|
20063
22602
|
return [SSS_DB_NAME];
|
|
20064
22603
|
},
|
|
20065
22604
|
async cleanup() {
|
|
22605
|
+
storageGeneration++;
|
|
22606
|
+
await Promise.allSettled(localWrites);
|
|
20066
22607
|
}
|
|
20067
22608
|
};
|
|
20068
22609
|
}
|
|
@@ -20155,13 +22696,7 @@ var decryptShareFromTransfer = /* @__PURE__ */ __name(async (payload, recipientP
|
|
|
20155
22696
|
}, "decryptShareFromTransfer");
|
|
20156
22697
|
|
|
20157
22698
|
// src/qr-login.ts
|
|
20158
|
-
var buildHeaders2 = /* @__PURE__ */ __name((
|
|
20159
|
-
const headers = { "Content-Type": "application/json" };
|
|
20160
|
-
if (token) {
|
|
20161
|
-
headers.Authorization = `Bearer ${token}`;
|
|
20162
|
-
}
|
|
20163
|
-
return headers;
|
|
20164
|
-
}, "buildHeaders");
|
|
22699
|
+
var buildHeaders2 = /* @__PURE__ */ __name(() => ({ "Content-Type": "application/json" }), "buildHeaders");
|
|
20165
22700
|
var createQrLoginSession = /* @__PURE__ */ __name(async (config) => {
|
|
20166
22701
|
const ephemeralKeypair = await generateEphemeralKeypair();
|
|
20167
22702
|
const response = await fetch(`${config.serverUrl}/qr-login/session`, {
|
|
@@ -20229,10 +22764,14 @@ var pollUntilApproved = /* @__PURE__ */ __name(async (config, sessionId, ephemer
|
|
|
20229
22764
|
await new Promise((resolve, reject) => {
|
|
20230
22765
|
const timer = setTimeout(resolve, intervalMs);
|
|
20231
22766
|
if (options?.signal) {
|
|
20232
|
-
options.signal.addEventListener(
|
|
20233
|
-
|
|
20234
|
-
|
|
20235
|
-
|
|
22767
|
+
options.signal.addEventListener(
|
|
22768
|
+
"abort",
|
|
22769
|
+
() => {
|
|
22770
|
+
clearTimeout(timer);
|
|
22771
|
+
reject(new Error("QR login polling aborted"));
|
|
22772
|
+
},
|
|
22773
|
+
{ once: true }
|
|
22774
|
+
);
|
|
20236
22775
|
}
|
|
20237
22776
|
});
|
|
20238
22777
|
}
|
|
@@ -20284,6 +22823,3 @@ var notifyDevicesForQrSession = /* @__PURE__ */ __name(async (config, sessionId,
|
|
|
20284
22823
|
return { sent: false, deviceCount: 0 };
|
|
20285
22824
|
}
|
|
20286
22825
|
}, "notifyDevicesForQrSession");
|
|
20287
|
-
|
|
20288
|
-
// src/types.ts
|
|
20289
|
-
var import_types = __toESM(require_dist());
|