@learncard/sss-key-manager 0.1.23 → 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 +3163 -440
  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 +3163 -440
  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
@@ -55,12 +55,15 @@ var require_types_cjs_development = __commonJS({
55
55
  var __toCommonJS2 = /* @__PURE__ */ __name((mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod), "__toCommonJS");
56
56
  var index_exports2 = {};
57
57
  __export2(index_exports2, {
58
+ ACT_AS_HEADER: /* @__PURE__ */ __name(() => ACT_AS_HEADER, "ACT_AS_HEADER"),
58
59
  AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX: /* @__PURE__ */ __name(() => AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX, "AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX"),
59
60
  AchievementCredentialValidator: /* @__PURE__ */ __name(() => AchievementCredentialValidator, "AchievementCredentialValidator"),
60
61
  AchievementCriteriaValidator: /* @__PURE__ */ __name(() => AchievementCriteriaValidator, "AchievementCriteriaValidator"),
61
62
  AchievementSubjectValidator: /* @__PURE__ */ __name(() => AchievementSubjectValidator, "AchievementSubjectValidator"),
62
63
  AchievementTypeValidator: /* @__PURE__ */ __name(() => AchievementTypeValidator, "AchievementTypeValidator"),
63
64
  AchievementValidator: /* @__PURE__ */ __name(() => AchievementValidator, "AchievementValidator"),
65
+ AcknowledgeViewInputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewInputValidator, "AcknowledgeViewInputValidator"),
66
+ AcknowledgeViewOutputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewOutputValidator, "AcknowledgeViewOutputValidator"),
64
67
  AddTagInputValidator: /* @__PURE__ */ __name(() => AddTagInputValidator, "AddTagInputValidator"),
65
68
  AddressValidator: /* @__PURE__ */ __name(() => AddressValidator, "AddressValidator"),
66
69
  AgeRatingValidator: /* @__PURE__ */ __name(() => AgeRatingValidator, "AgeRatingValidator"),
@@ -138,6 +141,7 @@ var require_types_cjs_development = __commonJS({
138
141
  CreateContactMethodSessionValidator: /* @__PURE__ */ __name(() => CreateContactMethodSessionValidator, "CreateContactMethodSessionValidator"),
139
142
  CreateManagedFrameworkBatchInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkBatchInputValidator, "CreateManagedFrameworkBatchInputValidator"),
140
143
  CreateManagedFrameworkInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkInputValidator, "CreateManagedFrameworkInputValidator"),
144
+ CreateShareLinkInputValidator: /* @__PURE__ */ __name(() => CreateShareLinkInputValidator, "CreateShareLinkInputValidator"),
141
145
  CreateSkillInputValidator: /* @__PURE__ */ __name(() => CreateSkillInputValidator, "CreateSkillInputValidator"),
142
146
  CreateSkillsBatchInputValidator: /* @__PURE__ */ __name(() => CreateSkillsBatchInputValidator, "CreateSkillsBatchInputValidator"),
143
147
  CredentialActivityEventTypeValidator: /* @__PURE__ */ __name(() => CredentialActivityEventTypeValidator, "CredentialActivityEventTypeValidator"),
@@ -169,10 +173,14 @@ var require_types_cjs_development = __commonJS({
169
173
  DeleteSkillInputValidator: /* @__PURE__ */ __name(() => DeleteSkillInputValidator, "DeleteSkillInputValidator"),
170
174
  DidDocumentValidator: /* @__PURE__ */ __name(() => DidDocumentValidator, "DidDocumentValidator"),
171
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"),
172
179
  EncryptedCredentialRecordValidator: /* @__PURE__ */ __name(() => EncryptedCredentialRecordValidator, "EncryptedCredentialRecordValidator"),
173
180
  EncryptedRecordValidator: /* @__PURE__ */ __name(() => EncryptedRecordValidator, "EncryptedRecordValidator"),
174
181
  EndorsementCredentialValidator: /* @__PURE__ */ __name(() => EndorsementCredentialValidator, "EndorsementCredentialValidator"),
175
182
  EndorsementSubjectValidator: /* @__PURE__ */ __name(() => EndorsementSubjectValidator, "EndorsementSubjectValidator"),
183
+ EscrowPinStatusValidator: /* @__PURE__ */ __name(() => EscrowPinStatusValidator, "EscrowPinStatusValidator"),
176
184
  EvidenceValidator: /* @__PURE__ */ __name(() => EvidenceValidator, "EvidenceValidator"),
177
185
  FlatAuthGrantValidator: /* @__PURE__ */ __name(() => FlatAuthGrantValidator, "FlatAuthGrantValidator"),
178
186
  FrameworkWithSkillsValidator: /* @__PURE__ */ __name(() => FrameworkWithSkillsValidator, "FrameworkWithSkillsValidator"),
@@ -194,10 +202,18 @@ var require_types_cjs_development = __commonJS({
194
202
  IdentifierTypeValidator: /* @__PURE__ */ __name(() => IdentifierTypeValidator, "IdentifierTypeValidator"),
195
203
  IdentityObjectValidator: /* @__PURE__ */ __name(() => IdentityObjectValidator, "IdentityObjectValidator"),
196
204
  ImageValidator: /* @__PURE__ */ __name(() => ImageValidator, "ImageValidator"),
205
+ InboxBatchErrorReasonValidator: /* @__PURE__ */ __name(() => InboxBatchErrorReasonValidator, "InboxBatchErrorReasonValidator"),
206
+ InboxBatchReceiptValidator: /* @__PURE__ */ __name(() => InboxBatchReceiptValidator, "InboxBatchReceiptValidator"),
207
+ InboxBatchStatusValidator: /* @__PURE__ */ __name(() => InboxBatchStatusValidator, "InboxBatchStatusValidator"),
197
208
  InboxCredentialQueryValidator: /* @__PURE__ */ __name(() => InboxCredentialQueryValidator, "InboxCredentialQueryValidator"),
209
+ InboxCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => InboxCredentialRefreshReceiptValidator, "InboxCredentialRefreshReceiptValidator"),
198
210
  InboxCredentialValidator: /* @__PURE__ */ __name(() => InboxCredentialValidator, "InboxCredentialValidator"),
199
211
  IncrementCounterEventValidator: /* @__PURE__ */ __name(() => IncrementCounterEventValidator, "IncrementCounterEventValidator"),
200
212
  InstalledAppValidator: /* @__PURE__ */ __name(() => InstalledAppValidator, "InstalledAppValidator"),
213
+ IssueInboxCredentialBatchItemResultValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchItemResultValidator, "IssueInboxCredentialBatchItemResultValidator"),
214
+ IssueInboxCredentialBatchItemValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchItemValidator, "IssueInboxCredentialBatchItemValidator"),
215
+ IssueInboxCredentialBatchResponseValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchResponseValidator, "IssueInboxCredentialBatchResponseValidator"),
216
+ IssueInboxCredentialBatchValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchValidator, "IssueInboxCredentialBatchValidator"),
201
217
  IssueInboxCredentialResponseValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialResponseValidator, "IssueInboxCredentialResponseValidator"),
202
218
  IssueInboxCredentialValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialValidator, "IssueInboxCredentialValidator"),
203
219
  IssueInboxSigningAuthorityValidator: /* @__PURE__ */ __name(() => IssueInboxSigningAuthorityValidator, "IssueInboxSigningAuthorityValidator"),
@@ -246,6 +262,15 @@ var require_types_cjs_development = __commonJS({
246
262
  LaunchTypeValidator: /* @__PURE__ */ __name(() => LaunchTypeValidator, "LaunchTypeValidator"),
247
263
  LearnCardRefreshAuthorizationValidator: /* @__PURE__ */ __name(() => LearnCardRefreshAuthorizationValidator, "LearnCardRefreshAuthorizationValidator"),
248
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"),
273
+ ManagedCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshReceiptValidator, "ManagedCredentialRefreshReceiptValidator"),
249
274
  ManagedCredentialRefreshServiceValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshServiceValidator, "ManagedCredentialRefreshServiceValidator"),
250
275
  PaginatedAppStoreListingsValidator: /* @__PURE__ */ __name(() => PaginatedAppStoreListingsValidator, "PaginatedAppStoreListingsValidator"),
251
276
  PaginatedBoostRecipientsValidator: /* @__PURE__ */ __name(() => PaginatedBoostRecipientsValidator, "PaginatedBoostRecipientsValidator"),
@@ -267,11 +292,14 @@ var require_types_cjs_development = __commonJS({
267
292
  PaginatedLCNProfileManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfileManagersValidator, "PaginatedLCNProfileManagersValidator"),
268
293
  PaginatedLCNProfilesAndManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesAndManagersValidator, "PaginatedLCNProfilesAndManagersValidator"),
269
294
  PaginatedLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesValidator, "PaginatedLCNProfilesValidator"),
295
+ PaginatedShareLinksValidator: /* @__PURE__ */ __name(() => PaginatedShareLinksValidator, "PaginatedShareLinksValidator"),
270
296
  PaginatedSkillFrameworksValidator: /* @__PURE__ */ __name(() => PaginatedSkillFrameworksValidator, "PaginatedSkillFrameworksValidator"),
271
297
  PaginatedSkillTreeValidator: /* @__PURE__ */ __name(() => PaginatedSkillTreeValidator, "PaginatedSkillTreeValidator"),
272
298
  PaginatedVisibleLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedVisibleLCNProfilesValidator, "PaginatedVisibleLCNProfilesValidator"),
273
299
  PaginationOptionsValidator: /* @__PURE__ */ __name(() => PaginationOptionsValidator, "PaginationOptionsValidator"),
274
300
  PaginationResponseValidator: /* @__PURE__ */ __name(() => PaginationResponseValidator, "PaginationResponseValidator"),
301
+ PrepareRefreshableSendInputValidator: /* @__PURE__ */ __name(() => PrepareRefreshableSendInputValidator, "PrepareRefreshableSendInputValidator"),
302
+ PrepareRefreshableSendResultValidator: /* @__PURE__ */ __name(() => PrepareRefreshableSendResultValidator, "PrepareRefreshableSendResultValidator"),
275
303
  ProfileValidator: /* @__PURE__ */ __name(() => ProfileValidator, "ProfileValidator"),
276
304
  ProfileVisibilityEnum: /* @__PURE__ */ __name(() => ProfileVisibilityEnum, "ProfileVisibilityEnum"),
277
305
  PromotionLevelValidator: /* @__PURE__ */ __name(() => PromotionLevelValidator, "PromotionLevelValidator"),
@@ -289,14 +317,24 @@ var require_types_cjs_development = __commonJS({
289
317
  ReplaceSkillFrameworkSkillsInputValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsInputValidator, "ReplaceSkillFrameworkSkillsInputValidator"),
290
318
  ReplaceSkillFrameworkSkillsResultValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsResultValidator, "ReplaceSkillFrameworkSkillsResultValidator"),
291
319
  RequestLearnerContextEventValidator: /* @__PURE__ */ __name(() => RequestLearnerContextEventValidator, "RequestLearnerContextEventValidator"),
320
+ ResolveShareLinkInputValidator: /* @__PURE__ */ __name(() => ResolveShareLinkInputValidator, "ResolveShareLinkInputValidator"),
292
321
  ResultDescriptionValidator: /* @__PURE__ */ __name(() => ResultDescriptionValidator, "ResultDescriptionValidator"),
293
322
  ResultStatusTypeValidator: /* @__PURE__ */ __name(() => ResultStatusTypeValidator, "ResultStatusTypeValidator"),
294
323
  ResultTypeValidator: /* @__PURE__ */ __name(() => ResultTypeValidator, "ResultTypeValidator"),
295
324
  ResultValidator: /* @__PURE__ */ __name(() => ResultValidator, "ResultValidator"),
296
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"),
297
334
  SendAiSessionCredentialEventValidator: /* @__PURE__ */ __name(() => SendAiSessionCredentialEventValidator, "SendAiSessionCredentialEventValidator"),
298
335
  SendBoostInputValidator: /* @__PURE__ */ __name(() => SendBoostInputValidator, "SendBoostInputValidator"),
299
336
  SendBoostResponseValidator: /* @__PURE__ */ __name(() => SendBoostResponseValidator, "SendBoostResponseValidator"),
