@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.
Files changed (56) hide show
  1. package/README.md +12 -95
  2. package/dist/api-client.d.ts +44 -7
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/atomic-operations.d.ts +10 -2
  5. package/dist/atomic-operations.d.ts.map +1 -1
  6. package/dist/email-relay-crypto.d.ts +49 -0
  7. package/dist/email-relay-crypto.d.ts.map +1 -0
  8. package/dist/escrow-attestation.d.ts +8 -0
  9. package/dist/escrow-attestation.d.ts.map +1 -0
  10. package/dist/escrow-crypto.d.ts +52 -0
  11. package/dist/escrow-crypto.d.ts.map +1 -0
  12. package/dist/escrow-pin.d.ts +22 -0
  13. package/dist/escrow-pin.d.ts.map +1 -0
  14. package/dist/index.d.ts +13 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/key-manager.d.ts +2 -0
  17. package/dist/key-manager.d.ts.map +1 -1
  18. package/dist/qr-login.d.ts.map +1 -1
  19. package/dist/recovery-phrase.d.ts +6 -0
  20. package/dist/recovery-phrase.d.ts.map +1 -1
  21. package/dist/sss-key-manager.cjs.development.js +2891 -355
  22. package/dist/sss-key-manager.cjs.development.js.map +4 -4
  23. package/dist/sss-key-manager.cjs.production.min.js +33 -33
  24. package/dist/sss-key-manager.cjs.production.min.js.map +4 -4
  25. package/dist/sss-key-manager.esm.js +2891 -355
  26. package/dist/sss-key-manager.esm.js.map +4 -4
  27. package/dist/sss-strategy.d.ts +74 -4
  28. package/dist/sss-strategy.d.ts.map +1 -1
  29. package/dist/storage.d.ts +1 -0
  30. package/dist/storage.d.ts.map +1 -1
  31. package/dist/types.d.ts +87 -4
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/api-client.test.ts +122 -0
  35. package/src/api-client.ts +107 -116
  36. package/src/atomic-operations.test.ts +56 -16
  37. package/src/atomic-operations.ts +55 -19
  38. package/src/critical-paths.test.ts +51 -1
  39. package/src/email-relay-crypto.test.ts +87 -0
  40. package/src/email-relay-crypto.ts +293 -0
  41. package/src/escrow-attestation.test.ts +85 -0
  42. package/src/escrow-attestation.ts +64 -0
  43. package/src/escrow-crypto.test.ts +381 -0
  44. package/src/escrow-crypto.ts +319 -0
  45. package/src/escrow-pin.test.ts +119 -0
  46. package/src/escrow-pin.ts +83 -0
  47. package/src/index.ts +63 -1
  48. package/src/key-manager.ts +4 -9
  49. package/src/qr-login.ts +22 -15
  50. package/src/recovery-phrase.ts +40 -0
  51. package/src/sss-session-writes.test.ts +109 -0
  52. package/src/sss-strategy.test.ts +2642 -110
  53. package/src/sss-strategy.ts +2186 -224
  54. package/src/storage.test.ts +66 -8
  55. package/src/storage.ts +41 -29
  56. 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
- Authorization: `Bearer ${token}`
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
- async storeAuthShare(input) {
18255
- const headers = await this.getAuthHeaders();
18256
- const providerType = this.authProvider.getProviderType();
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
- async addRecoveryMethod(input) {
18270
- const headers = await this.getAuthHeaders();
18271
- const providerType = this.authProvider.getProviderType();
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 markMigrated() {
18310
- const headers = await this.getAuthHeaders();
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({ providerType })
18836
+ headers: { "Content-Type": "application/json" },
18837
+ body: JSON.stringify({ email })
18316
18838
  });
18317
18839
  if (!response.ok) {
18318
- throw new Error(`Failed to mark as migrated: ${response.statusText}`);
18840
+ throw new Error(`Failed to send recovery code: ${response.statusText}`);
18319
18841
  }
18320
18842
  }
18321
- async sendEmailBackupShare(emailShare, overrideEmail) {
18322
- const headers = await this.getAuthHeaders();
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
- console.warn(`Failed to send email backup share: ${response.statusText}`);
18850
+ throw new Error(`Failed to verify recovery code: ${response.statusText}`);
18344
18851
  }
18852
+ return response.json();
18345
18853
  }