337
+ SendBoostTemplateValidator: /* @__PURE__ */ __name(() => SendBoostTemplateValidator, "SendBoostTemplateValidator"),
300
338
  SendBrandingOptionsValidator: /* @__PURE__ */ __name(() => SendBrandingOptionsValidator, "SendBrandingOptionsValidator"),
301
339
  SendCredentialEventValidator: /* @__PURE__ */ __name(() => SendCredentialEventValidator, "SendCredentialEventValidator"),
302
340
  SendInboxResponseValidator: /* @__PURE__ */ __name(() => SendInboxResponseValidator, "SendInboxResponseValidator"),
@@ -307,6 +345,32 @@ var require_types_cjs_development = __commonJS({
307
345
  SentCredentialInfoValidator: /* @__PURE__ */ __name(() => SentCredentialInfoValidator, "SentCredentialInfoValidator"),
308
346
  ServiceValidator: /* @__PURE__ */ __name(() => ServiceValidator, "ServiceValidator"),
309
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"),
310
374
  SkillDeletionStrategyValidator: /* @__PURE__ */ __name(() => SkillDeletionStrategyValidator, "SkillDeletionStrategyValidator"),
311
375
  SkillFrameworkAdminInputValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminInputValidator, "SkillFrameworkAdminInputValidator"),
312
376
  SkillFrameworkAdminsValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminsValidator, "SkillFrameworkAdminsValidator"),
@@ -338,6 +402,7 @@ var require_types_cjs_development = __commonJS({
338
402
  UnsignedVCValidator: /* @__PURE__ */ __name(() => UnsignedVCValidator, "UnsignedVCValidator"),
339
403
  UnsignedVPValidator: /* @__PURE__ */ __name(() => UnsignedVPValidator, "UnsignedVPValidator"),
340
404
  UpdateFrameworkInputValidator: /* @__PURE__ */ __name(() => UpdateFrameworkInputValidator, "UpdateFrameworkInputValidator"),
405
+ UpdateShareLinkInputValidator: /* @__PURE__ */ __name(() => UpdateShareLinkInputValidator, "UpdateShareLinkInputValidator"),
341
406
  UpdateSkillInputValidator: /* @__PURE__ */ __name(() => UpdateSkillInputValidator, "UpdateSkillInputValidator"),
342
407
  VC2EvidenceValidator: /* @__PURE__ */ __name(() => VC2EvidenceValidator, "VC2EvidenceValidator"),
343
408
  VCValidator: /* @__PURE__ */ __name(() => VCValidator, "VCValidator"),
@@ -349,7 +414,12 @@ var require_types_cjs_development = __commonJS({
349
414
  VerificationStatusValidator: /* @__PURE__ */ __name(() => VerificationStatusValidator, "VerificationStatusValidator"),
350
415
  appStoreActionValidator: /* @__PURE__ */ __name(() => appStoreActionValidator, "appStoreActionValidator"),
351
416
  capgoUpdateActionValidator: /* @__PURE__ */ __name(() => capgoUpdateActionValidator, "capgoUpdateActionValidator"),
417
+ classifyShareManifest: /* @__PURE__ */ __name(() => classifyShareManifest, "classifyShareManifest"),
418
+ decodeBase64Url: /* @__PURE__ */ __name(() => decodeBase64Url, "decodeBase64Url"),
419
+ decodedBase64UrlByteLength: /* @__PURE__ */ __name(() => decodedBase64UrlByteLength, "decodedBase64UrlByteLength"),
352
420
  dismissActionValidator: /* @__PURE__ */ __name(() => dismissActionValidator, "dismissActionValidator"),
421
+ encodeBase64Url: /* @__PURE__ */ __name(() => encodeBase64Url, "encodeBase64Url"),
422
+ escrowPinMismatchMessage: /* @__PURE__ */ __name(() => escrowPinMismatchMessage, "escrowPinMismatchMessage"),
353
423
  externalLinkActionValidator: /* @__PURE__ */ __name(() => externalLinkActionValidator, "externalLinkActionValidator"),
354
424
  inAppMessageActionStyleValidator: /* @__PURE__ */ __name(() => inAppMessageActionStyleValidator, "inAppMessageActionStyleValidator"),
355
425
  inAppMessageActionTargetValidator: /* @__PURE__ */ __name(() => inAppMessageActionTargetValidator, "inAppMessageActionTargetValidator"),
@@ -364,10 +434,12 @@ var require_types_cjs_development = __commonJS({
364
434
  inAppMessageVersionSourceValidator: /* @__PURE__ */ __name(() => inAppMessageVersionSourceValidator, "inAppMessageVersionSourceValidator"),
365
435
  inAppMessagesFlagValidator: /* @__PURE__ */ __name(() => inAppMessagesFlagValidator, "inAppMessagesFlagValidator"),
366
436
  internalLinkActionValidator: /* @__PURE__ */ __name(() => internalLinkActionValidator, "internalLinkActionValidator"),
437
+ isCanonicalBase64Url: /* @__PURE__ */ __name(() => isCanonicalBase64Url, "isCanonicalBase64Url"),
367
438
  isStoredCredentialEnvelope: /* @__PURE__ */ __name(() => isStoredCredentialEnvelope, "isStoredCredentialEnvelope"),
368
439
  parseInAppMessagesFlag: /* @__PURE__ */ __name(() => parseInAppMessagesFlag, "parseInAppMessagesFlag"),
369
440
  platformPredicateValidator: /* @__PURE__ */ __name(() => platformPredicateValidator, "platformPredicateValidator"),
370
441
  rolePredicateValidator: /* @__PURE__ */ __name(() => rolePredicateValidator, "rolePredicateValidator"),
442
+ utf8ByteLength: /* @__PURE__ */ __name(() => utf8ByteLength, "utf8ByteLength"),
371
443
  versionPredicateValidator: /* @__PURE__ */ __name(() => versionPredicateValidator, "versionPredicateValidator")
372
444
  });
373
445
  module2.exports = __toCommonJS2(index_exports2);
@@ -16221,6 +16293,204 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16221
16293
  $or: BaseStringQuery.array()
16222
16294
  })
16223
16295
  ]);
16296
+ var LearnCardRefreshAuthorizationValidator = external_exports.object({ type: external_exports.literal("LearnCardDIDAuth") }).catchall(external_exports.any());
16297
+ var ManagedCredentialRefreshServiceValidator = external_exports.object({
16298
+ id: external_exports.string().min(1),
16299
+ type: external_exports.literal("LearnCardCredentialRefresh2026"),
16300
+ authorization: LearnCardRefreshAuthorizationValidator.optional()
16301
+ }).catchall(external_exports.any());
16302
+ var StandardCredentialRefreshServiceValidator = external_exports.object({
16303
+ id: external_exports.string().min(1),
16304
+ type: external_exports.literal("1EdTechCredentialRefresh")
16305
+ }).catchall(external_exports.any());
16306
+ var SupportedCredentialRefreshServiceValidator = external_exports.union([
16307
+ ManagedCredentialRefreshServiceValidator,
16308
+ StandardCredentialRefreshServiceValidator
16309
+ ]);
16310
+ var AllocateCredentialRefreshInputValidator = external_exports.object({
16311
+ holder: external_exports.object({
16312
+ profileId: external_exports.string().optional(),
16313
+ did: external_exports.string().min(1)
16314
+ }),
16315
+ credentialId: external_exports.string().min(1)
16316
+ });
16317
+ var AllocateCredentialRefreshResultValidator = external_exports.object({
16318
+ refreshId: external_exports.string().min(1),
16319
+ refreshService: ManagedCredentialRefreshServiceValidator.extend({
16320
+ authorization: LearnCardRefreshAuthorizationValidator
16321
+ })
16322
+ });
16323
+ var ManagedCredentialRefreshReceiptValidator = external_exports.object({
16324
+ refreshId: external_exports.string().min(1),
16325
+ refreshService: ManagedCredentialRefreshServiceValidator,
16326
+ credentialId: external_exports.string().min(1),
16327
+ issuerDid: external_exports.string().min(1),
16328
+ holderDid: external_exports.string().min(1),
16329
+ credentialStatus: CredentialStatusValidator.or(
16330
+ CredentialStatusValidator.array()
16331
+ ).optional()
16332
+ }).strip();
16333
+ var InboxCredentialRefreshReceiptValidator = ManagedCredentialRefreshReceiptValidator.omit(
16334
+ { holderDid: true }
16335
+ ).extend({ holderDid: external_exports.string().min(1).optional() });
16336
+ var CredentialRefreshSigningModeValidator = external_exports.enum(["issuer-signed", "signing-authority"]);
16337
+ var PublishCredentialRefreshBaseFields = {
16338
+ refreshId: external_exports.string().min(1),
16339
+ notifyHolder: external_exports.boolean().optional(),
16340
+ updateSummary: external_exports.string().optional(),
16341
+ idempotencyKey: external_exports.string().optional()
16342
+ };
16343
+ var PublishIssuerSignedRefreshValidator = external_exports.object({
16344
+ ...PublishCredentialRefreshBaseFields,
16345
+ mode: external_exports.literal("issuer-signed"),
16346
+ signedCredential: VCValidator
16347
+ });
16348
+ var PublishSigningAuthorityRefreshValidator = external_exports.object({
16349
+ ...PublishCredentialRefreshBaseFields,
16350
+ mode: external_exports.literal("signing-authority"),
16351
+ credential: UnsignedVCValidator,
16352
+ signingAuthority: external_exports.object({
16353
+ type: external_exports.string().min(1)
16354
+ }).catchall(external_exports.any())
16355
+ });
16356
+ var PublishCredentialRefreshInputValidator = external_exports.object({
16357
+ ...PublishCredentialRefreshBaseFields,
16358
+ mode: CredentialRefreshSigningModeValidator,
16359
+ signedCredential: VCValidator.optional(),
16360
+ credential: UnsignedVCValidator.optional(),
16361
+ signingAuthority: external_exports.object({
16362
+ type: external_exports.string().min(1)
16363
+ }).catchall(external_exports.any()).optional()
16364
+ }).superRefine((input, ctx) => {
16365
+ if (input.mode === "issuer-signed" && !input.signedCredential) {
16366
+ ctx.addIssue({
16367
+ code: "custom",
16368
+ path: ["signedCredential"],
16369
+ message: "signedCredential is required for issuer-signed publication"
16370
+ });
16371
+ }
16372
+ if (input.mode === "issuer-signed" && (input.credential !== void 0 || input.signingAuthority !== void 0)) {
16373
+ ctx.addIssue({
16374
+ code: "custom",
16375
+ path: ["mode"],
16376
+ message: "issuer-signed publication cannot include signing-authority fields"
16377
+ });
16378
+ }
16379
+ if (input.mode === "signing-authority") {
16380
+ if (input.signedCredential !== void 0) {
16381
+ ctx.addIssue({
16382
+ code: "custom",
16383
+ path: ["signedCredential"],
16384
+ message: "signing-authority publication cannot include signedCredential"
16385
+ });
16386
+ }
16387
+ if (!input.credential) {
16388
+ ctx.addIssue({
16389
+ code: "custom",
16390
+ path: ["credential"],
16391
+ message: "credential is required for signing-authority publication"
16392
+ });
16393
+ }
16394
+ if (!input.signingAuthority) {
16395
+ ctx.addIssue({
16396
+ code: "custom",
16397
+ path: ["signingAuthority"],
16398
+ message: "signingAuthority is required for signing-authority publication"
16399
+ });
16400
+ }
16401
+ }
16402
+ });
16403
+ var PublishCredentialRefreshNotificationValidator = external_exports.enum([
16404
+ "queued",
16405
+ "suppressed",
16406
+ "not-applicable",
16407
+ /** Publication succeeded, but the post-commit notification enqueue must be retried. */
16408
+ "delivery-failed"
16409
+ ]);
16410
+ var PublishCredentialRefreshResultValidator = external_exports.object({
16411
+ refreshId: external_exports.string().min(1),
16412
+ version: external_exports.number().int().positive(),
16413
+ publishedAt: external_exports.string().min(1),
16414
+ notification: PublishCredentialRefreshNotificationValidator
16415
+ });
16416
+ var CredentialRefreshVersionMetadataValidator = external_exports.object({
16417
+ version: external_exports.number().int().positive(),
16418
+ publishedAt: external_exports.string().min(1),
16419
+ effectiveAt: external_exports.string().optional(),
16420
+ etag: external_exports.string().optional(),
16421
+ signingMode: CredentialRefreshSigningModeValidator.optional(),
16422
+ updateSummary: external_exports.string().optional()
16423
+ });
16424
+ var GetCredentialRefreshHistoryInputValidator = external_exports.object({
16425
+ refreshId: external_exports.string().min(1),
16426
+ cursor: external_exports.string().optional(),
16427
+ limit: external_exports.number().int().positive().optional()
16428
+ });
16429
+ var GetCredentialRefreshHistoryResultValidator = external_exports.object({
16430
+ records: CredentialRefreshVersionMetadataValidator.array(),
16431
+ hasMore: external_exports.boolean(),
16432
+ cursor: external_exports.string().optional()
16433
+ });
16434
+ var CredentialRefreshChallengeValidator = external_exports.object({
16435
+ challenge: external_exports.string().min(1),
16436
+ expiresAt: external_exports.string().min(1),
16437
+ domain: external_exports.string().optional(),
16438
+ scheme: external_exports.literal("LearnCardDIDAuth").optional()
16439
+ });
16440
+ var PublicCredentialRefreshEnvelopeValidator = external_exports.object({
16441
+ format: external_exports.literal("vc"),
16442
+ credential: VCValidator,
16443
+ etag: external_exports.string().optional()
16444
+ });
16445
+ var JweCredentialRefreshEnvelopeValidator = external_exports.object({
16446
+ format: external_exports.literal("jwe"),
16447
+ jwe: JWEValidator,
16448
+ etag: external_exports.string().optional(),
16449
+ /** Present on LearnCard-managed endpoints; optional for interoperable JWE services. */
16450
+ version: external_exports.number().int().positive().optional()
16451
+ });
16452
+ var CredentialRefreshResponseEnvelopeValidator = external_exports.discriminatedUnion("format", [
16453
+ PublicCredentialRefreshEnvelopeValidator,
16454
+ JweCredentialRefreshEnvelopeValidator
16455
+ ]);
16456
+ var CredentialRefreshFailureCodeValidator = external_exports.enum([
16457
+ "UNAVAILABLE",
16458
+ "TIMEOUT",
16459
+ "UNSUPPORTED_SERVICE",
16460
+ "UNAUTHORIZED",
16461
+ "MALFORMED_RESPONSE",
16462
+ "INVALID_PROOF",
16463
+ "ISSUER_MISMATCH",
16464
+ "ID_MISMATCH",
16465
+ "ROLLBACK",
16466
+ "REVOKED",
16467
+ "UNSAFE_ENDPOINT"
16468
+ ]);
16469
+ var CredentialRefreshUpdatedResultValidator = external_exports.object({
16470
+ status: external_exports.literal("updated"),
16471
+ credential: VCValidator,
16472
+ etag: external_exports.string().optional(),
16473
+ managedVersion: external_exports.number().int().positive().optional()
16474
+ });
16475
+ var CredentialRefreshUnchangedResultValidator = external_exports.object({
16476
+ status: external_exports.literal("unchanged"),
16477
+ checkedAt: external_exports.string().min(1),
16478
+ etag: external_exports.string().optional()
16479
+ });
16480
+ var CredentialRefreshUnsupportedResultValidator = external_exports.object({
16481
+ status: external_exports.literal("unsupported")
16482
+ });
16483
+ var CredentialRefreshFailedResultValidator = external_exports.object({
16484
+ status: external_exports.literal("failed"),
16485
+ code: CredentialRefreshFailureCodeValidator,
16486
+ retryable: external_exports.boolean()
16487
+ });
16488
+ var CredentialRefreshResultValidator = external_exports.discriminatedUnion("status", [
16489
+ CredentialRefreshUpdatedResultValidator,
16490
+ CredentialRefreshUnchangedResultValidator,
16491
+ CredentialRefreshUnsupportedResultValidator,
16492
+ CredentialRefreshFailedResultValidator
16493
+ ]);
16224
16494
  var LCNProfileDisplayValidator = external_exports.object({
16225
16495
  backgroundColor: external_exports.string().optional(),
16226
16496
  backgroundImage: external_exports.string().optional(),
@@ -16556,7 +16826,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16556
16826
  "Options for email/phone recipients (Universal Inbox)"
16557
16827
  ),
16558
16828
  templateData: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
16559
- integrationId: external_exports.string().optional().describe("Integration ID for activity tracking")
16829
+ integrationId: external_exports.string().optional().describe("Integration ID for activity tracking"),
16830
+ refresh: external_exports.boolean().optional().describe(
16831
+ "Request managed credential refresh for this send. Profile/DID recipients use immediate issuance; email/phone recipients use deferred Universal Inbox signing and bind the holder at claim."
16832
+ ),
16833
+ idempotencyKey: external_exports.string().min(1).max(200).optional().describe(
16834
+ "Caller-chosen key that makes a managed refresh send (refresh: true) safe to retry as a whole: retries with the same key reuse the same boost, refresh allocation and result. Reusing a key for a different request is rejected. With signedCredential, requires prior tRPC prepareRefreshableSend; direct REST callers omit the key and retry the exact signed credential and templateUri."
16835
+ )
16560
16836
  }).refine((data) => data.templateUri || data.template || data.signedCredential, {
16561
16837
  message: "Either templateUri, template, or signedCredential must be provided.",
16562
16838
  path: ["templateUri"]
@@ -16571,8 +16847,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16571
16847
  message: "guardianEmail must differ from recipient (self-approval not allowed)",
16572
16848
  path: ["options", "guardianEmail"]
16573
16849
  }
16574
- );
16850
+ ).refine((data) => !data.idempotencyKey || data.refresh === true, {
16851
+ message: "idempotencyKey is only supported with refresh: true.",
16852
+ path: ["idempotencyKey"]
16853
+ });
16575
16854
  var SendInboxResponseValidator = external_exports.object({
16855
+ refresh: InboxCredentialRefreshReceiptValidator.optional(),
16576
16856
  issuanceId: external_exports.string(),
16577
16857
  status: external_exports.enum(["PENDING", "ISSUED", "EXPIRED", "DELIVERED", "CLAIMED"]),
16578
16858
  claimUrl: external_exports.string().url().optional().describe("Present when suppressDelivery=true"),
@@ -16587,8 +16867,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16587
16867
  activityId: external_exports.string().describe("Links to the activity lifecycle for this issuance"),
16588
16868
  inbox: SendInboxResponseValidator.optional().describe(
16589
16869
  "Present when sent via email/phone (Universal Inbox)"
16870
+ ),
16871
+ refresh: ManagedCredentialRefreshReceiptValidator.optional().describe(
16872
+ "Present when managed refresh was requested: issuance metadata the issuer keeps to publish future updates"
16590
16873
  )
16591
16874
  });
16875
+ var PrepareRefreshableSendInputValidator = external_exports.object({
16876
+ recipient: external_exports.string(),
16877
+ templateUri: external_exports.string().optional(),
16878
+ template: SendBoostTemplateValidator.optional(),
16879
+ contractUri: external_exports.string().optional(),
16880
+ templateData: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
16881
+ integrationId: external_exports.string().optional(),
16882
+ /** Credential ID to allocate for; generated server-side when omitted. */
16883
+ credentialId: external_exports.string().min(1).optional(),
16884
+ idempotencyKey: external_exports.string().min(1).max(200).optional()
16885
+ }).refine((data) => Boolean(data.templateUri) !== Boolean(data.template), {
16886
+ message: "Provide exactly one of templateUri or template."
16887
+ });
16888
+ var PrepareRefreshableSendResultValidator = external_exports.object({
16889
+ boostUri: external_exports.string(),
16890
+ credentialId: external_exports.string(),
16891
+ refreshId: external_exports.string(),
16892
+ refreshService: ManagedCredentialRefreshServiceValidator,
16893
+ /** The DID to use as credentialSubject.id (recipient DID, or the profile's did:web). */
16894
+ holderDid: external_exports.string(),
16895
+ /** Present when this idempotencyKey already completed: return it without signing. */
16896
+ completed: SendBoostResponseValidator.optional()
16897
+ });
16592
16898
  var SendInputValidator = external_exports.discriminatedUnion("type", [SendBoostInputValidator]);
16593
16899
  var SendResponseValidator = external_exports.discriminatedUnion("type", [SendBoostResponseValidator]);
16594
16900
  var ConsentFlowTermsStatusValidator = external_exports.enum(["live", "stale", "withdrawn"]);
@@ -16907,6 +17213,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16907
17213
  webhookUrl: external_exports.string().optional()
16908
17214
  });
16909
17215
  var AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX = "auth-grant:";
17216
+ var ACT_AS_HEADER = "X-LearnCard-Act-As";
16910
17217
  var AuthGrantValidator = external_exports.object({
16911
17218
  id: external_exports.string(),
16912
17219
  name: external_exports.string(),
@@ -16919,6 +17226,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16919
17226
  }, "error")
16920
17227
  }),
16921
17228
  scope: external_exports.string(),
17229
+ actAs: external_exports.string().optional(),
16922
17230
  createdAt: external_exports.iso.datetime({ error: "createdAt must be a valid ISO 8601 datetime string" }),
16923
17231
  expiresAt: external_exports.iso.datetime({ error: "expiresAt must be a valid ISO 8601 datetime string" }).nullish().optional()
16924
17232
  });
@@ -16990,6 +17298,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16990
17298
  sessionJwt: external_exports.string()
16991
17299
  });
16992
17300
  var InboxCredentialValidator = external_exports.object({
17301
+ refresh: InboxCredentialRefreshReceiptValidator.optional(),
17302
+ refreshId: external_exports.string().optional(),
16993
17303
  id: external_exports.string(),
16994
17304
  credential: external_exports.string().optional(),
16995
17305
  isSigned: external_exports.boolean(),
@@ -17044,12 +17354,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17044
17354
  templateUri: external_exports.string().optional().describe(
17045
17355
  "URI of a boost template to use for issuance. The boost credential will be resolved and used. Mutually exclusive with credential field."
17046
17356
  ),
17357
+ refresh: external_exports.boolean().optional().describe(
17358
+ "Allocate managed refresh before signing. Requires unsigned content and a registered signing authority; binds the holder on claim."
17359
+ ),
17360
+ idempotencyKey: external_exports.string().min(1).max(200).optional(),
17047
17361
  // === OPTIONAL FEATURES ===
17048
17362
  // Add major, distinct features at the top level.
17049
17363
  //consentRequest: ConsentRequestValidator.optional(),
17050
17364
  // === PROCESS CONFIGURATION (Optional) ===
17051
17365
  // HOW should this issuance be handled?
17052
17366
  configuration: external_exports.object({
17367
+ guardianEmail: external_exports.string().email().optional().describe(
17368
+ "Require approval from this guardian before the recipient can claim. Must differ from the recipient email."
17369
+ ),
17053
17370
  signingAuthority: IssueInboxSigningAuthorityValidator.optional().describe(
17054
17371
  "The signing authority to use for the credential. If not provided, the users default signing authority will be used if the credential is not signed."
17055
17372
  ),
@@ -17103,17 +17420,130 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17103
17420
  }).optional().describe(
17104
17421
  "Configuration for the credential issuance. If not provided, the default configuration will be used."
17105
17422
  )
17423
+ }).refine((data) => !data.idempotencyKey || data.refresh === true, {
17424
+ message: "idempotencyKey requires refresh: true."
17106
17425
  }).refine((data) => data.credential || data.templateUri, {
17107
17426
  message: "Either credential or templateUri must be provided.",
17108
17427
  path: ["credential"]
17109
- });
17428
+ }).refine(
17429
+ (data) => !data.configuration?.guardianEmail || data.recipient.type !== "email" || data.configuration.guardianEmail.toLowerCase() !== data.recipient.value.toLowerCase(),
17430
+ {
17431
+ message: "guardianEmail must differ from recipient (self-approval not allowed)",
17432
+ path: ["configuration", "guardianEmail"]
17433
+ }
17434
+ );
17110
17435
  var IssueInboxCredentialResponseValidator = external_exports.object({
17436
+ refresh: InboxCredentialRefreshReceiptValidator.optional(),
17111
17437
  issuanceId: external_exports.string(),
17112
17438
  status: LCNInboxStatusEnumValidator,
17113
17439
  recipient: ContactMethodQueryValidator,
17114
17440
  claimUrl: external_exports.string().url().optional(),
17115
17441
  recipientDid: external_exports.string().optional()
17116
17442
  });