18346
- async addRecoveryEmail(email) {
18347
- const token = await this.authProvider.getIdToken();
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({ authToken: token, providerType, email })
18858
+ body: JSON.stringify({ recoverySessionToken, type, credentialId })
18353
18859
  });
18354
18860
  if (!response.ok) {
18355
- const data = await response.json().catch(() => ({}));
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 verifyRecoveryEmail(code) {
18360
- const token = await this.authProvider.getIdToken();
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: { "Content-Type": "application/json" },
18365
- body: JSON.stringify({ authToken: token, providerType, code })
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
- const data = await response.json().catch(() => ({}));
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
- const headers = await this.getAuthHeaders();
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
- { name: "AES-GCM", length: 256 },
18580
- false,
18581
- ["encrypt", "decrypt"]
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(`SSS Storage: removing orphaned entry that failed to decrypt: ${id}`);
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/atomic-operations.ts
19337
- var ShareVerificationError = class extends Error {
19338
- constructor(message, combination, expected, got) {
19339
- super(message);
19340
- this.combination = combination;
19341
- this.expected = expected;
19342
- this.got = got;
19343
- this.name = "ShareVerificationError";
19344
- }
19345
- static {
19346
- __name(this, "ShareVerificationError");
19347
- }
19348
- };
19349
- var AtomicUpdateError = class extends Error {
19350
- constructor(message, phase, rolledBack, cause) {
19351
- super(message);
19352
- this.phase = phase;
19353
- this.rolledBack = rolledBack;
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
- static {
19358
- __name(this, "AtomicUpdateError");
19359
- }
19360
- };
19361
- async function splitAndVerify(privateKey) {
19362
- const shares = await splitPrivateKey(privateKey);
19363
- const combinations = [
19364
- ["device+auth", shares.deviceShare, shares.authShare],
19365
- ["device+recovery", shares.deviceShare, shares.recoveryShare],
19366
- ["device+email", shares.deviceShare, shares.emailShare],
19367
- ["auth+recovery", shares.authShare, shares.recoveryShare],
19368
- ["auth+email", shares.authShare, shares.emailShare],
19369
- ["recovery+email", shares.recoveryShare, shares.emailShare]
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 [name, share1, share2] of combinations) {
19372
- const reconstructed = await reconstructFromShares([share1, share2]);
19373
- if (reconstructed !== privateKey) {
19374
- throw new ShareVerificationError(
19375
- `Share verification failed for ${name}: reconstruction mismatch`,
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
- privateKey,
19384
- shares,
19385
- verified: true
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
- try {
19404
- await storage.storeDevice(newShares.deviceShare);
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
- try {
19415
- await storage.storeAuth(newShares.authShare);
19416
- } catch (e) {
19417
- if (options.previousDeviceShare && deviceStored) {
19418
- try {
19419
- await storage.storeDevice(options.previousDeviceShare);
19420
- options.onRollback?.("Server storage failed, restored previous device share");
19421
- } catch (rollbackError) {
19422
- console.error("CRITICAL: Rollback failed after auth share storage failure", rollbackError);
19423
- }
19424
- }
19425
- throw new AtomicUpdateError(
19426
- "Failed to store auth share on server, rolled back device share",
19427
- "store_auth",
19428
- !!options.previousDeviceShare,
19429
- e instanceof Error ? e : void 0
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 newShares;
19433
- }
19434
- __name(atomicShareUpdate, "atomicShareUpdate");
19435
- async function verifyStoredShares(storage, expectedDid, didFromPrivateKey) {
19436
- const result = {
19437
- healthy: false,
19438
- hasDeviceShare: false,
19439
- hasAuthShare: false,
19440
- didMatches: false,
19441
- error: void 0
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
- try {
19444
- const deviceShare = await storage.getDevice?.();
19445
- result.hasDeviceShare = !!deviceShare;
19446
- if (!deviceShare) {
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.statusText}`);
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
- throw new Error(`Failed to store auth share: ${response.statusText}`);
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 { shareVersion: data.shareVersion ?? 1 };
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, authToken: token });
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: buildHeaders(token, void 0, tenantId)
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.statusText}`);
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
- try {
19568
- const payload = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
19569
- const response = await fetch(`${serverUrl}/keys/email-backup`, {
19570
- method: "POST",
19571
- headers: buildHeaders(token, void 0, tenantId),
19572
- body: JSON.stringify({ authToken: token, providerType, emailShare: payload, email })
19573
- });
19574
- if (!response.ok) {
19575
- console.warn(`Email backup share delivery failed: ${response.statusText}`);
19576
- }
19577
- } catch (e) {
19578
- console.warn("Email backup share delivery failed:", e);
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, void 0, tenantId),
20793
+ headers: buildHeaders(token, didAuthVp, tenantId),
19586
20794
  body: JSON.stringify({
19587
20795
  authToken: token,
19588
20796
  providerType,
19589
- emailShare: payload,
19590
- useRecoveryEmail: true
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 to recovery email: ${response.statusText}`);
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
- }, "sendEmailShareToRecoveryEmail");
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 { serverUrl, enableEmailBackupShare = false, tenantId } = config;
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 hasRecoveryEmail = false;
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 scoped = await storage.getDeviceShare(activeStorageId);
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 storage.getDeviceShare();
21316
+ const legacy = await storage2.getDeviceShare();
19629
21317
  if (legacy) {
19630
- await storage.storeDeviceShare(legacy, activeStorageId);
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
- return storage.clearAllShares(activeStorageId);
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 localVersion = await storage.getShareVersion(activeStorageId);
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
- hasRecoveryEmail = false;
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 = typeof rawAuthShare === "object" && rawAuthShare !== null ? rawAuthShare.encryptedData ?? null : typeof rawAuthShare === "string" ? rawAuthShare : null;
21554
+ const authShareString = authShareToString(rawAuthShare);
19688
21555
  const serverVersion = data.shareVersion ?? null;
19689
- if (serverVersion != null && localVersion == null) {
19690
- storage.storeShareVersion(serverVersion, activeStorageId).catch((e) => console.warn("SSS: failed to backfill local shareVersion", e));
19691
- }
19692
- hasRecoveryEmail = !!data.maskedRecoveryEmail;
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 { shareVersion } = await putAuthShare(
19705
- serverUrl,
19706
- token,
19707
- providerType,
19708
- authShare,
19709
- primaryDid,
19710
- didAuthVp,
19711
- tenantId
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 storage.storeShareVersion(shareVersion, activeStorageId);
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 mark migrated: ${response.statusText}`);
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 { token, providerType, input, didFromPrivateKey } = params;
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 storage.storeDeviceShare(recoveryShare, activeStorageId);
19823
- const versionToStore = recoveryShareVersion ?? serverData.shareVersion ?? 1;
19824
- await storage.storeShareVersion(versionToStore, activeStorageId);
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
- const { shareVersion } = await putAuthShare(
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
- vpJwt,
19858
- tenantId
21999
+ storage2,
22000
+ activeStorageId,
22001
+ void 0,
22002
+ signDidAuthVp,
22003
+ tenantId,
22004
+ recordPromotion(primaryDid),
22005
+ captureActiveUserGuard()
19859
22006
  );
19860
- await storage.storeShareVersion(shareVersion, activeStorageId);
19861
- if (enableEmailBackupShare && input.method !== "email" && lastEmailShare) {
19862
- const resend = hasRecoveryEmail ? sendEmailShareToRecoveryEmail(
19863
- serverUrl,
19864
- token,
19865
- providerType,
19866
- lastEmailShare,
19867
- shareVersion,
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
- vpJwt,
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
- vpJwt,
22091
+ signDidAuthVp ? await requestFreshDidAuthVp(
22092
+ serverUrl,
22093
+ privateKey,
22094
+ primaryDid,
22095
+ signDidAuthVp,
22096
+ tenantId
22097
+ ) : void 0,
19919
22098
  tenantId
19920
22099
  );
19921
- return { method: "phrase", phrase };
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
- vpJwt,
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 sendEmailShareToRecoveryEmail(
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 postRecoveryMethod(
19963
- serverUrl,
22195
+ await tryEnrollEscrow(
19964
22196
  token,
19965
22197
  providerType,
19966
- {
19967
- type: "email",
19968
- shareVersion
19969
- },
19970
- vpJwt,
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
- if (enableEmailBackupShare && !methods.some((m) => m.type === "email")) {
19989
- methods.push({ type: "email", createdAt: /* @__PURE__ */ new Date() });
19990
- }
19991
- return methods;
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 (hasRecoveryEmail) {
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((token) => {
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("abort", () => {
20233
- clearTimeout(timer);
20234
- reject(new Error("QR login polling aborted"));
20235
- }, { once: true });
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());