17443
+ var InboxBatchConfigurationValidator = IssueInboxCredentialValidator.shape.configuration.unwrap().extend({
17444
+ refresh: IssueInboxCredentialValidator.shape.refresh.describe(
17445
+ "Enable managed refresh by default. An item configuration.refresh overrides this value, including false."
17446
+ ),
17447
+ delivery: IssueInboxCredentialValidator.shape.configuration.unwrap().shape.delivery.unwrap().extend({ suppress: external_exports.boolean().optional() }).optional()
17448
+ });
17449
+ var InboxBatchItemConfigurationValidator = InboxBatchConfigurationValidator.extend({
17450
+ guardianEmail: InboxBatchConfigurationValidator.shape.guardianEmail.nullable().describe(
17451
+ "Require guardian approval, or set null to clear a batch-level guardianEmail for this item."
17452
+ )
17453
+ });
17454
+ var IssueInboxCredentialBatchItemValidator = external_exports.object({
17455
+ ...IssueInboxCredentialValidator.shape,
17456
+ configuration: InboxBatchItemConfigurationValidator.optional(),
17457
+ idempotencyKey: external_exports.string().max(256).optional()
17458
+ }).refine((data) => data.credential || data.templateUri, {
17459
+ message: "Either credential or templateUri must be provided.",
17460
+ path: ["credential"]
17461
+ }).describe(
17462
+ "One issuance: provide credential or templateUri. Invalid input is rejected at submission with its item index."
17463
+ );
17464
+ var IssueInboxCredentialBatchValidator = external_exports.object({
17465
+ requestId: external_exports.string().min(1).max(256).optional(),
17466
+ items: external_exports.array(IssueInboxCredentialBatchItemValidator).min(1).max(100),
17467
+ configuration: InboxBatchConfigurationValidator.optional()
17468
+ }).superRefine((batch, ctx) => {
17469
+ batch.items.forEach((item, index) => {
17470
+ const itemGuardian = item.configuration?.guardianEmail;
17471
+ const guardian = itemGuardian === null ? void 0 : itemGuardian ?? batch.configuration?.guardianEmail;
17472
+ if (guardian && item.recipient.type === "email" && guardian.toLowerCase() === item.recipient.value.toLowerCase()) {
17473
+ ctx.addIssue({
17474
+ code: "custom",
17475
+ path: ["items", index, "configuration", "guardianEmail"],
17476
+ message: "guardianEmail must differ from recipient (self-approval not allowed)"
17477
+ });
17478
+ }
17479
+ });
17480
+ });
17481
+ var InboxBatchErrorReasonValidator = external_exports.enum([
17482
+ "DUPLICATE_KEY",
17483
+ "IDEMPOTENCY_MISMATCH",
17484
+ "IN_PROGRESS",
17485
+ "UNCONFIRMED"
17486
+ ]);
17487
+ var IssueInboxCredentialBatchItemResultValidator = external_exports.discriminatedUnion("success", [
17488
+ IssueInboxCredentialResponseValidator.extend({
17489
+ success: external_exports.literal(true),
17490
+ index: external_exports.number().int().nonnegative(),
17491
+ deduplicated: external_exports.boolean().optional(),
17492
+ guardianStatus: GuardianStatusValidator.optional(),
17493
+ idempotencyKey: external_exports.string().optional()
17494
+ }),
17495
+ external_exports.object({
17496
+ success: external_exports.literal(false),
17497
+ index: external_exports.number().int().nonnegative(),
17498
+ idempotencyKey: external_exports.string().optional(),
17499
+ recipient: ContactMethodQueryValidator.optional(),
17500
+ error: external_exports.object({
17501
+ code: external_exports.string(),
17502
+ message: external_exports.string(),
17503
+ reason: InboxBatchErrorReasonValidator.optional()
17504
+ }),
17505
+ issuanceId: external_exports.string().optional().describe(
17506
+ "Present when issuance completed but replay storage could not be confirmed. Reconcile this issuance; do not issue again with a new key."
17507
+ ),
17508
+ claimUrl: external_exports.string().url().optional().describe(
17509
+ "Claim URL of the completed issuance, if available, when replay storage could not be confirmed."
17510
+ )
17511
+ })
17512
+ ]);
17513
+ var IssueInboxCredentialBatchResponseValidator = external_exports.object({
17514
+ results: external_exports.array(IssueInboxCredentialBatchItemResultValidator),
17515
+ summary: external_exports.object({
17516
+ total: external_exports.number(),
17517
+ succeeded: external_exports.number(),
17518
+ failed: external_exports.number(),
17519
+ deduplicated: external_exports.number()
17520
+ })
17521
+ });
17522
+ var InboxBatchReceiptValidator = external_exports.object({
17523
+ batchId: external_exports.string(),
17524
+ status: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
17525
+ createdAt: external_exports.string()
17526
+ });
17527
+ var InboxBatchStatusValidator = external_exports.object({
17528
+ batchId: external_exports.string(),
17529
+ createdAt: external_exports.string(),
17530
+ done: external_exports.boolean().describe(
17531
+ "True when no queued or processing items remain, including unconfirmed outcomes."
17532
+ ),
17533
+ status: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
17534
+ items: external_exports.array(
17535
+ external_exports.object({
17536
+ index: external_exports.number().int().nonnegative(),
17537
+ state: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
17538
+ result: IssueInboxCredentialBatchItemResultValidator.optional()
17539
+ })
17540
+ ),
17541
+ summary: IssueInboxCredentialBatchResponseValidator.shape.summary.extend({
17542
+ completed: external_exports.number(),
17543
+ pending: external_exports.number(),
17544
+ unconfirmed: external_exports.number()
17545
+ })
17546
+ });
17117
17547
  var CredentialNameRefValidator = external_exports.object({ name: external_exports.string() }).passthrough();
17118
17548
  var ClaimInboxCredentialValidator = external_exports.object({
17119
17549
  credential: VCValidator.or(VPValidator).or(UnsignedVCValidator).or(CredentialNameRefValidator).describe("The credential to issue, or a { name } reference to resolve a boost template."),
@@ -17599,6 +18029,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17599
18029
  eventType: CredentialActivityEventTypeValidator,
17600
18030
  timestamp: external_exports.string(),
17601
18031
  actorProfileId: external_exports.string().optional(),
18032
+ onBehalfOf: external_exports.string().optional(),
17602
18033
  recipientType: CredentialActivityRecipientTypeValidator,
17603
18034
  recipientIdentifier: external_exports.string(),
17604
18035
  boostUri: external_exports.string().optional(),
@@ -17645,6 +18076,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17645
18076
  __name2(this, "AuthSessionError");
17646
18077
  }
17647
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
+ });
17648
18089
  var BITSTRING_STATUS_PURPOSES = ["revocation", "suspension"];
17649
18090
  var DEFAULT_BITSTRING_STATUS_LIST_SIZE = 131072;
17650
18091
  var BitstringStatusPurposeValidator = external_exports.enum(BITSTRING_STATUS_PURPOSES);
@@ -17759,191 +18200,458 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17759
18200
  const result = inAppMessagesFlagValidator.safeParse(raw);
17760
18201
  return result.success ? result.data : EMPTY_IN_APP_MESSAGES_FLAG;
17761
18202
  }, "parseInAppMessagesFlag");
17762
- var LearnCardRefreshAuthorizationValidator = external_exports.object({ type: external_exports.literal("LearnCardDIDAuth") }).catchall(external_exports.any());
17763
- var ManagedCredentialRefreshServiceValidator = external_exports.object({
17764
- id: external_exports.string().min(1),
17765
- type: external_exports.literal("LearnCardCredentialRefresh2026"),
17766
- authorization: LearnCardRefreshAuthorizationValidator.optional()
17767
- }).catchall(external_exports.any());
17768
- var StandardCredentialRefreshServiceValidator = external_exports.object({
17769
- id: external_exports.string().min(1),
17770
- type: external_exports.literal("1EdTechCredentialRefresh")
17771
- }).catchall(external_exports.any());
17772
- var SupportedCredentialRefreshServiceValidator = external_exports.union([
17773
- ManagedCredentialRefreshServiceValidator,
17774
- StandardCredentialRefreshServiceValidator
17775
- ]);
17776
- var AllocateCredentialRefreshInputValidator = external_exports.object({
17777
- holder: external_exports.object({
17778
- profileId: external_exports.string().optional(),
17779
- did: external_exports.string().min(1)
17780
- }),
17781
- credentialId: external_exports.string().min(1)
17782
- });
17783
- var AllocateCredentialRefreshResultValidator = external_exports.object({
17784
- refreshId: external_exports.string().min(1),
17785
- refreshService: ManagedCredentialRefreshServiceValidator.extend({
17786
- authorization: LearnCardRefreshAuthorizationValidator
17787
- })
17788
- });
17789
- var CredentialRefreshSigningModeValidator = external_exports.enum(["issuer-signed", "signing-authority"]);
17790
- var PublishCredentialRefreshBaseFields = {
17791
- refreshId: external_exports.string().min(1),
17792
- notifyHolder: external_exports.boolean().optional(),
17793
- updateSummary: external_exports.string().optional(),
17794
- idempotencyKey: external_exports.string().optional()
17795
- };
17796
- var PublishIssuerSignedRefreshValidator = external_exports.object({
17797
- ...PublishCredentialRefreshBaseFields,
17798
- mode: external_exports.literal("issuer-signed"),
17799
- signedCredential: VCValidator
17800
- });
17801
- var PublishSigningAuthorityRefreshValidator = external_exports.object({
17802
- ...PublishCredentialRefreshBaseFields,
17803
- mode: external_exports.literal("signing-authority"),
17804
- credential: UnsignedVCValidator,
17805
- signingAuthority: external_exports.object({
17806
- type: external_exports.string().min(1)
17807
- }).catchall(external_exports.any())
17808
- });
17809
- var PublishCredentialRefreshInputValidator = external_exports.object({
17810
- ...PublishCredentialRefreshBaseFields,
17811
- mode: CredentialRefreshSigningModeValidator,
17812
- signedCredential: VCValidator.optional(),
17813
- credential: UnsignedVCValidator.optional(),
17814
- signingAuthority: external_exports.object({
17815
- type: external_exports.string().min(1)
17816
- }).catchall(external_exports.any()).optional()
17817
- }).superRefine((input, ctx) => {
17818
- if (input.mode === "issuer-signed" && !input.signedCredential) {
17819
- ctx.addIssue({
17820
- code: "custom",
17821
- path: ["signedCredential"],
17822
- message: "signedCredential is required for issuer-signed publication"
17823
- });
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`
17824
18287
  }
17825
- if (input.mode === "issuer-signed" && (input.credential !== void 0 || input.signingAuthority !== void 0)) {
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) {
17826
18453
  ctx.addIssue({
17827
18454
  code: "custom",
17828
- path: ["mode"],
17829
- message: "issuer-signed publication cannot include signing-authority fields"
18455
+ message: result.message,
18456
+ path: result.path,
18457
+ params: { shareLinkCode: result.code }
17830
18458
  });
17831
18459
  }
17832
- if (input.mode === "signing-authority") {
17833
- if (input.signedCredential !== void 0) {
17834
- ctx.addIssue({
17835
- code: "custom",
17836
- path: ["signedCredential"],
17837
- message: "signing-authority publication cannot include signedCredential"
17838
- });
17839
- }
17840
- if (!input.credential) {
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) {
17841
18478
  ctx.addIssue({
17842
18479
  code: "custom",
17843
- path: ["credential"],
17844
- message: "credential is required for signing-authority publication"
18480
+ message: "order must be within the selection bounds",
18481
+ path: ["selection", index, "order"]
17845
18482
  });
17846
18483
  }
17847
- if (!input.signingAuthority) {
18484
+ if (orders.has(entry.order)) {
17848
18485
  ctx.addIssue({
17849
18486
  code: "custom",
17850
- path: ["signingAuthority"],
17851
- message: "signingAuthority is required for signing-authority publication"
18487
+ message: "order must be unique within the selection",
18488
+ path: ["selection", index, "order"]
17852
18489
  });
17853
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
+ });
17854
18570
  }
17855
18571
  });
17856
- var PublishCredentialRefreshNotificationValidator = external_exports.enum([
17857
- "queued",
17858
- "suppressed",
17859
- "not-applicable",
17860
- /** Publication succeeded, but the post-commit notification enqueue must be retried. */
17861
- "delivery-failed"
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()
17862
18581
  ]);
17863
- var PublishCredentialRefreshResultValidator = external_exports.object({
17864
- refreshId: external_exports.string().min(1),
17865
- version: external_exports.number().int().positive(),
17866
- publishedAt: external_exports.string().min(1),
17867
- notification: PublishCredentialRefreshNotificationValidator
17868
- });
17869
- var CredentialRefreshVersionMetadataValidator = external_exports.object({
17870
- version: external_exports.number().int().positive(),
17871
- publishedAt: external_exports.string().min(1),
17872
- effectiveAt: external_exports.string().optional(),
17873
- etag: external_exports.string().optional(),
17874
- signingMode: CredentialRefreshSigningModeValidator.optional(),
17875
- updateSummary: external_exports.string().optional()
17876
- });
17877
- var GetCredentialRefreshHistoryInputValidator = external_exports.object({
17878
- refreshId: external_exports.string().min(1),
17879
- cursor: external_exports.string().optional(),
17880
- limit: external_exports.number().int().positive().optional()
17881
- });
17882
- var GetCredentialRefreshHistoryResultValidator = external_exports.object({
17883
- records: CredentialRefreshVersionMetadataValidator.array(),
17884
- hasMore: external_exports.boolean(),
17885
- cursor: external_exports.string().optional()
17886
- });
17887
- var CredentialRefreshChallengeValidator = external_exports.object({
17888
- challenge: external_exports.string().min(1),
17889
- expiresAt: external_exports.string().min(1),
17890
- domain: external_exports.string().optional(),
17891
- scheme: external_exports.literal("LearnCardDIDAuth").optional()
17892
- });
17893
- var PublicCredentialRefreshEnvelopeValidator = external_exports.object({
17894
- format: external_exports.literal("vc"),
17895
- credential: VCValidator,
17896
- etag: external_exports.string().optional()
17897
- });
17898
- var JweCredentialRefreshEnvelopeValidator = external_exports.object({
17899
- format: external_exports.literal("jwe"),
17900
- jwe: JWEValidator,
17901
- etag: external_exports.string().optional(),
17902
- /** Present on LearnCard-managed endpoints; optional for interoperable JWE services. */
17903
- version: external_exports.number().int().positive().optional()
17904
- });
17905
- var CredentialRefreshResponseEnvelopeValidator = external_exports.discriminatedUnion("format", [
17906
- PublicCredentialRefreshEnvelopeValidator,
17907
- JweCredentialRefreshEnvelopeValidator
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()
17908
18590
  ]);
17909
- var CredentialRefreshFailureCodeValidator = external_exports.enum([
17910
- "UNAVAILABLE",
17911
- "TIMEOUT",
17912
- "UNSUPPORTED_SERVICE",
17913
- "UNAUTHORIZED",
17914
- "MALFORMED_RESPONSE",
17915
- "INVALID_PROOF",
17916
- "ISSUER_MISMATCH",
17917
- "ID_MISMATCH",
17918
- "ROLLBACK",
17919
- "REVOKED",
17920
- "UNSAFE_ENDPOINT"
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()
17921
18635
  ]);
17922
- var CredentialRefreshUpdatedResultValidator = external_exports.object({
17923
- status: external_exports.literal("updated"),
17924
- credential: VCValidator,
17925
- etag: external_exports.string().optional(),
17926
- managedVersion: external_exports.number().int().positive().optional()
17927
- });
17928
- var CredentialRefreshUnchangedResultValidator = external_exports.object({
17929
- status: external_exports.literal("unchanged"),
17930
- checkedAt: external_exports.string().min(1),
17931
- etag: external_exports.string().optional()
17932
- });
17933
- var CredentialRefreshUnsupportedResultValidator = external_exports.object({
17934
- status: external_exports.literal("unsupported")
17935
- });
17936
- var CredentialRefreshFailedResultValidator = external_exports.object({
17937
- status: external_exports.literal("failed"),
17938
- code: CredentialRefreshFailureCodeValidator,
17939
- retryable: external_exports.boolean()
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()
17940
18642
  });
17941
- var CredentialRefreshResultValidator = external_exports.discriminatedUnion("status", [
17942
- CredentialRefreshUpdatedResultValidator,
17943
- CredentialRefreshUnchangedResultValidator,
17944
- CredentialRefreshUnsupportedResultValidator,
17945
- CredentialRefreshFailedResultValidator
17946
- ]);
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();
17947
18655
  }
17948
18656
  });
17949
18657
 
@@ -17965,35 +18673,63 @@ __export(index_exports, {
17965
18673
  AtomicUpdateError: () => AtomicUpdateError,
17966
18674
  AuthSessionError: () => import_types.AuthSessionError,
17967
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,
17968
18693
  SSSApiClient: () => SSSApiClient,
17969
18694
  SSSKeyManager: () => SSSKeyManager,
17970
18695
  SSS_THRESHOLD: () => SSS_THRESHOLD,
17971
18696
  SSS_TOTAL_SHARES: () => SSS_TOTAL_SHARES,
17972
18697
  ShareVerificationError: () => ShareVerificationError,
18698
+ ShareWriteRejectedError: () => ShareWriteRejectedError,
17973
18699
  approveQrLoginSession: () => approveQrLoginSession,
17974
18700
  atomicRecovery: () => atomicRecovery,
17975
18701
  atomicShareUpdate: () => atomicShareUpdate,
17976
18702
  base64ToBuffer: () => base64ToBuffer,
17977
18703
  bufferToBase64: () => bufferToBase64,
18704
+ buildRecoveryPhraseChallengeOptions: () => buildRecoveryPhraseChallengeOptions,
17978
18705
  bytesToHex: () => bytesToHex,
17979
18706
  clearAllShares: () => clearAllShares,
18707
+ constantTimeEqualHex: () => constantTimeEqualHex,
17980
18708
  countWords: () => countWords,
17981
18709
  createAdaptiveStorage: () => createAdaptiveStorage,
17982
18710
  createPasskeyCredential: () => createPasskeyCredential,
17983
18711
  createQrLoginSession: () => createQrLoginSession,
17984
18712
  createSSSKeyManager: () => createSSSKeyManager,
17985
18713
  createSSSStrategy: () => createSSSStrategy,
18714
+ decryptEmailRelayPayload: () => decryptEmailRelayPayload,
18715
+ decryptEscrowBlob: () => decryptEscrowBlob,
17986
18716
  decryptShareFromTransfer: () => decryptShareFromTransfer,
17987
18717
  decryptShareWithPasskey: () => decryptShareWithPasskey,
17988
18718
  decryptWithPassword: () => decryptWithPassword,
17989
18719
  deleteDeviceShare: () => deleteDeviceShare,
17990
18720
  deriveKeyFromPasskey: () => deriveKeyFromPasskey,
17991
18721
  deriveKeyFromPassword: () => deriveKeyFromPassword,
18722
+ derivePinProof: () => derivePinProof,
18723
+ encryptEmailRelayPayload: () => encryptEmailRelayPayload,
18724
+ encryptEscrowBlob: () => encryptEscrowBlob,
17992
18725
  encryptShareForTransfer: () => encryptShareForTransfer,
17993
18726
  encryptShareWithPasskey: () => encryptShareWithPasskey,
17994
18727
  encryptWithPassword: () => encryptWithPassword,
17995
18728
  generateEd25519PrivateKey: () => generateEd25519PrivateKey,
18729
+ generateEmailRelayConfirmationCode: () => generateEmailRelayConfirmationCode,
17996
18730
  generateEphemeralKeypair: () => generateEphemeralKeypair,
18731
+ generateEscrowKeyPair: () => generateEscrowKeyPair,
18732
+ generatePinSalt: () => generatePinSalt,
17997
18733
  generateRecoveryPhrase: () => generateRecoveryPhrase,
17998
18734
  getDeviceShare: () => getDeviceShare,
17999
18735
  getQrLoginSessionInfo: () => getQrLoginSessionInfo,
@@ -18004,24 +18740,38 @@ __export(index_exports, {
18004
18740
  isPublicComputerMode: () => isPublicComputerMode,
18005
18741
  isWebAuthnSupported: () => isWebAuthnSupported,
18006
18742
  listAllDeviceShares: () => listAllDeviceShares,
18743
+ normalizePin: () => normalizePin,
18007
18744
  notifyDevicesForQrSession: () => notifyDevicesForQrSession,
18745
+ openEscrowRelease: () => openEscrowRelease,
18746
+ parseEmailRelayEnvelope: () => parseEmailRelayEnvelope,
18747
+ parseEmailRelayPlaintext: () => parseEmailRelayPlaintext,
18748
+ parseEscrowBlobPlaintext: () => parseEscrowBlobPlaintext,
18749
+ parseEscrowEnvelope: () => parseEscrowEnvelope,
18750
+ parseEscrowReleasePlaintext: () => parseEscrowReleasePlaintext,
18008
18751
  pollQrLoginSession: () => pollQrLoginSession,
18009
18752
  pollUntilApproved: () => pollUntilApproved,
18010
18753
  reconstructFromShares: () => reconstructFromShares,
18011
18754
  reconstructPrivateKey: () => reconstructPrivateKey,
18012
18755
  recoveryPhraseToShare: () => recoveryPhraseToShare,
18756
+ sealEscrowRelease: () => sealEscrowRelease,
18013
18757
  setPublicComputerMode: () => setPublicComputerMode,
18014
18758
  shareToRecoveryPhrase: () => shareToRecoveryPhrase,
18015
18759
  splitAndVerify: () => splitAndVerify,
18016
18760
  splitPrivateKey: () => splitPrivateKey,
18017
18761
  storeDeviceShare: () => storeDeviceShare,
18018
18762
  storeShareVersion: () => storeShareVersion,
18763
+ validatePin: () => validatePin,
18019
18764
  validateRecoveryPhrase: () => validateRecoveryPhrase,
18765
+ verifyEnclaveAttestation: () => verifyEnclaveAttestation,
18020
18766
  verifyStoredShares: () => verifyStoredShares
18021
18767
  });
18022
18768
  module.exports = __toCommonJS(index_exports);
18023
18769
 
18024
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");
18025
18775
  var SSSApiClient = class {
18026
18776
  static {
18027
18777
  __name(this, "SSSApiClient");
@@ -18034,20 +18784,9 @@ var SSSApiClient = class {
18034
18784
  const token = await this.authProvider.getIdToken();
18035
18785
  return {
18036
18786
  "Content-Type": "application/json",
18037
- Authorization: `Bearer ${token}`
18787
+ "X-Auth-Token": token
18038
18788
  };
18039
18789
  }
18040
- async getContactMethodFromUser() {
18041
- const user = await this.authProvider.getCurrentUser();
18042
- if (!user) return null;
18043
- if (user.email) {
18044
- return { type: "email", value: user.email.toLowerCase() };
18045
- }
18046
- if (user.phone) {
18047
- return { type: "phone", value: user.phone };
18048
- }
18049
- return null;
18050
- }
18051
18790
  async getAuthShare() {
18052
18791
  const headers = await this.getAuthHeaders();
18053
18792
  const providerType = this.authProvider.getProviderType();
@@ -18064,52 +18803,24 @@ var SSSApiClient = class {
18064
18803
  }
18065
18804
  return response.json();
18066
18805
  }
18067
- async storeAuthShare(input) {
18068
- const headers = await this.getAuthHeaders();
18069
- const providerType = this.authProvider.getProviderType();
18070
- const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
18071
- method: "PUT",
18072
- headers,
18073
- body: JSON.stringify({
18074
- ...input,
18075
- providerType
18076
- })
18077
- });
18078
- if (!response.ok) {
18079
- throw new Error(`Failed to store auth share: ${response.statusText}`);
18080
- }
18806
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
18807
+ async storeAuthShare(_input) {
18808
+ throwDidChallengeRequired();
18081
18809
  }
18082
- async addRecoveryMethod(input) {
18083
- const headers = await this.getAuthHeaders();
18084
- const providerType = this.authProvider.getProviderType();
18085
- const response = await fetch(`${this.serverUrl}/keys/recovery`, {
18086
- method: "POST",
18087
- headers,
18088
- body: JSON.stringify({
18089
- ...input,
18090
- providerType
18091
- })
18092
- });
18093
- if (!response.ok) {
18094
- throw new Error(`Failed to add recovery method: ${response.statusText}`);
18095
- }
18810
+ /** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
18811
+ async addRecoveryMethod(_input) {
18812
+ throwDidChallengeRequired();
18096
18813
  }
18097
18814
  async getRecoveryShare(type, credentialId) {
18098
18815
  const token = await this.authProvider.getIdToken();
18099
18816
  const providerType = this.authProvider.getProviderType();
18100
- const params = new URLSearchParams({
18101
- type,
18102
- providerType,
18103
- authToken: token
18104
- });
18817
+ const params = new URLSearchParams({ type, providerType });
18105
18818
  if (credentialId) {
18106
18819
  params.append("credentialId", credentialId);
18107
18820
  }
18108
18821
  const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
18109
18822
  method: "GET",
18110
- headers: {
18111
- "Content-Type": "application/json"
18112
- }
18823
+ headers: { "Content-Type": "application/json", "X-Auth-Token": token }
18113
18824
  });
18114
18825
  if (!response.ok) {
18115
18826
  if (response.status === 404) {
@@ -18119,81 +18830,81 @@ var SSSApiClient = class {
18119
18830
  }
18120
18831
  return response.json();
18121
18832
  }
18122
- async markMigrated() {
18123
- const headers = await this.getAuthHeaders();
18124
- const providerType = this.authProvider.getProviderType();
18125
- const response = await fetch(`${this.serverUrl}/keys/migrate`, {
18833
+ async startIdentityRecovery(email) {
18834
+ const response = await fetch(`${this.serverUrl}/keys/recovery-session/start`, {
18126
18835
  method: "POST",
18127
- headers,
18128
- body: JSON.stringify({ providerType })
18836
+ headers: { "Content-Type": "application/json" },
18837
+ body: JSON.stringify({ email })
18129
18838
  });
18130
18839
  if (!response.ok) {
18131
- throw new Error(`Failed to mark as migrated: ${response.statusText}`);
18840
+ throw new Error(`Failed to send recovery code: ${response.statusText}`);
18132
18841
  }
18133
18842
  }
18134
- async sendEmailBackupShare(emailShare, overrideEmail) {
18135
- const headers = await this.getAuthHeaders();
18136
- const providerType = this.authProvider.getProviderType();
18137
- let email = overrideEmail;
18138
- if (!email) {
18139
- const user = await this.authProvider.getCurrentUser();
18140
- email = user?.email;
18141
- }
18142
- if (!email) {
18143
- console.warn("Cannot send email backup share: no email address available");
18144
- return;
18145
- }
18146
- 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`, {
18147
18845
  method: "POST",
18148
- headers,
18149
- body: JSON.stringify({
18150
- providerType,
18151
- emailShare,
18152
- email
18153
- })
18846
+ headers: { "Content-Type": "application/json" },
18847
+ body: JSON.stringify({ email, code })
18154
18848
  });
18155
18849
  if (!response.ok) {
18156
- console.warn(`Failed to send email backup share: ${response.statusText}`);
18850
+ throw new Error(`Failed to verify recovery code: ${response.statusText}`);
18157
18851
  }
18852
+ return response.json();
18158
18853
  }
18159
- async addRecoveryEmail(email) {
18160
- const token = await this.authProvider.getIdToken();
18161
- const providerType = this.authProvider.getProviderType();
18162
- 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`, {
18163
18856
  method: "POST",
18164
18857
  headers: { "Content-Type": "application/json" },
18165
- body: JSON.stringify({ authToken: token, providerType, email })
18858
+ body: JSON.stringify({ recoverySessionToken, type, credentialId })
18166
18859
  });
18167
18860
  if (!response.ok) {
18168
- const data = await response.json().catch(() => ({}));
18169
- throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
18861
+ throw new Error(`Failed to retrieve recovery data: ${response.statusText}`);
18170
18862
  }
18863
+ return response.json();
18171
18864
  }
18172
- async verifyRecoveryEmail(code) {
18173
- const token = await this.authProvider.getIdToken();
18174
- const providerType = this.authProvider.getProviderType();
18175
- 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`, {
18176
18867
  method: "POST",
18177
- headers: { "Content-Type": "application/json" },
18178
- 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
+ })
18179
18879
  });
18180
18880
  if (!response.ok) {
18181
- const data = await response.json().catch(() => ({}));
18182
- 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}`);
18183
18882
  }
18184
18883
  return response.json();
18185
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`. */
18186
18906
  async deleteUserKey() {
18187
- const headers = await this.getAuthHeaders();
18188
- const providerType = this.authProvider.getProviderType();
18189
- const response = await fetch(`${this.serverUrl}/keys`, {
18190
- method: "DELETE",
18191
- headers,
18192
- body: JSON.stringify({ providerType })
18193
- });
18194
- if (!response.ok) {
18195
- throw new Error(`Failed to delete user key: ${response.statusText}`);
18196
- }
18907
+ throwDidChallengeRequired();
18197
18908
  }
18198
18909
  };
18199
18910
 
@@ -18388,11 +19099,10 @@ async function getOrCreateMasterKey() {
18388
19099
  (s) => s.get("master-key")
18389
19100
  );
18390
19101
  if (existing) return existing;
18391
- const key = await crypto.subtle.generateKey(
18392
- { name: "AES-GCM", length: 256 },
18393
- false,
18394
- ["encrypt", "decrypt"]
18395
- );
19102
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
19103
+ "encrypt",
19104
+ "decrypt"
19105
+ ]);
18396
19106
  await tx(db, KEYS_STORE, "readwrite", (s) => s.put(key, "master-key"));
18397
19107
  return key;
18398
19108
  } finally {
@@ -18493,12 +19203,7 @@ __name(getShareVersion, "getShareVersion");
18493
19203
  async function getDeviceShare(id = DEFAULT_DEVICE_SHARE_ID) {
18494
19204
  const db = await openDB();
18495
19205
  try {
18496
- const raw = await tx(
18497
- db,
18498
- SHARES_STORE,
18499
- "readonly",
18500
- (s) => s.get(id)
18501
- );
19206
+ const raw = await tx(db, SHARES_STORE, "readonly", (s) => s.get(id));
18502
19207
  if (raw == null) {
18503
19208
  return null;
18504
19209
  }
@@ -18557,7 +19262,9 @@ async function listAllDeviceShares() {
18557
19262
  try {
18558
19263
  const share = await getDeviceShare(id);
18559
19264
  if (!share) {
18560
- 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
+ );
18561
19268
  await deleteDeviceShare(id);
18562
19269
  continue;
18563
19270
  }
@@ -18641,6 +19348,7 @@ function createAdaptiveStorage() {
18641
19348
  getDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetDeviceShare(id) : getDeviceShare(id), "getDeviceShare"),
18642
19349
  hasDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionHasDeviceShare(id) : hasDeviceShare(id), "hasDeviceShare"),
18643
19350
  clearAllShares: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionClearAllShares(id) : clearAllShares(id), "clearAllShares"),
19351
+ deleteDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionDeleteDeviceShare(id) : deleteDeviceShare(id), "deleteDeviceShare"),
18644
19352
  storeShareVersion: /* @__PURE__ */ __name((version, id) => isPublicComputerMode() ? sessionStoreShareVersion(version, id) : storeShareVersion(version, id), "storeShareVersion"),
18645
19353
  getShareVersion: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetShareVersion(id) : getShareVersion(id), "getShareVersion")
18646
19354
  };
@@ -18921,6 +19629,33 @@ function countWords(phrase) {
18921
19629
  return phrase.trim().split(/\s+/).filter((w) => w.length > 0).length;
18922
19630
  }
18923
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");
18924
19659
 
18925
19660
  // src/key-manager.ts
18926
19661
  var SSSKeyManager = class {
@@ -19129,22 +19864,548 @@ var SSSKeyManager = class {
19129
19864
  }
19130
19865
  };
19131
19866
  }
19132
- async clearLocalData() {
19133
- await deleteDeviceShare(this.config.deviceStorageKey);
19134
- this.currentPrivateKey = null;
19135
- this.initialized = false;
19867
+ async clearLocalData() {
19868
+ await deleteDeviceShare(this.config.deviceStorageKey);
19869
+ this.currentPrivateKey = null;
19870
+ this.initialized = false;
19871
+ }
19872
+ async deleteAccount() {
19873
+ await this.apiClient.deleteUserKey();
19874
+ await clearAllShares();
19875
+ this.currentPrivateKey = null;
19876
+ this.initialized = false;
19877
+ }
19878
+ };
19879
+ function createSSSKeyManager(config) {
19880
+ return new SSSKeyManager(config);
19881
+ }
19882
+ __name(createSSSKeyManager, "createSSSKeyManager");
19883
+
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}`);
19902
+ }
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"
19920
+ ];
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}`);
19926
+ }
19927
+ branding[key] = field;
19928
+ }
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");
19935
+ }
19936
+ if (value.algorithm !== EMAIL_RELAY_ALGORITHM) {
19937
+ throw new Error("Unsupported email relay algorithm");
19938
+ }
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");
19955
+ }
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)
19972
+ };
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";
19136
20338
  }
19137
- async deleteAccount() {
19138
- await this.apiClient.deleteUserKey();
19139
- await clearAllShares();
19140
- this.currentPrivateKey = null;
19141
- this.initialized = false;
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");
19142
20348
  }
19143
20349
  };
19144
- function createSSSKeyManager(config) {
19145
- return new SSSKeyManager(config);
19146
- }
19147
- __name(createSSSKeyManager, "createSSSKeyManager");
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");
19148
20409
 
19149
20410
  // src/atomic-operations.ts
19150
20411
  var ShareVerificationError = class extends Error {
@@ -19171,6 +20432,15 @@ var AtomicUpdateError = class extends Error {
19171
20432
  __name(this, "AtomicUpdateError");
19172
20433
  }
19173
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
+ };
19174
20444
  async function splitAndVerify(privateKey) {
19175
20445
  const shares = await splitPrivateKey(privateKey);
19176
20446
  const combinations = [
@@ -19227,18 +20497,37 @@ async function atomicShareUpdate(privateKey, storage, options = {}) {
19227
20497
  try {
19228
20498
  await storage.storeAuth(newShares.authShare);
19229
20499
  } catch (e) {
19230
- if (options.previousDeviceShare && deviceStored) {
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) {
19231
20510
  try {
19232
- await storage.storeDevice(options.previousDeviceShare);
19233
- options.onRollback?.("Server storage failed, restored previous device share");
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
+ }
19234
20520
  } catch (rollbackError) {
19235
- console.error("CRITICAL: Rollback failed after auth share storage failure", rollbackError);
20521
+ console.error(
20522
+ "CRITICAL: Rollback failed after auth share storage failure",
20523
+ rollbackError
20524
+ );
19236
20525
  }
19237
20526
  }
19238
20527
  throw new AtomicUpdateError(
19239
- "Failed to store auth share on server, rolled back device share",
20528
+ rolledBack ? "Failed to store auth share on server, rolled back device share" : "Failed to store auth share on server; rollback did not complete",
19240
20529
  "store_auth",
19241
- !!options.previousDeviceShare,
20530
+ rolledBack,
19242
20531
  e instanceof Error ? e : void 0
19243
20532
  );
19244
20533
  }
@@ -19292,12 +20581,16 @@ async function atomicRecovery(recoveryShare, authShareData, storage, options = {
19292
20581
  __name(atomicRecovery, "atomicRecovery");
19293
20582
 
19294
20583
  // src/sss-strategy.ts
20584
+ var import_types3 = __toESM(require_dist());
19295
20585
  var SSS_DB_NAME = "lcb-sss-keys";
20586
+ var DEFAULT_DEVICE_SHARE_ID2 = "sss-device-share";
20587
+ var MAX_RECONCILIATION_HISTORY = 5;
19296
20588
  var defaultStorage = {
19297
20589
  storeDeviceShare,
19298
20590
  getDeviceShare,
19299
20591
  hasDeviceShare,
19300
20592
  clearAllShares,
20593
+ deleteDeviceShare,
19301
20594
  storeShareVersion,
19302
20595
  getShareVersion
19303
20596
  };
@@ -19306,10 +20599,24 @@ var buildHeaders = /* @__PURE__ */ __name((token, didAuthVp, tenantId) => ({
19306
20599
  ...didAuthVp ? { Authorization: `Bearer ${didAuthVp}` } : {},
19307
20600
  ...tenantId ? { "X-Tenant-Id": tenantId } : {}
19308
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
+ };
19309
20615
  var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, providerType, shareVersion, tenantId) => {
19310
20616
  const response = await fetch(`${serverUrl}/keys/auth-share`, {
19311
20617
  method: "POST",
19312
20618
  headers: buildHeaders(token, void 0, tenantId),
20619
+ signal: requestTimeoutSignal(),
19313
20620
  body: JSON.stringify({
19314
20621
  authToken: token,
19315
20622
  providerType,
@@ -19318,42 +20625,103 @@ var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, provider
19318
20625
  });
19319
20626
  if (!response.ok) {
19320
20627
  if (response.status === 404) return null;
19321
- throw new Error(`Failed to fetch key status: ${response.statusText}`);
20628
+ throw new Error(`Failed to fetch key status: ${describeHttpStatus(response)}`);
19322
20629
  }
19323
20630
  return response.json();
19324
20631
  }, "fetchAuthShareRaw");
19325
- 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) => {
19326
20633
  const response = await fetch(`${serverUrl}/keys/auth-share`, {
19327
20634
  method: "PUT",
19328
20635
  headers: buildHeaders(token, didAuthVp, tenantId),
20636
+ signal: requestTimeoutSignal(),
19329
20637
  body: JSON.stringify({
19330
20638
  authToken: token,
19331
20639
  providerType,
19332
20640
  authShare: { encryptedData: authShare, encryptedDek: "", iv: "" },
19333
20641
  primaryDid,
19334
- securityLevel: "basic"
20642
+ securityLevel: "basic",
20643
+ sssActivationState: "provisional",
20644
+ expectedShareVersion
19335
20645
  })
19336
20646
  });
19337
20647
  if (!response.ok) {
19338
- 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);
19339
20653
  }
19340
20654
  const data = await response.json();
19341
- return { shareVersion: data.shareVersion ?? 1 };
20655
+ return {
20656
+ shareVersion: data.shareVersion ?? 1,
20657
+ expectedShareVersionChecked: data.expectedShareVersionChecked === true
20658
+ };
19342
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");
19343
20682
  var fetchRecoveryShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, type, credentialId, tenantId) => {
19344
- const params = new URLSearchParams({ type, providerType, authToken: token });
20683
+ const params = new URLSearchParams({ type, providerType });
19345
20684
  if (credentialId) {
19346
20685
  params.set("credentialId", credentialId);
19347
20686
  }
19348
20687
  const response = await fetch(`${serverUrl}/keys/recovery?${params}`, {
19349
20688
  method: "GET",
19350
- 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
+ }
19351
20694
  });
19352
20695
  if (!response.ok) {
19353
20696
  throw new Error(`No ${type} recovery share found`);
19354
20697
  }
19355
20698
  return response.json();
19356
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");
19357
20725
  var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
19358
20726
  const response = await fetch(`${serverUrl}/keys/recovery`, {
19359
20727
  method: "POST",
@@ -19361,9 +20729,40 @@ var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, provide
19361
20729
  body: JSON.stringify({ authToken: token, providerType, ...body })
19362
20730
  });
19363
20731
  if (!response.ok) {
19364
- throw new Error(`Failed to add recovery method: ${response.statusText}`);
20732
+ throw new Error(`Failed to add recovery method: ${describeHttpStatus(response)}`);
19365
20733
  }
19366
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");
19367
20766
  var VERSION_PREFIX_LEN = 4;
19368
20767
  var formatVersionedEmailShare = /* @__PURE__ */ __name((emailShare, shareVersion) => shareVersion.toString(16).padStart(VERSION_PREFIX_LEN, "0") + emailShare, "formatVersionedEmailShare");
19369
20768
  var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
@@ -19376,44 +20775,512 @@ var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
19376
20775
  }
19377
20776
  return { share: input, version: void 0 };
19378
20777
  }, "parseVersionedEmailShare");
19379
- var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, shareVersion, tenantId) => {
19380
- try {
19381
- const payload = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
19382
- const response = await fetch(`${serverUrl}/keys/email-backup`, {
19383
- method: "POST",
19384
- headers: buildHeaders(token, void 0, tenantId),
19385
- body: JSON.stringify({ authToken: token, providerType, emailShare: payload, email })
19386
- });
19387
- if (!response.ok) {
19388
- console.warn(`Email backup share delivery failed: ${response.statusText}`);
19389
- }
19390
- } catch (e) {
19391
- console.warn("Email backup share delivery failed:", e);
19392
- }
19393
- }, "sendEmailBackupShare");
19394
- var sendEmailShareToRecoveryEmail = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, shareVersion, tenantId) => {
19395
- 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
+ );
19396
20791
  const response = await fetch(`${serverUrl}/keys/email-backup`, {
19397
20792
  method: "POST",
19398
- headers: buildHeaders(token, void 0, tenantId),
20793
+ headers: buildHeaders(token, didAuthVp, tenantId),
19399
20794
  body: JSON.stringify({
19400
20795
  authToken: token,
19401
20796
  providerType,
19402
- emailShare: payload,
19403
- useRecoveryEmail: true
20797
+ relayPayload,
20798
+ confirmationCode,
20799
+ email,
20800
+ shareVersion
19404
20801
  })
19405
20802
  });
19406
20803
  if (!response.ok) {
19407
- 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");
20859
+ }
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");
19408
21028
  }
19409
- }, "sendEmailShareToRecoveryEmail");
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");
19410
21040
  function createSSSStrategy(config) {
19411
- const { serverUrl, enableEmailBackupShare = false, tenantId } = config;
21041
+ const {
21042
+ serverUrl,
21043
+ enableEmailBackupShare = false,
21044
+ escrowRelayPublicKey = "",
21045
+ escrowRelayKeyId = "",
21046
+ emailBranding,
21047
+ tenantId
21048
+ } = config;
19412
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");
19413
21072
  let activeStorageId;
19414
21073
  let lastEmailShare;
19415
21074
  let lastShareVersion;
19416
- 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");
19417
21284
  return {
19418
21285
  name: "sss",
19419
21286
  capabilities: {
@@ -19424,23 +21291,31 @@ function createSSSStrategy(config) {
19424
21291
  },
19425
21292
  // --- User scoping ---
19426
21293
  setActiveUser(userId) {
21294
+ if (activeStorageId !== `sss-device-share:${userId}`) storageGeneration++;
19427
21295
  activeStorageId = `sss-device-share:${userId}`;
21296
+ lastServerSnapshot = null;
21297
+ pendingPhraseConfirmation = void 0;
21298
+ pendingBackupConfirmation = void 0;
19428
21299
  },
19429
21300
  // --- Key lifecycle ---
19430
21301
  async hasLocalKey() {
19431
21302
  if (await storage.hasDeviceShare(activeStorageId)) return true;
21303
+ if ((await readPendingShareCandidates(storage, activeStorageId)).length) return true;
19432
21304
  if (activeStorageId) {
19433
21305
  return storage.hasDeviceShare();
19434
21306
  }
19435
21307
  return false;
19436
21308
  },
19437
21309
  async getLocalKey() {
19438
- const scoped = await storage.getDeviceShare(activeStorageId);
21310
+ const storage2 = guardedStorage();
21311
+ const scoped = await storage2.getDeviceShare(activeStorageId);
19439
21312
  if (scoped) return scoped;
21313
+ const pending = await readPendingShareCandidates(storage2, activeStorageId);
21314
+ if (pending.length) return pending[pending.length - 1].share;
19440
21315
  if (activeStorageId) {
19441
- const legacy = await storage.getDeviceShare();
21316
+ const legacy = await storage2.getDeviceShare();
19442
21317
  if (legacy) {
19443
- await storage.storeDeviceShare(legacy, activeStorageId);
21318
+ await storage2.storeDeviceShare(legacy, activeStorageId);
19444
21319
  return legacy;
19445
21320
  }
19446
21321
  }
@@ -19449,8 +21324,20 @@ function createSSSStrategy(config) {
19449
21324
  async storeLocalKey(key) {
19450
21325
  return storage.storeDeviceShare(key, activeStorageId);
19451
21326
  },
19452
- async clearLocalKeys() {
19453
- 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);
19454
21341
  },
19455
21342
  async splitKey(privateKey) {
19456
21343
  const { shares } = await splitAndVerify(privateKey);
@@ -19474,9 +21361,175 @@ function createSSSStrategy(config) {
19474
21361
  );
19475
21362
  return result.healthy;
19476
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
+ },
19477
21529
  // --- Server communication ---
19478
21530
  async fetchServerKeyStatus(token, providerType) {
19479
- const localVersion = await storage.getShareVersion(activeStorageId);
21531
+ const storage2 = guardedStorage();
21532
+ const localVersion = await storage2.getShareVersion(activeStorageId);
19480
21533
  const data = await fetchAuthShareRaw(
19481
21534
  serverUrl,
19482
21535
  token,
@@ -19485,7 +21538,7 @@ function createSSSStrategy(config) {
19485
21538
  tenantId
19486
21539
  );
19487
21540
  if (!data) {
19488
- hasRecoveryEmail = false;
21541
+ lastServerSnapshot = null;
19489
21542
  return {
19490
21543
  exists: false,
19491
21544
  needsMigration: false,
@@ -19493,16 +21546,25 @@ function createSSSStrategy(config) {
19493
21546
  recoveryMethods: [],
19494
21547
  authShare: null,
19495
21548
  shareVersion: null,
19496
- maskedRecoveryEmail: null
21549
+ maskedRecoveryEmail: null,
21550
+ sssActivationState: null
19497
21551
  };
19498
21552
  }
19499
21553
  const rawAuthShare = data.authShare;
19500
- const authShareString = typeof rawAuthShare === "object" && rawAuthShare !== null ? rawAuthShare.encryptedData ?? null : typeof rawAuthShare === "string" ? rawAuthShare : null;
21554
+ const authShareString = authShareToString(rawAuthShare);
19501
21555
  const serverVersion = data.shareVersion ?? null;
19502
- if (serverVersion != null && localVersion == null) {
19503
- storage.storeShareVersion(serverVersion, activeStorageId).catch((e) => console.warn("SSS: failed to backfill local shareVersion", e));
19504
- }
19505
- 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
+ };
19506
21568
  return {
19507
21569
  exists: !!rawAuthShare || !!data.keyProvider || !!data.primaryDid,
19508
21570
  needsMigration: data.keyProvider === "web3auth",
@@ -19510,35 +21572,258 @@ function createSSSStrategy(config) {
19510
21572
  recoveryMethods: data.recoveryMethods || [],
19511
21573
  authShare: authShareString,
19512
21574
  shareVersion: serverVersion,
19513
- maskedRecoveryEmail: data.maskedRecoveryEmail ?? null
21575
+ maskedRecoveryEmail: data.maskedRecoveryEmail ?? null,
21576
+ escrowOptedOut: data.escrowOptedOut === true,
21577
+ escrowPin: data.escrowPin,
21578
+ sssActivationState: data.sssActivationState ?? "active"
19514
21579
  };
19515
21580
  },
19516
21581
  async storeAuthShare(token, providerType, authShare, primaryDid, didAuthVp) {
19517
- const { shareVersion } = await putAuthShare(
19518
- serverUrl,
19519
- token,
19520
- providerType,
19521
- authShare,
19522
- primaryDid,
19523
- didAuthVp,
19524
- 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
+ )
19525
21593
  );
19526
- await storage.storeShareVersion(shareVersion, activeStorageId);
21594
+ await storage2.storeShareVersion(shareVersion, activeStorageId);
19527
21595
  lastShareVersion = shareVersion;
19528
21596
  },
19529
- async markMigrated(token, providerType, didAuthVp) {
19530
- 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`, {
19531
21609
  method: "POST",
19532
21610
  headers: buildHeaders(token, didAuthVp, tenantId),
19533
21611
  body: JSON.stringify({ authToken: token, providerType })
19534
21612
  });
19535
21613
  if (!response.ok) {
19536
- throw new Error(`Failed to mark migrated: ${response.statusText}`);
21614
+ throw new Error(`Failed to activate SSS key: ${describeHttpStatus(response)}`);
19537
21615
  }
19538
21616
  },
19539
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
+ },
19540
21770
  async executeRecovery(params) {
19541
- 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
+ }
19542
21827
  let recoveryShare;
19543
21828
  let recoveryShareVersion;
19544
21829
  switch (input.method) {
@@ -19632,14 +21917,61 @@ function createSSSStrategy(config) {
19632
21917
  );
19633
21918
  }
19634
21919
  }
19635
- await storage.storeDeviceShare(recoveryShare, activeStorageId);
19636
- const versionToStore = recoveryShareVersion ?? serverData.shareVersion ?? 1;
19637
- 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
+ );
19638
21951
  return { privateKey, did: primaryDid };
19639
21952
  },
19640
21953
  // --- Recovery setup ---
19641
21954
  async setupRecoveryMethod(params) {
21955
+ const storage2 = guardedStorage();
19642
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;
19643
21975
  let passkeyCredential;
19644
21976
  if (input.method === "passkey") {
19645
21977
  if (!isWebAuthnSupported()) {
@@ -19649,10 +21981,6 @@ function createSSSStrategy(config) {
19649
21981
  const userName = authUser?.email || authUser?.phone || authUser?.id || "";
19650
21982
  passkeyCredential = await createPasskeyCredential(userId, userName);
19651
21983
  }
19652
- const vpJwt = signDidAuthVp ? await signDidAuthVp(privateKey) : void 0;
19653
- const { shares } = await splitAndVerify(privateKey);
19654
- lastEmailShare = shares.emailShare;
19655
- await storage.storeDeviceShare(shares.deviceShare, activeStorageId);
19656
21984
  const serverData = await fetchAuthShareRaw(
19657
21985
  serverUrl,
19658
21986
  token,
@@ -19661,39 +21989,42 @@ function createSSSStrategy(config) {
19661
21989
  tenantId
19662
21990
  );
19663
21991
  const primaryDid = serverData?.primaryDid || "";
19664
- 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,
19665
21995
  serverUrl,
19666
21996
  token,
19667
21997
  providerType,
19668
- shares.authShare,
19669
21998
  primaryDid,
19670
- vpJwt,
19671
- tenantId
21999
+ storage2,
22000
+ activeStorageId,
22001
+ void 0,
22002
+ signDidAuthVp,
22003
+ tenantId,
22004
+ recordPromotion(primaryDid),
22005
+ captureActiveUserGuard()
19672
22006
  );
19673
- await storage.storeShareVersion(shareVersion, activeStorageId);
19674
- if (enableEmailBackupShare && input.method !== "email" && lastEmailShare) {
19675
- const resend = hasRecoveryEmail ? sendEmailShareToRecoveryEmail(
19676
- serverUrl,
19677
- token,
19678
- providerType,
19679
- lastEmailShare,
19680
- shareVersion,
19681
- tenantId
19682
- ) : authUser?.email ? sendEmailBackupShare(
19683
- serverUrl,
19684
- token,
19685
- providerType,
19686
- lastEmailShare,
19687
- authUser.email,
19688
- shareVersion,
19689
- tenantId
19690
- ) : Promise.resolve();
19691
- resend.catch(
19692
- (e) => console.warn("Email backup share re-send failed (non-fatal):", e)
19693
- );
19694
- lastEmailShare = void 0;
19695
- }
22007
+ lastEmailShare = shares.emailShare;
22008
+ lastShareVersion = shareVersion;
22009
+ lastServerSnapshot = {
22010
+ currentVersion: shareVersion,
22011
+ resolvedVersion: shareVersion,
22012
+ authShare: shares.authShare,
22013
+ primaryDid
22014
+ };
19696
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 };
19697
22028
  case "passkey": {
19698
22029
  const credential = passkeyCredential;
19699
22030
  const encryptedShare = await encryptShareWithPasskey(
@@ -19713,9 +22044,38 @@ function createSSSStrategy(config) {
19713
22044
  credentialId: credential.credentialId,
19714
22045
  shareVersion
19715
22046
  },
19716
- vpJwt,
22047
+ signDidAuthVp ? await requestFreshDidAuthVp(
22048
+ serverUrl,
22049
+ privateKey,
22050
+ primaryDid,
22051
+ signDidAuthVp,
22052
+ tenantId
22053
+ ) : void 0,
19717
22054
  tenantId
19718
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
+ );
19719
22079
  return { method: "passkey", credentialId: credential.credentialId };
19720
22080
  }
19721
22081
  case "phrase": {
@@ -19728,10 +22088,38 @@ function createSSSStrategy(config) {
19728
22088
  type: "phrase",
19729
22089
  shareVersion
19730
22090
  },
19731
- vpJwt,
22091
+ signDidAuthVp ? await requestFreshDidAuthVp(
22092
+ serverUrl,
22093
+ privateKey,
22094
+ primaryDid,
22095
+ signDidAuthVp,
22096
+ tenantId
22097
+ ) : void 0,
19732
22098
  tenantId
19733
22099
  );
19734
- 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
+ };
19735
22123
  }
19736
22124
  case "backup": {
19737
22125
  const encrypted = await encryptWithPassword(
@@ -19743,6 +22131,7 @@ function createSSSStrategy(config) {
19743
22131
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
19744
22132
  primaryDid: input.did,
19745
22133
  shareVersion,
22134
+ shareChecksum: await calculateShareChecksum(shares.recoveryShare),
19746
22135
  encryptedShare: {
19747
22136
  ciphertext: encrypted.ciphertext,
19748
22137
  iv: encrypted.iv,
@@ -19758,36 +22147,121 @@ function createSSSStrategy(config) {
19758
22147
  type: "backup",
19759
22148
  shareVersion
19760
22149
  },
19761
- vpJwt,
22150
+ signDidAuthVp ? await requestFreshDidAuthVp(
22151
+ serverUrl,
22152
+ privateKey,
22153
+ primaryDid,
22154
+ signDidAuthVp,
22155
+ tenantId
22156
+ ) : void 0,
19762
22157
  tenantId
19763
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
+ );
19764
22172
  return { method: "backup", backupFile };
19765
22173
  }
19766
22174
  case "email": {
19767
- await sendEmailShareToRecoveryEmail(
22175
+ const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
22176
+ serverUrl,
22177
+ privateKey,
22178
+ primaryDid,
22179
+ signDidAuthVp,
22180
+ tenantId
22181
+ ) : void 0;
22182
+ await sendEmailBackupShare(
19768
22183
  serverUrl,
19769
22184
  token,
19770
22185
  providerType,
19771
22186
  shares.emailShare,
22187
+ input.email,
22188
+ escrowRelayPublicKey,
22189
+ escrowRelayKeyId,
22190
+ emailBranding,
19772
22191
  shareVersion,
22192
+ didAuthVp,
19773
22193
  tenantId
19774
22194
  );
19775
- await postRecoveryMethod(
19776
- serverUrl,
22195
+ await tryEnrollEscrow(
19777
22196
  token,
19778
22197
  providerType,
19779
- {
19780
- type: "email",
19781
- shareVersion
19782
- },
19783
- vpJwt,
19784
- tenantId
22198
+ privateKey,
22199
+ primaryDid,
22200
+ shares,
22201
+ shareVersion,
22202
+ signDidAuthVp
19785
22203
  );
19786
- hasRecoveryEmail = true;
19787
22204
  return { method: "email" };
19788
22205
  }
19789
22206
  }
19790
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
+ },
19791
22265
  async getAvailableRecoveryMethods(token, providerType) {
19792
22266
  try {
19793
22267
  const serverData = await fetchAuthShareRaw(
@@ -19798,15 +22272,274 @@ function createSSSStrategy(config) {
19798
22272
  tenantId
19799
22273
  );
19800
22274
  const methods = serverData?.recoveryMethods || [];
19801
- if (enableEmailBackupShare && !methods.some((m) => m.type === "email")) {
19802
- methods.push({ type: "email", createdAt: /* @__PURE__ */ new Date() });
19803
- }
19804
- 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
+ });
19805
22281
  } catch (e) {
19806
22282
  console.error("Error getting recovery methods:", e);
19807
22283
  return [];
19808
22284
  }
19809
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
+ },
19810
22543
  // --- Contact method management ---
19811
22544
  async upgradeContactMethod(token, providerType, previousPhone, email, code) {
19812
22545
  const res = await fetch(`${serverUrl}/keys/upgrade-contact-method`, {
@@ -19829,7 +22562,7 @@ function createSSSStrategy(config) {
19829
22562
  return { customToken: data?.customToken };
19830
22563
  },
19831
22564
  // --- Email backup ---
19832
- async sendEmailBackupShare(token, providerType, _privateKey, email) {
22565
+ async sendEmailBackupShare(token, providerType, _privateKey, email, didAuthVp) {
19833
22566
  if (!enableEmailBackupShare) return;
19834
22567
  if (!lastEmailShare) {
19835
22568
  console.warn(
@@ -19837,29 +22570,22 @@ function createSSSStrategy(config) {
19837
22570
  );
19838
22571
  return;
19839
22572
  }
19840
- if (hasRecoveryEmail) {
19841
- await sendEmailShareToRecoveryEmail(
19842
- serverUrl,
19843
- token,
19844
- providerType,
19845
- lastEmailShare,
19846
- lastShareVersion,
19847
- tenantId
19848
- );
19849
- } else {
19850
- if (!email) {
19851
- console.warn("Cannot send email backup share: no email address");
19852
- return;
19853
- }
22573
+ if (email) {
19854
22574
  await sendEmailBackupShare(
19855
22575
  serverUrl,
19856
22576
  token,
19857
22577
  providerType,
19858
22578
  lastEmailShare,
19859
22579
  email,
22580
+ escrowRelayPublicKey,
22581
+ escrowRelayKeyId,
22582
+ emailBranding,
19860
22583
  lastShareVersion,
22584
+ didAuthVp,
19861
22585
  tenantId
19862
22586
  );
22587
+ } else {
22588
+ throw new Error("Enter a recovery email before sending a recovery key");
19863
22589
  }
19864
22590
  lastEmailShare = void 0;
19865
22591
  lastShareVersion = void 0;
@@ -19876,6 +22602,8 @@ function createSSSStrategy(config) {
19876
22602
  return [SSS_DB_NAME];
19877
22603
  },
19878
22604
  async cleanup() {
22605
+ storageGeneration++;
22606
+ await Promise.allSettled(localWrites);
19879
22607
  }
19880
22608
  };
19881
22609
  }
@@ -19968,13 +22696,7 @@ var decryptShareFromTransfer = /* @__PURE__ */ __name(async (payload, recipientP
19968
22696
  }, "decryptShareFromTransfer");
19969
22697
 
19970
22698
  // src/qr-login.ts
19971
- var buildHeaders2 = /* @__PURE__ */ __name((token) => {
19972
- const headers = { "Content-Type": "application/json" };
19973
- if (token) {
19974
- headers.Authorization = `Bearer ${token}`;
19975
- }
19976
- return headers;
19977
- }, "buildHeaders");
22699
+ var buildHeaders2 = /* @__PURE__ */ __name(() => ({ "Content-Type": "application/json" }), "buildHeaders");
19978
22700
  var createQrLoginSession = /* @__PURE__ */ __name(async (config) => {
19979
22701
  const ephemeralKeypair = await generateEphemeralKeypair();
19980
22702
  const response = await fetch(`${config.serverUrl}/qr-login/session`, {
@@ -20042,10 +22764,14 @@ var pollUntilApproved = /* @__PURE__ */ __name(async (config, sessionId, ephemer
20042
22764
  await new Promise((resolve, reject) => {
20043
22765
  const timer = setTimeout(resolve, intervalMs);
20044
22766
  if (options?.signal) {
20045
- options.signal.addEventListener("abort", () => {
20046
- clearTimeout(timer);
20047
- reject(new Error("QR login polling aborted"));
20048
- }, { 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
+ );
20049
22775
  }
20050
22776
  });
20051
22777
  }
@@ -20097,6 +22823,3 @@ var notifyDevicesForQrSession = /* @__PURE__ */ __name(async (config, sessionId,
20097
22823
  return { sent: false, deviceCount: 0 };
20098
22824
  }
20099
22825
  }, "notifyDevicesForQrSession");
20100
-
20101
- // src/types.ts
20102
- var import_types = __toESM(require_dist());