@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.
- package/README.md +12 -95
- package/dist/api-client.d.ts +44 -7
- package/dist/api-client.d.ts.map +1 -1
- package/dist/atomic-operations.d.ts +10 -2
- package/dist/atomic-operations.d.ts.map +1 -1
- package/dist/email-relay-crypto.d.ts +49 -0
- package/dist/email-relay-crypto.d.ts.map +1 -0
- package/dist/escrow-attestation.d.ts +8 -0
- package/dist/escrow-attestation.d.ts.map +1 -0
- package/dist/escrow-crypto.d.ts +52 -0
- package/dist/escrow-crypto.d.ts.map +1 -0
- package/dist/escrow-pin.d.ts +22 -0
- package/dist/escrow-pin.d.ts.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/key-manager.d.ts +2 -0
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/qr-login.d.ts.map +1 -1
- package/dist/recovery-phrase.d.ts +6 -0
- package/dist/recovery-phrase.d.ts.map +1 -1
- package/dist/sss-key-manager.cjs.development.js +3163 -440
- package/dist/sss-key-manager.cjs.development.js.map +4 -4
- package/dist/sss-key-manager.cjs.production.min.js +33 -33
- package/dist/sss-key-manager.cjs.production.min.js.map +4 -4
- package/dist/sss-key-manager.esm.js +3163 -440
- package/dist/sss-key-manager.esm.js.map +4 -4
- package/dist/sss-strategy.d.ts +74 -4
- package/dist/sss-strategy.d.ts.map +1 -1
- package/dist/storage.d.ts +1 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/types.d.ts +87 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api-client.test.ts +122 -0
- package/src/api-client.ts +107 -116
- package/src/atomic-operations.test.ts +56 -16
- package/src/atomic-operations.ts +55 -19
- package/src/critical-paths.test.ts +51 -1
- package/src/email-relay-crypto.test.ts +87 -0
- package/src/email-relay-crypto.ts +293 -0
- package/src/escrow-attestation.test.ts +85 -0
- package/src/escrow-attestation.ts +64 -0
- package/src/escrow-crypto.test.ts +381 -0
- package/src/escrow-crypto.ts +319 -0
- package/src/escrow-pin.test.ts +119 -0
- package/src/escrow-pin.ts +83 -0
- package/src/index.ts +63 -1
- package/src/key-manager.ts +4 -9
- package/src/qr-login.ts +22 -15
- package/src/recovery-phrase.ts +40 -0
- package/src/sss-session-writes.test.ts +109 -0
- package/src/sss-strategy.test.ts +2642 -110
- package/src/sss-strategy.ts +2186 -224
- package/src/storage.test.ts +66 -8
- package/src/storage.ts +41 -29
- package/src/types.ts +107 -6
|
@@ -49,12 +49,15 @@ var require_types_cjs_development = __commonJS({
|
|
|
49
49
|
var __toCommonJS = /* @__PURE__ */ __name((mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod), "__toCommonJS");
|
|
50
50
|
var index_exports = {};
|
|
51
51
|
__export(index_exports, {
|
|
52
|
+
ACT_AS_HEADER: /* @__PURE__ */ __name(() => ACT_AS_HEADER, "ACT_AS_HEADER"),
|
|
52
53
|
AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX: /* @__PURE__ */ __name(() => AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX, "AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX"),
|
|
53
54
|
AchievementCredentialValidator: /* @__PURE__ */ __name(() => AchievementCredentialValidator, "AchievementCredentialValidator"),
|
|
54
55
|
AchievementCriteriaValidator: /* @__PURE__ */ __name(() => AchievementCriteriaValidator, "AchievementCriteriaValidator"),
|
|
55
56
|
AchievementSubjectValidator: /* @__PURE__ */ __name(() => AchievementSubjectValidator, "AchievementSubjectValidator"),
|
|
56
57
|
AchievementTypeValidator: /* @__PURE__ */ __name(() => AchievementTypeValidator, "AchievementTypeValidator"),
|
|
57
58
|
AchievementValidator: /* @__PURE__ */ __name(() => AchievementValidator, "AchievementValidator"),
|
|
59
|
+
AcknowledgeViewInputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewInputValidator, "AcknowledgeViewInputValidator"),
|
|
60
|
+
AcknowledgeViewOutputValidator: /* @__PURE__ */ __name(() => AcknowledgeViewOutputValidator, "AcknowledgeViewOutputValidator"),
|
|
58
61
|
AddTagInputValidator: /* @__PURE__ */ __name(() => AddTagInputValidator, "AddTagInputValidator"),
|
|
59
62
|
AddressValidator: /* @__PURE__ */ __name(() => AddressValidator, "AddressValidator"),
|
|
60
63
|
AgeRatingValidator: /* @__PURE__ */ __name(() => AgeRatingValidator, "AgeRatingValidator"),
|
|
@@ -132,6 +135,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
132
135
|
CreateContactMethodSessionValidator: /* @__PURE__ */ __name(() => CreateContactMethodSessionValidator, "CreateContactMethodSessionValidator"),
|
|
133
136
|
CreateManagedFrameworkBatchInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkBatchInputValidator, "CreateManagedFrameworkBatchInputValidator"),
|
|
134
137
|
CreateManagedFrameworkInputValidator: /* @__PURE__ */ __name(() => CreateManagedFrameworkInputValidator, "CreateManagedFrameworkInputValidator"),
|
|
138
|
+
CreateShareLinkInputValidator: /* @__PURE__ */ __name(() => CreateShareLinkInputValidator, "CreateShareLinkInputValidator"),
|
|
135
139
|
CreateSkillInputValidator: /* @__PURE__ */ __name(() => CreateSkillInputValidator, "CreateSkillInputValidator"),
|
|
136
140
|
CreateSkillsBatchInputValidator: /* @__PURE__ */ __name(() => CreateSkillsBatchInputValidator, "CreateSkillsBatchInputValidator"),
|
|
137
141
|
CredentialActivityEventTypeValidator: /* @__PURE__ */ __name(() => CredentialActivityEventTypeValidator, "CredentialActivityEventTypeValidator"),
|
|
@@ -163,10 +167,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
163
167
|
DeleteSkillInputValidator: /* @__PURE__ */ __name(() => DeleteSkillInputValidator, "DeleteSkillInputValidator"),
|
|
164
168
|
DidDocumentValidator: /* @__PURE__ */ __name(() => DidDocumentValidator, "DidDocumentValidator"),
|
|
165
169
|
EMPTY_IN_APP_MESSAGES_FLAG: /* @__PURE__ */ __name(() => EMPTY_IN_APP_MESSAGES_FLAG, "EMPTY_IN_APP_MESSAGES_FLAG"),
|
|
170
|
+
ESCROW_PIN_LOCKED_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_LOCKED_MESSAGE2, "ESCROW_PIN_LOCKED_MESSAGE"),
|
|
171
|
+
ESCROW_PIN_MISMATCH_PATTERN: /* @__PURE__ */ __name(() => ESCROW_PIN_MISMATCH_PATTERN2, "ESCROW_PIN_MISMATCH_PATTERN"),
|
|
172
|
+
ESCROW_PIN_UNAVAILABLE_MESSAGE: /* @__PURE__ */ __name(() => ESCROW_PIN_UNAVAILABLE_MESSAGE2, "ESCROW_PIN_UNAVAILABLE_MESSAGE"),
|
|
166
173
|
EncryptedCredentialRecordValidator: /* @__PURE__ */ __name(() => EncryptedCredentialRecordValidator, "EncryptedCredentialRecordValidator"),
|
|
167
174
|
EncryptedRecordValidator: /* @__PURE__ */ __name(() => EncryptedRecordValidator, "EncryptedRecordValidator"),
|
|
168
175
|
EndorsementCredentialValidator: /* @__PURE__ */ __name(() => EndorsementCredentialValidator, "EndorsementCredentialValidator"),
|
|
169
176
|
EndorsementSubjectValidator: /* @__PURE__ */ __name(() => EndorsementSubjectValidator, "EndorsementSubjectValidator"),
|
|
177
|
+
EscrowPinStatusValidator: /* @__PURE__ */ __name(() => EscrowPinStatusValidator, "EscrowPinStatusValidator"),
|
|
170
178
|
EvidenceValidator: /* @__PURE__ */ __name(() => EvidenceValidator, "EvidenceValidator"),
|
|
171
179
|
FlatAuthGrantValidator: /* @__PURE__ */ __name(() => FlatAuthGrantValidator, "FlatAuthGrantValidator"),
|
|
172
180
|
FrameworkWithSkillsValidator: /* @__PURE__ */ __name(() => FrameworkWithSkillsValidator, "FrameworkWithSkillsValidator"),
|
|
@@ -188,10 +196,18 @@ var require_types_cjs_development = __commonJS({
|
|
|
188
196
|
IdentifierTypeValidator: /* @__PURE__ */ __name(() => IdentifierTypeValidator, "IdentifierTypeValidator"),
|
|
189
197
|
IdentityObjectValidator: /* @__PURE__ */ __name(() => IdentityObjectValidator, "IdentityObjectValidator"),
|
|
190
198
|
ImageValidator: /* @__PURE__ */ __name(() => ImageValidator, "ImageValidator"),
|
|
199
|
+
InboxBatchErrorReasonValidator: /* @__PURE__ */ __name(() => InboxBatchErrorReasonValidator, "InboxBatchErrorReasonValidator"),
|
|
200
|
+
InboxBatchReceiptValidator: /* @__PURE__ */ __name(() => InboxBatchReceiptValidator, "InboxBatchReceiptValidator"),
|
|
201
|
+
InboxBatchStatusValidator: /* @__PURE__ */ __name(() => InboxBatchStatusValidator, "InboxBatchStatusValidator"),
|
|
191
202
|
InboxCredentialQueryValidator: /* @__PURE__ */ __name(() => InboxCredentialQueryValidator, "InboxCredentialQueryValidator"),
|
|
203
|
+
InboxCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => InboxCredentialRefreshReceiptValidator, "InboxCredentialRefreshReceiptValidator"),
|
|
192
204
|
InboxCredentialValidator: /* @__PURE__ */ __name(() => InboxCredentialValidator, "InboxCredentialValidator"),
|
|
193
205
|
IncrementCounterEventValidator: /* @__PURE__ */ __name(() => IncrementCounterEventValidator, "IncrementCounterEventValidator"),
|
|
194
206
|
InstalledAppValidator: /* @__PURE__ */ __name(() => InstalledAppValidator, "InstalledAppValidator"),
|
|
207
|
+
IssueInboxCredentialBatchItemResultValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchItemResultValidator, "IssueInboxCredentialBatchItemResultValidator"),
|
|
208
|
+
IssueInboxCredentialBatchItemValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchItemValidator, "IssueInboxCredentialBatchItemValidator"),
|
|
209
|
+
IssueInboxCredentialBatchResponseValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchResponseValidator, "IssueInboxCredentialBatchResponseValidator"),
|
|
210
|
+
IssueInboxCredentialBatchValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialBatchValidator, "IssueInboxCredentialBatchValidator"),
|
|
195
211
|
IssueInboxCredentialResponseValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialResponseValidator, "IssueInboxCredentialResponseValidator"),
|
|
196
212
|
IssueInboxCredentialValidator: /* @__PURE__ */ __name(() => IssueInboxCredentialValidator, "IssueInboxCredentialValidator"),
|
|
197
213
|
IssueInboxSigningAuthorityValidator: /* @__PURE__ */ __name(() => IssueInboxSigningAuthorityValidator, "IssueInboxSigningAuthorityValidator"),
|
|
@@ -240,6 +256,15 @@ var require_types_cjs_development = __commonJS({
|
|
|
240
256
|
LaunchTypeValidator: /* @__PURE__ */ __name(() => LaunchTypeValidator, "LaunchTypeValidator"),
|
|
241
257
|
LearnCardRefreshAuthorizationValidator: /* @__PURE__ */ __name(() => LearnCardRefreshAuthorizationValidator, "LearnCardRefreshAuthorizationValidator"),
|
|
242
258
|
LinkProviderFrameworkInputValidator: /* @__PURE__ */ __name(() => LinkProviderFrameworkInputValidator, "LinkProviderFrameworkInputValidator"),
|
|
259
|
+
ListShareLinksInputValidator: /* @__PURE__ */ __name(() => ListShareLinksInputValidator, "ListShareLinksInputValidator"),
|
|
260
|
+
MAX_ENDORSEMENTS: /* @__PURE__ */ __name(() => MAX_ENDORSEMENTS, "MAX_ENDORSEMENTS"),
|
|
261
|
+
MAX_SELECTED_CREDENTIALS: /* @__PURE__ */ __name(() => MAX_SELECTED_CREDENTIALS, "MAX_SELECTED_CREDENTIALS"),
|
|
262
|
+
MAX_SHARE_CIPHERTEXT_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_CIPHERTEXT_BYTES, "MAX_SHARE_CIPHERTEXT_BYTES"),
|
|
263
|
+
MAX_SHARE_LINK_REQUEST_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_LINK_REQUEST_BYTES, "MAX_SHARE_LINK_REQUEST_BYTES"),
|
|
264
|
+
MAX_SHARE_RECOVERY_JWE_BYTES: /* @__PURE__ */ __name(() => MAX_SHARE_RECOVERY_JWE_BYTES, "MAX_SHARE_RECOVERY_JWE_BYTES"),
|
|
265
|
+
MAX_SHARE_SOURCE_URI_CHARS: /* @__PURE__ */ __name(() => MAX_SHARE_SOURCE_URI_CHARS, "MAX_SHARE_SOURCE_URI_CHARS"),
|
|
266
|
+
MAX_VP_MEMBERS: /* @__PURE__ */ __name(() => MAX_VP_MEMBERS, "MAX_VP_MEMBERS"),
|
|
267
|
+
ManagedCredentialRefreshReceiptValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshReceiptValidator, "ManagedCredentialRefreshReceiptValidator"),
|
|
243
268
|
ManagedCredentialRefreshServiceValidator: /* @__PURE__ */ __name(() => ManagedCredentialRefreshServiceValidator, "ManagedCredentialRefreshServiceValidator"),
|
|
244
269
|
PaginatedAppStoreListingsValidator: /* @__PURE__ */ __name(() => PaginatedAppStoreListingsValidator, "PaginatedAppStoreListingsValidator"),
|
|
245
270
|
PaginatedBoostRecipientsValidator: /* @__PURE__ */ __name(() => PaginatedBoostRecipientsValidator, "PaginatedBoostRecipientsValidator"),
|
|
@@ -261,11 +286,14 @@ var require_types_cjs_development = __commonJS({
|
|
|
261
286
|
PaginatedLCNProfileManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfileManagersValidator, "PaginatedLCNProfileManagersValidator"),
|
|
262
287
|
PaginatedLCNProfilesAndManagersValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesAndManagersValidator, "PaginatedLCNProfilesAndManagersValidator"),
|
|
263
288
|
PaginatedLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedLCNProfilesValidator, "PaginatedLCNProfilesValidator"),
|
|
289
|
+
PaginatedShareLinksValidator: /* @__PURE__ */ __name(() => PaginatedShareLinksValidator, "PaginatedShareLinksValidator"),
|
|
264
290
|
PaginatedSkillFrameworksValidator: /* @__PURE__ */ __name(() => PaginatedSkillFrameworksValidator, "PaginatedSkillFrameworksValidator"),
|
|
265
291
|
PaginatedSkillTreeValidator: /* @__PURE__ */ __name(() => PaginatedSkillTreeValidator, "PaginatedSkillTreeValidator"),
|
|
266
292
|
PaginatedVisibleLCNProfilesValidator: /* @__PURE__ */ __name(() => PaginatedVisibleLCNProfilesValidator, "PaginatedVisibleLCNProfilesValidator"),
|
|
267
293
|
PaginationOptionsValidator: /* @__PURE__ */ __name(() => PaginationOptionsValidator, "PaginationOptionsValidator"),
|
|
268
294
|
PaginationResponseValidator: /* @__PURE__ */ __name(() => PaginationResponseValidator, "PaginationResponseValidator"),
|
|
295
|
+
PrepareRefreshableSendInputValidator: /* @__PURE__ */ __name(() => PrepareRefreshableSendInputValidator, "PrepareRefreshableSendInputValidator"),
|
|
296
|
+
PrepareRefreshableSendResultValidator: /* @__PURE__ */ __name(() => PrepareRefreshableSendResultValidator, "PrepareRefreshableSendResultValidator"),
|
|
269
297
|
ProfileValidator: /* @__PURE__ */ __name(() => ProfileValidator, "ProfileValidator"),
|
|
270
298
|
ProfileVisibilityEnum: /* @__PURE__ */ __name(() => ProfileVisibilityEnum, "ProfileVisibilityEnum"),
|
|
271
299
|
PromotionLevelValidator: /* @__PURE__ */ __name(() => PromotionLevelValidator, "PromotionLevelValidator"),
|
|
@@ -283,14 +311,24 @@ var require_types_cjs_development = __commonJS({
|
|
|
283
311
|
ReplaceSkillFrameworkSkillsInputValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsInputValidator, "ReplaceSkillFrameworkSkillsInputValidator"),
|
|
284
312
|
ReplaceSkillFrameworkSkillsResultValidator: /* @__PURE__ */ __name(() => ReplaceSkillFrameworkSkillsResultValidator, "ReplaceSkillFrameworkSkillsResultValidator"),
|
|
285
313
|
RequestLearnerContextEventValidator: /* @__PURE__ */ __name(() => RequestLearnerContextEventValidator, "RequestLearnerContextEventValidator"),
|
|
314
|
+
ResolveShareLinkInputValidator: /* @__PURE__ */ __name(() => ResolveShareLinkInputValidator, "ResolveShareLinkInputValidator"),
|
|
286
315
|
ResultDescriptionValidator: /* @__PURE__ */ __name(() => ResultDescriptionValidator, "ResultDescriptionValidator"),
|
|
287
316
|
ResultStatusTypeValidator: /* @__PURE__ */ __name(() => ResultStatusTypeValidator, "ResultStatusTypeValidator"),
|
|
288
317
|
ResultTypeValidator: /* @__PURE__ */ __name(() => ResultTypeValidator, "ResultTypeValidator"),
|
|
289
318
|
ResultValidator: /* @__PURE__ */ __name(() => ResultValidator, "ResultValidator"),
|
|
290
319
|
RubricCriterionValidator: /* @__PURE__ */ __name(() => RubricCriterionValidator, "RubricCriterionValidator"),
|
|
320
|
+
SHARE_AAD_PREFIX: /* @__PURE__ */ __name(() => SHARE_AAD_PREFIX, "SHARE_AAD_PREFIX"),
|
|
321
|
+
SHARE_CONTENT_KEY_BYTES: /* @__PURE__ */ __name(() => SHARE_CONTENT_KEY_BYTES, "SHARE_CONTENT_KEY_BYTES"),
|
|
322
|
+
SHARE_IV_BYTES: /* @__PURE__ */ __name(() => SHARE_IV_BYTES, "SHARE_IV_BYTES"),
|
|
323
|
+
SHARE_LINK_ID_BYTES: /* @__PURE__ */ __name(() => SHARE_LINK_ID_BYTES, "SHARE_LINK_ID_BYTES"),
|
|
324
|
+
SHARE_LINK_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_LINK_PROTOCOL, "SHARE_LINK_PROTOCOL"),
|
|
325
|
+
SHARE_RECOVERY_PROTOCOL: /* @__PURE__ */ __name(() => SHARE_RECOVERY_PROTOCOL, "SHARE_RECOVERY_PROTOCOL"),
|
|
326
|
+
SHARE_TAG_BYTES: /* @__PURE__ */ __name(() => SHARE_TAG_BYTES, "SHARE_TAG_BYTES"),
|
|
327
|
+
SHARE_VIEW_RECEIPT_TTL_SECONDS: /* @__PURE__ */ __name(() => SHARE_VIEW_RECEIPT_TTL_SECONDS, "SHARE_VIEW_RECEIPT_TTL_SECONDS"),
|
|
291
328
|
SendAiSessionCredentialEventValidator: /* @__PURE__ */ __name(() => SendAiSessionCredentialEventValidator, "SendAiSessionCredentialEventValidator"),
|
|
292
329
|
SendBoostInputValidator: /* @__PURE__ */ __name(() => SendBoostInputValidator, "SendBoostInputValidator"),
|
|
293
330
|
SendBoostResponseValidator: /* @__PURE__ */ __name(() => SendBoostResponseValidator, "SendBoostResponseValidator"),
|
|
331
|
+
SendBoostTemplateValidator: /* @__PURE__ */ __name(() => SendBoostTemplateValidator, "SendBoostTemplateValidator"),
|
|
294
332
|
SendBrandingOptionsValidator: /* @__PURE__ */ __name(() => SendBrandingOptionsValidator, "SendBrandingOptionsValidator"),
|
|
295
333
|
SendCredentialEventValidator: /* @__PURE__ */ __name(() => SendCredentialEventValidator, "SendCredentialEventValidator"),
|
|
296
334
|
SendInboxResponseValidator: /* @__PURE__ */ __name(() => SendInboxResponseValidator, "SendInboxResponseValidator"),
|
|
@@ -301,6 +339,32 @@ var require_types_cjs_development = __commonJS({
|
|
|
301
339
|
SentCredentialInfoValidator: /* @__PURE__ */ __name(() => SentCredentialInfoValidator, "SentCredentialInfoValidator"),
|
|
302
340
|
ServiceValidator: /* @__PURE__ */ __name(() => ServiceValidator, "ServiceValidator"),
|
|
303
341
|
SetPrimaryContactMethodValidator: /* @__PURE__ */ __name(() => SetPrimaryContactMethodValidator, "SetPrimaryContactMethodValidator"),
|
|
342
|
+
ShareCiphertextValidator: /* @__PURE__ */ __name(() => ShareCiphertextValidator, "ShareCiphertextValidator"),
|
|
343
|
+
ShareContentKeyValidator: /* @__PURE__ */ __name(() => ShareContentKeyValidator, "ShareContentKeyValidator"),
|
|
344
|
+
ShareContentStateValidator: /* @__PURE__ */ __name(() => ShareContentStateValidator, "ShareContentStateValidator"),
|
|
345
|
+
ShareEndorsementEntryValidator: /* @__PURE__ */ __name(() => ShareEndorsementEntryValidator, "ShareEndorsementEntryValidator"),
|
|
346
|
+
ShareEnvelopeValidator: /* @__PURE__ */ __name(() => ShareEnvelopeValidator, "ShareEnvelopeValidator"),
|
|
347
|
+
ShareIvValidator: /* @__PURE__ */ __name(() => ShareIvValidator, "ShareIvValidator"),
|
|
348
|
+
ShareLinkIdInputValidator: /* @__PURE__ */ __name(() => ShareLinkIdInputValidator, "ShareLinkIdInputValidator"),
|
|
349
|
+
ShareLinkIdValidator: /* @__PURE__ */ __name(() => ShareLinkIdValidator, "ShareLinkIdValidator"),
|
|
350
|
+
ShareLinkOperationKeyInputValidator: /* @__PURE__ */ __name(() => ShareLinkOperationKeyInputValidator, "ShareLinkOperationKeyInputValidator"),
|
|
351
|
+
ShareLinkOwnerCommitOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerCommitOutputValidator, "ShareLinkOwnerCommitOutputValidator"),
|
|
352
|
+
ShareLinkOwnerContentOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerContentOutputValidator, "ShareLinkOwnerContentOutputValidator"),
|
|
353
|
+
ShareLinkOwnerRecoveryOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerRecoveryOutputValidator, "ShareLinkOwnerRecoveryOutputValidator"),
|
|
354
|
+
ShareLinkOwnerStatusOutputValidator: /* @__PURE__ */ __name(() => ShareLinkOwnerStatusOutputValidator, "ShareLinkOwnerStatusOutputValidator"),
|
|
355
|
+
ShareLinkPublicContentViewValidator: /* @__PURE__ */ __name(() => ShareLinkPublicContentViewValidator, "ShareLinkPublicContentViewValidator"),
|
|
356
|
+
ShareLinkPublicStateValidator: /* @__PURE__ */ __name(() => ShareLinkPublicStateValidator, "ShareLinkPublicStateValidator"),
|
|
357
|
+
ShareLinkStatusValidator: /* @__PURE__ */ __name(() => ShareLinkStatusValidator, "ShareLinkStatusValidator"),
|
|
358
|
+
ShareLinkValidator: /* @__PURE__ */ __name(() => ShareLinkValidator, "ShareLinkValidator"),
|
|
359
|
+
ShareManifestPresentationValidator: /* @__PURE__ */ __name(() => ShareManifestPresentationValidator, "ShareManifestPresentationValidator"),
|
|
360
|
+
ShareOwnerRecoveryValidator: /* @__PURE__ */ __name(() => ShareOwnerRecoveryValidator, "ShareOwnerRecoveryValidator"),
|
|
361
|
+
SharePayloadValidator: /* @__PURE__ */ __name(() => SharePayloadValidator, "SharePayloadValidator"),
|
|
362
|
+
SharePayloadValidatorShape: /* @__PURE__ */ __name(() => SharePayloadValidatorShape, "SharePayloadValidatorShape"),
|
|
363
|
+
ShareRecoveryEndorsementValidator: /* @__PURE__ */ __name(() => ShareRecoveryEndorsementValidator, "ShareRecoveryEndorsementValidator"),
|
|
364
|
+
ShareRecoveryPlaintextValidator: /* @__PURE__ */ __name(() => ShareRecoveryPlaintextValidator, "ShareRecoveryPlaintextValidator"),
|
|
365
|
+
ShareRecoverySelectionValidator: /* @__PURE__ */ __name(() => ShareRecoverySelectionValidator, "ShareRecoverySelectionValidator"),
|
|
366
|
+
ShareSelectionEntryValidator: /* @__PURE__ */ __name(() => ShareSelectionEntryValidator, "ShareSelectionEntryValidator"),
|
|
367
|
+
ShareSharerValidator: /* @__PURE__ */ __name(() => ShareSharerValidator, "ShareSharerValidator"),
|
|
304
368
|
SkillDeletionStrategyValidator: /* @__PURE__ */ __name(() => SkillDeletionStrategyValidator, "SkillDeletionStrategyValidator"),
|
|
305
369
|
SkillFrameworkAdminInputValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminInputValidator, "SkillFrameworkAdminInputValidator"),
|
|
306
370
|
SkillFrameworkAdminsValidator: /* @__PURE__ */ __name(() => SkillFrameworkAdminsValidator, "SkillFrameworkAdminsValidator"),
|
|
@@ -332,6 +396,7 @@ var require_types_cjs_development = __commonJS({
|
|
|
332
396
|
UnsignedVCValidator: /* @__PURE__ */ __name(() => UnsignedVCValidator, "UnsignedVCValidator"),
|
|
333
397
|
UnsignedVPValidator: /* @__PURE__ */ __name(() => UnsignedVPValidator, "UnsignedVPValidator"),
|
|
334
398
|
UpdateFrameworkInputValidator: /* @__PURE__ */ __name(() => UpdateFrameworkInputValidator, "UpdateFrameworkInputValidator"),
|
|
399
|
+
UpdateShareLinkInputValidator: /* @__PURE__ */ __name(() => UpdateShareLinkInputValidator, "UpdateShareLinkInputValidator"),
|
|
335
400
|
UpdateSkillInputValidator: /* @__PURE__ */ __name(() => UpdateSkillInputValidator, "UpdateSkillInputValidator"),
|
|
336
401
|
VC2EvidenceValidator: /* @__PURE__ */ __name(() => VC2EvidenceValidator, "VC2EvidenceValidator"),
|
|
337
402
|
VCValidator: /* @__PURE__ */ __name(() => VCValidator, "VCValidator"),
|
|
@@ -343,7 +408,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
343
408
|
VerificationStatusValidator: /* @__PURE__ */ __name(() => VerificationStatusValidator, "VerificationStatusValidator"),
|
|
344
409
|
appStoreActionValidator: /* @__PURE__ */ __name(() => appStoreActionValidator, "appStoreActionValidator"),
|
|
345
410
|
capgoUpdateActionValidator: /* @__PURE__ */ __name(() => capgoUpdateActionValidator, "capgoUpdateActionValidator"),
|
|
411
|
+
classifyShareManifest: /* @__PURE__ */ __name(() => classifyShareManifest, "classifyShareManifest"),
|
|
412
|
+
decodeBase64Url: /* @__PURE__ */ __name(() => decodeBase64Url, "decodeBase64Url"),
|
|
413
|
+
decodedBase64UrlByteLength: /* @__PURE__ */ __name(() => decodedBase64UrlByteLength, "decodedBase64UrlByteLength"),
|
|
346
414
|
dismissActionValidator: /* @__PURE__ */ __name(() => dismissActionValidator, "dismissActionValidator"),
|
|
415
|
+
encodeBase64Url: /* @__PURE__ */ __name(() => encodeBase64Url, "encodeBase64Url"),
|
|
416
|
+
escrowPinMismatchMessage: /* @__PURE__ */ __name(() => escrowPinMismatchMessage, "escrowPinMismatchMessage"),
|
|
347
417
|
externalLinkActionValidator: /* @__PURE__ */ __name(() => externalLinkActionValidator, "externalLinkActionValidator"),
|
|
348
418
|
inAppMessageActionStyleValidator: /* @__PURE__ */ __name(() => inAppMessageActionStyleValidator, "inAppMessageActionStyleValidator"),
|
|
349
419
|
inAppMessageActionTargetValidator: /* @__PURE__ */ __name(() => inAppMessageActionTargetValidator, "inAppMessageActionTargetValidator"),
|
|
@@ -358,10 +428,12 @@ var require_types_cjs_development = __commonJS({
|
|
|
358
428
|
inAppMessageVersionSourceValidator: /* @__PURE__ */ __name(() => inAppMessageVersionSourceValidator, "inAppMessageVersionSourceValidator"),
|
|
359
429
|
inAppMessagesFlagValidator: /* @__PURE__ */ __name(() => inAppMessagesFlagValidator, "inAppMessagesFlagValidator"),
|
|
360
430
|
internalLinkActionValidator: /* @__PURE__ */ __name(() => internalLinkActionValidator, "internalLinkActionValidator"),
|
|
431
|
+
isCanonicalBase64Url: /* @__PURE__ */ __name(() => isCanonicalBase64Url, "isCanonicalBase64Url"),
|
|
361
432
|
isStoredCredentialEnvelope: /* @__PURE__ */ __name(() => isStoredCredentialEnvelope, "isStoredCredentialEnvelope"),
|
|
362
433
|
parseInAppMessagesFlag: /* @__PURE__ */ __name(() => parseInAppMessagesFlag, "parseInAppMessagesFlag"),
|
|
363
434
|
platformPredicateValidator: /* @__PURE__ */ __name(() => platformPredicateValidator, "platformPredicateValidator"),
|
|
364
435
|
rolePredicateValidator: /* @__PURE__ */ __name(() => rolePredicateValidator, "rolePredicateValidator"),
|
|
436
|
+
utf8ByteLength: /* @__PURE__ */ __name(() => utf8ByteLength, "utf8ByteLength"),
|
|
365
437
|
versionPredicateValidator: /* @__PURE__ */ __name(() => versionPredicateValidator, "versionPredicateValidator")
|
|
366
438
|
});
|
|
367
439
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -16215,6 +16287,204 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16215
16287
|
$or: BaseStringQuery.array()
|
|
16216
16288
|
})
|
|
16217
16289
|
]);
|
|
16290
|
+
var LearnCardRefreshAuthorizationValidator = external_exports.object({ type: external_exports.literal("LearnCardDIDAuth") }).catchall(external_exports.any());
|
|
16291
|
+
var ManagedCredentialRefreshServiceValidator = external_exports.object({
|
|
16292
|
+
id: external_exports.string().min(1),
|
|
16293
|
+
type: external_exports.literal("LearnCardCredentialRefresh2026"),
|
|
16294
|
+
authorization: LearnCardRefreshAuthorizationValidator.optional()
|
|
16295
|
+
}).catchall(external_exports.any());
|
|
16296
|
+
var StandardCredentialRefreshServiceValidator = external_exports.object({
|
|
16297
|
+
id: external_exports.string().min(1),
|
|
16298
|
+
type: external_exports.literal("1EdTechCredentialRefresh")
|
|
16299
|
+
}).catchall(external_exports.any());
|
|
16300
|
+
var SupportedCredentialRefreshServiceValidator = external_exports.union([
|
|
16301
|
+
ManagedCredentialRefreshServiceValidator,
|
|
16302
|
+
StandardCredentialRefreshServiceValidator
|
|
16303
|
+
]);
|
|
16304
|
+
var AllocateCredentialRefreshInputValidator = external_exports.object({
|
|
16305
|
+
holder: external_exports.object({
|
|
16306
|
+
profileId: external_exports.string().optional(),
|
|
16307
|
+
did: external_exports.string().min(1)
|
|
16308
|
+
}),
|
|
16309
|
+
credentialId: external_exports.string().min(1)
|
|
16310
|
+
});
|
|
16311
|
+
var AllocateCredentialRefreshResultValidator = external_exports.object({
|
|
16312
|
+
refreshId: external_exports.string().min(1),
|
|
16313
|
+
refreshService: ManagedCredentialRefreshServiceValidator.extend({
|
|
16314
|
+
authorization: LearnCardRefreshAuthorizationValidator
|
|
16315
|
+
})
|
|
16316
|
+
});
|
|
16317
|
+
var ManagedCredentialRefreshReceiptValidator = external_exports.object({
|
|
16318
|
+
refreshId: external_exports.string().min(1),
|
|
16319
|
+
refreshService: ManagedCredentialRefreshServiceValidator,
|
|
16320
|
+
credentialId: external_exports.string().min(1),
|
|
16321
|
+
issuerDid: external_exports.string().min(1),
|
|
16322
|
+
holderDid: external_exports.string().min(1),
|
|
16323
|
+
credentialStatus: CredentialStatusValidator.or(
|
|
16324
|
+
CredentialStatusValidator.array()
|
|
16325
|
+
).optional()
|
|
16326
|
+
}).strip();
|
|
16327
|
+
var InboxCredentialRefreshReceiptValidator = ManagedCredentialRefreshReceiptValidator.omit(
|
|
16328
|
+
{ holderDid: true }
|
|
16329
|
+
).extend({ holderDid: external_exports.string().min(1).optional() });
|
|
16330
|
+
var CredentialRefreshSigningModeValidator = external_exports.enum(["issuer-signed", "signing-authority"]);
|
|
16331
|
+
var PublishCredentialRefreshBaseFields = {
|
|
16332
|
+
refreshId: external_exports.string().min(1),
|
|
16333
|
+
notifyHolder: external_exports.boolean().optional(),
|
|
16334
|
+
updateSummary: external_exports.string().optional(),
|
|
16335
|
+
idempotencyKey: external_exports.string().optional()
|
|
16336
|
+
};
|
|
16337
|
+
var PublishIssuerSignedRefreshValidator = external_exports.object({
|
|
16338
|
+
...PublishCredentialRefreshBaseFields,
|
|
16339
|
+
mode: external_exports.literal("issuer-signed"),
|
|
16340
|
+
signedCredential: VCValidator
|
|
16341
|
+
});
|
|
16342
|
+
var PublishSigningAuthorityRefreshValidator = external_exports.object({
|
|
16343
|
+
...PublishCredentialRefreshBaseFields,
|
|
16344
|
+
mode: external_exports.literal("signing-authority"),
|
|
16345
|
+
credential: UnsignedVCValidator,
|
|
16346
|
+
signingAuthority: external_exports.object({
|
|
16347
|
+
type: external_exports.string().min(1)
|
|
16348
|
+
}).catchall(external_exports.any())
|
|
16349
|
+
});
|
|
16350
|
+
var PublishCredentialRefreshInputValidator = external_exports.object({
|
|
16351
|
+
...PublishCredentialRefreshBaseFields,
|
|
16352
|
+
mode: CredentialRefreshSigningModeValidator,
|
|
16353
|
+
signedCredential: VCValidator.optional(),
|
|
16354
|
+
credential: UnsignedVCValidator.optional(),
|
|
16355
|
+
signingAuthority: external_exports.object({
|
|
16356
|
+
type: external_exports.string().min(1)
|
|
16357
|
+
}).catchall(external_exports.any()).optional()
|
|
16358
|
+
}).superRefine((input, ctx) => {
|
|
16359
|
+
if (input.mode === "issuer-signed" && !input.signedCredential) {
|
|
16360
|
+
ctx.addIssue({
|
|
16361
|
+
code: "custom",
|
|
16362
|
+
path: ["signedCredential"],
|
|
16363
|
+
message: "signedCredential is required for issuer-signed publication"
|
|
16364
|
+
});
|
|
16365
|
+
}
|
|
16366
|
+
if (input.mode === "issuer-signed" && (input.credential !== void 0 || input.signingAuthority !== void 0)) {
|
|
16367
|
+
ctx.addIssue({
|
|
16368
|
+
code: "custom",
|
|
16369
|
+
path: ["mode"],
|
|
16370
|
+
message: "issuer-signed publication cannot include signing-authority fields"
|
|
16371
|
+
});
|
|
16372
|
+
}
|
|
16373
|
+
if (input.mode === "signing-authority") {
|
|
16374
|
+
if (input.signedCredential !== void 0) {
|
|
16375
|
+
ctx.addIssue({
|
|
16376
|
+
code: "custom",
|
|
16377
|
+
path: ["signedCredential"],
|
|
16378
|
+
message: "signing-authority publication cannot include signedCredential"
|
|
16379
|
+
});
|
|
16380
|
+
}
|
|
16381
|
+
if (!input.credential) {
|
|
16382
|
+
ctx.addIssue({
|
|
16383
|
+
code: "custom",
|
|
16384
|
+
path: ["credential"],
|
|
16385
|
+
message: "credential is required for signing-authority publication"
|
|
16386
|
+
});
|
|
16387
|
+
}
|
|
16388
|
+
if (!input.signingAuthority) {
|
|
16389
|
+
ctx.addIssue({
|
|
16390
|
+
code: "custom",
|
|
16391
|
+
path: ["signingAuthority"],
|
|
16392
|
+
message: "signingAuthority is required for signing-authority publication"
|
|
16393
|
+
});
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
});
|
|
16397
|
+
var PublishCredentialRefreshNotificationValidator = external_exports.enum([
|
|
16398
|
+
"queued",
|
|
16399
|
+
"suppressed",
|
|
16400
|
+
"not-applicable",
|
|
16401
|
+
/** Publication succeeded, but the post-commit notification enqueue must be retried. */
|
|
16402
|
+
"delivery-failed"
|
|
16403
|
+
]);
|
|
16404
|
+
var PublishCredentialRefreshResultValidator = external_exports.object({
|
|
16405
|
+
refreshId: external_exports.string().min(1),
|
|
16406
|
+
version: external_exports.number().int().positive(),
|
|
16407
|
+
publishedAt: external_exports.string().min(1),
|
|
16408
|
+
notification: PublishCredentialRefreshNotificationValidator
|
|
16409
|
+
});
|
|
16410
|
+
var CredentialRefreshVersionMetadataValidator = external_exports.object({
|
|
16411
|
+
version: external_exports.number().int().positive(),
|
|
16412
|
+
publishedAt: external_exports.string().min(1),
|
|
16413
|
+
effectiveAt: external_exports.string().optional(),
|
|
16414
|
+
etag: external_exports.string().optional(),
|
|
16415
|
+
signingMode: CredentialRefreshSigningModeValidator.optional(),
|
|
16416
|
+
updateSummary: external_exports.string().optional()
|
|
16417
|
+
});
|
|
16418
|
+
var GetCredentialRefreshHistoryInputValidator = external_exports.object({
|
|
16419
|
+
refreshId: external_exports.string().min(1),
|
|
16420
|
+
cursor: external_exports.string().optional(),
|
|
16421
|
+
limit: external_exports.number().int().positive().optional()
|
|
16422
|
+
});
|
|
16423
|
+
var GetCredentialRefreshHistoryResultValidator = external_exports.object({
|
|
16424
|
+
records: CredentialRefreshVersionMetadataValidator.array(),
|
|
16425
|
+
hasMore: external_exports.boolean(),
|
|
16426
|
+
cursor: external_exports.string().optional()
|
|
16427
|
+
});
|
|
16428
|
+
var CredentialRefreshChallengeValidator = external_exports.object({
|
|
16429
|
+
challenge: external_exports.string().min(1),
|
|
16430
|
+
expiresAt: external_exports.string().min(1),
|
|
16431
|
+
domain: external_exports.string().optional(),
|
|
16432
|
+
scheme: external_exports.literal("LearnCardDIDAuth").optional()
|
|
16433
|
+
});
|
|
16434
|
+
var PublicCredentialRefreshEnvelopeValidator = external_exports.object({
|
|
16435
|
+
format: external_exports.literal("vc"),
|
|
16436
|
+
credential: VCValidator,
|
|
16437
|
+
etag: external_exports.string().optional()
|
|
16438
|
+
});
|
|
16439
|
+
var JweCredentialRefreshEnvelopeValidator = external_exports.object({
|
|
16440
|
+
format: external_exports.literal("jwe"),
|
|
16441
|
+
jwe: JWEValidator,
|
|
16442
|
+
etag: external_exports.string().optional(),
|
|
16443
|
+
/** Present on LearnCard-managed endpoints; optional for interoperable JWE services. */
|
|
16444
|
+
version: external_exports.number().int().positive().optional()
|
|
16445
|
+
});
|
|
16446
|
+
var CredentialRefreshResponseEnvelopeValidator = external_exports.discriminatedUnion("format", [
|
|
16447
|
+
PublicCredentialRefreshEnvelopeValidator,
|
|
16448
|
+
JweCredentialRefreshEnvelopeValidator
|
|
16449
|
+
]);
|
|
16450
|
+
var CredentialRefreshFailureCodeValidator = external_exports.enum([
|
|
16451
|
+
"UNAVAILABLE",
|
|
16452
|
+
"TIMEOUT",
|
|
16453
|
+
"UNSUPPORTED_SERVICE",
|
|
16454
|
+
"UNAUTHORIZED",
|
|
16455
|
+
"MALFORMED_RESPONSE",
|
|
16456
|
+
"INVALID_PROOF",
|
|
16457
|
+
"ISSUER_MISMATCH",
|
|
16458
|
+
"ID_MISMATCH",
|
|
16459
|
+
"ROLLBACK",
|
|
16460
|
+
"REVOKED",
|
|
16461
|
+
"UNSAFE_ENDPOINT"
|
|
16462
|
+
]);
|
|
16463
|
+
var CredentialRefreshUpdatedResultValidator = external_exports.object({
|
|
16464
|
+
status: external_exports.literal("updated"),
|
|
16465
|
+
credential: VCValidator,
|
|
16466
|
+
etag: external_exports.string().optional(),
|
|
16467
|
+
managedVersion: external_exports.number().int().positive().optional()
|
|
16468
|
+
});
|
|
16469
|
+
var CredentialRefreshUnchangedResultValidator = external_exports.object({
|
|
16470
|
+
status: external_exports.literal("unchanged"),
|
|
16471
|
+
checkedAt: external_exports.string().min(1),
|
|
16472
|
+
etag: external_exports.string().optional()
|
|
16473
|
+
});
|
|
16474
|
+
var CredentialRefreshUnsupportedResultValidator = external_exports.object({
|
|
16475
|
+
status: external_exports.literal("unsupported")
|
|
16476
|
+
});
|
|
16477
|
+
var CredentialRefreshFailedResultValidator = external_exports.object({
|
|
16478
|
+
status: external_exports.literal("failed"),
|
|
16479
|
+
code: CredentialRefreshFailureCodeValidator,
|
|
16480
|
+
retryable: external_exports.boolean()
|
|
16481
|
+
});
|
|
16482
|
+
var CredentialRefreshResultValidator = external_exports.discriminatedUnion("status", [
|
|
16483
|
+
CredentialRefreshUpdatedResultValidator,
|
|
16484
|
+
CredentialRefreshUnchangedResultValidator,
|
|
16485
|
+
CredentialRefreshUnsupportedResultValidator,
|
|
16486
|
+
CredentialRefreshFailedResultValidator
|
|
16487
|
+
]);
|
|
16218
16488
|
var LCNProfileDisplayValidator = external_exports.object({
|
|
16219
16489
|
backgroundColor: external_exports.string().optional(),
|
|
16220
16490
|
backgroundImage: external_exports.string().optional(),
|
|
@@ -16550,7 +16820,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16550
16820
|
"Options for email/phone recipients (Universal Inbox)"
|
|
16551
16821
|
),
|
|
16552
16822
|
templateData: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
16553
|
-
integrationId: external_exports.string().optional().describe("Integration ID for activity tracking")
|
|
16823
|
+
integrationId: external_exports.string().optional().describe("Integration ID for activity tracking"),
|
|
16824
|
+
refresh: external_exports.boolean().optional().describe(
|
|
16825
|
+
"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."
|
|
16826
|
+
),
|
|
16827
|
+
idempotencyKey: external_exports.string().min(1).max(200).optional().describe(
|
|
16828
|
+
"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."
|
|
16829
|
+
)
|
|
16554
16830
|
}).refine((data) => data.templateUri || data.template || data.signedCredential, {
|
|
16555
16831
|
message: "Either templateUri, template, or signedCredential must be provided.",
|
|
16556
16832
|
path: ["templateUri"]
|
|
@@ -16565,8 +16841,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16565
16841
|
message: "guardianEmail must differ from recipient (self-approval not allowed)",
|
|
16566
16842
|
path: ["options", "guardianEmail"]
|
|
16567
16843
|
}
|
|
16568
|
-
)
|
|
16844
|
+
).refine((data) => !data.idempotencyKey || data.refresh === true, {
|
|
16845
|
+
message: "idempotencyKey is only supported with refresh: true.",
|
|
16846
|
+
path: ["idempotencyKey"]
|
|
16847
|
+
});
|
|
16569
16848
|
var SendInboxResponseValidator = external_exports.object({
|
|
16849
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
16570
16850
|
issuanceId: external_exports.string(),
|
|
16571
16851
|
status: external_exports.enum(["PENDING", "ISSUED", "EXPIRED", "DELIVERED", "CLAIMED"]),
|
|
16572
16852
|
claimUrl: external_exports.string().url().optional().describe("Present when suppressDelivery=true"),
|
|
@@ -16581,8 +16861,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16581
16861
|
activityId: external_exports.string().describe("Links to the activity lifecycle for this issuance"),
|
|
16582
16862
|
inbox: SendInboxResponseValidator.optional().describe(
|
|
16583
16863
|
"Present when sent via email/phone (Universal Inbox)"
|
|
16864
|
+
),
|
|
16865
|
+
refresh: ManagedCredentialRefreshReceiptValidator.optional().describe(
|
|
16866
|
+
"Present when managed refresh was requested: issuance metadata the issuer keeps to publish future updates"
|
|
16584
16867
|
)
|
|
16585
16868
|
});
|
|
16869
|
+
var PrepareRefreshableSendInputValidator = external_exports.object({
|
|
16870
|
+
recipient: external_exports.string(),
|
|
16871
|
+
templateUri: external_exports.string().optional(),
|
|
16872
|
+
template: SendBoostTemplateValidator.optional(),
|
|
16873
|
+
contractUri: external_exports.string().optional(),
|
|
16874
|
+
templateData: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
16875
|
+
integrationId: external_exports.string().optional(),
|
|
16876
|
+
/** Credential ID to allocate for; generated server-side when omitted. */
|
|
16877
|
+
credentialId: external_exports.string().min(1).optional(),
|
|
16878
|
+
idempotencyKey: external_exports.string().min(1).max(200).optional()
|
|
16879
|
+
}).refine((data) => Boolean(data.templateUri) !== Boolean(data.template), {
|
|
16880
|
+
message: "Provide exactly one of templateUri or template."
|
|
16881
|
+
});
|
|
16882
|
+
var PrepareRefreshableSendResultValidator = external_exports.object({
|
|
16883
|
+
boostUri: external_exports.string(),
|
|
16884
|
+
credentialId: external_exports.string(),
|
|
16885
|
+
refreshId: external_exports.string(),
|
|
16886
|
+
refreshService: ManagedCredentialRefreshServiceValidator,
|
|
16887
|
+
/** The DID to use as credentialSubject.id (recipient DID, or the profile's did:web). */
|
|
16888
|
+
holderDid: external_exports.string(),
|
|
16889
|
+
/** Present when this idempotencyKey already completed: return it without signing. */
|
|
16890
|
+
completed: SendBoostResponseValidator.optional()
|
|
16891
|
+
});
|
|
16586
16892
|
var SendInputValidator = external_exports.discriminatedUnion("type", [SendBoostInputValidator]);
|
|
16587
16893
|
var SendResponseValidator = external_exports.discriminatedUnion("type", [SendBoostResponseValidator]);
|
|
16588
16894
|
var ConsentFlowTermsStatusValidator = external_exports.enum(["live", "stale", "withdrawn"]);
|
|
@@ -16901,6 +17207,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16901
17207
|
webhookUrl: external_exports.string().optional()
|
|
16902
17208
|
});
|
|
16903
17209
|
var AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX = "auth-grant:";
|
|
17210
|
+
var ACT_AS_HEADER = "X-LearnCard-Act-As";
|
|
16904
17211
|
var AuthGrantValidator = external_exports.object({
|
|
16905
17212
|
id: external_exports.string(),
|
|
16906
17213
|
name: external_exports.string(),
|
|
@@ -16913,6 +17220,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16913
17220
|
}, "error")
|
|
16914
17221
|
}),
|
|
16915
17222
|
scope: external_exports.string(),
|
|
17223
|
+
actAs: external_exports.string().optional(),
|
|
16916
17224
|
createdAt: external_exports.iso.datetime({ error: "createdAt must be a valid ISO 8601 datetime string" }),
|
|
16917
17225
|
expiresAt: external_exports.iso.datetime({ error: "expiresAt must be a valid ISO 8601 datetime string" }).nullish().optional()
|
|
16918
17226
|
});
|
|
@@ -16984,6 +17292,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
16984
17292
|
sessionJwt: external_exports.string()
|
|
16985
17293
|
});
|
|
16986
17294
|
var InboxCredentialValidator = external_exports.object({
|
|
17295
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
17296
|
+
refreshId: external_exports.string().optional(),
|
|
16987
17297
|
id: external_exports.string(),
|
|
16988
17298
|
credential: external_exports.string().optional(),
|
|
16989
17299
|
isSigned: external_exports.boolean(),
|
|
@@ -17038,12 +17348,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
17038
17348
|
templateUri: external_exports.string().optional().describe(
|
|
17039
17349
|
"URI of a boost template to use for issuance. The boost credential will be resolved and used. Mutually exclusive with credential field."
|
|
17040
17350
|
),
|
|
17351
|
+
refresh: external_exports.boolean().optional().describe(
|
|
17352
|
+
"Allocate managed refresh before signing. Requires unsigned content and a registered signing authority; binds the holder on claim."
|
|
17353
|
+
),
|
|
17354
|
+
idempotencyKey: external_exports.string().min(1).max(200).optional(),
|
|
17041
17355
|
// === OPTIONAL FEATURES ===
|
|
17042
17356
|
// Add major, distinct features at the top level.
|
|
17043
17357
|
//consentRequest: ConsentRequestValidator.optional(),
|
|
17044
17358
|
// === PROCESS CONFIGURATION (Optional) ===
|
|
17045
17359
|
// HOW should this issuance be handled?
|
|
17046
17360
|
configuration: external_exports.object({
|
|
17361
|
+
guardianEmail: external_exports.string().email().optional().describe(
|
|
17362
|
+
"Require approval from this guardian before the recipient can claim. Must differ from the recipient email."
|
|
17363
|
+
),
|
|
17047
17364
|
signingAuthority: IssueInboxSigningAuthorityValidator.optional().describe(
|
|
17048
17365
|
"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."
|
|
17049
17366
|
),
|
|
@@ -17097,17 +17414,130 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
17097
17414
|
}).optional().describe(
|
|
17098
17415
|
"Configuration for the credential issuance. If not provided, the default configuration will be used."
|
|
17099
17416
|
)
|
|
17417
|
+
}).refine((data) => !data.idempotencyKey || data.refresh === true, {
|
|
17418
|
+
message: "idempotencyKey requires refresh: true."
|
|
17100
17419
|
}).refine((data) => data.credential || data.templateUri, {
|
|
17101
17420
|
message: "Either credential or templateUri must be provided.",
|
|
17102
17421
|
path: ["credential"]
|
|
17103
|
-
})
|
|
17422
|
+
}).refine(
|
|
17423
|
+
(data) => !data.configuration?.guardianEmail || data.recipient.type !== "email" || data.configuration.guardianEmail.toLowerCase() !== data.recipient.value.toLowerCase(),
|
|
17424
|
+
{
|
|
17425
|
+
message: "guardianEmail must differ from recipient (self-approval not allowed)",
|
|
17426
|
+
path: ["configuration", "guardianEmail"]
|
|
17427
|
+
}
|
|
17428
|
+
);
|
|
17104
17429
|
var IssueInboxCredentialResponseValidator = external_exports.object({
|
|
17430
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
17105
17431
|
issuanceId: external_exports.string(),
|
|
17106
17432
|
status: LCNInboxStatusEnumValidator,
|
|
17107
17433
|
recipient: ContactMethodQueryValidator,
|
|
17108
17434
|
claimUrl: external_exports.string().url().optional(),
|
|
17109
17435
|
recipientDid: external_exports.string().optional()
|
|
17110
17436
|
});
|
|
17437
|
+
var InboxBatchConfigurationValidator = IssueInboxCredentialValidator.shape.configuration.unwrap().extend({
|
|
17438
|
+
refresh: IssueInboxCredentialValidator.shape.refresh.describe(
|
|
17439
|
+
"Enable managed refresh by default. An item configuration.refresh overrides this value, including false."
|
|
17440
|
+
),
|
|
17441
|
+
delivery: IssueInboxCredentialValidator.shape.configuration.unwrap().shape.delivery.unwrap().extend({ suppress: external_exports.boolean().optional() }).optional()
|
|
17442
|
+
});
|
|
17443
|
+
var InboxBatchItemConfigurationValidator = InboxBatchConfigurationValidator.extend({
|
|
17444
|
+
guardianEmail: InboxBatchConfigurationValidator.shape.guardianEmail.nullable().describe(
|
|
17445
|
+
"Require guardian approval, or set null to clear a batch-level guardianEmail for this item."
|
|
17446
|
+
)
|
|
17447
|
+
});
|
|
17448
|
+
var IssueInboxCredentialBatchItemValidator = external_exports.object({
|
|
17449
|
+
...IssueInboxCredentialValidator.shape,
|
|
17450
|
+
configuration: InboxBatchItemConfigurationValidator.optional(),
|
|
17451
|
+
idempotencyKey: external_exports.string().max(256).optional()
|
|
17452
|
+
}).refine((data) => data.credential || data.templateUri, {
|
|
17453
|
+
message: "Either credential or templateUri must be provided.",
|
|
17454
|
+
path: ["credential"]
|
|
17455
|
+
}).describe(
|
|
17456
|
+
"One issuance: provide credential or templateUri. Invalid input is rejected at submission with its item index."
|
|
17457
|
+
);
|
|
17458
|
+
var IssueInboxCredentialBatchValidator = external_exports.object({
|
|
17459
|
+
requestId: external_exports.string().min(1).max(256).optional(),
|
|
17460
|
+
items: external_exports.array(IssueInboxCredentialBatchItemValidator).min(1).max(100),
|
|
17461
|
+
configuration: InboxBatchConfigurationValidator.optional()
|
|
17462
|
+
}).superRefine((batch, ctx) => {
|
|
17463
|
+
batch.items.forEach((item, index) => {
|
|
17464
|
+
const itemGuardian = item.configuration?.guardianEmail;
|
|
17465
|
+
const guardian = itemGuardian === null ? void 0 : itemGuardian ?? batch.configuration?.guardianEmail;
|
|
17466
|
+
if (guardian && item.recipient.type === "email" && guardian.toLowerCase() === item.recipient.value.toLowerCase()) {
|
|
17467
|
+
ctx.addIssue({
|
|
17468
|
+
code: "custom",
|
|
17469
|
+
path: ["items", index, "configuration", "guardianEmail"],
|
|
17470
|
+
message: "guardianEmail must differ from recipient (self-approval not allowed)"
|
|
17471
|
+
});
|
|
17472
|
+
}
|
|
17473
|
+
});
|
|
17474
|
+
});
|
|
17475
|
+
var InboxBatchErrorReasonValidator = external_exports.enum([
|
|
17476
|
+
"DUPLICATE_KEY",
|
|
17477
|
+
"IDEMPOTENCY_MISMATCH",
|
|
17478
|
+
"IN_PROGRESS",
|
|
17479
|
+
"UNCONFIRMED"
|
|
17480
|
+
]);
|
|
17481
|
+
var IssueInboxCredentialBatchItemResultValidator = external_exports.discriminatedUnion("success", [
|
|
17482
|
+
IssueInboxCredentialResponseValidator.extend({
|
|
17483
|
+
success: external_exports.literal(true),
|
|
17484
|
+
index: external_exports.number().int().nonnegative(),
|
|
17485
|
+
deduplicated: external_exports.boolean().optional(),
|
|
17486
|
+
guardianStatus: GuardianStatusValidator.optional(),
|
|
17487
|
+
idempotencyKey: external_exports.string().optional()
|
|
17488
|
+
}),
|
|
17489
|
+
external_exports.object({
|
|
17490
|
+
success: external_exports.literal(false),
|
|
17491
|
+
index: external_exports.number().int().nonnegative(),
|
|
17492
|
+
idempotencyKey: external_exports.string().optional(),
|
|
17493
|
+
recipient: ContactMethodQueryValidator.optional(),
|
|
17494
|
+
error: external_exports.object({
|
|
17495
|
+
code: external_exports.string(),
|
|
17496
|
+
message: external_exports.string(),
|
|
17497
|
+
reason: InboxBatchErrorReasonValidator.optional()
|
|
17498
|
+
}),
|
|
17499
|
+
issuanceId: external_exports.string().optional().describe(
|
|
17500
|
+
"Present when issuance completed but replay storage could not be confirmed. Reconcile this issuance; do not issue again with a new key."
|
|
17501
|
+
),
|
|
17502
|
+
claimUrl: external_exports.string().url().optional().describe(
|
|
17503
|
+
"Claim URL of the completed issuance, if available, when replay storage could not be confirmed."
|
|
17504
|
+
)
|
|
17505
|
+
})
|
|
17506
|
+
]);
|
|
17507
|
+
var IssueInboxCredentialBatchResponseValidator = external_exports.object({
|
|
17508
|
+
results: external_exports.array(IssueInboxCredentialBatchItemResultValidator),
|
|
17509
|
+
summary: external_exports.object({
|
|
17510
|
+
total: external_exports.number(),
|
|
17511
|
+
succeeded: external_exports.number(),
|
|
17512
|
+
failed: external_exports.number(),
|
|
17513
|
+
deduplicated: external_exports.number()
|
|
17514
|
+
})
|
|
17515
|
+
});
|
|
17516
|
+
var InboxBatchReceiptValidator = external_exports.object({
|
|
17517
|
+
batchId: external_exports.string(),
|
|
17518
|
+
status: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
|
|
17519
|
+
createdAt: external_exports.string()
|
|
17520
|
+
});
|
|
17521
|
+
var InboxBatchStatusValidator = external_exports.object({
|
|
17522
|
+
batchId: external_exports.string(),
|
|
17523
|
+
createdAt: external_exports.string(),
|
|
17524
|
+
done: external_exports.boolean().describe(
|
|
17525
|
+
"True when no queued or processing items remain, including unconfirmed outcomes."
|
|
17526
|
+
),
|
|
17527
|
+
status: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
|
|
17528
|
+
items: external_exports.array(
|
|
17529
|
+
external_exports.object({
|
|
17530
|
+
index: external_exports.number().int().nonnegative(),
|
|
17531
|
+
state: external_exports.enum(["QUEUED", "PROCESSING", "COMPLETED", "NEEDS_RECONCILIATION"]),
|
|
17532
|
+
result: IssueInboxCredentialBatchItemResultValidator.optional()
|
|
17533
|
+
})
|
|
17534
|
+
),
|
|
17535
|
+
summary: IssueInboxCredentialBatchResponseValidator.shape.summary.extend({
|
|
17536
|
+
completed: external_exports.number(),
|
|
17537
|
+
pending: external_exports.number(),
|
|
17538
|
+
unconfirmed: external_exports.number()
|
|
17539
|
+
})
|
|
17540
|
+
});
|
|
17111
17541
|
var CredentialNameRefValidator = external_exports.object({ name: external_exports.string() }).passthrough();
|
|
17112
17542
|
var ClaimInboxCredentialValidator = external_exports.object({
|
|
17113
17543
|
credential: VCValidator.or(VPValidator).or(UnsignedVCValidator).or(CredentialNameRefValidator).describe("The credential to issue, or a { name } reference to resolve a boost template."),
|
|
@@ -17593,6 +18023,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
17593
18023
|
eventType: CredentialActivityEventTypeValidator,
|
|
17594
18024
|
timestamp: external_exports.string(),
|
|
17595
18025
|
actorProfileId: external_exports.string().optional(),
|
|
18026
|
+
onBehalfOf: external_exports.string().optional(),
|
|
17596
18027
|
recipientType: CredentialActivityRecipientTypeValidator,
|
|
17597
18028
|
recipientIdentifier: external_exports.string(),
|
|
17598
18029
|
boostUri: external_exports.string().optional(),
|
|
@@ -17639,6 +18070,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
17639
18070
|
__name2(this, "AuthSessionError");
|
|
17640
18071
|
}
|
|
17641
18072
|
};
|
|
18073
|
+
var ESCROW_PIN_LOCKED_MESSAGE2 = "Too many incorrect PIN attempts. You can still recover by waiting.";
|
|
18074
|
+
var ESCROW_PIN_UNAVAILABLE_MESSAGE2 = "PIN recovery is not available for this account.";
|
|
18075
|
+
var ESCROW_PIN_MISMATCH_PATTERN2 = /^Incorrect PIN\. (\d+) attempts left\.$/;
|
|
18076
|
+
var escrowPinMismatchMessage = /* @__PURE__ */ __name2((attemptsRemaining) => `Incorrect PIN. ${attemptsRemaining} attempts left.`, "escrowPinMismatchMessage");
|
|
18077
|
+
var EscrowPinStatusValidator = external_exports.object({
|
|
18078
|
+
state: external_exports.enum(["none", "enabled", "locked", "stale"]),
|
|
18079
|
+
enabled: external_exports.boolean(),
|
|
18080
|
+
attemptsRemaining: external_exports.number().int().nonnegative(),
|
|
18081
|
+
salt: external_exports.string().optional()
|
|
18082
|
+
});
|
|
17642
18083
|
var BITSTRING_STATUS_PURPOSES = ["revocation", "suspension"];
|
|
17643
18084
|
var DEFAULT_BITSTRING_STATUS_LIST_SIZE = 131072;
|
|
17644
18085
|
var BitstringStatusPurposeValidator = external_exports.enum(BITSTRING_STATUS_PURPOSES);
|
|
@@ -17753,191 +18194,458 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
17753
18194
|
const result = inAppMessagesFlagValidator.safeParse(raw);
|
|
17754
18195
|
return result.success ? result.data : EMPTY_IN_APP_MESSAGES_FLAG;
|
|
17755
18196
|
}, "parseInAppMessagesFlag");
|
|
17756
|
-
var
|
|
17757
|
-
var
|
|
17758
|
-
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
|
|
17762
|
-
var
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
var
|
|
17767
|
-
|
|
17768
|
-
|
|
17769
|
-
|
|
17770
|
-
var
|
|
17771
|
-
|
|
17772
|
-
|
|
17773
|
-
|
|
17774
|
-
|
|
17775
|
-
|
|
17776
|
-
|
|
17777
|
-
|
|
17778
|
-
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
}
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
18197
|
+
var SHARE_LINK_ID_BYTES = 16;
|
|
18198
|
+
var SHARE_CONTENT_KEY_BYTES = 32;
|
|
18199
|
+
var SHARE_IV_BYTES = 12;
|
|
18200
|
+
var SHARE_TAG_BYTES = 16;
|
|
18201
|
+
var MAX_SHARE_CIPHERTEXT_BYTES = 512 * 1024;
|
|
18202
|
+
var MAX_SHARE_RECOVERY_JWE_BYTES = 64 * 1024;
|
|
18203
|
+
var MAX_SELECTED_CREDENTIALS = 50;
|
|
18204
|
+
var MAX_ENDORSEMENTS = 200;
|
|
18205
|
+
var MAX_VP_MEMBERS = MAX_SELECTED_CREDENTIALS + MAX_ENDORSEMENTS;
|
|
18206
|
+
var MAX_SHARE_SOURCE_URI_CHARS = 2048;
|
|
18207
|
+
var MAX_SHARE_LINK_REQUEST_BYTES = 1024 * 1024;
|
|
18208
|
+
var SHARE_LINK_PROTOCOL = "lc-share/v1";
|
|
18209
|
+
var SHARE_RECOVERY_PROTOCOL = "lc-share-recovery/v1";
|
|
18210
|
+
var SHARE_AAD_PREFIX = "lc-share:v1";
|
|
18211
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
18212
|
+
var BASE64URL_LOOKUP = (() => {
|
|
18213
|
+
const table = new Int16Array(128).fill(-1);
|
|
18214
|
+
for (let index = 0; index < BASE64URL_ALPHABET.length; index += 1) {
|
|
18215
|
+
table[BASE64URL_ALPHABET.charCodeAt(index)] = index;
|
|
18216
|
+
}
|
|
18217
|
+
return table;
|
|
18218
|
+
})();
|
|
18219
|
+
var encodeBase64Url = /* @__PURE__ */ __name2((bytes) => {
|
|
18220
|
+
let output = "";
|
|
18221
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
18222
|
+
const first = bytes[index];
|
|
18223
|
+
const second = bytes[index + 1];
|
|
18224
|
+
const third = bytes[index + 2];
|
|
18225
|
+
output += BASE64URL_ALPHABET[first >> 2];
|
|
18226
|
+
output += BASE64URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
|
|
18227
|
+
if (second === void 0) break;
|
|
18228
|
+
output += BASE64URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
|
|
18229
|
+
if (third === void 0) break;
|
|
18230
|
+
output += BASE64URL_ALPHABET[third & 63];
|
|
18231
|
+
}
|
|
18232
|
+
return output;
|
|
18233
|
+
}, "encodeBase64Url");
|
|
18234
|
+
var decodeBase64Url = /* @__PURE__ */ __name2((value) => {
|
|
18235
|
+
if (typeof value !== "string") return null;
|
|
18236
|
+
const output = new Uint8Array(Math.floor(value.length * 6 / 8));
|
|
18237
|
+
let bits = 0;
|
|
18238
|
+
let bitCount = 0;
|
|
18239
|
+
let outputIndex = 0;
|
|
18240
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
18241
|
+
const code = value.charCodeAt(index);
|
|
18242
|
+
const digit = code < 128 ? BASE64URL_LOOKUP[code] : -1;
|
|
18243
|
+
if (digit < 0) return null;
|
|
18244
|
+
bits = bits << 6 | digit;
|
|
18245
|
+
bitCount += 6;
|
|
18246
|
+
if (bitCount >= 8) {
|
|
18247
|
+
bitCount -= 8;
|
|
18248
|
+
output[outputIndex] = bits >> bitCount & 255;
|
|
18249
|
+
outputIndex += 1;
|
|
18250
|
+
}
|
|
18251
|
+
}
|
|
18252
|
+
return output;
|
|
18253
|
+
}, "decodeBase64Url");
|
|
18254
|
+
var isCanonicalBase64Url = /* @__PURE__ */ __name2((value, expectedBytes) => {
|
|
18255
|
+
if (typeof value !== "string") return false;
|
|
18256
|
+
if (expectedBytes !== void 0 && value.length !== Math.ceil(expectedBytes * 4 / 3))
|
|
18257
|
+
return false;
|
|
18258
|
+
const decoded = decodeBase64Url(value);
|
|
18259
|
+
if (decoded === null) return false;
|
|
18260
|
+
if (expectedBytes !== void 0 && decoded.length !== expectedBytes) return false;
|
|
18261
|
+
return encodeBase64Url(decoded) === value;
|
|
18262
|
+
}, "isCanonicalBase64Url");
|
|
18263
|
+
var decodedBase64UrlByteLength = /* @__PURE__ */ __name2((value) => decodeBase64Url(value)?.length ?? null, "decodedBase64UrlByteLength");
|
|
18264
|
+
var utf8ByteLength = /* @__PURE__ */ __name2((value) => new TextEncoder().encode(value).length, "utf8ByteLength");
|
|
18265
|
+
var canonicalBase64Url = /* @__PURE__ */ __name2((bytes) => external_exports.string().refine((value) => isCanonicalBase64Url(value, bytes), {
|
|
18266
|
+
message: `must be canonical base64url for exactly ${bytes} bytes`
|
|
18267
|
+
}), "canonicalBase64Url");
|
|
18268
|
+
var ShareLinkIdValidator = canonicalBase64Url(SHARE_LINK_ID_BYTES);
|
|
18269
|
+
var ShareContentKeyValidator = canonicalBase64Url(SHARE_CONTENT_KEY_BYTES);
|
|
18270
|
+
var ShareIvValidator = canonicalBase64Url(SHARE_IV_BYTES);
|
|
18271
|
+
var ShareCiphertextValidator = external_exports.string().refine(
|
|
18272
|
+
(value) => {
|
|
18273
|
+
if (value.length > Math.ceil(MAX_SHARE_CIPHERTEXT_BYTES * 4 / 3)) return false;
|
|
18274
|
+
const decoded = decodeBase64Url(value);
|
|
18275
|
+
if (decoded === null) return false;
|
|
18276
|
+
if (encodeBase64Url(decoded) !== value) return false;
|
|
18277
|
+
return decoded.length >= SHARE_TAG_BYTES && decoded.length <= MAX_SHARE_CIPHERTEXT_BYTES;
|
|
18278
|
+
},
|
|
18279
|
+
{
|
|
18280
|
+
message: `must be canonical base64url decoding to ${SHARE_TAG_BYTES}..${MAX_SHARE_CIPHERTEXT_BYTES} bytes`
|
|
17818
18281
|
}
|
|
17819
|
-
|
|
18282
|
+
);
|
|
18283
|
+
var ShareEnvelopeValidator = external_exports.object({
|
|
18284
|
+
v: external_exports.literal(1),
|
|
18285
|
+
alg: external_exports.literal("A256GCM"),
|
|
18286
|
+
iv: ShareIvValidator,
|
|
18287
|
+
ct: ShareCiphertextValidator
|
|
18288
|
+
}).strict();
|
|
18289
|
+
var safeVersion = external_exports.number().int().min(1).max(2 ** 31 - 1);
|
|
18290
|
+
var safeCredentialIndex = external_exports.number().int().min(0).max(MAX_VP_MEMBERS - 1);
|
|
18291
|
+
var ShareSelectionEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex }).strict();
|
|
18292
|
+
var ShareEndorsementEntryValidator = external_exports.object({ credentialIndex: safeCredentialIndex, targetCredentialIndex: safeCredentialIndex }).strict();
|
|
18293
|
+
var ShareSharerValidator = external_exports.object({
|
|
18294
|
+
profileId: external_exports.string().min(1).max(128),
|
|
18295
|
+
displayName: external_exports.string().min(1).max(120),
|
|
18296
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18297
|
+
}).strict();
|
|
18298
|
+
var presentationShape = VPValidator.extend({
|
|
18299
|
+
verifiableCredential: VCValidator.array().min(1).max(MAX_VP_MEMBERS)
|
|
18300
|
+
});
|
|
18301
|
+
var hasBoundedPresentation = /* @__PURE__ */ __name2((value) => {
|
|
18302
|
+
let credentialCount = 0;
|
|
18303
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
18304
|
+
const visit = /* @__PURE__ */ __name2((node, depth) => {
|
|
18305
|
+
if (node === null || typeof node !== "object") return true;
|
|
18306
|
+
if (depth > 32 || ancestors.has(node)) return false;
|
|
18307
|
+
ancestors.add(node);
|
|
18308
|
+
try {
|
|
18309
|
+
for (const [key, child] of Object.entries(node)) {
|
|
18310
|
+
if (key === "verifiableCredential") {
|
|
18311
|
+
credentialCount += Array.isArray(child) ? child.length : 1;
|
|
18312
|
+
if (credentialCount > MAX_VP_MEMBERS) return false;
|
|
18313
|
+
}
|
|
18314
|
+
if (!visit(child, depth + 1)) return false;
|
|
18315
|
+
}
|
|
18316
|
+
return true;
|
|
18317
|
+
} finally {
|
|
18318
|
+
ancestors.delete(node);
|
|
18319
|
+
}
|
|
18320
|
+
}, "visit");
|
|
18321
|
+
return visit(value, 0);
|
|
18322
|
+
}, "hasBoundedPresentation");
|
|
18323
|
+
var ShareManifestPresentationValidator = external_exports.custom(
|
|
18324
|
+
(value) => hasBoundedPresentation(value) && presentationShape.safeParse(value).success,
|
|
18325
|
+
{ message: "invalid share presentation" }
|
|
18326
|
+
);
|
|
18327
|
+
var SharePayloadValidatorShape = external_exports.object({
|
|
18328
|
+
protocol: external_exports.literal(SHARE_LINK_PROTOCOL),
|
|
18329
|
+
shareId: ShareLinkIdValidator,
|
|
18330
|
+
contentVersion: safeVersion,
|
|
18331
|
+
createdAt: external_exports.iso.datetime(),
|
|
18332
|
+
sharer: ShareSharerValidator,
|
|
18333
|
+
presentation: ShareManifestPresentationValidator,
|
|
18334
|
+
selection: ShareSelectionEntryValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18335
|
+
endorsements: ShareEndorsementEntryValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18336
|
+
}).strict();
|
|
18337
|
+
var classificationFailure = /* @__PURE__ */ __name2((code, message, path = []) => ({ ok: false, code, message, path }), "classificationFailure");
|
|
18338
|
+
var readSignedClaimId = /* @__PURE__ */ __name2((member) => {
|
|
18339
|
+
if (member === null || typeof member !== "object") return void 0;
|
|
18340
|
+
const record2 = member;
|
|
18341
|
+
const claim = record2.credentialSubject ?? record2.endorsement;
|
|
18342
|
+
if (claim === null || typeof claim !== "object") return void 0;
|
|
18343
|
+
const claimRecord = claim;
|
|
18344
|
+
const nested = claimRecord.endorsement;
|
|
18345
|
+
const nestedId = nested !== null && typeof nested === "object" ? nested.id : void 0;
|
|
18346
|
+
return claimRecord.id ?? nestedId;
|
|
18347
|
+
}, "readSignedClaimId");
|
|
18348
|
+
var classifyShareManifest = /* @__PURE__ */ __name2((payload) => {
|
|
18349
|
+
const members = payload.presentation?.verifiableCredential;
|
|
18350
|
+
if (!Array.isArray(members) || members.length === 0) {
|
|
18351
|
+
return classificationFailure("NO_PRESENTATION_MEMBERS", "presentation has no credentials");
|
|
18352
|
+
}
|
|
18353
|
+
if (members.length > MAX_VP_MEMBERS) {
|
|
18354
|
+
return classificationFailure(
|
|
18355
|
+
"TOO_MANY_VP_MEMBERS",
|
|
18356
|
+
`presentation has ${members.length} members, over the ${MAX_VP_MEMBERS} bound`
|
|
18357
|
+
);
|
|
18358
|
+
}
|
|
18359
|
+
const selection = payload.selection;
|
|
18360
|
+
const endorsements = payload.endorsements ?? [];
|
|
18361
|
+
if (!Array.isArray(selection) || selection.length < 1 || selection.length > MAX_SELECTED_CREDENTIALS) {
|
|
18362
|
+
return classificationFailure("BAD_SELECTION", "selection must contain 1..50 entries");
|
|
18363
|
+
}
|
|
18364
|
+
if (!Array.isArray(endorsements) || endorsements.length > MAX_ENDORSEMENTS) {
|
|
18365
|
+
return classificationFailure(
|
|
18366
|
+
"BAD_ENDORSEMENTS",
|
|
18367
|
+
"endorsements must contain at most 200 entries"
|
|
18368
|
+
);
|
|
18369
|
+
}
|
|
18370
|
+
const selectedIndices = [];
|
|
18371
|
+
const selected = /* @__PURE__ */ new Set();
|
|
18372
|
+
for (let index = 0; index < selection.length; index += 1) {
|
|
18373
|
+
const entry = selection[index];
|
|
18374
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18375
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18376
|
+
return classificationFailure(
|
|
18377
|
+
"SELECTION_INDEX_OUT_OF_BOUNDS",
|
|
18378
|
+
`selection[${index}].credentialIndex is out of bounds`,
|
|
18379
|
+
["selection", index, "credentialIndex"]
|
|
18380
|
+
);
|
|
18381
|
+
}
|
|
18382
|
+
if (selected.has(credentialIndex)) {
|
|
18383
|
+
return classificationFailure(
|
|
18384
|
+
"SELECTION_INDEX_DUPLICATE",
|
|
18385
|
+
`selection[${index}].credentialIndex is duplicated`,
|
|
18386
|
+
["selection", index, "credentialIndex"]
|
|
18387
|
+
);
|
|
18388
|
+
}
|
|
18389
|
+
selected.add(credentialIndex);
|
|
18390
|
+
selectedIndices.push(credentialIndex);
|
|
18391
|
+
}
|
|
18392
|
+
const classified = new Set(selected);
|
|
18393
|
+
const endorsementIndices = [];
|
|
18394
|
+
for (let index = 0; index < endorsements.length; index += 1) {
|
|
18395
|
+
const entry = endorsements[index];
|
|
18396
|
+
const credentialIndex = entry?.credentialIndex;
|
|
18397
|
+
const targetIndex = entry?.targetCredentialIndex;
|
|
18398
|
+
if (!Number.isSafeInteger(credentialIndex) || credentialIndex < 0 || credentialIndex >= members.length) {
|
|
18399
|
+
return classificationFailure(
|
|
18400
|
+
"ENDORSEMENT_INDEX_OUT_OF_BOUNDS",
|
|
18401
|
+
`endorsements[${index}].credentialIndex is out of bounds`,
|
|
18402
|
+
["endorsements", index, "credentialIndex"]
|
|
18403
|
+
);
|
|
18404
|
+
}
|
|
18405
|
+
if (classified.has(credentialIndex)) {
|
|
18406
|
+
return classificationFailure(
|
|
18407
|
+
"VP_MEMBER_CLASSIFIED_TWICE",
|
|
18408
|
+
`endorsements[${index}].credentialIndex was already classified`,
|
|
18409
|
+
["endorsements", index, "credentialIndex"]
|
|
18410
|
+
);
|
|
18411
|
+
}
|
|
18412
|
+
if (!selected.has(targetIndex)) {
|
|
18413
|
+
return classificationFailure(
|
|
18414
|
+
"ENDORSEMENT_TARGET_NOT_SELECTED",
|
|
18415
|
+
`endorsements[${index}].targetCredentialIndex must reference a selected credential`,
|
|
18416
|
+
["endorsements", index, "targetCredentialIndex"]
|
|
18417
|
+
);
|
|
18418
|
+
}
|
|
18419
|
+
const referenced = readSignedClaimId(members[credentialIndex]);
|
|
18420
|
+
const target = members[targetIndex];
|
|
18421
|
+
const targetId = target !== null && typeof target === "object" ? target.id : void 0;
|
|
18422
|
+
if (typeof referenced !== "string" || typeof targetId !== "string" || referenced !== targetId) {
|
|
18423
|
+
return classificationFailure(
|
|
18424
|
+
"ENDORSEMENT_TARGET_UNBOUND",
|
|
18425
|
+
`endorsements[${index}] claim does not reference the target credential id`,
|
|
18426
|
+
["endorsements", index]
|
|
18427
|
+
);
|
|
18428
|
+
}
|
|
18429
|
+
classified.add(credentialIndex);
|
|
18430
|
+
endorsementIndices.push(credentialIndex);
|
|
18431
|
+
}
|
|
18432
|
+
if (classified.size !== members.length) {
|
|
18433
|
+
return classificationFailure(
|
|
18434
|
+
"UNCLASSIFIED_VP_MEMBER",
|
|
18435
|
+
"every presentation member must be classified exactly once",
|
|
18436
|
+
["presentation", "verifiableCredential"]
|
|
18437
|
+
);
|
|
18438
|
+
}
|
|
18439
|
+
return {
|
|
18440
|
+
ok: true,
|
|
18441
|
+
classification: { selectedIndices, endorsementIndices, memberCount: members.length }
|
|
18442
|
+
};
|
|
18443
|
+
}, "classifyShareManifest");
|
|
18444
|
+
var SharePayloadValidator = SharePayloadValidatorShape.superRefine((payload, ctx) => {
|
|
18445
|
+
const result = classifyShareManifest(payload);
|
|
18446
|
+
if (!result.ok) {
|
|
17820
18447
|
ctx.addIssue({
|
|
17821
18448
|
code: "custom",
|
|
17822
|
-
|
|
17823
|
-
|
|
18449
|
+
message: result.message,
|
|
18450
|
+
path: result.path,
|
|
18451
|
+
params: { shareLinkCode: result.code }
|
|
17824
18452
|
});
|
|
17825
18453
|
}
|
|
17826
|
-
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
|
|
18454
|
+
});
|
|
18455
|
+
var ShareRecoverySelectionValidator = external_exports.object({
|
|
18456
|
+
ref: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS),
|
|
18457
|
+
order: external_exports.number().int().min(0).max(MAX_SELECTED_CREDENTIALS - 1)
|
|
18458
|
+
}).strict();
|
|
18459
|
+
var ShareRecoveryEndorsementValidator = external_exports.object({ targetRef: external_exports.string().min(1).max(MAX_SHARE_SOURCE_URI_CHARS) }).strict();
|
|
18460
|
+
var ShareRecoveryPlaintextValidator = external_exports.object({
|
|
18461
|
+
protocol: external_exports.literal(SHARE_RECOVERY_PROTOCOL),
|
|
18462
|
+
shareId: ShareLinkIdValidator,
|
|
18463
|
+
ownerProfileId: external_exports.string().min(1).max(128),
|
|
18464
|
+
createdAt: external_exports.iso.datetime(),
|
|
18465
|
+
latest: external_exports.object({ contentVersion: safeVersion, key: ShareContentKeyValidator }).strict(),
|
|
18466
|
+
selection: ShareRecoverySelectionValidator.array().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18467
|
+
endorsements: ShareRecoveryEndorsementValidator.array().max(MAX_ENDORSEMENTS).default([])
|
|
18468
|
+
}).strict().superRefine((recovery, ctx) => {
|
|
18469
|
+
const orders = /* @__PURE__ */ new Set();
|
|
18470
|
+
recovery.selection.forEach((entry, index) => {
|
|
18471
|
+
if (entry.order >= recovery.selection.length) {
|
|
17835
18472
|
ctx.addIssue({
|
|
17836
18473
|
code: "custom",
|
|
17837
|
-
|
|
17838
|
-
|
|
18474
|
+
message: "order must be within the selection bounds",
|
|
18475
|
+
path: ["selection", index, "order"]
|
|
17839
18476
|
});
|
|
17840
18477
|
}
|
|
17841
|
-
if (
|
|
18478
|
+
if (orders.has(entry.order)) {
|
|
17842
18479
|
ctx.addIssue({
|
|
17843
18480
|
code: "custom",
|
|
17844
|
-
|
|
17845
|
-
|
|
18481
|
+
message: "order must be unique within the selection",
|
|
18482
|
+
path: ["selection", index, "order"]
|
|
17846
18483
|
});
|
|
17847
18484
|
}
|
|
18485
|
+
orders.add(entry.order);
|
|
18486
|
+
});
|
|
18487
|
+
});
|
|
18488
|
+
var ShareOwnerRecoveryValidator = JWEValidator.refine(
|
|
18489
|
+
(value) => utf8ByteLength(JSON.stringify(value)) <= MAX_SHARE_RECOVERY_JWE_BYTES,
|
|
18490
|
+
{ message: `serialized recovery JWE must be at most ${MAX_SHARE_RECOVERY_JWE_BYTES} bytes` }
|
|
18491
|
+
);
|
|
18492
|
+
var ShareLinkStatusValidator = external_exports.enum(["pending", "active", "stopped"]);
|
|
18493
|
+
var ShareContentStateValidator = external_exports.enum(["staging", "finalized", "content_missing"]);
|
|
18494
|
+
var ShareLinkValidator = external_exports.object({
|
|
18495
|
+
id: ShareLinkIdValidator,
|
|
18496
|
+
title: external_exports.string().min(1).max(120),
|
|
18497
|
+
note: external_exports.string().max(500).optional(),
|
|
18498
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18499
|
+
version: safeVersion,
|
|
18500
|
+
contentVersion: safeVersion,
|
|
18501
|
+
status: ShareLinkStatusValidator,
|
|
18502
|
+
contentState: ShareContentStateValidator,
|
|
18503
|
+
createdAt: external_exports.iso.datetime(),
|
|
18504
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18505
|
+
expiresAt: external_exports.iso.datetime().nullable(),
|
|
18506
|
+
stoppedAt: external_exports.iso.datetime().nullable(),
|
|
18507
|
+
lastViewedAt: external_exports.iso.datetime().nullable(),
|
|
18508
|
+
viewCount: external_exports.number().int().min(0).optional(),
|
|
18509
|
+
passcodeProtected: external_exports.boolean(),
|
|
18510
|
+
notifyOnView: external_exports.boolean(),
|
|
18511
|
+
minorPolicy: external_exports.object({
|
|
18512
|
+
isMinor: external_exports.boolean().nullable(),
|
|
18513
|
+
policyResolved: external_exports.boolean(),
|
|
18514
|
+
defaultExpiryDays: external_exports.union([external_exports.literal(30), external_exports.literal(365)]),
|
|
18515
|
+
viewCountingEnabled: external_exports.boolean()
|
|
18516
|
+
}),
|
|
18517
|
+
/**
|
|
18518
|
+
* Guarded application/API URL for the content. Optional because owner APIs in
|
|
18519
|
+
* D1 have no public resolve/content endpoint yet; it must never be a
|
|
18520
|
+
* LearnCloud object URL or carry a fabricated decryption key.
|
|
18521
|
+
*/
|
|
18522
|
+
contentUrl: external_exports.string().optional()
|
|
18523
|
+
});
|
|
18524
|
+
var clientRequestId = external_exports.string().uuid();
|
|
18525
|
+
var CreateShareLinkInputValidator = external_exports.object({
|
|
18526
|
+
id: ShareLinkIdValidator,
|
|
18527
|
+
clientRequestId,
|
|
18528
|
+
title: external_exports.string().min(1).max(120),
|
|
18529
|
+
note: external_exports.string().max(500).optional(),
|
|
18530
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18531
|
+
passcode: external_exports.string().min(8).max(64).optional(),
|
|
18532
|
+
notifyOnView: external_exports.boolean().default(false),
|
|
18533
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18534
|
+
contentVersion: external_exports.literal(1),
|
|
18535
|
+
envelope: ShareEnvelopeValidator,
|
|
18536
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator
|
|
18537
|
+
}).strict();
|
|
18538
|
+
var UpdateShareLinkInputValidator = external_exports.object({
|
|
18539
|
+
id: ShareLinkIdValidator,
|
|
18540
|
+
expectedVersion: safeVersion,
|
|
18541
|
+
clientRequestId,
|
|
18542
|
+
title: external_exports.string().min(1).max(120).optional(),
|
|
18543
|
+
note: external_exports.string().max(500).nullable().optional(),
|
|
18544
|
+
expiresAt: external_exports.iso.datetime().nullable().optional(),
|
|
18545
|
+
/** Omitted preserves the current passcode, null removes it, and a string replaces it. */
|
|
18546
|
+
passcode: external_exports.string().min(8).max(64).nullable().optional(),
|
|
18547
|
+
notifyOnView: external_exports.boolean().optional(),
|
|
18548
|
+
contentVersion: safeVersion.optional(),
|
|
18549
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS).optional(),
|
|
18550
|
+
envelope: ShareEnvelopeValidator.optional(),
|
|
18551
|
+
ownerEncryptedRecovery: ShareOwnerRecoveryValidator.optional()
|
|
18552
|
+
}).strict().superRefine((value, ctx) => {
|
|
18553
|
+
const provided = [
|
|
18554
|
+
value.contentVersion,
|
|
18555
|
+
value.selectedCount,
|
|
18556
|
+
value.envelope,
|
|
18557
|
+
value.ownerEncryptedRecovery
|
|
18558
|
+
].filter((field) => field !== void 0).length;
|
|
18559
|
+
if (provided !== 0 && provided !== 4) {
|
|
18560
|
+
ctx.addIssue({
|
|
18561
|
+
code: "custom",
|
|
18562
|
+
message: "content replacement requires contentVersion, selectedCount, envelope and ownerEncryptedRecovery together"
|
|
18563
|
+
});
|
|
17848
18564
|
}
|
|
17849
18565
|
});
|
|
17850
|
-
var
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
"
|
|
17854
|
-
|
|
17855
|
-
|
|
18566
|
+
var ShareLinkIdInputValidator = external_exports.object({ id: ShareLinkIdValidator }).strict();
|
|
18567
|
+
var ShareLinkOperationKeyInputValidator = external_exports.object({ id: ShareLinkIdValidator, operationId: external_exports.string().uuid() }).strict();
|
|
18568
|
+
var ShareLinkOwnerCommitOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18569
|
+
external_exports.object({ status: external_exports.literal("completed"), share: ShareLinkValidator }).strict(),
|
|
18570
|
+
external_exports.object({
|
|
18571
|
+
status: external_exports.literal("pending"),
|
|
18572
|
+
id: ShareLinkIdValidator,
|
|
18573
|
+
operationId: external_exports.string().uuid()
|
|
18574
|
+
}).strict()
|
|
17856
18575
|
]);
|
|
17857
|
-
var
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
publishedAt: external_exports.string().min(1),
|
|
17866
|
-
effectiveAt: external_exports.string().optional(),
|
|
17867
|
-
etag: external_exports.string().optional(),
|
|
17868
|
-
signingMode: CredentialRefreshSigningModeValidator.optional(),
|
|
17869
|
-
updateSummary: external_exports.string().optional()
|
|
17870
|
-
});
|
|
17871
|
-
var GetCredentialRefreshHistoryInputValidator = external_exports.object({
|
|
17872
|
-
refreshId: external_exports.string().min(1),
|
|
17873
|
-
cursor: external_exports.string().optional(),
|
|
17874
|
-
limit: external_exports.number().int().positive().optional()
|
|
17875
|
-
});
|
|
17876
|
-
var GetCredentialRefreshHistoryResultValidator = external_exports.object({
|
|
17877
|
-
records: CredentialRefreshVersionMetadataValidator.array(),
|
|
17878
|
-
hasMore: external_exports.boolean(),
|
|
17879
|
-
cursor: external_exports.string().optional()
|
|
17880
|
-
});
|
|
17881
|
-
var CredentialRefreshChallengeValidator = external_exports.object({
|
|
17882
|
-
challenge: external_exports.string().min(1),
|
|
17883
|
-
expiresAt: external_exports.string().min(1),
|
|
17884
|
-
domain: external_exports.string().optional(),
|
|
17885
|
-
scheme: external_exports.literal("LearnCardDIDAuth").optional()
|
|
17886
|
-
});
|
|
17887
|
-
var PublicCredentialRefreshEnvelopeValidator = external_exports.object({
|
|
17888
|
-
format: external_exports.literal("vc"),
|
|
17889
|
-
credential: VCValidator,
|
|
17890
|
-
etag: external_exports.string().optional()
|
|
17891
|
-
});
|
|
17892
|
-
var JweCredentialRefreshEnvelopeValidator = external_exports.object({
|
|
17893
|
-
format: external_exports.literal("jwe"),
|
|
17894
|
-
jwe: JWEValidator,
|
|
17895
|
-
etag: external_exports.string().optional(),
|
|
17896
|
-
/** Present on LearnCard-managed endpoints; optional for interoperable JWE services. */
|
|
17897
|
-
version: external_exports.number().int().positive().optional()
|
|
17898
|
-
});
|
|
17899
|
-
var CredentialRefreshResponseEnvelopeValidator = external_exports.discriminatedUnion("format", [
|
|
17900
|
-
PublicCredentialRefreshEnvelopeValidator,
|
|
17901
|
-
JweCredentialRefreshEnvelopeValidator
|
|
18576
|
+
var ShareLinkOwnerStatusOutputValidator = external_exports.discriminatedUnion("status", [
|
|
18577
|
+
external_exports.object({ status: external_exports.literal("found"), share: ShareLinkValidator }).strict(),
|
|
18578
|
+
external_exports.object({
|
|
18579
|
+
status: external_exports.literal("pending"),
|
|
18580
|
+
id: ShareLinkIdValidator,
|
|
18581
|
+
operationId: external_exports.string().uuid()
|
|
18582
|
+
}).strict(),
|
|
18583
|
+
external_exports.object({ status: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
17902
18584
|
]);
|
|
17903
|
-
var
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
18585
|
+
var ShareLinkOwnerRecoveryOutputValidator = external_exports.object({ recovery: ShareOwnerRecoveryValidator }).strict();
|
|
18586
|
+
var ShareLinkOwnerContentOutputValidator = external_exports.object({
|
|
18587
|
+
id: ShareLinkIdValidator,
|
|
18588
|
+
contentVersion: safeVersion,
|
|
18589
|
+
envelope: ShareEnvelopeValidator
|
|
18590
|
+
}).strict();
|
|
18591
|
+
var ResolveShareLinkInputValidator = external_exports.object({
|
|
18592
|
+
id: ShareLinkIdValidator,
|
|
18593
|
+
/** Sent only in a POST body; it must never be placed in a share URL. */
|
|
18594
|
+
passcode: external_exports.string().min(4).max(64).optional()
|
|
18595
|
+
}).strict();
|
|
18596
|
+
var ShareLinkPublicStateValidator = external_exports.discriminatedUnion("state", [
|
|
18597
|
+
external_exports.object({
|
|
18598
|
+
state: external_exports.literal("passcode_required"),
|
|
18599
|
+
id: ShareLinkIdValidator
|
|
18600
|
+
}).strict(),
|
|
18601
|
+
external_exports.object({ state: external_exports.literal("try_later"), id: ShareLinkIdValidator }).strict(),
|
|
18602
|
+
external_exports.object({
|
|
18603
|
+
state: external_exports.literal("active"),
|
|
18604
|
+
id: ShareLinkIdValidator,
|
|
18605
|
+
title: external_exports.string().max(120),
|
|
18606
|
+
note: external_exports.string().max(500).optional(),
|
|
18607
|
+
selectedCount: external_exports.number().int().min(1).max(MAX_SELECTED_CREDENTIALS),
|
|
18608
|
+
contentVersion: safeVersion,
|
|
18609
|
+
contentUrl: external_exports.string(),
|
|
18610
|
+
sharer: external_exports.object({
|
|
18611
|
+
displayName: external_exports.string().max(120),
|
|
18612
|
+
avatar: external_exports.string().max(2048).optional()
|
|
18613
|
+
}),
|
|
18614
|
+
createdAt: external_exports.iso.datetime(),
|
|
18615
|
+
updatedAt: external_exports.iso.datetime(),
|
|
18616
|
+
expiresAt: external_exports.iso.datetime().nullable()
|
|
18617
|
+
}).strict(),
|
|
18618
|
+
external_exports.object({
|
|
18619
|
+
state: external_exports.literal("expired"),
|
|
18620
|
+
id: ShareLinkIdValidator,
|
|
18621
|
+
expiresAt: external_exports.iso.datetime()
|
|
18622
|
+
}).strict(),
|
|
18623
|
+
external_exports.object({
|
|
18624
|
+
state: external_exports.literal("stopped"),
|
|
18625
|
+
id: ShareLinkIdValidator,
|
|
18626
|
+
stoppedAt: external_exports.iso.datetime()
|
|
18627
|
+
}).strict(),
|
|
18628
|
+
external_exports.object({ state: external_exports.literal("not_found"), id: ShareLinkIdValidator }).strict()
|
|
17915
18629
|
]);
|
|
17916
|
-
var
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
var CredentialRefreshUnchangedResultValidator = external_exports.object({
|
|
17923
|
-
status: external_exports.literal("unchanged"),
|
|
17924
|
-
checkedAt: external_exports.string().min(1),
|
|
17925
|
-
etag: external_exports.string().optional()
|
|
17926
|
-
});
|
|
17927
|
-
var CredentialRefreshUnsupportedResultValidator = external_exports.object({
|
|
17928
|
-
status: external_exports.literal("unsupported")
|
|
17929
|
-
});
|
|
17930
|
-
var CredentialRefreshFailedResultValidator = external_exports.object({
|
|
17931
|
-
status: external_exports.literal("failed"),
|
|
17932
|
-
code: CredentialRefreshFailureCodeValidator,
|
|
17933
|
-
retryable: external_exports.boolean()
|
|
18630
|
+
var ListShareLinksInputValidator = external_exports.object({
|
|
18631
|
+
limit: external_exports.number().int().min(1).max(50).default(25),
|
|
18632
|
+
cursor: external_exports.string().max(512).optional()
|
|
18633
|
+
}).strict();
|
|
18634
|
+
var PaginatedShareLinksValidator = PaginationResponseValidator.extend({
|
|
18635
|
+
records: ShareLinkValidator.array()
|
|
17934
18636
|
});
|
|
17935
|
-
var
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
18637
|
+
var AcknowledgeViewInputValidator = external_exports.object({
|
|
18638
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18639
|
+
}).strict();
|
|
18640
|
+
var AcknowledgeViewOutputValidator = external_exports.object({ ok: external_exports.literal(true) }).strict();
|
|
18641
|
+
var SHARE_VIEW_RECEIPT_TTL_SECONDS = 10 * 60;
|
|
18642
|
+
var ShareLinkPublicContentViewValidator = external_exports.object({
|
|
18643
|
+
id: ShareLinkIdValidator,
|
|
18644
|
+
contentVersion: safeVersion,
|
|
18645
|
+
envelope: ShareEnvelopeValidator,
|
|
18646
|
+
contentUrl: external_exports.string(),
|
|
18647
|
+
receipt: external_exports.string().min(22).max(128).regex(/^[A-Za-z0-9_-]+$/)
|
|
18648
|
+
}).strict();
|
|
17941
18649
|
}
|
|
17942
18650
|
});
|
|
17943
18651
|
|
|
@@ -17954,6 +18662,10 @@ var require_dist = __commonJS({
|
|
|
17954
18662
|
});
|
|
17955
18663
|
|
|
17956
18664
|
// src/api-client.ts
|
|
18665
|
+
var DID_CHALLENGE_REQUIRED_ERROR = "This operation requires the DID-challenge flow; use createSSSStrategy";
|
|
18666
|
+
var throwDidChallengeRequired = /* @__PURE__ */ __name(() => {
|
|
18667
|
+
throw new Error(DID_CHALLENGE_REQUIRED_ERROR);
|
|
18668
|
+
}, "throwDidChallengeRequired");
|
|
17957
18669
|
var SSSApiClient = class {
|
|
17958
18670
|
static {
|
|
17959
18671
|
__name(this, "SSSApiClient");
|
|
@@ -17966,20 +18678,9 @@ var SSSApiClient = class {
|
|
|
17966
18678
|
const token = await this.authProvider.getIdToken();
|
|
17967
18679
|
return {
|
|
17968
18680
|
"Content-Type": "application/json",
|
|
17969
|
-
|
|
18681
|
+
"X-Auth-Token": token
|
|
17970
18682
|
};
|
|
17971
18683
|
}
|
|
17972
|
-
async getContactMethodFromUser() {
|
|
17973
|
-
const user = await this.authProvider.getCurrentUser();
|
|
17974
|
-
if (!user) return null;
|
|
17975
|
-
if (user.email) {
|
|
17976
|
-
return { type: "email", value: user.email.toLowerCase() };
|
|
17977
|
-
}
|
|
17978
|
-
if (user.phone) {
|
|
17979
|
-
return { type: "phone", value: user.phone };
|
|
17980
|
-
}
|
|
17981
|
-
return null;
|
|
17982
|
-
}
|
|
17983
18684
|
async getAuthShare() {
|
|
17984
18685
|
const headers = await this.getAuthHeaders();
|
|
17985
18686
|
const providerType = this.authProvider.getProviderType();
|
|
@@ -17996,52 +18697,24 @@ var SSSApiClient = class {
|
|
|
17996
18697
|
}
|
|
17997
18698
|
return response.json();
|
|
17998
18699
|
}
|
|
17999
|
-
|
|
18000
|
-
|
|
18001
|
-
|
|
18002
|
-
const response = await fetch(`${this.serverUrl}/keys/auth-share`, {
|
|
18003
|
-
method: "PUT",
|
|
18004
|
-
headers,
|
|
18005
|
-
body: JSON.stringify({
|
|
18006
|
-
...input,
|
|
18007
|
-
providerType
|
|
18008
|
-
})
|
|
18009
|
-
});
|
|
18010
|
-
if (!response.ok) {
|
|
18011
|
-
throw new Error(`Failed to store auth share: ${response.statusText}`);
|
|
18012
|
-
}
|
|
18700
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18701
|
+
async storeAuthShare(_input) {
|
|
18702
|
+
throwDidChallengeRequired();
|
|
18013
18703
|
}
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery`, {
|
|
18018
|
-
method: "POST",
|
|
18019
|
-
headers,
|
|
18020
|
-
body: JSON.stringify({
|
|
18021
|
-
...input,
|
|
18022
|
-
providerType
|
|
18023
|
-
})
|
|
18024
|
-
});
|
|
18025
|
-
if (!response.ok) {
|
|
18026
|
-
throw new Error(`Failed to add recovery method: ${response.statusText}`);
|
|
18027
|
-
}
|
|
18704
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18705
|
+
async addRecoveryMethod(_input) {
|
|
18706
|
+
throwDidChallengeRequired();
|
|
18028
18707
|
}
|
|
18029
18708
|
async getRecoveryShare(type, credentialId) {
|
|
18030
18709
|
const token = await this.authProvider.getIdToken();
|
|
18031
18710
|
const providerType = this.authProvider.getProviderType();
|
|
18032
|
-
const params = new URLSearchParams({
|
|
18033
|
-
type,
|
|
18034
|
-
providerType,
|
|
18035
|
-
authToken: token
|
|
18036
|
-
});
|
|
18711
|
+
const params = new URLSearchParams({ type, providerType });
|
|
18037
18712
|
if (credentialId) {
|
|
18038
18713
|
params.append("credentialId", credentialId);
|
|
18039
18714
|
}
|
|
18040
18715
|
const response = await fetch(`${this.serverUrl}/keys/recovery?${params}`, {
|
|
18041
18716
|
method: "GET",
|
|
18042
|
-
headers: {
|
|
18043
|
-
"Content-Type": "application/json"
|
|
18044
|
-
}
|
|
18717
|
+
headers: { "Content-Type": "application/json", "X-Auth-Token": token }
|
|
18045
18718
|
});
|
|
18046
18719
|
if (!response.ok) {
|
|
18047
18720
|
if (response.status === 404) {
|
|
@@ -18051,81 +18724,81 @@ var SSSApiClient = class {
|
|
|
18051
18724
|
}
|
|
18052
18725
|
return response.json();
|
|
18053
18726
|
}
|
|
18054
|
-
async
|
|
18055
|
-
const
|
|
18056
|
-
const providerType = this.authProvider.getProviderType();
|
|
18057
|
-
const response = await fetch(`${this.serverUrl}/keys/migrate`, {
|
|
18727
|
+
async startIdentityRecovery(email) {
|
|
18728
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/start`, {
|
|
18058
18729
|
method: "POST",
|
|
18059
|
-
headers,
|
|
18060
|
-
body: JSON.stringify({
|
|
18730
|
+
headers: { "Content-Type": "application/json" },
|
|
18731
|
+
body: JSON.stringify({ email })
|
|
18061
18732
|
});
|
|
18062
18733
|
if (!response.ok) {
|
|
18063
|
-
throw new Error(`Failed to
|
|
18734
|
+
throw new Error(`Failed to send recovery code: ${response.statusText}`);
|
|
18064
18735
|
}
|
|
18065
18736
|
}
|
|
18066
|
-
async
|
|
18067
|
-
const
|
|
18068
|
-
const providerType = this.authProvider.getProviderType();
|
|
18069
|
-
let email = overrideEmail;
|
|
18070
|
-
if (!email) {
|
|
18071
|
-
const user = await this.authProvider.getCurrentUser();
|
|
18072
|
-
email = user?.email;
|
|
18073
|
-
}
|
|
18074
|
-
if (!email) {
|
|
18075
|
-
console.warn("Cannot send email backup share: no email address available");
|
|
18076
|
-
return;
|
|
18077
|
-
}
|
|
18078
|
-
const response = await fetch(`${this.serverUrl}/keys/email-backup`, {
|
|
18737
|
+
async verifyIdentityRecovery(email, code) {
|
|
18738
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/verify`, {
|
|
18079
18739
|
method: "POST",
|
|
18080
|
-
headers,
|
|
18081
|
-
body: JSON.stringify({
|
|
18082
|
-
providerType,
|
|
18083
|
-
emailShare,
|
|
18084
|
-
email
|
|
18085
|
-
})
|
|
18740
|
+
headers: { "Content-Type": "application/json" },
|
|
18741
|
+
body: JSON.stringify({ email, code })
|
|
18086
18742
|
});
|
|
18087
18743
|
if (!response.ok) {
|
|
18088
|
-
|
|
18744
|
+
throw new Error(`Failed to verify recovery code: ${response.statusText}`);
|
|
18089
18745
|
}
|
|
18746
|
+
return response.json();
|
|
18090
18747
|
}
|
|
18091
|
-
async
|
|
18092
|
-
const
|
|
18093
|
-
const providerType = this.authProvider.getProviderType();
|
|
18094
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/add`, {
|
|
18748
|
+
async useIdentityRecoverySession(recoverySessionToken, type, credentialId) {
|
|
18749
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/recover`, {
|
|
18095
18750
|
method: "POST",
|
|
18096
18751
|
headers: { "Content-Type": "application/json" },
|
|
18097
|
-
body: JSON.stringify({
|
|
18752
|
+
body: JSON.stringify({ recoverySessionToken, type, credentialId })
|
|
18098
18753
|
});
|
|
18099
18754
|
if (!response.ok) {
|
|
18100
|
-
|
|
18101
|
-
throw new Error(data?.message || `Failed to add recovery email: ${response.statusText}`);
|
|
18755
|
+
throw new Error(`Failed to retrieve recovery data: ${response.statusText}`);
|
|
18102
18756
|
}
|
|
18757
|
+
return response.json();
|
|
18103
18758
|
}
|
|
18104
|
-
async
|
|
18105
|
-
const
|
|
18106
|
-
const providerType = this.authProvider.getProviderType();
|
|
18107
|
-
const response = await fetch(`${this.serverUrl}/keys/recovery-email/verify`, {
|
|
18759
|
+
async completeIdentityRecoveryRebind(input) {
|
|
18760
|
+
const response = await fetch(`${this.serverUrl}/keys/recovery-session/rebind`, {
|
|
18108
18761
|
method: "POST",
|
|
18109
|
-
headers: {
|
|
18110
|
-
|
|
18762
|
+
headers: {
|
|
18763
|
+
"Content-Type": "application/json",
|
|
18764
|
+
Authorization: `Bearer ${input.didAuthVp}`,
|
|
18765
|
+
"X-Auth-Token": input.newAuthToken
|
|
18766
|
+
},
|
|
18767
|
+
body: JSON.stringify({
|
|
18768
|
+
recoverySessionToken: input.recoverySessionToken,
|
|
18769
|
+
providerType: input.providerType,
|
|
18770
|
+
primaryDid: input.primaryDid,
|
|
18771
|
+
authShare: input.authShare
|
|
18772
|
+
})
|
|
18111
18773
|
});
|
|
18112
18774
|
if (!response.ok) {
|
|
18113
|
-
|
|
18114
|
-
throw new Error(data?.message || `Failed to verify recovery email: ${response.statusText}`);
|
|
18775
|
+
throw new Error(`Failed to bind the new sign-in: ${response.statusText}`);
|
|
18115
18776
|
}
|
|
18116
18777
|
return response.json();
|
|
18117
18778
|
}
|
|
18779
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18780
|
+
async markMigrated() {
|
|
18781
|
+
throwDidChallengeRequired();
|
|
18782
|
+
}
|
|
18783
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18784
|
+
async activate() {
|
|
18785
|
+
throwDidChallengeRequired();
|
|
18786
|
+
}
|
|
18787
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18788
|
+
async sendEmailBackupShare(_emailShare, _overrideEmail) {
|
|
18789
|
+
throwDidChallengeRequired();
|
|
18790
|
+
}
|
|
18791
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18792
|
+
async addRecoveryEmail(_email) {
|
|
18793
|
+
throwDidChallengeRequired();
|
|
18794
|
+
}
|
|
18795
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18796
|
+
async verifyRecoveryEmail(_code) {
|
|
18797
|
+
return throwDidChallengeRequired();
|
|
18798
|
+
}
|
|
18799
|
+
/** @deprecated Requires DID-challenge authorization. Use `createSSSStrategy`. */
|
|
18118
18800
|
async deleteUserKey() {
|
|
18119
|
-
|
|
18120
|
-
const providerType = this.authProvider.getProviderType();
|
|
18121
|
-
const response = await fetch(`${this.serverUrl}/keys`, {
|
|
18122
|
-
method: "DELETE",
|
|
18123
|
-
headers,
|
|
18124
|
-
body: JSON.stringify({ providerType })
|
|
18125
|
-
});
|
|
18126
|
-
if (!response.ok) {
|
|
18127
|
-
throw new Error(`Failed to delete user key: ${response.statusText}`);
|
|
18128
|
-
}
|
|
18801
|
+
throwDidChallengeRequired();
|
|
18129
18802
|
}
|
|
18130
18803
|
};
|
|
18131
18804
|
|
|
@@ -18320,11 +18993,10 @@ async function getOrCreateMasterKey() {
|
|
|
18320
18993
|
(s) => s.get("master-key")
|
|
18321
18994
|
);
|
|
18322
18995
|
if (existing) return existing;
|
|
18323
|
-
const key = await crypto.subtle.generateKey(
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
);
|
|
18996
|
+
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
|
|
18997
|
+
"encrypt",
|
|
18998
|
+
"decrypt"
|
|
18999
|
+
]);
|
|
18328
19000
|
await tx(db, KEYS_STORE, "readwrite", (s) => s.put(key, "master-key"));
|
|
18329
19001
|
return key;
|
|
18330
19002
|
} finally {
|
|
@@ -18425,12 +19097,7 @@ __name(getShareVersion, "getShareVersion");
|
|
|
18425
19097
|
async function getDeviceShare(id = DEFAULT_DEVICE_SHARE_ID) {
|
|
18426
19098
|
const db = await openDB();
|
|
18427
19099
|
try {
|
|
18428
|
-
const raw = await tx(
|
|
18429
|
-
db,
|
|
18430
|
-
SHARES_STORE,
|
|
18431
|
-
"readonly",
|
|
18432
|
-
(s) => s.get(id)
|
|
18433
|
-
);
|
|
19100
|
+
const raw = await tx(db, SHARES_STORE, "readonly", (s) => s.get(id));
|
|
18434
19101
|
if (raw == null) {
|
|
18435
19102
|
return null;
|
|
18436
19103
|
}
|
|
@@ -18489,7 +19156,9 @@ async function listAllDeviceShares() {
|
|
|
18489
19156
|
try {
|
|
18490
19157
|
const share = await getDeviceShare(id);
|
|
18491
19158
|
if (!share) {
|
|
18492
|
-
console.warn(
|
|
19159
|
+
console.warn(
|
|
19160
|
+
`SSS Storage: removing orphaned entry that failed to decrypt: ${id}`
|
|
19161
|
+
);
|
|
18493
19162
|
await deleteDeviceShare(id);
|
|
18494
19163
|
continue;
|
|
18495
19164
|
}
|
|
@@ -18573,6 +19242,7 @@ function createAdaptiveStorage() {
|
|
|
18573
19242
|
getDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetDeviceShare(id) : getDeviceShare(id), "getDeviceShare"),
|
|
18574
19243
|
hasDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionHasDeviceShare(id) : hasDeviceShare(id), "hasDeviceShare"),
|
|
18575
19244
|
clearAllShares: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionClearAllShares(id) : clearAllShares(id), "clearAllShares"),
|
|
19245
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionDeleteDeviceShare(id) : deleteDeviceShare(id), "deleteDeviceShare"),
|
|
18576
19246
|
storeShareVersion: /* @__PURE__ */ __name((version, id) => isPublicComputerMode() ? sessionStoreShareVersion(version, id) : storeShareVersion(version, id), "storeShareVersion"),
|
|
18577
19247
|
getShareVersion: /* @__PURE__ */ __name((id) => isPublicComputerMode() ? sessionGetShareVersion(id) : getShareVersion(id), "getShareVersion")
|
|
18578
19248
|
};
|
|
@@ -18853,6 +19523,33 @@ function countWords(phrase) {
|
|
|
18853
19523
|
return phrase.trim().split(/\s+/).filter((w) => w.length > 0).length;
|
|
18854
19524
|
}
|
|
18855
19525
|
__name(countWords, "countWords");
|
|
19526
|
+
function randomIndex(max) {
|
|
19527
|
+
const limit = 4294967296 - 4294967296 % max;
|
|
19528
|
+
for (; ; ) {
|
|
19529
|
+
const value = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
19530
|
+
if (value === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
19531
|
+
if (value < limit) return value % max;
|
|
19532
|
+
}
|
|
19533
|
+
}
|
|
19534
|
+
__name(randomIndex, "randomIndex");
|
|
19535
|
+
async function buildRecoveryPhraseChallengeOptions(phrase, challengeWordIndices, optionCount = 6) {
|
|
19536
|
+
const words = phrase.trim().split(/\s+/);
|
|
19537
|
+
const phraseWords = new Set(words);
|
|
19538
|
+
const decoyPool = (await getWordlist()).filter((word) => !phraseWords.has(word));
|
|
19539
|
+
return challengeWordIndices.map((wordIndex) => {
|
|
19540
|
+
const answer = words[wordIndex];
|
|
19541
|
+
if (!answer) throw new Error("Recovery phrase challenge index is out of range");
|
|
19542
|
+
const options = /* @__PURE__ */ new Set([answer]);
|
|
19543
|
+
while (options.size < optionCount) options.add(decoyPool[randomIndex(decoyPool.length)]);
|
|
19544
|
+
const shuffled = [...options];
|
|
19545
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
19546
|
+
const j = randomIndex(i + 1);
|
|
19547
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
19548
|
+
}
|
|
19549
|
+
return shuffled;
|
|
19550
|
+
});
|
|
19551
|
+
}
|
|
19552
|
+
__name(buildRecoveryPhraseChallengeOptions, "buildRecoveryPhraseChallengeOptions");
|
|
18856
19553
|
|
|
18857
19554
|
// src/key-manager.ts
|
|
18858
19555
|
var SSSKeyManager = class {
|
|
@@ -19061,22 +19758,548 @@ var SSSKeyManager = class {
|
|
|
19061
19758
|
}
|
|
19062
19759
|
};
|
|
19063
19760
|
}
|
|
19064
|
-
async clearLocalData() {
|
|
19065
|
-
await deleteDeviceShare(this.config.deviceStorageKey);
|
|
19066
|
-
this.currentPrivateKey = null;
|
|
19067
|
-
this.initialized = false;
|
|
19761
|
+
async clearLocalData() {
|
|
19762
|
+
await deleteDeviceShare(this.config.deviceStorageKey);
|
|
19763
|
+
this.currentPrivateKey = null;
|
|
19764
|
+
this.initialized = false;
|
|
19765
|
+
}
|
|
19766
|
+
async deleteAccount() {
|
|
19767
|
+
await this.apiClient.deleteUserKey();
|
|
19768
|
+
await clearAllShares();
|
|
19769
|
+
this.currentPrivateKey = null;
|
|
19770
|
+
this.initialized = false;
|
|
19771
|
+
}
|
|
19772
|
+
};
|
|
19773
|
+
function createSSSKeyManager(config) {
|
|
19774
|
+
return new SSSKeyManager(config);
|
|
19775
|
+
}
|
|
19776
|
+
__name(createSSSKeyManager, "createSSSKeyManager");
|
|
19777
|
+
|
|
19778
|
+
// src/email-relay-crypto.ts
|
|
19779
|
+
var EMAIL_RELAY_ENVELOPE_VERSION = 1;
|
|
19780
|
+
var EMAIL_RELAY_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
19781
|
+
var encodeUtf8 = /* @__PURE__ */ __name((value) => {
|
|
19782
|
+
const bytes = new TextEncoder().encode(value);
|
|
19783
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
19784
|
+
copy.set(bytes);
|
|
19785
|
+
return copy;
|
|
19786
|
+
}, "encodeUtf8");
|
|
19787
|
+
var EMAIL_RELAY_INFO = encodeUtf8("learncard-email-relay-v1");
|
|
19788
|
+
var SIX_DIGIT_CODE_RANGE = 9e5;
|
|
19789
|
+
var UINT32_RANGE = 4294967296;
|
|
19790
|
+
var MAX_UNBIASED_UINT32 = Math.floor(UINT32_RANGE / SIX_DIGIT_CODE_RANGE) * SIX_DIGIT_CODE_RANGE;
|
|
19791
|
+
var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
19792
|
+
var requireString = /* @__PURE__ */ __name((record, key, options) => {
|
|
19793
|
+
const value = record[key];
|
|
19794
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
19795
|
+
throw new Error(`Invalid email relay ${key}`);
|
|
19796
|
+
}
|
|
19797
|
+
return value;
|
|
19798
|
+
}, "requireString");
|
|
19799
|
+
var parseBranding = /* @__PURE__ */ __name((value) => {
|
|
19800
|
+
if (value === void 0) return void 0;
|
|
19801
|
+
if (!isRecord(value)) throw new Error("Invalid email relay branding");
|
|
19802
|
+
const branding = {};
|
|
19803
|
+
const keys = [
|
|
19804
|
+
"brandName",
|
|
19805
|
+
"logoUrl",
|
|
19806
|
+
"logoAlt",
|
|
19807
|
+
"primaryColor",
|
|
19808
|
+
"primaryTextColor",
|
|
19809
|
+
"supportEmail",
|
|
19810
|
+
"websiteUrl",
|
|
19811
|
+
"appUrl",
|
|
19812
|
+
"fromDomain",
|
|
19813
|
+
"copyrightHolder"
|
|
19814
|
+
];
|
|
19815
|
+
for (const key of keys) {
|
|
19816
|
+
const field = value[key];
|
|
19817
|
+
if (field === void 0) continue;
|
|
19818
|
+
if (typeof field !== "string" || field.length > 2048) {
|
|
19819
|
+
throw new Error(`Invalid email relay branding.${key}`);
|
|
19820
|
+
}
|
|
19821
|
+
branding[key] = field;
|
|
19822
|
+
}
|
|
19823
|
+
return branding;
|
|
19824
|
+
}, "parseBranding");
|
|
19825
|
+
var parseEmailRelayEnvelope = /* @__PURE__ */ __name((value) => {
|
|
19826
|
+
if (!isRecord(value)) throw new Error("Invalid email relay envelope");
|
|
19827
|
+
if (value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19828
|
+
throw new Error("Unsupported email relay envelope version");
|
|
19829
|
+
}
|
|
19830
|
+
if (value.algorithm !== EMAIL_RELAY_ALGORITHM) {
|
|
19831
|
+
throw new Error("Unsupported email relay algorithm");
|
|
19832
|
+
}
|
|
19833
|
+
return {
|
|
19834
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19835
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
19836
|
+
keyId: requireString(value, "keyId", {
|
|
19837
|
+
max: 128,
|
|
19838
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
19839
|
+
}),
|
|
19840
|
+
ephemeralPublicKey: requireString(value, "ephemeralPublicKey", { max: 256 }),
|
|
19841
|
+
salt: requireString(value, "salt", { max: 128 }),
|
|
19842
|
+
iv: requireString(value, "iv", { max: 64 }),
|
|
19843
|
+
ciphertext: requireString(value, "ciphertext", { max: 16384 })
|
|
19844
|
+
};
|
|
19845
|
+
}, "parseEmailRelayEnvelope");
|
|
19846
|
+
var parseEmailRelayPlaintext = /* @__PURE__ */ __name((value) => {
|
|
19847
|
+
if (!isRecord(value) || value.version !== EMAIL_RELAY_ENVELOPE_VERSION) {
|
|
19848
|
+
throw new Error("Invalid email relay plaintext");
|
|
19849
|
+
}
|
|
19850
|
+
return {
|
|
19851
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19852
|
+
targetEmail: requireString(value, "targetEmail", {
|
|
19853
|
+
max: 320,
|
|
19854
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
19855
|
+
}).trim().toLowerCase(),
|
|
19856
|
+
recoveryKey: requireString(value, "recoveryKey", {
|
|
19857
|
+
min: 5,
|
|
19858
|
+
max: 4096,
|
|
19859
|
+
pattern: /^[0-9a-f]+$/i
|
|
19860
|
+
}),
|
|
19861
|
+
confirmationCode: requireString(value, "confirmationCode", {
|
|
19862
|
+
max: 6,
|
|
19863
|
+
pattern: /^\d{6}$/
|
|
19864
|
+
}),
|
|
19865
|
+
branding: parseBranding(value.branding)
|
|
19866
|
+
};
|
|
19867
|
+
}, "parseEmailRelayPlaintext");
|
|
19868
|
+
var getAad = /* @__PURE__ */ __name((envelope) => encodeUtf8(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
19869
|
+
var deriveAesKey = /* @__PURE__ */ __name(async (sharedSecret, salt, usage) => {
|
|
19870
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
19871
|
+
"deriveKey"
|
|
19872
|
+
]);
|
|
19873
|
+
return crypto.subtle.deriveKey(
|
|
19874
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: EMAIL_RELAY_INFO },
|
|
19875
|
+
keyMaterial,
|
|
19876
|
+
{ name: "AES-GCM", length: 256 },
|
|
19877
|
+
false,
|
|
19878
|
+
[usage]
|
|
19879
|
+
);
|
|
19880
|
+
}, "deriveAesKey");
|
|
19881
|
+
var generateEmailRelayConfirmationCode = /* @__PURE__ */ __name(() => {
|
|
19882
|
+
const random = new Uint32Array(1);
|
|
19883
|
+
let value;
|
|
19884
|
+
do {
|
|
19885
|
+
crypto.getRandomValues(random);
|
|
19886
|
+
value = random[0] ?? UINT32_RANGE;
|
|
19887
|
+
} while (value >= MAX_UNBIASED_UINT32);
|
|
19888
|
+
return String(1e5 + value % SIX_DIGIT_CODE_RANGE);
|
|
19889
|
+
}, "generateEmailRelayConfirmationCode");
|
|
19890
|
+
var encryptEmailRelayPayload = /* @__PURE__ */ __name(async (input, publicKeyBase64, keyId) => {
|
|
19891
|
+
const plaintext = parseEmailRelayPlaintext({
|
|
19892
|
+
...input,
|
|
19893
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION
|
|
19894
|
+
});
|
|
19895
|
+
const normalizedKeyId = keyId.trim();
|
|
19896
|
+
if (!publicKeyBase64.trim()) throw new Error("Email relay public key is not configured");
|
|
19897
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(normalizedKeyId)) {
|
|
19898
|
+
throw new Error("Email relay key ID is not configured");
|
|
19899
|
+
}
|
|
19900
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
19901
|
+
"spki",
|
|
19902
|
+
base64ToBuffer(publicKeyBase64.trim()).buffer,
|
|
19903
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19904
|
+
false,
|
|
19905
|
+
[]
|
|
19906
|
+
);
|
|
19907
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
19908
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19909
|
+
true,
|
|
19910
|
+
["deriveBits"]
|
|
19911
|
+
);
|
|
19912
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
19913
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
19914
|
+
ephemeralKeyPair.privateKey,
|
|
19915
|
+
256
|
|
19916
|
+
);
|
|
19917
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
19918
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
19919
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "encrypt");
|
|
19920
|
+
const envelopeMetadata = {
|
|
19921
|
+
version: EMAIL_RELAY_ENVELOPE_VERSION,
|
|
19922
|
+
algorithm: EMAIL_RELAY_ALGORITHM,
|
|
19923
|
+
keyId: normalizedKeyId
|
|
19924
|
+
};
|
|
19925
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
19926
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelopeMetadata) },
|
|
19927
|
+
aesKey,
|
|
19928
|
+
encodeUtf8(JSON.stringify(plaintext))
|
|
19929
|
+
);
|
|
19930
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
19931
|
+
return {
|
|
19932
|
+
...envelopeMetadata,
|
|
19933
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
19934
|
+
salt: bufferToBase64(salt.buffer),
|
|
19935
|
+
iv: bufferToBase64(iv.buffer),
|
|
19936
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
19937
|
+
};
|
|
19938
|
+
}, "encryptEmailRelayPayload");
|
|
19939
|
+
var decryptEmailRelayPayload = /* @__PURE__ */ __name(async (value, privateKeyBase64) => {
|
|
19940
|
+
const envelope = parseEmailRelayEnvelope(value);
|
|
19941
|
+
const privateKey = await crypto.subtle.importKey(
|
|
19942
|
+
"pkcs8",
|
|
19943
|
+
base64ToBuffer(privateKeyBase64.trim()).buffer,
|
|
19944
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19945
|
+
false,
|
|
19946
|
+
["deriveBits"]
|
|
19947
|
+
);
|
|
19948
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
19949
|
+
"raw",
|
|
19950
|
+
base64ToBuffer(envelope.ephemeralPublicKey).buffer,
|
|
19951
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
19952
|
+
false,
|
|
19953
|
+
[]
|
|
19954
|
+
);
|
|
19955
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
19956
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
19957
|
+
privateKey,
|
|
19958
|
+
256
|
|
19959
|
+
);
|
|
19960
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
19961
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
19962
|
+
const aesKey = await deriveAesKey(sharedSecret, salt, "decrypt");
|
|
19963
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
19964
|
+
{ name: "AES-GCM", iv, additionalData: getAad(envelope) },
|
|
19965
|
+
aesKey,
|
|
19966
|
+
base64ToBuffer(envelope.ciphertext)
|
|
19967
|
+
);
|
|
19968
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
19969
|
+
return parseEmailRelayPlaintext(parsed);
|
|
19970
|
+
}, "decryptEmailRelayPayload");
|
|
19971
|
+
|
|
19972
|
+
// src/escrow-crypto.ts
|
|
19973
|
+
var ESCROW_ENVELOPE_VERSION = 1;
|
|
19974
|
+
var ESCROW_ALGORITHM = "P-256-HKDF-SHA256-AES-256-GCM";
|
|
19975
|
+
var ESCROW_BLOB_INFO = "learncard-escrow-blob-v1";
|
|
19976
|
+
var ESCROW_RELEASE_INFO = "learncard-escrow-release-v1";
|
|
19977
|
+
var ESCROW_CLIENT_KEY_ID = "client";
|
|
19978
|
+
var encodeUtf82 = /* @__PURE__ */ __name((value) => {
|
|
19979
|
+
const bytes = new TextEncoder().encode(value);
|
|
19980
|
+
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
19981
|
+
copy.set(bytes);
|
|
19982
|
+
return copy;
|
|
19983
|
+
}, "encodeUtf8");
|
|
19984
|
+
var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
19985
|
+
var requireString2 = /* @__PURE__ */ __name((record, key, options) => {
|
|
19986
|
+
const value = record[key];
|
|
19987
|
+
if (typeof value !== "string" || value.length < (options.min ?? 1) || value.length > options.max || options.pattern && !options.pattern.test(value)) {
|
|
19988
|
+
throw new Error(`Invalid escrow ${key}`);
|
|
19989
|
+
}
|
|
19990
|
+
return value;
|
|
19991
|
+
}, "requireString");
|
|
19992
|
+
var parseEscrowEnvelope = /* @__PURE__ */ __name((value) => {
|
|
19993
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow envelope");
|
|
19994
|
+
if (value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
19995
|
+
throw new Error("Unsupported escrow envelope version");
|
|
19996
|
+
}
|
|
19997
|
+
if (value.algorithm !== ESCROW_ALGORITHM) {
|
|
19998
|
+
throw new Error("Unsupported escrow algorithm");
|
|
19999
|
+
}
|
|
20000
|
+
return {
|
|
20001
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20002
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20003
|
+
keyId: requireString2(value, "keyId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ }),
|
|
20004
|
+
ephemeralPublicKey: requireString2(value, "ephemeralPublicKey", { max: 256 }),
|
|
20005
|
+
salt: requireString2(value, "salt", { max: 128 }),
|
|
20006
|
+
iv: requireString2(value, "iv", { max: 64 }),
|
|
20007
|
+
ciphertext: requireString2(value, "ciphertext", { max: 16384 })
|
|
20008
|
+
};
|
|
20009
|
+
}, "parseEscrowEnvelope");
|
|
20010
|
+
var parseEscrowBlobPlaintext = /* @__PURE__ */ __name((value) => {
|
|
20011
|
+
if (!isRecord2(value) || value.version !== ESCROW_ENVELOPE_VERSION) {
|
|
20012
|
+
throw new Error("Invalid escrow plaintext");
|
|
20013
|
+
}
|
|
20014
|
+
if (typeof value.shareVersion !== "number" || !Number.isInteger(value.shareVersion) || value.shareVersion <= 0) {
|
|
20015
|
+
throw new Error("Invalid escrow shareVersion");
|
|
20016
|
+
}
|
|
20017
|
+
return {
|
|
20018
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20019
|
+
recoveryShare: requireString2(value, "recoveryShare", {
|
|
20020
|
+
min: 5,
|
|
20021
|
+
max: 4096,
|
|
20022
|
+
pattern: /^[0-9a-f]+$/i
|
|
20023
|
+
}).toLowerCase(),
|
|
20024
|
+
did: requireString2(value, "did", { max: 2048, pattern: /^did:/ }),
|
|
20025
|
+
shareVersion: value.shareVersion,
|
|
20026
|
+
...value.pinVerifier !== void 0 ? {
|
|
20027
|
+
pinVerifier: requireString2(value, "pinVerifier", {
|
|
20028
|
+
min: 64,
|
|
20029
|
+
max: 64,
|
|
20030
|
+
pattern: /^[0-9a-f]{64}$/i
|
|
20031
|
+
}).toLowerCase()
|
|
20032
|
+
} : {}
|
|
20033
|
+
};
|
|
20034
|
+
}, "parseEscrowBlobPlaintext");
|
|
20035
|
+
var parseEscrowReleasePlaintext = /* @__PURE__ */ __name((value) => {
|
|
20036
|
+
const plaintext = parseEscrowBlobPlaintext(value);
|
|
20037
|
+
if (!isRecord2(value)) throw new Error("Invalid escrow plaintext");
|
|
20038
|
+
return {
|
|
20039
|
+
...plaintext,
|
|
20040
|
+
holdId: requireString2(value, "holdId", { max: 128, pattern: /^[A-Za-z0-9._-]+$/ })
|
|
20041
|
+
};
|
|
20042
|
+
}, "parseEscrowReleasePlaintext");
|
|
20043
|
+
var getAad2 = /* @__PURE__ */ __name((envelope) => encodeUtf82(`${envelope.version}|${envelope.algorithm}|${envelope.keyId}`), "getAad");
|
|
20044
|
+
var deriveAesKey2 = /* @__PURE__ */ __name(async (sharedSecret, salt, usage, info) => {
|
|
20045
|
+
const keyMaterial = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, [
|
|
20046
|
+
"deriveKey"
|
|
20047
|
+
]);
|
|
20048
|
+
return crypto.subtle.deriveKey(
|
|
20049
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: encodeUtf82(info) },
|
|
20050
|
+
keyMaterial,
|
|
20051
|
+
{ name: "AES-GCM", length: 256 },
|
|
20052
|
+
false,
|
|
20053
|
+
[usage]
|
|
20054
|
+
);
|
|
20055
|
+
}, "deriveAesKey");
|
|
20056
|
+
var generateEscrowKeyPair = /* @__PURE__ */ __name(async () => {
|
|
20057
|
+
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [
|
|
20058
|
+
"deriveBits"
|
|
20059
|
+
]);
|
|
20060
|
+
const [publicKey, privateKey] = await Promise.all([
|
|
20061
|
+
crypto.subtle.exportKey("spki", keyPair.publicKey),
|
|
20062
|
+
crypto.subtle.exportKey("pkcs8", keyPair.privateKey)
|
|
20063
|
+
]);
|
|
20064
|
+
return { publicKey: bufferToBase64(publicKey), privateKey: bufferToBase64(privateKey) };
|
|
20065
|
+
}, "generateEscrowKeyPair");
|
|
20066
|
+
var encryptToRecipient = /* @__PURE__ */ __name(async (plaintextJson, spki, keyId, info) => {
|
|
20067
|
+
if (typeof spki !== "string" || !spki.trim()) {
|
|
20068
|
+
throw new Error("Escrow public key is not configured");
|
|
20069
|
+
}
|
|
20070
|
+
const recipientKeyId = requireString2({ keyId }, "keyId", {
|
|
20071
|
+
max: 128,
|
|
20072
|
+
pattern: /^[A-Za-z0-9._-]+$/
|
|
20073
|
+
});
|
|
20074
|
+
const recipientPublicKey = await crypto.subtle.importKey(
|
|
20075
|
+
"spki",
|
|
20076
|
+
base64ToBuffer(spki.trim()),
|
|
20077
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20078
|
+
false,
|
|
20079
|
+
[]
|
|
20080
|
+
);
|
|
20081
|
+
const ephemeralKeyPair = await crypto.subtle.generateKey(
|
|
20082
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20083
|
+
true,
|
|
20084
|
+
["deriveBits"]
|
|
20085
|
+
);
|
|
20086
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20087
|
+
{ name: "ECDH", public: recipientPublicKey },
|
|
20088
|
+
ephemeralKeyPair.privateKey,
|
|
20089
|
+
256
|
|
20090
|
+
);
|
|
20091
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
20092
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
20093
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "encrypt", info);
|
|
20094
|
+
const metadata = {
|
|
20095
|
+
version: ESCROW_ENVELOPE_VERSION,
|
|
20096
|
+
algorithm: ESCROW_ALGORITHM,
|
|
20097
|
+
keyId: recipientKeyId
|
|
20098
|
+
};
|
|
20099
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
20100
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(metadata) },
|
|
20101
|
+
aesKey,
|
|
20102
|
+
encodeUtf82(plaintextJson)
|
|
20103
|
+
);
|
|
20104
|
+
const ephemeralPublicKey = await crypto.subtle.exportKey("raw", ephemeralKeyPair.publicKey);
|
|
20105
|
+
return {
|
|
20106
|
+
...metadata,
|
|
20107
|
+
ephemeralPublicKey: bufferToBase64(ephemeralPublicKey),
|
|
20108
|
+
salt: bufferToBase64(salt.buffer),
|
|
20109
|
+
iv: bufferToBase64(iv.buffer),
|
|
20110
|
+
ciphertext: bufferToBase64(ciphertext)
|
|
20111
|
+
};
|
|
20112
|
+
}, "encryptToRecipient");
|
|
20113
|
+
var decryptFromEnvelope = /* @__PURE__ */ __name(async (value, pkcs8, info) => {
|
|
20114
|
+
const envelope = parseEscrowEnvelope(value);
|
|
20115
|
+
const privateKey = await crypto.subtle.importKey(
|
|
20116
|
+
"pkcs8",
|
|
20117
|
+
base64ToBuffer(pkcs8.trim()),
|
|
20118
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20119
|
+
false,
|
|
20120
|
+
["deriveBits"]
|
|
20121
|
+
);
|
|
20122
|
+
const ephemeralPublicKey = await crypto.subtle.importKey(
|
|
20123
|
+
"raw",
|
|
20124
|
+
base64ToBuffer(envelope.ephemeralPublicKey),
|
|
20125
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20126
|
+
false,
|
|
20127
|
+
[]
|
|
20128
|
+
);
|
|
20129
|
+
const sharedSecret = await crypto.subtle.deriveBits(
|
|
20130
|
+
{ name: "ECDH", public: ephemeralPublicKey },
|
|
20131
|
+
privateKey,
|
|
20132
|
+
256
|
|
20133
|
+
);
|
|
20134
|
+
const salt = base64ToBuffer(envelope.salt);
|
|
20135
|
+
const iv = base64ToBuffer(envelope.iv);
|
|
20136
|
+
const aesKey = await deriveAesKey2(sharedSecret, salt, "decrypt", info);
|
|
20137
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
20138
|
+
{ name: "AES-GCM", iv, additionalData: getAad2(envelope) },
|
|
20139
|
+
aesKey,
|
|
20140
|
+
base64ToBuffer(envelope.ciphertext)
|
|
20141
|
+
);
|
|
20142
|
+
const parsed = JSON.parse(new TextDecoder().decode(decrypted));
|
|
20143
|
+
return parsed;
|
|
20144
|
+
}, "decryptFromEnvelope");
|
|
20145
|
+
var encryptEscrowBlob = /* @__PURE__ */ __name(async (input, enclavePublicKeySpkiB64, keyId) => {
|
|
20146
|
+
const plaintext = parseEscrowBlobPlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20147
|
+
return encryptToRecipient(
|
|
20148
|
+
JSON.stringify(plaintext),
|
|
20149
|
+
enclavePublicKeySpkiB64,
|
|
20150
|
+
keyId,
|
|
20151
|
+
ESCROW_BLOB_INFO
|
|
20152
|
+
);
|
|
20153
|
+
}, "encryptEscrowBlob");
|
|
20154
|
+
var decryptEscrowBlob = /* @__PURE__ */ __name(async (envelope, enclavePrivateKeyPkcs8B64) => parseEscrowBlobPlaintext(
|
|
20155
|
+
await decryptFromEnvelope(envelope, enclavePrivateKeyPkcs8B64, ESCROW_BLOB_INFO)
|
|
20156
|
+
), "decryptEscrowBlob");
|
|
20157
|
+
var sealEscrowRelease = /* @__PURE__ */ __name(async (input, clientEphemeralPublicKeySpkiB64) => {
|
|
20158
|
+
const plaintext = parseEscrowReleasePlaintext({ ...input, version: ESCROW_ENVELOPE_VERSION });
|
|
20159
|
+
return encryptToRecipient(
|
|
20160
|
+
JSON.stringify(plaintext),
|
|
20161
|
+
clientEphemeralPublicKeySpkiB64,
|
|
20162
|
+
ESCROW_CLIENT_KEY_ID,
|
|
20163
|
+
ESCROW_RELEASE_INFO
|
|
20164
|
+
);
|
|
20165
|
+
}, "sealEscrowRelease");
|
|
20166
|
+
var openEscrowRelease = /* @__PURE__ */ __name(async (envelope, clientEphemeralPrivateKeyPkcs8B64) => parseEscrowReleasePlaintext(
|
|
20167
|
+
await decryptFromEnvelope(envelope, clientEphemeralPrivateKeyPkcs8B64, ESCROW_RELEASE_INFO)
|
|
20168
|
+
), "openEscrowRelease");
|
|
20169
|
+
|
|
20170
|
+
// src/escrow-attestation.ts
|
|
20171
|
+
var isRecord3 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
20172
|
+
var normalizePublicKey = /* @__PURE__ */ __name((value) => {
|
|
20173
|
+
const trimmed = value.trim();
|
|
20174
|
+
if (!trimmed || trimmed.length > 512 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(trimmed)) {
|
|
20175
|
+
throw new Error("Invalid escrow attestation public key");
|
|
20176
|
+
}
|
|
20177
|
+
return bufferToBase64(base64ToBuffer(trimmed).buffer);
|
|
20178
|
+
}, "normalizePublicKey");
|
|
20179
|
+
var verifyEnclaveAttestation = /* @__PURE__ */ __name(async (attestation, policy) => {
|
|
20180
|
+
const fields = ["mode", "keyId", "publicKey", "measurements", "document", "issuedAt"];
|
|
20181
|
+
if (!isRecord3(attestation) || Object.keys(attestation).some((key) => !fields.includes(key)) || attestation.mode !== "software" && attestation.mode !== "nitro" || typeof attestation.keyId !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(attestation.keyId) || typeof attestation.publicKey !== "string" || !isRecord3(attestation.measurements) || Object.entries(attestation.measurements).some(
|
|
20182
|
+
([key, value]) => !["imageSha384", "pcr0", "pcr1", "pcr2"].includes(key) || typeof value !== "string" || !value || value.length > 1024
|
|
20183
|
+
) || typeof attestation.document !== "string" || !attestation.document || attestation.document.length > 65536 || typeof attestation.issuedAt !== "string" || !Number.isFinite(Date.parse(attestation.issuedAt))) {
|
|
20184
|
+
throw new Error("Invalid escrow attestation");
|
|
20185
|
+
}
|
|
20186
|
+
const publicKey = normalizePublicKey(attestation.publicKey);
|
|
20187
|
+
await crypto.subtle.importKey(
|
|
20188
|
+
"spki",
|
|
20189
|
+
base64ToBuffer(publicKey),
|
|
20190
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
20191
|
+
false,
|
|
20192
|
+
[]
|
|
20193
|
+
);
|
|
20194
|
+
if (policy.mode === "nitro") {
|
|
20195
|
+
throw new Error("Nitro attestation verification is not implemented yet");
|
|
20196
|
+
}
|
|
20197
|
+
if (attestation.mode !== policy.mode) throw new Error("Escrow attestation mode mismatch");
|
|
20198
|
+
if (!policy.pinnedPublicKeys.some((pin) => normalizePublicKey(pin) === publicKey)) {
|
|
20199
|
+
throw new Error("Escrow attestation public key is not trusted");
|
|
20200
|
+
}
|
|
20201
|
+
return { publicKey, keyId: attestation.keyId, mode: attestation.mode };
|
|
20202
|
+
}, "verifyEnclaveAttestation");
|
|
20203
|
+
|
|
20204
|
+
// src/types.ts
|
|
20205
|
+
var import_types = __toESM(require_dist());
|
|
20206
|
+
var EscrowPinUnavailableError = class extends Error {
|
|
20207
|
+
static {
|
|
20208
|
+
__name(this, "EscrowPinUnavailableError");
|
|
20209
|
+
}
|
|
20210
|
+
constructor() {
|
|
20211
|
+
super("PIN recovery is not available for this account.");
|
|
20212
|
+
this.name = "EscrowPinUnavailableError";
|
|
20213
|
+
}
|
|
20214
|
+
};
|
|
20215
|
+
var EscrowPinMismatchError = class extends Error {
|
|
20216
|
+
constructor(attemptsRemaining) {
|
|
20217
|
+
super("Incorrect PIN.");
|
|
20218
|
+
this.attemptsRemaining = attemptsRemaining;
|
|
20219
|
+
this.name = "EscrowPinMismatchError";
|
|
20220
|
+
}
|
|
20221
|
+
static {
|
|
20222
|
+
__name(this, "EscrowPinMismatchError");
|
|
20223
|
+
}
|
|
20224
|
+
};
|
|
20225
|
+
var EscrowPinThrottledError = class extends Error {
|
|
20226
|
+
static {
|
|
20227
|
+
__name(this, "EscrowPinThrottledError");
|
|
20228
|
+
}
|
|
20229
|
+
constructor() {
|
|
20230
|
+
super("Too many tries right now. Wait a minute and try again.");
|
|
20231
|
+
this.name = "EscrowPinThrottledError";
|
|
20232
|
+
}
|
|
20233
|
+
};
|
|
20234
|
+
var EscrowHoldRestartThrottledError = class extends Error {
|
|
20235
|
+
constructor(retryAfter) {
|
|
20236
|
+
super("A recovery request was started recently. Please wait before restarting.");
|
|
20237
|
+
this.retryAfter = retryAfter;
|
|
20238
|
+
this.name = "EscrowHoldRestartThrottledError";
|
|
19068
20239
|
}
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
await clearAllShares();
|
|
19072
|
-
this.currentPrivateKey = null;
|
|
19073
|
-
this.initialized = false;
|
|
20240
|
+
static {
|
|
20241
|
+
__name(this, "EscrowHoldRestartThrottledError");
|
|
19074
20242
|
}
|
|
19075
20243
|
};
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
20244
|
+
var EscrowPinLockedError = class extends Error {
|
|
20245
|
+
static {
|
|
20246
|
+
__name(this, "EscrowPinLockedError");
|
|
20247
|
+
}
|
|
20248
|
+
constructor() {
|
|
20249
|
+
super("Too many incorrect PIN attempts. You can still recover by waiting.");
|
|
20250
|
+
this.name = "EscrowPinLockedError";
|
|
20251
|
+
}
|
|
20252
|
+
};
|
|
20253
|
+
|
|
20254
|
+
// src/escrow-pin.ts
|
|
20255
|
+
var PIN_MIN_LENGTH = 6;
|
|
20256
|
+
var PIN_MAX_LENGTH = 12;
|
|
20257
|
+
var ESCROW_PIN_MAX_ATTEMPTS = 10;
|
|
20258
|
+
var TRIVIAL_PINS = /* @__PURE__ */ new Set([
|
|
20259
|
+
"123456",
|
|
20260
|
+
"654321",
|
|
20261
|
+
"000000",
|
|
20262
|
+
"111111",
|
|
20263
|
+
"121212",
|
|
20264
|
+
"112233",
|
|
20265
|
+
"123123",
|
|
20266
|
+
"696969",
|
|
20267
|
+
"123321"
|
|
20268
|
+
]);
|
|
20269
|
+
var normalizePin = /* @__PURE__ */ __name((raw) => raw.trim().normalize("NFKD").replace(/\D/g, ""), "normalizePin");
|
|
20270
|
+
var validatePin = /* @__PURE__ */ __name((pin) => {
|
|
20271
|
+
const normalized = normalizePin(pin);
|
|
20272
|
+
if (normalized.length < PIN_MIN_LENGTH || normalized.length > PIN_MAX_LENGTH) {
|
|
20273
|
+
return { ok: false, reason: "length" };
|
|
20274
|
+
}
|
|
20275
|
+
if (/^(\d)\1+$/.test(normalized) || "0123456789".includes(normalized) || "9876543210".includes(normalized) || TRIVIAL_PINS.has(normalized)) {
|
|
20276
|
+
return { ok: false, reason: "trivial" };
|
|
20277
|
+
}
|
|
20278
|
+
return { ok: true };
|
|
20279
|
+
}, "validatePin");
|
|
20280
|
+
var generatePinSalt = /* @__PURE__ */ __name(() => bufferToBase64(crypto.getRandomValues(new Uint8Array(16)).buffer), "generatePinSalt");
|
|
20281
|
+
var derivePinProof = /* @__PURE__ */ __name(async (pin, pinSaltBase64) => {
|
|
20282
|
+
const salt = base64ToBuffer(pinSaltBase64);
|
|
20283
|
+
if (salt.length !== 16) throw new Error("Invalid escrow PIN salt length");
|
|
20284
|
+
const key = await deriveKeyFromPassword(normalizePin(pin), salt);
|
|
20285
|
+
try {
|
|
20286
|
+
return bytesToHex(key);
|
|
20287
|
+
} finally {
|
|
20288
|
+
key.fill(0);
|
|
20289
|
+
}
|
|
20290
|
+
}, "derivePinProof");
|
|
20291
|
+
var constantTimeEqualHex = /* @__PURE__ */ __name((a, b) => {
|
|
20292
|
+
if (a.length !== b.length || !/^(?:[0-9a-f]{2})+$/i.test(a) || !/^(?:[0-9a-f]{2})+$/i.test(b)) {
|
|
20293
|
+
return false;
|
|
20294
|
+
}
|
|
20295
|
+
const aBytes = hexToBytes(a);
|
|
20296
|
+
const bBytes = hexToBytes(b);
|
|
20297
|
+
let difference = 0;
|
|
20298
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
20299
|
+
difference |= aBytes[i] ^ bBytes[i];
|
|
20300
|
+
}
|
|
20301
|
+
return difference === 0;
|
|
20302
|
+
}, "constantTimeEqualHex");
|
|
19080
20303
|
|
|
19081
20304
|
// src/atomic-operations.ts
|
|
19082
20305
|
var ShareVerificationError = class extends Error {
|
|
@@ -19103,6 +20326,15 @@ var AtomicUpdateError = class extends Error {
|
|
|
19103
20326
|
__name(this, "AtomicUpdateError");
|
|
19104
20327
|
}
|
|
19105
20328
|
};
|
|
20329
|
+
var ShareWriteRejectedError = class extends Error {
|
|
20330
|
+
static {
|
|
20331
|
+
__name(this, "ShareWriteRejectedError");
|
|
20332
|
+
}
|
|
20333
|
+
constructor(message) {
|
|
20334
|
+
super(message);
|
|
20335
|
+
this.name = "ShareWriteRejectedError";
|
|
20336
|
+
}
|
|
20337
|
+
};
|
|
19106
20338
|
async function splitAndVerify(privateKey) {
|
|
19107
20339
|
const shares = await splitPrivateKey(privateKey);
|
|
19108
20340
|
const combinations = [
|
|
@@ -19159,18 +20391,37 @@ async function atomicShareUpdate(privateKey, storage, options = {}) {
|
|
|
19159
20391
|
try {
|
|
19160
20392
|
await storage.storeAuth(newShares.authShare);
|
|
19161
20393
|
} catch (e) {
|
|
19162
|
-
if (
|
|
20394
|
+
if (!(e instanceof ShareWriteRejectedError)) {
|
|
20395
|
+
throw new AtomicUpdateError(
|
|
20396
|
+
"Auth share write outcome unknown; device share preserved for reconciliation",
|
|
20397
|
+
"store_auth",
|
|
20398
|
+
false,
|
|
20399
|
+
e instanceof Error ? e : void 0
|
|
20400
|
+
);
|
|
20401
|
+
}
|
|
20402
|
+
let rolledBack = false;
|
|
20403
|
+
if (deviceStored) {
|
|
19163
20404
|
try {
|
|
19164
|
-
|
|
19165
|
-
|
|
20405
|
+
if (options.previousDeviceShare !== void 0) {
|
|
20406
|
+
await storage.storeDevice(options.previousDeviceShare);
|
|
20407
|
+
options.onRollback?.("Server storage failed, restored previous device share");
|
|
20408
|
+
rolledBack = true;
|
|
20409
|
+
} else if (storage.clearDevice) {
|
|
20410
|
+
await storage.clearDevice();
|
|
20411
|
+
options.onRollback?.("Server storage failed, removed new device share");
|
|
20412
|
+
rolledBack = true;
|
|
20413
|
+
}
|
|
19166
20414
|
} catch (rollbackError) {
|
|
19167
|
-
console.error(
|
|
20415
|
+
console.error(
|
|
20416
|
+
"CRITICAL: Rollback failed after auth share storage failure",
|
|
20417
|
+
rollbackError
|
|
20418
|
+
);
|
|
19168
20419
|
}
|
|
19169
20420
|
}
|
|
19170
20421
|
throw new AtomicUpdateError(
|
|
19171
|
-
"Failed to store auth share on server, rolled back device share",
|
|
20422
|
+
rolledBack ? "Failed to store auth share on server, rolled back device share" : "Failed to store auth share on server; rollback did not complete",
|
|
19172
20423
|
"store_auth",
|
|
19173
|
-
|
|
20424
|
+
rolledBack,
|
|
19174
20425
|
e instanceof Error ? e : void 0
|
|
19175
20426
|
);
|
|
19176
20427
|
}
|
|
@@ -19224,12 +20475,16 @@ async function atomicRecovery(recoveryShare, authShareData, storage, options = {
|
|
|
19224
20475
|
__name(atomicRecovery, "atomicRecovery");
|
|
19225
20476
|
|
|
19226
20477
|
// src/sss-strategy.ts
|
|
20478
|
+
var import_types3 = __toESM(require_dist());
|
|
19227
20479
|
var SSS_DB_NAME = "lcb-sss-keys";
|
|
20480
|
+
var DEFAULT_DEVICE_SHARE_ID2 = "sss-device-share";
|
|
20481
|
+
var MAX_RECONCILIATION_HISTORY = 5;
|
|
19228
20482
|
var defaultStorage = {
|
|
19229
20483
|
storeDeviceShare,
|
|
19230
20484
|
getDeviceShare,
|
|
19231
20485
|
hasDeviceShare,
|
|
19232
20486
|
clearAllShares,
|
|
20487
|
+
deleteDeviceShare,
|
|
19233
20488
|
storeShareVersion,
|
|
19234
20489
|
getShareVersion
|
|
19235
20490
|
};
|
|
@@ -19238,10 +20493,24 @@ var buildHeaders = /* @__PURE__ */ __name((token, didAuthVp, tenantId) => ({
|
|
|
19238
20493
|
...didAuthVp ? { Authorization: `Bearer ${didAuthVp}` } : {},
|
|
19239
20494
|
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
19240
20495
|
}), "buildHeaders");
|
|
20496
|
+
var SERVER_REQUEST_TIMEOUT_MS = 3e4;
|
|
20497
|
+
var requestTimeoutSignal = /* @__PURE__ */ __name((ms = SERVER_REQUEST_TIMEOUT_MS) => typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(ms) : void 0, "requestTimeoutSignal");
|
|
20498
|
+
var describeHttpStatus = /* @__PURE__ */ __name((response) => response.statusText ? `${response.status} ${response.statusText}` : `${response.status}`, "describeHttpStatus");
|
|
20499
|
+
var EscrowRequestError = class extends Error {
|
|
20500
|
+
constructor(status) {
|
|
20501
|
+
super("Escrow request failed");
|
|
20502
|
+
this.status = status;
|
|
20503
|
+
this.name = "EscrowRequestError";
|
|
20504
|
+
}
|
|
20505
|
+
static {
|
|
20506
|
+
__name(this, "EscrowRequestError");
|
|
20507
|
+
}
|
|
20508
|
+
};
|
|
19241
20509
|
var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, providerType, shareVersion, tenantId) => {
|
|
19242
20510
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19243
20511
|
method: "POST",
|
|
19244
20512
|
headers: buildHeaders(token, void 0, tenantId),
|
|
20513
|
+
signal: requestTimeoutSignal(),
|
|
19245
20514
|
body: JSON.stringify({
|
|
19246
20515
|
authToken: token,
|
|
19247
20516
|
providerType,
|
|
@@ -19250,42 +20519,103 @@ var fetchAuthShareRaw = /* @__PURE__ */ __name(async (serverUrl, token, provider
|
|
|
19250
20519
|
});
|
|
19251
20520
|
if (!response.ok) {
|
|
19252
20521
|
if (response.status === 404) return null;
|
|
19253
|
-
throw new Error(`Failed to fetch key status: ${response
|
|
20522
|
+
throw new Error(`Failed to fetch key status: ${describeHttpStatus(response)}`);
|
|
19254
20523
|
}
|
|
19255
20524
|
return response.json();
|
|
19256
20525
|
}, "fetchAuthShareRaw");
|
|
19257
|
-
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId) => {
|
|
20526
|
+
var putAuthShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, authShare, primaryDid, didAuthVp, tenantId, expectedShareVersion) => {
|
|
19258
20527
|
const response = await fetch(`${serverUrl}/keys/auth-share`, {
|
|
19259
20528
|
method: "PUT",
|
|
19260
20529
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20530
|
+
signal: requestTimeoutSignal(),
|
|
19261
20531
|
body: JSON.stringify({
|
|
19262
20532
|
authToken: token,
|
|
19263
20533
|
providerType,
|
|
19264
20534
|
authShare: { encryptedData: authShare, encryptedDek: "", iv: "" },
|
|
19265
20535
|
primaryDid,
|
|
19266
|
-
securityLevel: "basic"
|
|
20536
|
+
securityLevel: "basic",
|
|
20537
|
+
sssActivationState: "provisional",
|
|
20538
|
+
expectedShareVersion
|
|
19267
20539
|
})
|
|
19268
20540
|
});
|
|
19269
20541
|
if (!response.ok) {
|
|
19270
|
-
|
|
20542
|
+
const message = `Failed to store auth share: ${describeHttpStatus(response)}`;
|
|
20543
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20544
|
+
throw new ShareWriteRejectedError(message);
|
|
20545
|
+
}
|
|
20546
|
+
throw new Error(message);
|
|
19271
20547
|
}
|
|
19272
20548
|
const data = await response.json();
|
|
19273
|
-
return {
|
|
20549
|
+
return {
|
|
20550
|
+
shareVersion: data.shareVersion ?? 1,
|
|
20551
|
+
expectedShareVersionChecked: data.expectedShareVersionChecked === true
|
|
20552
|
+
};
|
|
19274
20553
|
}, "putAuthShare");
|
|
20554
|
+
var authShareToString = /* @__PURE__ */ __name((authShare) => {
|
|
20555
|
+
if (typeof authShare === "string") return authShare;
|
|
20556
|
+
if (typeof authShare === "object" && authShare !== null && "encryptedData" in authShare && typeof authShare.encryptedData === "string") {
|
|
20557
|
+
return authShare.encryptedData;
|
|
20558
|
+
}
|
|
20559
|
+
return null;
|
|
20560
|
+
}, "authShareToString");
|
|
20561
|
+
var requestFreshDidAuthVp = /* @__PURE__ */ __name(async (serverUrl, privateKey, did, signDidAuthVp, tenantId) => {
|
|
20562
|
+
const bootstrapVp = await signDidAuthVp(privateKey);
|
|
20563
|
+
const response = await fetch(`${serverUrl}/keys/challenge`, {
|
|
20564
|
+
method: "POST",
|
|
20565
|
+
headers: buildHeaders("", bootstrapVp, tenantId),
|
|
20566
|
+
signal: requestTimeoutSignal(),
|
|
20567
|
+
body: JSON.stringify({ did })
|
|
20568
|
+
});
|
|
20569
|
+
if (!response.ok) {
|
|
20570
|
+
throw new Error(`Failed to request DID challenge: ${describeHttpStatus(response)}`);
|
|
20571
|
+
}
|
|
20572
|
+
const data = await response.json();
|
|
20573
|
+
if (!data.challenge) throw new Error("DID challenge response was empty");
|
|
20574
|
+
return signDidAuthVp(privateKey, data.challenge);
|
|
20575
|
+
}, "requestFreshDidAuthVp");
|
|
19275
20576
|
var fetchRecoveryShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, type, credentialId, tenantId) => {
|
|
19276
|
-
const params = new URLSearchParams({ type, providerType
|
|
20577
|
+
const params = new URLSearchParams({ type, providerType });
|
|
19277
20578
|
if (credentialId) {
|
|
19278
20579
|
params.set("credentialId", credentialId);
|
|
19279
20580
|
}
|
|
19280
20581
|
const response = await fetch(`${serverUrl}/keys/recovery?${params}`, {
|
|
19281
20582
|
method: "GET",
|
|
19282
|
-
headers:
|
|
20583
|
+
headers: {
|
|
20584
|
+
"Content-Type": "application/json",
|
|
20585
|
+
"X-Auth-Token": token,
|
|
20586
|
+
...tenantId ? { "X-Tenant-Id": tenantId } : {}
|
|
20587
|
+
}
|
|
19283
20588
|
});
|
|
19284
20589
|
if (!response.ok) {
|
|
19285
20590
|
throw new Error(`No ${type} recovery share found`);
|
|
19286
20591
|
}
|
|
19287
20592
|
return response.json();
|
|
19288
20593
|
}, "fetchRecoveryShare");
|
|
20594
|
+
var IdentityRecoverySessionConsumedError = class extends Error {
|
|
20595
|
+
static {
|
|
20596
|
+
__name(this, "IdentityRecoverySessionConsumedError");
|
|
20597
|
+
}
|
|
20598
|
+
constructor(message) {
|
|
20599
|
+
super(message);
|
|
20600
|
+
this.name = "IdentityRecoverySessionConsumedError";
|
|
20601
|
+
}
|
|
20602
|
+
};
|
|
20603
|
+
var postJson = /* @__PURE__ */ __name(async (url, body, headers) => {
|
|
20604
|
+
const response = await fetch(url, {
|
|
20605
|
+
method: "POST",
|
|
20606
|
+
headers,
|
|
20607
|
+
body: JSON.stringify(body)
|
|
20608
|
+
});
|
|
20609
|
+
if (!response.ok) {
|
|
20610
|
+
const data = await response.json().catch(() => null);
|
|
20611
|
+
const message = data?.error?.message || data?.message || response.statusText;
|
|
20612
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
20613
|
+
throw new ShareWriteRejectedError(message);
|
|
20614
|
+
}
|
|
20615
|
+
throw new Error(message);
|
|
20616
|
+
}
|
|
20617
|
+
return response.json();
|
|
20618
|
+
}, "postJson");
|
|
19289
20619
|
var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
19290
20620
|
const response = await fetch(`${serverUrl}/keys/recovery`, {
|
|
19291
20621
|
method: "POST",
|
|
@@ -19293,9 +20623,40 @@ var postRecoveryMethod = /* @__PURE__ */ __name(async (serverUrl, token, provide
|
|
|
19293
20623
|
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
19294
20624
|
});
|
|
19295
20625
|
if (!response.ok) {
|
|
19296
|
-
throw new Error(`Failed to add recovery method: ${response
|
|
20626
|
+
throw new Error(`Failed to add recovery method: ${describeHttpStatus(response)}`);
|
|
19297
20627
|
}
|
|
19298
20628
|
}, "postRecoveryMethod");
|
|
20629
|
+
var postRecoveryConfirmation = /* @__PURE__ */ __name(async (serverUrl, token, providerType, body, didAuthVp, tenantId) => {
|
|
20630
|
+
const response = await fetch(`${serverUrl}/keys/recovery/confirm`, {
|
|
20631
|
+
method: "POST",
|
|
20632
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
20633
|
+
body: JSON.stringify({ authToken: token, providerType, ...body })
|
|
20634
|
+
});
|
|
20635
|
+
if (!response.ok) {
|
|
20636
|
+
const data = await response.json().catch(() => null);
|
|
20637
|
+
const message = data?.error?.message || data?.message || `Failed to confirm recovery method`;
|
|
20638
|
+
throw new Error(message);
|
|
20639
|
+
}
|
|
20640
|
+
}, "postRecoveryConfirmation");
|
|
20641
|
+
var calculateShareChecksum = /* @__PURE__ */ __name(async (share) => {
|
|
20642
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(share));
|
|
20643
|
+
return bytesToHex(new Uint8Array(digest));
|
|
20644
|
+
}, "calculateShareChecksum");
|
|
20645
|
+
var RECOVERY_PHRASE_CHALLENGE_COUNT = 2;
|
|
20646
|
+
var selectRecoveryPhraseChallengeIndices = /* @__PURE__ */ __name((wordCount, challengeCount = RECOVERY_PHRASE_CHALLENGE_COUNT) => {
|
|
20647
|
+
if (wordCount < challengeCount) {
|
|
20648
|
+
throw new Error("Recovery phrase does not contain enough words for confirmation");
|
|
20649
|
+
}
|
|
20650
|
+
const indices = /* @__PURE__ */ new Set();
|
|
20651
|
+
const limit = 4294967296 - 4294967296 % wordCount;
|
|
20652
|
+
while (indices.size < challengeCount) {
|
|
20653
|
+
const randomValue = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
20654
|
+
if (randomValue === void 0) throw new Error("Failed to choose recovery phrase words");
|
|
20655
|
+
if (randomValue >= limit) continue;
|
|
20656
|
+
indices.add(randomValue % wordCount);
|
|
20657
|
+
}
|
|
20658
|
+
return [...indices].sort((a, b) => a - b);
|
|
20659
|
+
}, "selectRecoveryPhraseChallengeIndices");
|
|
19299
20660
|
var VERSION_PREFIX_LEN = 4;
|
|
19300
20661
|
var formatVersionedEmailShare = /* @__PURE__ */ __name((emailShare, shareVersion) => shareVersion.toString(16).padStart(VERSION_PREFIX_LEN, "0") + emailShare, "formatVersionedEmailShare");
|
|
19301
20662
|
var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
@@ -19308,44 +20669,512 @@ var parseVersionedEmailShare = /* @__PURE__ */ __name((input) => {
|
|
|
19308
20669
|
}
|
|
19309
20670
|
return { share: input, version: void 0 };
|
|
19310
20671
|
}, "parseVersionedEmailShare");
|
|
19311
|
-
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, shareVersion, tenantId) => {
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
}
|
|
19325
|
-
}, "sendEmailBackupShare");
|
|
19326
|
-
var sendEmailShareToRecoveryEmail = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, shareVersion, tenantId) => {
|
|
19327
|
-
const payload = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20672
|
+
var sendEmailBackupShare = /* @__PURE__ */ __name(async (serverUrl, token, providerType, emailShare, email, escrowRelayPublicKey, escrowRelayKeyId, emailBranding, shareVersion, didAuthVp, tenantId) => {
|
|
20673
|
+
const recoveryKey = shareVersion != null ? formatVersionedEmailShare(emailShare, shareVersion) : emailShare;
|
|
20674
|
+
const confirmationCode = generateEmailRelayConfirmationCode();
|
|
20675
|
+
const relayPayload = await encryptEmailRelayPayload(
|
|
20676
|
+
{
|
|
20677
|
+
targetEmail: email,
|
|
20678
|
+
recoveryKey,
|
|
20679
|
+
confirmationCode,
|
|
20680
|
+
branding: emailBranding
|
|
20681
|
+
},
|
|
20682
|
+
escrowRelayPublicKey,
|
|
20683
|
+
escrowRelayKeyId
|
|
20684
|
+
);
|
|
19328
20685
|
const response = await fetch(`${serverUrl}/keys/email-backup`, {
|
|
19329
20686
|
method: "POST",
|
|
19330
|
-
headers: buildHeaders(token,
|
|
20687
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19331
20688
|
body: JSON.stringify({
|
|
19332
20689
|
authToken: token,
|
|
19333
20690
|
providerType,
|
|
19334
|
-
|
|
19335
|
-
|
|
20691
|
+
relayPayload,
|
|
20692
|
+
confirmationCode,
|
|
20693
|
+
email,
|
|
20694
|
+
shareVersion
|
|
19336
20695
|
})
|
|
19337
20696
|
});
|
|
19338
20697
|
if (!response.ok) {
|
|
19339
|
-
throw new Error(`Failed to send recovery share
|
|
20698
|
+
throw new Error(`Failed to send recovery share: ${describeHttpStatus(response)}`);
|
|
20699
|
+
}
|
|
20700
|
+
}, "sendEmailBackupShare");
|
|
20701
|
+
var rotationChain = Promise.resolve();
|
|
20702
|
+
var withRotationLock = /* @__PURE__ */ __name((operation) => {
|
|
20703
|
+
const run = rotationChain.then(operation, operation);
|
|
20704
|
+
rotationChain = run.catch(() => void 0);
|
|
20705
|
+
return run;
|
|
20706
|
+
}, "withRotationLock");
|
|
20707
|
+
var pendingShareId = /* @__PURE__ */ __name((storageId) => `sss-pending-share:${storageId ?? DEFAULT_DEVICE_SHARE_ID2}`, "pendingShareId");
|
|
20708
|
+
var PENDING_WRITE_EXPIRY_MS = 10 * 60 * 1e3;
|
|
20709
|
+
var MAX_PENDING_SHARE_CANDIDATES = 8;
|
|
20710
|
+
var readPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => {
|
|
20711
|
+
const raw = await storage.getDeviceShare(pendingShareId(storageId));
|
|
20712
|
+
if (!raw) return [];
|
|
20713
|
+
if (!raw.trimStart().startsWith("{")) return [{ share: raw, createdAt: 0 }];
|
|
20714
|
+
const data = JSON.parse(raw);
|
|
20715
|
+
if (!data || typeof data !== "object" || !("v" in data) || data.v !== 1 || !("candidates" in data) || !Array.isArray(data.candidates) || !data.candidates.every(
|
|
20716
|
+
(candidate) => candidate !== null && typeof candidate === "object" && "share" in candidate && typeof candidate.share === "string" && "createdAt" in candidate && typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt) && candidate.createdAt >= 0
|
|
20717
|
+
))
|
|
20718
|
+
throw new Error("Invalid pending share candidates");
|
|
20719
|
+
return data.candidates;
|
|
20720
|
+
}, "readPendingShareCandidates");
|
|
20721
|
+
var clearPendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId) => storage.clearAllShares(pendingShareId(storageId)), "clearPendingShareCandidates");
|
|
20722
|
+
var writePendingShareCandidates = /* @__PURE__ */ __name(async (storage, candidates, storageId) => {
|
|
20723
|
+
if (!candidates.length) return clearPendingShareCandidates(storage, storageId);
|
|
20724
|
+
await storage.storeDeviceShare(JSON.stringify({ v: 1, candidates }), pendingShareId(storageId));
|
|
20725
|
+
}, "writePendingShareCandidates");
|
|
20726
|
+
var addPendingShareCandidate = /* @__PURE__ */ __name(async (storage, candidate, storageId) => {
|
|
20727
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20728
|
+
if (candidates.length >= MAX_PENDING_SHARE_CANDIDATES) {
|
|
20729
|
+
throw new AtomicUpdateError(
|
|
20730
|
+
"Reconcile the pending share update before writing new shares",
|
|
20731
|
+
"store_device",
|
|
20732
|
+
false
|
|
20733
|
+
);
|
|
20734
|
+
}
|
|
20735
|
+
await writePendingShareCandidates(storage, [...candidates, candidate], storageId);
|
|
20736
|
+
}, "addPendingShareCandidate");
|
|
20737
|
+
var removePendingShareCandidate = /* @__PURE__ */ __name(async (storage, share, storageId) => {
|
|
20738
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20739
|
+
await writePendingShareCandidates(
|
|
20740
|
+
storage,
|
|
20741
|
+
candidates.filter((candidate) => candidate.share !== share),
|
|
20742
|
+
storageId
|
|
20743
|
+
);
|
|
20744
|
+
}, "removePendingShareCandidate");
|
|
20745
|
+
var resolvePendingShareCandidates = /* @__PURE__ */ __name(async (storage, storageId, fetchCurrent, expectedDid, didFromPrivateKey, assertActiveUser = () => {
|
|
20746
|
+
}, onPromoted, fetchHistorical) => {
|
|
20747
|
+
const candidates = await readPendingShareCandidates(storage, storageId);
|
|
20748
|
+
const snapshot = await fetchCurrent();
|
|
20749
|
+
assertActiveUser();
|
|
20750
|
+
const { authShare, currentVersion } = snapshot;
|
|
20751
|
+
if (authShare && (currentVersion === null || !Number.isInteger(currentVersion) || currentVersion < 1)) {
|
|
20752
|
+
throw new Error("Current auth share is missing a valid share version");
|
|
20753
|
+
}
|
|
20754
|
+
if (authShare && currentVersion !== null) {
|
|
20755
|
+
for (const candidate of candidates) {
|
|
20756
|
+
const health = await verifyStoredShares(
|
|
20757
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth") },
|
|
20758
|
+
expectedDid,
|
|
20759
|
+
didFromPrivateKey
|
|
20760
|
+
);
|
|
20761
|
+
assertActiveUser();
|
|
20762
|
+
if (!health.healthy) continue;
|
|
20763
|
+
const privateKey = await reconstructFromShares([candidate.share, authShare]);
|
|
20764
|
+
assertActiveUser();
|
|
20765
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20766
|
+
await storage.storeShareVersion(currentVersion, storageId);
|
|
20767
|
+
await removePendingShareCandidate(storage, candidate.share, storageId);
|
|
20768
|
+
assertActiveUser();
|
|
20769
|
+
onPromoted?.(snapshot);
|
|
20770
|
+
return { snapshot, privateKey };
|
|
20771
|
+
}
|
|
20772
|
+
}
|
|
20773
|
+
const matched = /* @__PURE__ */ new Set();
|
|
20774
|
+
const history = /* @__PURE__ */ new Map();
|
|
20775
|
+
let historyComplete = true;
|
|
20776
|
+
if (candidates.length && currentVersion !== null) {
|
|
20777
|
+
for (let version = currentVersion - 1; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
20778
|
+
if (!fetchHistorical) {
|
|
20779
|
+
historyComplete = false;
|
|
20780
|
+
break;
|
|
20781
|
+
}
|
|
20782
|
+
try {
|
|
20783
|
+
const share = await fetchHistorical(version);
|
|
20784
|
+
if (share) history.set(version, share);
|
|
20785
|
+
} catch {
|
|
20786
|
+
historyComplete = false;
|
|
20787
|
+
}
|
|
20788
|
+
assertActiveUser();
|
|
20789
|
+
}
|
|
20790
|
+
}
|
|
20791
|
+
const mainShare = await storage.getDeviceShare(storageId);
|
|
20792
|
+
let mainHealthy = false;
|
|
20793
|
+
const retainedShares = [...history.values(), ...authShare ? [authShare] : []];
|
|
20794
|
+
if (mainShare) {
|
|
20795
|
+
for (const share of retainedShares) {
|
|
20796
|
+
const health = await verifyStoredShares(
|
|
20797
|
+
{ getDevice: /* @__PURE__ */ __name(async () => mainShare, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20798
|
+
expectedDid,
|
|
20799
|
+
didFromPrivateKey
|
|
20800
|
+
);
|
|
20801
|
+
assertActiveUser();
|
|
20802
|
+
if (health.healthy) {
|
|
20803
|
+
mainHealthy = true;
|
|
20804
|
+
break;
|
|
20805
|
+
}
|
|
20806
|
+
}
|
|
20807
|
+
}
|
|
20808
|
+
let promoted;
|
|
20809
|
+
for (const candidate of candidates) {
|
|
20810
|
+
for (const [version, share] of history) {
|
|
20811
|
+
const health = await verifyStoredShares(
|
|
20812
|
+
{ getDevice: /* @__PURE__ */ __name(async () => candidate.share, "getDevice"), getAuth: /* @__PURE__ */ __name(async () => share, "getAuth") },
|
|
20813
|
+
expectedDid,
|
|
20814
|
+
didFromPrivateKey
|
|
20815
|
+
);
|
|
20816
|
+
assertActiveUser();
|
|
20817
|
+
if (!health.healthy) continue;
|
|
20818
|
+
matched.add(candidate.share);
|
|
20819
|
+
if (!mainHealthy && !promoted) {
|
|
20820
|
+
await storage.storeDeviceShare(candidate.share, storageId);
|
|
20821
|
+
await storage.storeShareVersion(version, storageId);
|
|
20822
|
+
assertActiveUser();
|
|
20823
|
+
onPromoted?.({ currentVersion: version, authShare: share });
|
|
20824
|
+
promoted = candidate.share;
|
|
20825
|
+
}
|
|
20826
|
+
break;
|
|
20827
|
+
}
|
|
20828
|
+
}
|
|
20829
|
+
const live = candidates.filter(
|
|
20830
|
+
(candidate) => candidate.share !== promoted && (matched.has(candidate.share) || !historyComplete || candidate.createdAt !== 0 && Date.now() - candidate.createdAt <= PENDING_WRITE_EXPIRY_MS)
|
|
20831
|
+
);
|
|
20832
|
+
if (live.length !== candidates.length) {
|
|
20833
|
+
assertActiveUser();
|
|
20834
|
+
await writePendingShareCandidates(storage, live, storageId);
|
|
20835
|
+
}
|
|
20836
|
+
return { snapshot, privateKey: null };
|
|
20837
|
+
}, "resolvePendingShareCandidates");
|
|
20838
|
+
var activeShareUpdates = /* @__PURE__ */ new WeakMap();
|
|
20839
|
+
var shareUpdateStorageOwners = /* @__PURE__ */ new WeakMap();
|
|
20840
|
+
var withShareUpdateLock = /* @__PURE__ */ __name(async (storage, storageId, operation) => {
|
|
20841
|
+
const id = pendingShareId(storageId);
|
|
20842
|
+
const owner = shareUpdateStorageOwners.get(storage) ?? storage;
|
|
20843
|
+
const active = activeShareUpdates.get(owner) ?? /* @__PURE__ */ new Set();
|
|
20844
|
+
activeShareUpdates.set(owner, active);
|
|
20845
|
+
const busy = /* @__PURE__ */ __name(() => new AtomicUpdateError("A share update is already in progress", "store_device", false), "busy");
|
|
20846
|
+
if (active.has(id)) throw busy();
|
|
20847
|
+
active.add(id);
|
|
20848
|
+
try {
|
|
20849
|
+
if (typeof navigator !== "undefined" && navigator.locks) {
|
|
20850
|
+
return await navigator.locks.request(id, { ifAvailable: true }, async (lock) => {
|
|
20851
|
+
if (!lock) throw busy();
|
|
20852
|
+
return operation();
|
|
20853
|
+
});
|
|
20854
|
+
}
|
|
20855
|
+
return await operation();
|
|
20856
|
+
} finally {
|
|
20857
|
+
active.delete(id);
|
|
20858
|
+
}
|
|
20859
|
+
}, "withShareUpdateLock");
|
|
20860
|
+
var persistSharesWithoutLock = /* @__PURE__ */ __name(async (privateKey, serverUrl, token, providerType, primaryDid, storage, storageId, didFromPrivateKey, signDidAuthVp, tenantId, onPromoted, assertActiveUser = () => {
|
|
20861
|
+
}) => {
|
|
20862
|
+
if (primaryDid && didFromPrivateKey) {
|
|
20863
|
+
const derivedDid = await didFromPrivateKey(privateKey);
|
|
20864
|
+
if (derivedDid && derivedDid !== primaryDid) {
|
|
20865
|
+
throw new Error(
|
|
20866
|
+
"rotateShares: key does not match expected DID \u2014 refusing to overwrite server shares"
|
|
20867
|
+
);
|
|
20868
|
+
}
|
|
20869
|
+
}
|
|
20870
|
+
const { snapshot } = await resolvePendingShareCandidates(
|
|
20871
|
+
storage,
|
|
20872
|
+
storageId,
|
|
20873
|
+
async () => {
|
|
20874
|
+
const data = await fetchAuthShareRaw(
|
|
20875
|
+
serverUrl,
|
|
20876
|
+
token,
|
|
20877
|
+
providerType,
|
|
20878
|
+
void 0,
|
|
20879
|
+
tenantId
|
|
20880
|
+
);
|
|
20881
|
+
return {
|
|
20882
|
+
currentVersion: data?.shareVersion ?? null,
|
|
20883
|
+
authShare: authShareToString(data?.authShare)
|
|
20884
|
+
};
|
|
20885
|
+
},
|
|
20886
|
+
primaryDid,
|
|
20887
|
+
didFromPrivateKey ?? (async (key) => key === privateKey ? primaryDid : `${primaryDid}:mismatch`),
|
|
20888
|
+
assertActiveUser,
|
|
20889
|
+
onPromoted,
|
|
20890
|
+
async (version) => authShareToString(
|
|
20891
|
+
(await fetchAuthShareRaw(serverUrl, token, providerType, version, tenantId))?.authShare
|
|
20892
|
+
)
|
|
20893
|
+
);
|
|
20894
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(serverUrl, privateKey, primaryDid, signDidAuthVp, tenantId) : void 0;
|
|
20895
|
+
assertActiveUser();
|
|
20896
|
+
let shareVersion;
|
|
20897
|
+
let stagedShare = "";
|
|
20898
|
+
const shares = await atomicShareUpdate(privateKey, {
|
|
20899
|
+
storeDevice: /* @__PURE__ */ __name(async (share) => {
|
|
20900
|
+
assertActiveUser();
|
|
20901
|
+
stagedShare = share;
|
|
20902
|
+
await addPendingShareCandidate(storage, { share, createdAt: Date.now() }, storageId);
|
|
20903
|
+
}, "storeDevice"),
|
|
20904
|
+
clearDevice: /* @__PURE__ */ __name(() => removePendingShareCandidate(storage, stagedShare, storageId), "clearDevice"),
|
|
20905
|
+
storeAuth: /* @__PURE__ */ __name(async (share) => {
|
|
20906
|
+
assertActiveUser();
|
|
20907
|
+
const result = await putAuthShare(
|
|
20908
|
+
serverUrl,
|
|
20909
|
+
token,
|
|
20910
|
+
providerType,
|
|
20911
|
+
share,
|
|
20912
|
+
primaryDid,
|
|
20913
|
+
didAuthVp,
|
|
20914
|
+
tenantId,
|
|
20915
|
+
snapshot.currentVersion ?? 0
|
|
20916
|
+
);
|
|
20917
|
+
shareVersion = result.shareVersion;
|
|
20918
|
+
}, "storeAuth")
|
|
20919
|
+
});
|
|
20920
|
+
if (shareVersion === void 0) {
|
|
20921
|
+
throw new Error("Server did not return a share version");
|
|
19340
20922
|
}
|
|
19341
|
-
|
|
20923
|
+
await storage.storeDeviceShare(shares.deviceShare, storageId);
|
|
20924
|
+
await storage.storeShareVersion(shareVersion, storageId);
|
|
20925
|
+
await removePendingShareCandidate(storage, shares.deviceShare, storageId);
|
|
20926
|
+
assertActiveUser();
|
|
20927
|
+
return { shares, shareVersion };
|
|
20928
|
+
}, "persistSharesWithoutLock");
|
|
20929
|
+
var persistSharesAtomically = /* @__PURE__ */ __name((...args) => withShareUpdateLock(
|
|
20930
|
+
args[5],
|
|
20931
|
+
args[6],
|
|
20932
|
+
() => withRotationLock(() => persistSharesWithoutLock(...args))
|
|
20933
|
+
), "persistSharesAtomically");
|
|
19342
20934
|
function createSSSStrategy(config) {
|
|
19343
|
-
const {
|
|
20935
|
+
const {
|
|
20936
|
+
serverUrl,
|
|
20937
|
+
enableEmailBackupShare = false,
|
|
20938
|
+
escrowRelayPublicKey = "",
|
|
20939
|
+
escrowRelayKeyId = "",
|
|
20940
|
+
emailBranding,
|
|
20941
|
+
tenantId
|
|
20942
|
+
} = config;
|
|
19344
20943
|
const storage = config.storage || defaultStorage;
|
|
20944
|
+
let storageGeneration = 0;
|
|
20945
|
+
const localWrites = /* @__PURE__ */ new Set();
|
|
20946
|
+
const guardedStorage = /* @__PURE__ */ __name(() => {
|
|
20947
|
+
const generation = storageGeneration;
|
|
20948
|
+
const write = /* @__PURE__ */ __name((operation) => {
|
|
20949
|
+
if (generation !== storageGeneration) {
|
|
20950
|
+
return Promise.reject(new Error("This recovery request was cancelled."));
|
|
20951
|
+
}
|
|
20952
|
+
const pending = operation();
|
|
20953
|
+
localWrites.add(pending);
|
|
20954
|
+
return pending.finally(() => localWrites.delete(pending));
|
|
20955
|
+
}, "write");
|
|
20956
|
+
const guarded = {
|
|
20957
|
+
...storage,
|
|
20958
|
+
storeDeviceShare: /* @__PURE__ */ __name((share, id) => write(() => storage.storeDeviceShare(share, id)), "storeDeviceShare"),
|
|
20959
|
+
storeShareVersion: /* @__PURE__ */ __name((version, id) => write(() => storage.storeShareVersion(version, id)), "storeShareVersion"),
|
|
20960
|
+
clearAllShares: /* @__PURE__ */ __name((id) => write(() => storage.clearAllShares(id)), "clearAllShares"),
|
|
20961
|
+
deleteDeviceShare: /* @__PURE__ */ __name((id) => write(() => storage.deleteDeviceShare(id)), "deleteDeviceShare")
|
|
20962
|
+
};
|
|
20963
|
+
shareUpdateStorageOwners.set(guarded, storage);
|
|
20964
|
+
return guarded;
|
|
20965
|
+
}, "guardedStorage");
|
|
19345
20966
|
let activeStorageId;
|
|
19346
20967
|
let lastEmailShare;
|
|
19347
20968
|
let lastShareVersion;
|
|
19348
|
-
let
|
|
20969
|
+
let pendingPhraseConfirmation;
|
|
20970
|
+
let pendingBackupConfirmation;
|
|
20971
|
+
let pendingIdentityRecovery;
|
|
20972
|
+
let lastServerSnapshot = null;
|
|
20973
|
+
let escrowEnrollmentInFlight;
|
|
20974
|
+
let unenrolledRotation;
|
|
20975
|
+
const escrowRequest = /* @__PURE__ */ __name(async (path, init, pinAttempt = false) => {
|
|
20976
|
+
const response = await fetch(`${serverUrl}/keys/escrow${path}`, {
|
|
20977
|
+
signal: requestTimeoutSignal(),
|
|
20978
|
+
...init
|
|
20979
|
+
});
|
|
20980
|
+
if (!response.ok) {
|
|
20981
|
+
if (!pinAttempt && path === "/recover" && response.status === 429) {
|
|
20982
|
+
const body = await response.json().catch(() => void 0);
|
|
20983
|
+
const message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : "";
|
|
20984
|
+
const retryAfter = /Try again after (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\./.exec(
|
|
20985
|
+
message
|
|
20986
|
+
)?.[1];
|
|
20987
|
+
throw new EscrowHoldRestartThrottledError(
|
|
20988
|
+
retryAfter && Number.isFinite(Date.parse(retryAfter)) ? retryAfter : void 0
|
|
20989
|
+
);
|
|
20990
|
+
}
|
|
20991
|
+
if (pinAttempt && response.status === 429) {
|
|
20992
|
+
const body = await response.json().catch(() => void 0);
|
|
20993
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_LOCKED_MESSAGE) {
|
|
20994
|
+
throw new EscrowPinLockedError();
|
|
20995
|
+
}
|
|
20996
|
+
throw new EscrowPinThrottledError();
|
|
20997
|
+
}
|
|
20998
|
+
if (pinAttempt && response.status === 403) {
|
|
20999
|
+
const body = await response.json().catch(() => void 0);
|
|
21000
|
+
if (body && typeof body === "object" && "message" in body && body.message === import_types3.ESCROW_PIN_UNAVAILABLE_MESSAGE) {
|
|
21001
|
+
throw new EscrowPinUnavailableError();
|
|
21002
|
+
}
|
|
21003
|
+
if (body && typeof body === "object" && "message" in body && typeof body.message === "string" && body.message.startsWith("Incorrect PIN.")) {
|
|
21004
|
+
const match = import_types3.ESCROW_PIN_MISMATCH_PATTERN.exec(body.message);
|
|
21005
|
+
const remaining = match ? Number(match[1]) : 0;
|
|
21006
|
+
throw new EscrowPinMismatchError(
|
|
21007
|
+
Number.isSafeInteger(remaining) && remaining >= 0 && remaining < ESCROW_PIN_MAX_ATTEMPTS ? remaining : 0
|
|
21008
|
+
);
|
|
21009
|
+
}
|
|
21010
|
+
}
|
|
21011
|
+
throw new EscrowRequestError(response.status);
|
|
21012
|
+
}
|
|
21013
|
+
try {
|
|
21014
|
+
return await response.json();
|
|
21015
|
+
} catch {
|
|
21016
|
+
throw new EscrowRequestError(response.status);
|
|
21017
|
+
}
|
|
21018
|
+
}, "escrowRequest");
|
|
21019
|
+
const fetchVerifiedEnclaveKey = /* @__PURE__ */ __name(async () => {
|
|
21020
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21021
|
+
const { attestation } = await escrowRequest("/attestation", {
|
|
21022
|
+
method: "GET",
|
|
21023
|
+
headers: buildHeaders("", void 0, tenantId)
|
|
21024
|
+
});
|
|
21025
|
+
return verifyEnclaveAttestation(attestation, config.escrow.attestation);
|
|
21026
|
+
}, "fetchVerifiedEnclaveKey");
|
|
21027
|
+
const enrollEscrow = /* @__PURE__ */ __name(async (token, providerType, privateKey, primaryDid, shares, shareVersion, signDidAuthVp, verifiedKey, pinMaterial, clearPin = false) => {
|
|
21028
|
+
if (!config.escrow?.enabled) throw new Error("Escrow enrollment is disabled");
|
|
21029
|
+
if (!signDidAuthVp) throw new Error("DID proof signing is required for escrow enrollment");
|
|
21030
|
+
const { publicKey, keyId } = verifiedKey ?? await fetchVerifiedEnclaveKey();
|
|
21031
|
+
const envelope = await encryptEscrowBlob(
|
|
21032
|
+
{
|
|
21033
|
+
recoveryShare: shares.recoveryShare,
|
|
21034
|
+
did: primaryDid,
|
|
21035
|
+
shareVersion,
|
|
21036
|
+
...pinMaterial ? { pinVerifier: pinMaterial.pinVerifier } : {}
|
|
21037
|
+
},
|
|
21038
|
+
publicKey,
|
|
21039
|
+
keyId
|
|
21040
|
+
);
|
|
21041
|
+
const vp = await requestFreshDidAuthVp(
|
|
21042
|
+
serverUrl,
|
|
21043
|
+
privateKey,
|
|
21044
|
+
primaryDid,
|
|
21045
|
+
signDidAuthVp,
|
|
21046
|
+
tenantId
|
|
21047
|
+
);
|
|
21048
|
+
await escrowRequest("", {
|
|
21049
|
+
method: "POST",
|
|
21050
|
+
headers: buildHeaders(token, vp, tenantId),
|
|
21051
|
+
body: JSON.stringify({
|
|
21052
|
+
authToken: token,
|
|
21053
|
+
providerType,
|
|
21054
|
+
envelope,
|
|
21055
|
+
shareVersion,
|
|
21056
|
+
enclaveKeyId: keyId,
|
|
21057
|
+
...pinMaterial ? { pinSalt: pinMaterial.pinSalt } : {},
|
|
21058
|
+
...clearPin ? { clearPin: true } : {}
|
|
21059
|
+
})
|
|
21060
|
+
});
|
|
21061
|
+
}, "enrollEscrow");
|
|
21062
|
+
const tryEnrollEscrow = /* @__PURE__ */ __name(async (...args) => {
|
|
21063
|
+
if (!config.escrow?.enabled) return;
|
|
21064
|
+
try {
|
|
21065
|
+
await enrollEscrow(...args);
|
|
21066
|
+
} catch (err) {
|
|
21067
|
+
try {
|
|
21068
|
+
if (config.onEscrowError) config.onEscrowError(err);
|
|
21069
|
+
else console.warn("SSS: escrow enrollment failed");
|
|
21070
|
+
} catch {
|
|
21071
|
+
console.warn("SSS: escrow error reporting failed");
|
|
21072
|
+
}
|
|
21073
|
+
}
|
|
21074
|
+
}, "tryEnrollEscrow");
|
|
21075
|
+
const ensureEscrowEnrollmentUnguarded = /* @__PURE__ */ __name(async (self, params, storage2, forceRotate = false) => {
|
|
21076
|
+
if (!config.escrow?.enabled) return { enrolled: false, reason: "disabled" };
|
|
21077
|
+
const pin = params.options?.pin;
|
|
21078
|
+
if (pin !== void 0) {
|
|
21079
|
+
const validation = validatePin(pin);
|
|
21080
|
+
if (!validation.ok)
|
|
21081
|
+
throw new Error(
|
|
21082
|
+
validation.reason === "length" ? "PIN must contain 6\u201312 digits." : "Choose a PIN without trivial or sequential patterns."
|
|
21083
|
+
);
|
|
21084
|
+
}
|
|
21085
|
+
const status = await self.fetchServerKeyStatus(params.token, params.providerType);
|
|
21086
|
+
if (status.escrowOptedOut) return { enrolled: false, reason: "opted-out" };
|
|
21087
|
+
if (!forceRotate && pin === void 0 && status.shareVersion !== null && status.recoveryMethods.some(
|
|
21088
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21089
|
+
))
|
|
21090
|
+
return { enrolled: true, changed: false };
|
|
21091
|
+
if (!status.primaryDid) throw new Error("Cannot enroll escrow without a primary DID");
|
|
21092
|
+
if (!params.signDidAuthVp) {
|
|
21093
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21094
|
+
}
|
|
21095
|
+
if (!forceRotate && pin === void 0 && unenrolledRotation && unenrolledRotation.shareVersion === status.shareVersion && unenrolledRotation.primaryDid === status.primaryDid) {
|
|
21096
|
+
const retry = unenrolledRotation;
|
|
21097
|
+
await enrollEscrow(
|
|
21098
|
+
params.token,
|
|
21099
|
+
params.providerType,
|
|
21100
|
+
params.privateKey,
|
|
21101
|
+
retry.primaryDid,
|
|
21102
|
+
retry.shares,
|
|
21103
|
+
retry.shareVersion,
|
|
21104
|
+
params.signDidAuthVp,
|
|
21105
|
+
void 0,
|
|
21106
|
+
void 0,
|
|
21107
|
+
retry.clearPin
|
|
21108
|
+
);
|
|
21109
|
+
unenrolledRotation = void 0;
|
|
21110
|
+
return { enrolled: true, changed: true, shareVersion: retry.shareVersion };
|
|
21111
|
+
}
|
|
21112
|
+
const verifiedKey = await fetchVerifiedEnclaveKey();
|
|
21113
|
+
const pinSalt = pin !== void 0 ? generatePinSalt() : void 0;
|
|
21114
|
+
const pinMaterial = pin !== void 0 && pinSalt !== void 0 ? { pinSalt, pinVerifier: await derivePinProof(pin, pinSalt) } : void 0;
|
|
21115
|
+
const storageId = activeStorageId;
|
|
21116
|
+
const primaryDid = status.primaryDid;
|
|
21117
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
21118
|
+
storage2,
|
|
21119
|
+
storageId,
|
|
21120
|
+
() => persistSharesWithoutLock(
|
|
21121
|
+
params.privateKey,
|
|
21122
|
+
serverUrl,
|
|
21123
|
+
params.token,
|
|
21124
|
+
params.providerType,
|
|
21125
|
+
primaryDid,
|
|
21126
|
+
storage2,
|
|
21127
|
+
storageId,
|
|
21128
|
+
void 0,
|
|
21129
|
+
params.signDidAuthVp,
|
|
21130
|
+
tenantId
|
|
21131
|
+
)
|
|
21132
|
+
);
|
|
21133
|
+
lastEmailShare = shares.emailShare;
|
|
21134
|
+
lastShareVersion = shareVersion;
|
|
21135
|
+
lastServerSnapshot = {
|
|
21136
|
+
currentVersion: shareVersion,
|
|
21137
|
+
resolvedVersion: shareVersion,
|
|
21138
|
+
authShare: shares.authShare,
|
|
21139
|
+
primaryDid: status.primaryDid
|
|
21140
|
+
};
|
|
21141
|
+
unenrolledRotation = pinMaterial ? void 0 : { shares, shareVersion, primaryDid: status.primaryDid, clearPin: forceRotate };
|
|
21142
|
+
await enrollEscrow(
|
|
21143
|
+
params.token,
|
|
21144
|
+
params.providerType,
|
|
21145
|
+
params.privateKey,
|
|
21146
|
+
status.primaryDid,
|
|
21147
|
+
shares,
|
|
21148
|
+
shareVersion,
|
|
21149
|
+
params.signDidAuthVp,
|
|
21150
|
+
verifiedKey,
|
|
21151
|
+
pinMaterial,
|
|
21152
|
+
forceRotate
|
|
21153
|
+
);
|
|
21154
|
+
unenrolledRotation = void 0;
|
|
21155
|
+
return { enrolled: true, changed: true, shareVersion };
|
|
21156
|
+
}, "ensureEscrowEnrollmentUnguarded");
|
|
21157
|
+
const recordPromotion = /* @__PURE__ */ __name((primaryDid) => (snapshot) => {
|
|
21158
|
+
lastEmailShare = void 0;
|
|
21159
|
+
lastShareVersion = snapshot.currentVersion ?? void 0;
|
|
21160
|
+
lastServerSnapshot = {
|
|
21161
|
+
...snapshot,
|
|
21162
|
+
resolvedVersion: snapshot.currentVersion,
|
|
21163
|
+
primaryDid
|
|
21164
|
+
};
|
|
21165
|
+
}, "recordPromotion");
|
|
21166
|
+
const captureActiveUserGuard = /* @__PURE__ */ __name(() => {
|
|
21167
|
+
const storageId = activeStorageId;
|
|
21168
|
+
return () => {
|
|
21169
|
+
if (activeStorageId !== storageId) {
|
|
21170
|
+
throw new AtomicUpdateError(
|
|
21171
|
+
"Active account changed during share update",
|
|
21172
|
+
"verify_stored",
|
|
21173
|
+
false
|
|
21174
|
+
);
|
|
21175
|
+
}
|
|
21176
|
+
};
|
|
21177
|
+
}, "captureActiveUserGuard");
|
|
19349
21178
|
return {
|
|
19350
21179
|
name: "sss",
|
|
19351
21180
|
capabilities: {
|
|
@@ -19356,23 +21185,31 @@ function createSSSStrategy(config) {
|
|
|
19356
21185
|
},
|
|
19357
21186
|
// --- User scoping ---
|
|
19358
21187
|
setActiveUser(userId) {
|
|
21188
|
+
if (activeStorageId !== `sss-device-share:${userId}`) storageGeneration++;
|
|
19359
21189
|
activeStorageId = `sss-device-share:${userId}`;
|
|
21190
|
+
lastServerSnapshot = null;
|
|
21191
|
+
pendingPhraseConfirmation = void 0;
|
|
21192
|
+
pendingBackupConfirmation = void 0;
|
|
19360
21193
|
},
|
|
19361
21194
|
// --- Key lifecycle ---
|
|
19362
21195
|
async hasLocalKey() {
|
|
19363
21196
|
if (await storage.hasDeviceShare(activeStorageId)) return true;
|
|
21197
|
+
if ((await readPendingShareCandidates(storage, activeStorageId)).length) return true;
|
|
19364
21198
|
if (activeStorageId) {
|
|
19365
21199
|
return storage.hasDeviceShare();
|
|
19366
21200
|
}
|
|
19367
21201
|
return false;
|
|
19368
21202
|
},
|
|
19369
21203
|
async getLocalKey() {
|
|
19370
|
-
const
|
|
21204
|
+
const storage2 = guardedStorage();
|
|
21205
|
+
const scoped = await storage2.getDeviceShare(activeStorageId);
|
|
19371
21206
|
if (scoped) return scoped;
|
|
21207
|
+
const pending = await readPendingShareCandidates(storage2, activeStorageId);
|
|
21208
|
+
if (pending.length) return pending[pending.length - 1].share;
|
|
19372
21209
|
if (activeStorageId) {
|
|
19373
|
-
const legacy = await
|
|
21210
|
+
const legacy = await storage2.getDeviceShare();
|
|
19374
21211
|
if (legacy) {
|
|
19375
|
-
await
|
|
21212
|
+
await storage2.storeDeviceShare(legacy, activeStorageId);
|
|
19376
21213
|
return legacy;
|
|
19377
21214
|
}
|
|
19378
21215
|
}
|
|
@@ -19381,8 +21218,20 @@ function createSSSStrategy(config) {
|
|
|
19381
21218
|
async storeLocalKey(key) {
|
|
19382
21219
|
return storage.storeDeviceShare(key, activeStorageId);
|
|
19383
21220
|
},
|
|
19384
|
-
async clearLocalKeys() {
|
|
19385
|
-
|
|
21221
|
+
async clearLocalKeys(options) {
|
|
21222
|
+
const storageId = activeStorageId;
|
|
21223
|
+
if (options?.preservePending) {
|
|
21224
|
+
const storage2 = guardedStorage();
|
|
21225
|
+
return withShareUpdateLock(
|
|
21226
|
+
storage2,
|
|
21227
|
+
storageId,
|
|
21228
|
+
() => storage2.deleteDeviceShare(storageId)
|
|
21229
|
+
);
|
|
21230
|
+
}
|
|
21231
|
+
storageGeneration++;
|
|
21232
|
+
await Promise.allSettled(localWrites);
|
|
21233
|
+
await clearPendingShareCandidates(storage, storageId);
|
|
21234
|
+
return storage.clearAllShares(storageId);
|
|
19386
21235
|
},
|
|
19387
21236
|
async splitKey(privateKey) {
|
|
19388
21237
|
const { shares } = await splitAndVerify(privateKey);
|
|
@@ -19406,9 +21255,175 @@ function createSSSStrategy(config) {
|
|
|
19406
21255
|
);
|
|
19407
21256
|
return result.healthy;
|
|
19408
21257
|
},
|
|
21258
|
+
async getFreshDidAuthVp(privateKey, did, signDidAuthVp) {
|
|
21259
|
+
return requestFreshDidAuthVp(serverUrl, privateKey, did, signDidAuthVp, tenantId);
|
|
21260
|
+
},
|
|
21261
|
+
async atomicUpdateShares(params) {
|
|
21262
|
+
const storage2 = guardedStorage();
|
|
21263
|
+
const result = await persistSharesAtomically(
|
|
21264
|
+
params.privateKey,
|
|
21265
|
+
serverUrl,
|
|
21266
|
+
params.token,
|
|
21267
|
+
params.providerType,
|
|
21268
|
+
params.did,
|
|
21269
|
+
storage2,
|
|
21270
|
+
activeStorageId,
|
|
21271
|
+
void 0,
|
|
21272
|
+
params.signDidAuthVp,
|
|
21273
|
+
tenantId,
|
|
21274
|
+
recordPromotion(params.did),
|
|
21275
|
+
captureActiveUserGuard()
|
|
21276
|
+
);
|
|
21277
|
+
lastEmailShare = result.shares.emailShare;
|
|
21278
|
+
lastShareVersion = result.shareVersion;
|
|
21279
|
+
lastServerSnapshot = {
|
|
21280
|
+
currentVersion: result.shareVersion,
|
|
21281
|
+
resolvedVersion: result.shareVersion,
|
|
21282
|
+
authShare: result.shares.authShare,
|
|
21283
|
+
primaryDid: params.did
|
|
21284
|
+
};
|
|
21285
|
+
},
|
|
21286
|
+
async reconcileShares(params) {
|
|
21287
|
+
const storage2 = guardedStorage();
|
|
21288
|
+
const storageId = activeStorageId;
|
|
21289
|
+
const assertActiveUser = /* @__PURE__ */ __name(() => {
|
|
21290
|
+
if (activeStorageId !== storageId) {
|
|
21291
|
+
throw new AtomicUpdateError(
|
|
21292
|
+
"Active account changed during reconciliation",
|
|
21293
|
+
"verify_stored",
|
|
21294
|
+
false
|
|
21295
|
+
);
|
|
21296
|
+
}
|
|
21297
|
+
}, "assertActiveUser");
|
|
21298
|
+
return withShareUpdateLock(storage2, storageId, async () => {
|
|
21299
|
+
let deviceShare = await storage2.getDeviceShare(storageId);
|
|
21300
|
+
const pending = await readPendingShareCandidates(storage2, storageId);
|
|
21301
|
+
if (!deviceShare && !pending.length) return null;
|
|
21302
|
+
const resolution = await resolvePendingShareCandidates(
|
|
21303
|
+
storage2,
|
|
21304
|
+
storageId,
|
|
21305
|
+
async () => {
|
|
21306
|
+
const data = await fetchAuthShareRaw(
|
|
21307
|
+
serverUrl,
|
|
21308
|
+
params.token,
|
|
21309
|
+
params.providerType,
|
|
21310
|
+
void 0,
|
|
21311
|
+
tenantId
|
|
21312
|
+
);
|
|
21313
|
+
return {
|
|
21314
|
+
currentVersion: data?.shareVersion ?? null,
|
|
21315
|
+
authShare: authShareToString(data?.authShare)
|
|
21316
|
+
};
|
|
21317
|
+
},
|
|
21318
|
+
params.expectedDid,
|
|
21319
|
+
params.didFromPrivateKey,
|
|
21320
|
+
assertActiveUser,
|
|
21321
|
+
recordPromotion(params.expectedDid),
|
|
21322
|
+
async (version) => authShareToString(
|
|
21323
|
+
(await fetchAuthShareRaw(
|
|
21324
|
+
serverUrl,
|
|
21325
|
+
params.token,
|
|
21326
|
+
params.providerType,
|
|
21327
|
+
version,
|
|
21328
|
+
tenantId
|
|
21329
|
+
))?.authShare
|
|
21330
|
+
)
|
|
21331
|
+
);
|
|
21332
|
+
assertActiveUser();
|
|
21333
|
+
if (resolution.privateKey) {
|
|
21334
|
+
return { privateKey: resolution.privateKey, did: params.expectedDid };
|
|
21335
|
+
}
|
|
21336
|
+
const { currentVersion, authShare: currentAuthShare } = resolution.snapshot;
|
|
21337
|
+
if (currentVersion === null && !currentAuthShare) return null;
|
|
21338
|
+
deviceShare = await storage2.getDeviceShare(storageId);
|
|
21339
|
+
const localVersion = await storage2.getShareVersion(storageId);
|
|
21340
|
+
if (!deviceShare) return null;
|
|
21341
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
21342
|
+
const addCandidate = /* @__PURE__ */ __name((version, authShare) => {
|
|
21343
|
+
if (version !== null && authShare && !candidates.has(version)) {
|
|
21344
|
+
candidates.set(version, authShare);
|
|
21345
|
+
}
|
|
21346
|
+
}, "addCandidate");
|
|
21347
|
+
addCandidate(
|
|
21348
|
+
lastServerSnapshot?.resolvedVersion ?? null,
|
|
21349
|
+
lastServerSnapshot?.authShare ?? null
|
|
21350
|
+
);
|
|
21351
|
+
addCandidate(currentVersion, currentAuthShare);
|
|
21352
|
+
const versionsToTry = /* @__PURE__ */ new Set();
|
|
21353
|
+
if (localVersion !== null) versionsToTry.add(localVersion);
|
|
21354
|
+
if (currentVersion !== null) {
|
|
21355
|
+
for (let version = currentVersion; version >= 1 && version >= currentVersion - MAX_RECONCILIATION_HISTORY; version--) {
|
|
21356
|
+
versionsToTry.add(version);
|
|
21357
|
+
}
|
|
21358
|
+
}
|
|
21359
|
+
for (const version of versionsToTry) {
|
|
21360
|
+
if (candidates.has(version)) continue;
|
|
21361
|
+
const data = await fetchAuthShareRaw(
|
|
21362
|
+
serverUrl,
|
|
21363
|
+
params.token,
|
|
21364
|
+
params.providerType,
|
|
21365
|
+
version,
|
|
21366
|
+
tenantId
|
|
21367
|
+
);
|
|
21368
|
+
addCandidate(version, authShareToString(data?.authShare));
|
|
21369
|
+
}
|
|
21370
|
+
for (const [candidateVersion, authShare] of candidates) {
|
|
21371
|
+
const health = await verifyStoredShares(
|
|
21372
|
+
{
|
|
21373
|
+
getDevice: /* @__PURE__ */ __name(async () => deviceShare, "getDevice"),
|
|
21374
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
21375
|
+
},
|
|
21376
|
+
params.expectedDid,
|
|
21377
|
+
params.didFromPrivateKey
|
|
21378
|
+
);
|
|
21379
|
+
if (!health.healthy) continue;
|
|
21380
|
+
assertActiveUser();
|
|
21381
|
+
const privateKey = await reconstructFromShares([deviceShare, authShare]);
|
|
21382
|
+
if (candidateVersion === currentVersion) {
|
|
21383
|
+
if (localVersion !== currentVersion) {
|
|
21384
|
+
await storage2.storeShareVersion(candidateVersion, storageId);
|
|
21385
|
+
}
|
|
21386
|
+
assertActiveUser();
|
|
21387
|
+
lastServerSnapshot = {
|
|
21388
|
+
currentVersion,
|
|
21389
|
+
resolvedVersion: candidateVersion,
|
|
21390
|
+
authShare,
|
|
21391
|
+
primaryDid: params.expectedDid
|
|
21392
|
+
};
|
|
21393
|
+
return localVersion === currentVersion ? null : { privateKey, did: params.expectedDid };
|
|
21394
|
+
}
|
|
21395
|
+
const result = await persistSharesWithoutLock(
|
|
21396
|
+
privateKey,
|
|
21397
|
+
serverUrl,
|
|
21398
|
+
params.token,
|
|
21399
|
+
params.providerType,
|
|
21400
|
+
params.expectedDid,
|
|
21401
|
+
storage2,
|
|
21402
|
+
storageId,
|
|
21403
|
+
params.didFromPrivateKey,
|
|
21404
|
+
params.signDidAuthVp,
|
|
21405
|
+
tenantId,
|
|
21406
|
+
recordPromotion(params.expectedDid),
|
|
21407
|
+
assertActiveUser
|
|
21408
|
+
);
|
|
21409
|
+
assertActiveUser();
|
|
21410
|
+
lastEmailShare = result.shares.emailShare;
|
|
21411
|
+
lastShareVersion = result.shareVersion;
|
|
21412
|
+
lastServerSnapshot = {
|
|
21413
|
+
currentVersion: result.shareVersion,
|
|
21414
|
+
resolvedVersion: result.shareVersion,
|
|
21415
|
+
authShare: result.shares.authShare,
|
|
21416
|
+
primaryDid: params.expectedDid
|
|
21417
|
+
};
|
|
21418
|
+
return { privateKey, did: params.expectedDid };
|
|
21419
|
+
}
|
|
21420
|
+
return null;
|
|
21421
|
+
});
|
|
21422
|
+
},
|
|
19409
21423
|
// --- Server communication ---
|
|
19410
21424
|
async fetchServerKeyStatus(token, providerType) {
|
|
19411
|
-
const
|
|
21425
|
+
const storage2 = guardedStorage();
|
|
21426
|
+
const localVersion = await storage2.getShareVersion(activeStorageId);
|
|
19412
21427
|
const data = await fetchAuthShareRaw(
|
|
19413
21428
|
serverUrl,
|
|
19414
21429
|
token,
|
|
@@ -19417,7 +21432,7 @@ function createSSSStrategy(config) {
|
|
|
19417
21432
|
tenantId
|
|
19418
21433
|
);
|
|
19419
21434
|
if (!data) {
|
|
19420
|
-
|
|
21435
|
+
lastServerSnapshot = null;
|
|
19421
21436
|
return {
|
|
19422
21437
|
exists: false,
|
|
19423
21438
|
needsMigration: false,
|
|
@@ -19425,16 +21440,25 @@ function createSSSStrategy(config) {
|
|
|
19425
21440
|
recoveryMethods: [],
|
|
19426
21441
|
authShare: null,
|
|
19427
21442
|
shareVersion: null,
|
|
19428
|
-
maskedRecoveryEmail: null
|
|
21443
|
+
maskedRecoveryEmail: null,
|
|
21444
|
+
sssActivationState: null
|
|
19429
21445
|
};
|
|
19430
21446
|
}
|
|
19431
21447
|
const rawAuthShare = data.authShare;
|
|
19432
|
-
const authShareString =
|
|
21448
|
+
const authShareString = authShareToString(rawAuthShare);
|
|
19433
21449
|
const serverVersion = data.shareVersion ?? null;
|
|
19434
|
-
if (
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
21450
|
+
if (
|
|
21451
|
+
// Version 0 means "no auth share yet" and must never be pinned locally.
|
|
21452
|
+
serverVersion != null && serverVersion > 0 && localVersion == null && !(await readPendingShareCandidates(storage2, activeStorageId)).length
|
|
21453
|
+
) {
|
|
21454
|
+
storage2.storeShareVersion(serverVersion, activeStorageId).catch((e) => console.warn("SSS: failed to backfill local shareVersion", e));
|
|
21455
|
+
}
|
|
21456
|
+
lastServerSnapshot = {
|
|
21457
|
+
currentVersion: serverVersion,
|
|
21458
|
+
resolvedVersion: authShareString ? localVersion ?? serverVersion : null,
|
|
21459
|
+
authShare: authShareString,
|
|
21460
|
+
primaryDid: data.primaryDid || null
|
|
21461
|
+
};
|
|
19438
21462
|
return {
|
|
19439
21463
|
exists: !!rawAuthShare || !!data.keyProvider || !!data.primaryDid,
|
|
19440
21464
|
needsMigration: data.keyProvider === "web3auth",
|
|
@@ -19442,35 +21466,258 @@ function createSSSStrategy(config) {
|
|
|
19442
21466
|
recoveryMethods: data.recoveryMethods || [],
|
|
19443
21467
|
authShare: authShareString,
|
|
19444
21468
|
shareVersion: serverVersion,
|
|
19445
|
-
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null
|
|
21469
|
+
maskedRecoveryEmail: data.maskedRecoveryEmail ?? null,
|
|
21470
|
+
escrowOptedOut: data.escrowOptedOut === true,
|
|
21471
|
+
escrowPin: data.escrowPin,
|
|
21472
|
+
sssActivationState: data.sssActivationState ?? "active"
|
|
19446
21473
|
};
|
|
19447
21474
|
},
|
|
19448
21475
|
async storeAuthShare(token, providerType, authShare, primaryDid, didAuthVp) {
|
|
19449
|
-
const
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
21476
|
+
const storage2 = guardedStorage();
|
|
21477
|
+
const { shareVersion } = await withRotationLock(
|
|
21478
|
+
() => putAuthShare(
|
|
21479
|
+
serverUrl,
|
|
21480
|
+
token,
|
|
21481
|
+
providerType,
|
|
21482
|
+
authShare,
|
|
21483
|
+
primaryDid,
|
|
21484
|
+
didAuthVp,
|
|
21485
|
+
tenantId
|
|
21486
|
+
)
|
|
19457
21487
|
);
|
|
19458
|
-
await
|
|
21488
|
+
await storage2.storeShareVersion(shareVersion, activeStorageId);
|
|
19459
21489
|
lastShareVersion = shareVersion;
|
|
19460
21490
|
},
|
|
19461
|
-
async markMigrated(token, providerType, didAuthVp) {
|
|
19462
|
-
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21491
|
+
async markMigrated(token, providerType, didAuthVp) {
|
|
21492
|
+
const response = await fetch(`${serverUrl}/keys/migrate`, {
|
|
21493
|
+
method: "POST",
|
|
21494
|
+
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
21495
|
+
body: JSON.stringify({ authToken: token, providerType })
|
|
21496
|
+
});
|
|
21497
|
+
if (!response.ok) {
|
|
21498
|
+
throw new Error(`Failed to mark migrated: ${describeHttpStatus(response)}`);
|
|
21499
|
+
}
|
|
21500
|
+
},
|
|
21501
|
+
async activate(token, providerType, didAuthVp) {
|
|
21502
|
+
const response = await fetch(`${serverUrl}/keys/activate`, {
|
|
19463
21503
|
method: "POST",
|
|
19464
21504
|
headers: buildHeaders(token, didAuthVp, tenantId),
|
|
19465
21505
|
body: JSON.stringify({ authToken: token, providerType })
|
|
19466
21506
|
});
|
|
19467
21507
|
if (!response.ok) {
|
|
19468
|
-
throw new Error(`Failed to
|
|
21508
|
+
throw new Error(`Failed to activate SSS key: ${describeHttpStatus(response)}`);
|
|
19469
21509
|
}
|
|
19470
21510
|
},
|
|
19471
21511
|
// --- Recovery execution ---
|
|
21512
|
+
async getEscrowEnrollmentState(params) {
|
|
21513
|
+
if (!config.escrow?.enabled) return { state: "disabled" };
|
|
21514
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21515
|
+
if (status.escrowOptedOut) return { state: "opted-out", escrowPin: status.escrowPin };
|
|
21516
|
+
const state = status.shareVersion !== null && status.recoveryMethods.some(
|
|
21517
|
+
(method) => method.type === "escrow" && method.shareVersion === status.shareVersion && (Boolean(method.confirmedAt) || "confirmationStatus" in method && method.confirmationStatus === "confirmed")
|
|
21518
|
+
) ? "enrolled" : "not-enrolled";
|
|
21519
|
+
return { state, escrowPin: status.escrowPin };
|
|
21520
|
+
},
|
|
21521
|
+
async disableEscrowRecovery(params) {
|
|
21522
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21523
|
+
if (!status.primaryDid) throw new Error("Cannot disable escrow without a primary DID");
|
|
21524
|
+
const vp = await requestFreshDidAuthVp(
|
|
21525
|
+
serverUrl,
|
|
21526
|
+
params.privateKey,
|
|
21527
|
+
status.primaryDid,
|
|
21528
|
+
params.signDidAuthVp,
|
|
21529
|
+
tenantId
|
|
21530
|
+
);
|
|
21531
|
+
await escrowRequest("", {
|
|
21532
|
+
method: "DELETE",
|
|
21533
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21534
|
+
body: JSON.stringify({
|
|
21535
|
+
authToken: params.token,
|
|
21536
|
+
providerType: params.providerType,
|
|
21537
|
+
optOut: true
|
|
21538
|
+
})
|
|
21539
|
+
});
|
|
21540
|
+
},
|
|
21541
|
+
async enableEscrowRecovery(params) {
|
|
21542
|
+
const storage2 = guardedStorage();
|
|
21543
|
+
return withRotationLock(async () => {
|
|
21544
|
+
if (!config.escrow?.enabled)
|
|
21545
|
+
return { enrolled: false, reason: "disabled" };
|
|
21546
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21547
|
+
if (!status.primaryDid)
|
|
21548
|
+
throw new Error("Cannot enable escrow without a primary DID");
|
|
21549
|
+
const vp = await requestFreshDidAuthVp(
|
|
21550
|
+
serverUrl,
|
|
21551
|
+
params.privateKey,
|
|
21552
|
+
status.primaryDid,
|
|
21553
|
+
params.signDidAuthVp,
|
|
21554
|
+
tenantId
|
|
21555
|
+
);
|
|
21556
|
+
await escrowRequest("/opt-in", {
|
|
21557
|
+
method: "POST",
|
|
21558
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21559
|
+
body: JSON.stringify({
|
|
21560
|
+
authToken: params.token,
|
|
21561
|
+
providerType: params.providerType
|
|
21562
|
+
})
|
|
21563
|
+
});
|
|
21564
|
+
return ensureEscrowEnrollmentUnguarded(this, params, storage2);
|
|
21565
|
+
});
|
|
21566
|
+
},
|
|
21567
|
+
async setEscrowPin({ pin, ...params }) {
|
|
21568
|
+
const storage2 = guardedStorage();
|
|
21569
|
+
const result = await withRotationLock(
|
|
21570
|
+
() => ensureEscrowEnrollmentUnguarded(this, { ...params, options: { pin } }, storage2)
|
|
21571
|
+
);
|
|
21572
|
+
if (!result.enrolled) {
|
|
21573
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21574
|
+
}
|
|
21575
|
+
},
|
|
21576
|
+
async clearEscrowPin(params) {
|
|
21577
|
+
const storage2 = guardedStorage();
|
|
21578
|
+
const result = await withRotationLock(
|
|
21579
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2, true)
|
|
21580
|
+
);
|
|
21581
|
+
if (!result.enrolled) {
|
|
21582
|
+
throw new Error("Automatic recovery is not available for this account.");
|
|
21583
|
+
}
|
|
21584
|
+
},
|
|
21585
|
+
/** Enroll missing/stale escrow material using the standard atomic rotation. */
|
|
21586
|
+
async ensureEscrowEnrollment(params) {
|
|
21587
|
+
const storage2 = guardedStorage();
|
|
21588
|
+
if (params.options?.pin !== void 0)
|
|
21589
|
+
return withRotationLock(
|
|
21590
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21591
|
+
);
|
|
21592
|
+
if (escrowEnrollmentInFlight) return escrowEnrollmentInFlight;
|
|
21593
|
+
escrowEnrollmentInFlight = withRotationLock(
|
|
21594
|
+
() => ensureEscrowEnrollmentUnguarded(this, params, storage2)
|
|
21595
|
+
);
|
|
21596
|
+
try {
|
|
21597
|
+
return await escrowEnrollmentInFlight;
|
|
21598
|
+
} finally {
|
|
21599
|
+
escrowEnrollmentInFlight = void 0;
|
|
21600
|
+
}
|
|
21601
|
+
},
|
|
21602
|
+
/** Start or explicitly restart a hold with fresh client-bound resume secrets. */
|
|
21603
|
+
async startEscrowRecovery(params) {
|
|
21604
|
+
const sessionProof = params.recoverySessionToken !== void 0;
|
|
21605
|
+
if (sessionProof ? params.token !== void 0 || params.providerType !== void 0 : !params.token || !params.providerType) {
|
|
21606
|
+
throw new Error("Provide exactly one identity proof");
|
|
21607
|
+
}
|
|
21608
|
+
const pair = await generateEscrowKeyPair();
|
|
21609
|
+
const result = await escrowRequest(
|
|
21610
|
+
"/recover",
|
|
21611
|
+
{
|
|
21612
|
+
method: "POST",
|
|
21613
|
+
headers: buildHeaders("", void 0, params.tenantId ?? tenantId),
|
|
21614
|
+
body: JSON.stringify({
|
|
21615
|
+
clientEphemeralPublicKey: pair.publicKey,
|
|
21616
|
+
...params.options?.restart ? { restart: true } : {},
|
|
21617
|
+
...params.options?.releasePolicy ? { releasePolicy: params.options.releasePolicy } : {},
|
|
21618
|
+
...sessionProof ? { recoverySessionToken: params.recoverySessionToken } : { authToken: params.token, providerType: params.providerType }
|
|
21619
|
+
})
|
|
21620
|
+
},
|
|
21621
|
+
params.options?.releasePolicy === "pin"
|
|
21622
|
+
);
|
|
21623
|
+
return { ...result, clientEphemeralPrivateKey: pair.privateKey };
|
|
21624
|
+
},
|
|
21625
|
+
/** Read hold status; secrets (auth or resume token) never travel in a URL. */
|
|
21626
|
+
async getEscrowRecoveryStatus(params) {
|
|
21627
|
+
const query = new URLSearchParams(
|
|
21628
|
+
"holdId" in params ? { holdId: params.holdId } : { providerType: params.providerType }
|
|
21629
|
+
);
|
|
21630
|
+
const secret = "holdId" in params ? params.resumeToken : params.token;
|
|
21631
|
+
const result = await escrowRequest(
|
|
21632
|
+
`/status?${query}`,
|
|
21633
|
+
{
|
|
21634
|
+
method: "GET",
|
|
21635
|
+
headers: {
|
|
21636
|
+
...buildHeaders("", void 0, tenantId),
|
|
21637
|
+
"X-Auth-Token": secret
|
|
21638
|
+
}
|
|
21639
|
+
}
|
|
21640
|
+
);
|
|
21641
|
+
return result.hold;
|
|
21642
|
+
},
|
|
21643
|
+
/** Cancel a pending hold with a fresh DID-owner proof. */
|
|
21644
|
+
async cancelEscrowRecovery(params) {
|
|
21645
|
+
const status = await this.fetchServerKeyStatus(params.token, params.providerType);
|
|
21646
|
+
if (!status.primaryDid) throw new Error("Cannot cancel escrow without a primary DID");
|
|
21647
|
+
const vp = await requestFreshDidAuthVp(
|
|
21648
|
+
serverUrl,
|
|
21649
|
+
params.privateKey,
|
|
21650
|
+
status.primaryDid,
|
|
21651
|
+
params.signDidAuthVp,
|
|
21652
|
+
tenantId
|
|
21653
|
+
);
|
|
21654
|
+
const result = await escrowRequest("/cancel", {
|
|
21655
|
+
method: "POST",
|
|
21656
|
+
headers: buildHeaders(params.token, vp, tenantId),
|
|
21657
|
+
body: JSON.stringify({
|
|
21658
|
+
authToken: params.token,
|
|
21659
|
+
providerType: params.providerType
|
|
21660
|
+
})
|
|
21661
|
+
});
|
|
21662
|
+
return { cancelled: result.cancelled };
|
|
21663
|
+
},
|
|
19472
21664
|
async executeRecovery(params) {
|
|
19473
|
-
const
|
|
21665
|
+
const storage2 = guardedStorage();
|
|
21666
|
+
const { token, providerType, input, didFromPrivateKey, signDidAuthVp } = params;
|
|
21667
|
+
if (input.method === "escrow" || input.method === "escrow-pin") {
|
|
21668
|
+
if (!didFromPrivateKey)
|
|
21669
|
+
throw new Error("DID verification is required for escrow recovery");
|
|
21670
|
+
const start = input.method === "escrow-pin" ? await this.startEscrowRecovery({
|
|
21671
|
+
token,
|
|
21672
|
+
providerType,
|
|
21673
|
+
options: { releasePolicy: "pin" }
|
|
21674
|
+
}) : input;
|
|
21675
|
+
let pinProof;
|
|
21676
|
+
if (input.method === "escrow-pin") {
|
|
21677
|
+
if (!("pinSalt" in start) || !start.pinSalt || !start.resumeToken)
|
|
21678
|
+
throw new Error("PIN recovery response is missing required material");
|
|
21679
|
+
pinProof = await derivePinProof(input.pin, start.pinSalt);
|
|
21680
|
+
}
|
|
21681
|
+
const material = await escrowRequest(
|
|
21682
|
+
"/complete",
|
|
21683
|
+
{
|
|
21684
|
+
method: "POST",
|
|
21685
|
+
headers: buildHeaders("", void 0, tenantId),
|
|
21686
|
+
body: JSON.stringify({
|
|
21687
|
+
holdId: start.holdId,
|
|
21688
|
+
resumeToken: start.resumeToken,
|
|
21689
|
+
...pinProof ? { pinProof } : {}
|
|
21690
|
+
})
|
|
21691
|
+
},
|
|
21692
|
+
input.method === "escrow-pin"
|
|
21693
|
+
);
|
|
21694
|
+
const plaintext = await openEscrowRelease(
|
|
21695
|
+
material.sealedShare,
|
|
21696
|
+
start.clientEphemeralPrivateKey
|
|
21697
|
+
);
|
|
21698
|
+
if (plaintext.holdId !== start.holdId || plaintext.did !== material.primaryDid || plaintext.shareVersion !== material.shareVersion) {
|
|
21699
|
+
throw new Error("Escrow recovery material does not match the request");
|
|
21700
|
+
}
|
|
21701
|
+
const authShare = material.authShare.encryptedData;
|
|
21702
|
+
const privateKey2 = await reconstructFromShares([
|
|
21703
|
+
plaintext.recoveryShare,
|
|
21704
|
+
authShare
|
|
21705
|
+
]);
|
|
21706
|
+
if (!material.primaryDid || await didFromPrivateKey(privateKey2) !== material.primaryDid) {
|
|
21707
|
+
throw new Error(
|
|
21708
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
21709
|
+
);
|
|
21710
|
+
}
|
|
21711
|
+
pendingIdentityRecovery = {
|
|
21712
|
+
privateKey: privateKey2,
|
|
21713
|
+
primaryDid: material.primaryDid,
|
|
21714
|
+
recoveryShare: plaintext.recoveryShare,
|
|
21715
|
+
authShare,
|
|
21716
|
+
rebindSessionToken: material.rebindSessionToken
|
|
21717
|
+
};
|
|
21718
|
+
if (!token) return { privateKey: privateKey2, did: material.primaryDid };
|
|
21719
|
+
return this.completeIdentityRecovery({ token, providerType, signDidAuthVp });
|
|
21720
|
+
}
|
|
19474
21721
|
let recoveryShare;
|
|
19475
21722
|
let recoveryShareVersion;
|
|
19476
21723
|
switch (input.method) {
|
|
@@ -19564,14 +21811,61 @@ function createSSSStrategy(config) {
|
|
|
19564
21811
|
);
|
|
19565
21812
|
}
|
|
19566
21813
|
}
|
|
19567
|
-
await
|
|
19568
|
-
|
|
19569
|
-
|
|
21814
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21815
|
+
privateKey,
|
|
21816
|
+
serverUrl,
|
|
21817
|
+
token,
|
|
21818
|
+
providerType,
|
|
21819
|
+
primaryDid,
|
|
21820
|
+
storage2,
|
|
21821
|
+
activeStorageId,
|
|
21822
|
+
didFromPrivateKey,
|
|
21823
|
+
signDidAuthVp,
|
|
21824
|
+
tenantId,
|
|
21825
|
+
recordPromotion(primaryDid),
|
|
21826
|
+
captureActiveUserGuard()
|
|
21827
|
+
);
|
|
21828
|
+
lastEmailShare = shares.emailShare;
|
|
21829
|
+
lastShareVersion = shareVersion;
|
|
21830
|
+
lastServerSnapshot = {
|
|
21831
|
+
currentVersion: shareVersion,
|
|
21832
|
+
resolvedVersion: shareVersion,
|
|
21833
|
+
authShare: shares.authShare,
|
|
21834
|
+
primaryDid
|
|
21835
|
+
};
|
|
21836
|
+
await tryEnrollEscrow(
|
|
21837
|
+
token,
|
|
21838
|
+
providerType,
|
|
21839
|
+
privateKey,
|
|
21840
|
+
primaryDid,
|
|
21841
|
+
shares,
|
|
21842
|
+
shareVersion,
|
|
21843
|
+
signDidAuthVp
|
|
21844
|
+
);
|
|
19570
21845
|
return { privateKey, did: primaryDid };
|
|
19571
21846
|
},
|
|
19572
21847
|
// --- Recovery setup ---
|
|
19573
21848
|
async setupRecoveryMethod(params) {
|
|
21849
|
+
const storage2 = guardedStorage();
|
|
19574
21850
|
const { token, providerType, privateKey, input, authUser, signDidAuthVp } = params;
|
|
21851
|
+
if (input.method === "escrow" && input.pin !== void 0) {
|
|
21852
|
+
if (!signDidAuthVp)
|
|
21853
|
+
throw new Error("DID proof signing is required for escrow enrollment");
|
|
21854
|
+
const result = await this.enableEscrowRecovery({
|
|
21855
|
+
token,
|
|
21856
|
+
providerType,
|
|
21857
|
+
privateKey,
|
|
21858
|
+
signDidAuthVp,
|
|
21859
|
+
options: { pin: input.pin }
|
|
21860
|
+
});
|
|
21861
|
+
if (!result.enrolled || !result.changed)
|
|
21862
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21863
|
+
return { method: "escrow", shareVersion: result.shareVersion };
|
|
21864
|
+
}
|
|
21865
|
+
if (input.method === "escrow" && !config.escrow?.enabled) {
|
|
21866
|
+
throw new Error("Escrow enrollment is disabled");
|
|
21867
|
+
}
|
|
21868
|
+
const escrowKey = input.method === "escrow" ? await fetchVerifiedEnclaveKey() : void 0;
|
|
19575
21869
|
let passkeyCredential;
|
|
19576
21870
|
if (input.method === "passkey") {
|
|
19577
21871
|
if (!isWebAuthnSupported()) {
|
|
@@ -19581,10 +21875,6 @@ function createSSSStrategy(config) {
|
|
|
19581
21875
|
const userName = authUser?.email || authUser?.phone || authUser?.id || "";
|
|
19582
21876
|
passkeyCredential = await createPasskeyCredential(userId, userName);
|
|
19583
21877
|
}
|
|
19584
|
-
const vpJwt = signDidAuthVp ? await signDidAuthVp(privateKey) : void 0;
|
|
19585
|
-
const { shares } = await splitAndVerify(privateKey);
|
|
19586
|
-
lastEmailShare = shares.emailShare;
|
|
19587
|
-
await storage.storeDeviceShare(shares.deviceShare, activeStorageId);
|
|
19588
21878
|
const serverData = await fetchAuthShareRaw(
|
|
19589
21879
|
serverUrl,
|
|
19590
21880
|
token,
|
|
@@ -19593,39 +21883,42 @@ function createSSSStrategy(config) {
|
|
|
19593
21883
|
tenantId
|
|
19594
21884
|
);
|
|
19595
21885
|
const primaryDid = serverData?.primaryDid || "";
|
|
19596
|
-
|
|
21886
|
+
if (!primaryDid) throw new Error("Cannot set up recovery without a primary DID");
|
|
21887
|
+
const { shares, shareVersion } = await persistSharesAtomically(
|
|
21888
|
+
privateKey,
|
|
19597
21889
|
serverUrl,
|
|
19598
21890
|
token,
|
|
19599
21891
|
providerType,
|
|
19600
|
-
shares.authShare,
|
|
19601
21892
|
primaryDid,
|
|
19602
|
-
|
|
19603
|
-
|
|
21893
|
+
storage2,
|
|
21894
|
+
activeStorageId,
|
|
21895
|
+
void 0,
|
|
21896
|
+
signDidAuthVp,
|
|
21897
|
+
tenantId,
|
|
21898
|
+
recordPromotion(primaryDid),
|
|
21899
|
+
captureActiveUserGuard()
|
|
19604
21900
|
);
|
|
19605
|
-
|
|
19606
|
-
|
|
19607
|
-
|
|
19608
|
-
|
|
19609
|
-
|
|
19610
|
-
|
|
19611
|
-
|
|
19612
|
-
|
|
19613
|
-
tenantId
|
|
19614
|
-
) : authUser?.email ? sendEmailBackupShare(
|
|
19615
|
-
serverUrl,
|
|
19616
|
-
token,
|
|
19617
|
-
providerType,
|
|
19618
|
-
lastEmailShare,
|
|
19619
|
-
authUser.email,
|
|
19620
|
-
shareVersion,
|
|
19621
|
-
tenantId
|
|
19622
|
-
) : Promise.resolve();
|
|
19623
|
-
resend.catch(
|
|
19624
|
-
(e) => console.warn("Email backup share re-send failed (non-fatal):", e)
|
|
19625
|
-
);
|
|
19626
|
-
lastEmailShare = void 0;
|
|
19627
|
-
}
|
|
21901
|
+
lastEmailShare = shares.emailShare;
|
|
21902
|
+
lastShareVersion = shareVersion;
|
|
21903
|
+
lastServerSnapshot = {
|
|
21904
|
+
currentVersion: shareVersion,
|
|
21905
|
+
resolvedVersion: shareVersion,
|
|
21906
|
+
authShare: shares.authShare,
|
|
21907
|
+
primaryDid
|
|
21908
|
+
};
|
|
19628
21909
|
switch (input.method) {
|
|
21910
|
+
case "escrow":
|
|
21911
|
+
await enrollEscrow(
|
|
21912
|
+
token,
|
|
21913
|
+
providerType,
|
|
21914
|
+
privateKey,
|
|
21915
|
+
primaryDid,
|
|
21916
|
+
shares,
|
|
21917
|
+
shareVersion,
|
|
21918
|
+
signDidAuthVp,
|
|
21919
|
+
escrowKey
|
|
21920
|
+
);
|
|
21921
|
+
return { method: "escrow", shareVersion };
|
|
19629
21922
|
case "passkey": {
|
|
19630
21923
|
const credential = passkeyCredential;
|
|
19631
21924
|
const encryptedShare = await encryptShareWithPasskey(
|
|
@@ -19645,9 +21938,38 @@ function createSSSStrategy(config) {
|
|
|
19645
21938
|
credentialId: credential.credentialId,
|
|
19646
21939
|
shareVersion
|
|
19647
21940
|
},
|
|
19648
|
-
|
|
21941
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21942
|
+
serverUrl,
|
|
21943
|
+
privateKey,
|
|
21944
|
+
primaryDid,
|
|
21945
|
+
signDidAuthVp,
|
|
21946
|
+
tenantId
|
|
21947
|
+
) : void 0,
|
|
19649
21948
|
tenantId
|
|
19650
21949
|
);
|
|
21950
|
+
await postRecoveryConfirmation(
|
|
21951
|
+
serverUrl,
|
|
21952
|
+
token,
|
|
21953
|
+
providerType,
|
|
21954
|
+
{ type: "passkey", credentialId: credential.credentialId },
|
|
21955
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21956
|
+
serverUrl,
|
|
21957
|
+
privateKey,
|
|
21958
|
+
primaryDid,
|
|
21959
|
+
signDidAuthVp,
|
|
21960
|
+
tenantId
|
|
21961
|
+
) : void 0,
|
|
21962
|
+
tenantId
|
|
21963
|
+
);
|
|
21964
|
+
await tryEnrollEscrow(
|
|
21965
|
+
token,
|
|
21966
|
+
providerType,
|
|
21967
|
+
privateKey,
|
|
21968
|
+
primaryDid,
|
|
21969
|
+
shares,
|
|
21970
|
+
shareVersion,
|
|
21971
|
+
signDidAuthVp
|
|
21972
|
+
);
|
|
19651
21973
|
return { method: "passkey", credentialId: credential.credentialId };
|
|
19652
21974
|
}
|
|
19653
21975
|
case "phrase": {
|
|
@@ -19660,10 +21982,38 @@ function createSSSStrategy(config) {
|
|
|
19660
21982
|
type: "phrase",
|
|
19661
21983
|
shareVersion
|
|
19662
21984
|
},
|
|
19663
|
-
|
|
21985
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
21986
|
+
serverUrl,
|
|
21987
|
+
privateKey,
|
|
21988
|
+
primaryDid,
|
|
21989
|
+
signDidAuthVp,
|
|
21990
|
+
tenantId
|
|
21991
|
+
) : void 0,
|
|
19664
21992
|
tenantId
|
|
19665
21993
|
);
|
|
19666
|
-
|
|
21994
|
+
const challengeWordIndices = selectRecoveryPhraseChallengeIndices(
|
|
21995
|
+
phrase.split(/\s+/).length
|
|
21996
|
+
);
|
|
21997
|
+
const challengeWordOptions = await buildRecoveryPhraseChallengeOptions(
|
|
21998
|
+
phrase,
|
|
21999
|
+
challengeWordIndices
|
|
22000
|
+
);
|
|
22001
|
+
pendingPhraseConfirmation = { phrase, challengeWordIndices };
|
|
22002
|
+
await tryEnrollEscrow(
|
|
22003
|
+
token,
|
|
22004
|
+
providerType,
|
|
22005
|
+
privateKey,
|
|
22006
|
+
primaryDid,
|
|
22007
|
+
shares,
|
|
22008
|
+
shareVersion,
|
|
22009
|
+
signDidAuthVp
|
|
22010
|
+
);
|
|
22011
|
+
return {
|
|
22012
|
+
method: "phrase",
|
|
22013
|
+
phrase,
|
|
22014
|
+
challengeWordIndices,
|
|
22015
|
+
challengeWordOptions
|
|
22016
|
+
};
|
|
19667
22017
|
}
|
|
19668
22018
|
case "backup": {
|
|
19669
22019
|
const encrypted = await encryptWithPassword(
|
|
@@ -19675,6 +22025,7 @@ function createSSSStrategy(config) {
|
|
|
19675
22025
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19676
22026
|
primaryDid: input.did,
|
|
19677
22027
|
shareVersion,
|
|
22028
|
+
shareChecksum: await calculateShareChecksum(shares.recoveryShare),
|
|
19678
22029
|
encryptedShare: {
|
|
19679
22030
|
ciphertext: encrypted.ciphertext,
|
|
19680
22031
|
iv: encrypted.iv,
|
|
@@ -19690,36 +22041,121 @@ function createSSSStrategy(config) {
|
|
|
19690
22041
|
type: "backup",
|
|
19691
22042
|
shareVersion
|
|
19692
22043
|
},
|
|
19693
|
-
|
|
22044
|
+
signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22045
|
+
serverUrl,
|
|
22046
|
+
privateKey,
|
|
22047
|
+
primaryDid,
|
|
22048
|
+
signDidAuthVp,
|
|
22049
|
+
tenantId
|
|
22050
|
+
) : void 0,
|
|
19694
22051
|
tenantId
|
|
19695
22052
|
);
|
|
22053
|
+
pendingBackupConfirmation = {
|
|
22054
|
+
backupFile,
|
|
22055
|
+
recoveryShare: shares.recoveryShare
|
|
22056
|
+
};
|
|
22057
|
+
await tryEnrollEscrow(
|
|
22058
|
+
token,
|
|
22059
|
+
providerType,
|
|
22060
|
+
privateKey,
|
|
22061
|
+
primaryDid,
|
|
22062
|
+
shares,
|
|
22063
|
+
shareVersion,
|
|
22064
|
+
signDidAuthVp
|
|
22065
|
+
);
|
|
19696
22066
|
return { method: "backup", backupFile };
|
|
19697
22067
|
}
|
|
19698
22068
|
case "email": {
|
|
19699
|
-
await
|
|
22069
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22070
|
+
serverUrl,
|
|
22071
|
+
privateKey,
|
|
22072
|
+
primaryDid,
|
|
22073
|
+
signDidAuthVp,
|
|
22074
|
+
tenantId
|
|
22075
|
+
) : void 0;
|
|
22076
|
+
await sendEmailBackupShare(
|
|
19700
22077
|
serverUrl,
|
|
19701
22078
|
token,
|
|
19702
22079
|
providerType,
|
|
19703
22080
|
shares.emailShare,
|
|
22081
|
+
input.email,
|
|
22082
|
+
escrowRelayPublicKey,
|
|
22083
|
+
escrowRelayKeyId,
|
|
22084
|
+
emailBranding,
|
|
19704
22085
|
shareVersion,
|
|
22086
|
+
didAuthVp,
|
|
19705
22087
|
tenantId
|
|
19706
22088
|
);
|
|
19707
|
-
await
|
|
19708
|
-
serverUrl,
|
|
22089
|
+
await tryEnrollEscrow(
|
|
19709
22090
|
token,
|
|
19710
22091
|
providerType,
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
tenantId
|
|
22092
|
+
privateKey,
|
|
22093
|
+
primaryDid,
|
|
22094
|
+
shares,
|
|
22095
|
+
shareVersion,
|
|
22096
|
+
signDidAuthVp
|
|
19717
22097
|
);
|
|
19718
|
-
hasRecoveryEmail = true;
|
|
19719
22098
|
return { method: "email" };
|
|
19720
22099
|
}
|
|
19721
22100
|
}
|
|
19722
22101
|
},
|
|
22102
|
+
async confirmRecoveryMethod(params) {
|
|
22103
|
+
const { token, providerType, privateKey, input, signDidAuthVp } = params;
|
|
22104
|
+
const primaryDid = lastServerSnapshot?.primaryDid;
|
|
22105
|
+
if (!primaryDid) throw new Error("Cannot confirm recovery without a primary DID");
|
|
22106
|
+
const confirmationBody = { type: input.method };
|
|
22107
|
+
if (input.method === "phrase") {
|
|
22108
|
+
const pending = pendingPhraseConfirmation;
|
|
22109
|
+
if (!pending) throw new Error("Generate a new recovery phrase to continue");
|
|
22110
|
+
const phraseWords = pending.phrase.trim().toLowerCase().split(/\s+/);
|
|
22111
|
+
const matches = input.challengeWords.length === pending.challengeWordIndices.length && pending.challengeWordIndices.every(
|
|
22112
|
+
(wordIndex, answerIndex) => input.challengeWords[answerIndex]?.trim().toLowerCase() === phraseWords[wordIndex]
|
|
22113
|
+
);
|
|
22114
|
+
if (!matches) {
|
|
22115
|
+
throw new Error("Those words do not match. Check your phrase and try again.");
|
|
22116
|
+
}
|
|
22117
|
+
} else if (input.method === "backup") {
|
|
22118
|
+
const pending = pendingBackupConfirmation;
|
|
22119
|
+
if (!pending) throw new Error("Generate a new backup file to continue");
|
|
22120
|
+
let suppliedBackup;
|
|
22121
|
+
let decryptedShare;
|
|
22122
|
+
try {
|
|
22123
|
+
suppliedBackup = JSON.parse(input.fileContents);
|
|
22124
|
+
decryptedShare = await decryptWithPassword(
|
|
22125
|
+
suppliedBackup.encryptedShare.ciphertext,
|
|
22126
|
+
suppliedBackup.encryptedShare.iv,
|
|
22127
|
+
suppliedBackup.encryptedShare.salt,
|
|
22128
|
+
input.password,
|
|
22129
|
+
suppliedBackup.encryptedShare.kdfParams
|
|
22130
|
+
);
|
|
22131
|
+
} catch {
|
|
22132
|
+
throw new Error("Incorrect password or corrupted data. Please try again.");
|
|
22133
|
+
}
|
|
22134
|
+
const checksum = await calculateShareChecksum(decryptedShare);
|
|
22135
|
+
if (suppliedBackup.shareChecksum !== checksum || pending.backupFile.shareChecksum !== checksum || decryptedShare !== pending.recoveryShare) {
|
|
22136
|
+
throw new Error("This backup file could not be verified. Please try again.");
|
|
22137
|
+
}
|
|
22138
|
+
} else {
|
|
22139
|
+
confirmationBody.code = input.code;
|
|
22140
|
+
}
|
|
22141
|
+
const didAuthVp = signDidAuthVp ? await requestFreshDidAuthVp(
|
|
22142
|
+
serverUrl,
|
|
22143
|
+
privateKey,
|
|
22144
|
+
primaryDid,
|
|
22145
|
+
signDidAuthVp,
|
|
22146
|
+
tenantId
|
|
22147
|
+
) : void 0;
|
|
22148
|
+
await postRecoveryConfirmation(
|
|
22149
|
+
serverUrl,
|
|
22150
|
+
token,
|
|
22151
|
+
providerType,
|
|
22152
|
+
confirmationBody,
|
|
22153
|
+
didAuthVp,
|
|
22154
|
+
tenantId
|
|
22155
|
+
);
|
|
22156
|
+
if (input.method === "phrase") pendingPhraseConfirmation = void 0;
|
|
22157
|
+
if (input.method === "backup") pendingBackupConfirmation = void 0;
|
|
22158
|
+
},
|
|
19723
22159
|
async getAvailableRecoveryMethods(token, providerType) {
|
|
19724
22160
|
try {
|
|
19725
22161
|
const serverData = await fetchAuthShareRaw(
|
|
@@ -19730,15 +22166,274 @@ function createSSSStrategy(config) {
|
|
|
19730
22166
|
tenantId
|
|
19731
22167
|
);
|
|
19732
22168
|
const methods = serverData?.recoveryMethods || [];
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
22169
|
+
const activationState = serverData?.sssActivationState ?? "active";
|
|
22170
|
+
return methods.filter((method) => {
|
|
22171
|
+
if (method.confirmedAt) return true;
|
|
22172
|
+
if (method.confirmationStatus) return false;
|
|
22173
|
+
return activationState === "active";
|
|
22174
|
+
});
|
|
19737
22175
|
} catch (e) {
|
|
19738
22176
|
console.error("Error getting recovery methods:", e);
|
|
19739
22177
|
return [];
|
|
19740
22178
|
}
|
|
19741
22179
|
},
|
|
22180
|
+
async startIdentityRecovery(email) {
|
|
22181
|
+
await postJson(
|
|
22182
|
+
`${serverUrl}/keys/recovery-session/start`,
|
|
22183
|
+
{ email },
|
|
22184
|
+
buildHeaders("", void 0, tenantId)
|
|
22185
|
+
);
|
|
22186
|
+
},
|
|
22187
|
+
async verifyIdentityRecovery(email, code) {
|
|
22188
|
+
const result = await postJson(
|
|
22189
|
+
`${serverUrl}/keys/recovery-session/verify`,
|
|
22190
|
+
{ email, code },
|
|
22191
|
+
buildHeaders("", void 0, tenantId)
|
|
22192
|
+
);
|
|
22193
|
+
return {
|
|
22194
|
+
...result,
|
|
22195
|
+
recoveryMethods: result.recoveryMethods.map((method) => ({
|
|
22196
|
+
...method,
|
|
22197
|
+
createdAt: new Date(method.createdAt)
|
|
22198
|
+
}))
|
|
22199
|
+
};
|
|
22200
|
+
},
|
|
22201
|
+
async prepareIdentityRecovery(params) {
|
|
22202
|
+
const { input } = params;
|
|
22203
|
+
if (input.method === "escrow") {
|
|
22204
|
+
throw new Error("Use executeRecovery to complete an escrow hold");
|
|
22205
|
+
}
|
|
22206
|
+
let recoveryShare;
|
|
22207
|
+
switch (input.method) {
|
|
22208
|
+
case "passkey":
|
|
22209
|
+
break;
|
|
22210
|
+
case "phrase":
|
|
22211
|
+
if (!await validateRecoveryPhrase(input.phrase)) {
|
|
22212
|
+
throw new Error("Invalid recovery phrase");
|
|
22213
|
+
}
|
|
22214
|
+
recoveryShare = await recoveryPhraseToShare(input.phrase);
|
|
22215
|
+
break;
|
|
22216
|
+
case "backup": {
|
|
22217
|
+
const backup = JSON.parse(input.fileContents);
|
|
22218
|
+
if (backup.version !== 1) throw new Error("Unsupported backup file version");
|
|
22219
|
+
recoveryShare = await decryptWithPassword(
|
|
22220
|
+
backup.encryptedShare.ciphertext,
|
|
22221
|
+
backup.encryptedShare.iv,
|
|
22222
|
+
backup.encryptedShare.salt,
|
|
22223
|
+
input.password,
|
|
22224
|
+
backup.encryptedShare.kdfParams
|
|
22225
|
+
);
|
|
22226
|
+
break;
|
|
22227
|
+
}
|
|
22228
|
+
case "email":
|
|
22229
|
+
recoveryShare = parseVersionedEmailShare(input.emailShare.trim()).share;
|
|
22230
|
+
break;
|
|
22231
|
+
}
|
|
22232
|
+
try {
|
|
22233
|
+
const material = await postJson(
|
|
22234
|
+
`${serverUrl}/keys/recovery-session/recover`,
|
|
22235
|
+
{
|
|
22236
|
+
recoverySessionToken: params.recoverySessionToken,
|
|
22237
|
+
type: input.method,
|
|
22238
|
+
...input.method === "passkey" ? { credentialId: input.credentialId } : {}
|
|
22239
|
+
},
|
|
22240
|
+
buildHeaders("", void 0, tenantId)
|
|
22241
|
+
);
|
|
22242
|
+
if (input.method === "passkey") {
|
|
22243
|
+
if (!material.encryptedShare) {
|
|
22244
|
+
throw new Error("No passkey recovery data found");
|
|
22245
|
+
}
|
|
22246
|
+
recoveryShare = await decryptShareWithPasskey({
|
|
22247
|
+
encryptedData: material.encryptedShare.encryptedData,
|
|
22248
|
+
iv: material.encryptedShare.iv,
|
|
22249
|
+
credentialId: input.credentialId
|
|
22250
|
+
});
|
|
22251
|
+
}
|
|
22252
|
+
if (!recoveryShare) throw new Error("Recovery data was empty");
|
|
22253
|
+
const authShare = material.authShare.encryptedData;
|
|
22254
|
+
let privateKey;
|
|
22255
|
+
try {
|
|
22256
|
+
privateKey = await reconstructFromShares([recoveryShare, authShare]);
|
|
22257
|
+
} catch {
|
|
22258
|
+
throw new Error(
|
|
22259
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22260
|
+
);
|
|
22261
|
+
}
|
|
22262
|
+
const derivedDid = await params.didFromPrivateKey(privateKey);
|
|
22263
|
+
if (!derivedDid || derivedDid !== material.primaryDid) {
|
|
22264
|
+
throw new Error(
|
|
22265
|
+
"Recovery produced an incorrect key. Try another recovery method."
|
|
22266
|
+
);
|
|
22267
|
+
}
|
|
22268
|
+
pendingIdentityRecovery = {
|
|
22269
|
+
privateKey,
|
|
22270
|
+
primaryDid: material.primaryDid,
|
|
22271
|
+
recoveryShare,
|
|
22272
|
+
authShare,
|
|
22273
|
+
rebindSessionToken: material.rebindSessionToken
|
|
22274
|
+
};
|
|
22275
|
+
return { privateKey, did: material.primaryDid };
|
|
22276
|
+
} catch (error) {
|
|
22277
|
+
const message = error instanceof Error ? error.message : "Identity recovery failed";
|
|
22278
|
+
throw new IdentityRecoverySessionConsumedError(message);
|
|
22279
|
+
}
|
|
22280
|
+
},
|
|
22281
|
+
hasPendingIdentityRecovery() {
|
|
22282
|
+
return Boolean(pendingIdentityRecovery);
|
|
22283
|
+
},
|
|
22284
|
+
cancelIdentityRecovery() {
|
|
22285
|
+
pendingIdentityRecovery = void 0;
|
|
22286
|
+
},
|
|
22287
|
+
async completeIdentityRecovery(params) {
|
|
22288
|
+
const storage2 = guardedStorage();
|
|
22289
|
+
const generation = storageGeneration;
|
|
22290
|
+
const pending = pendingIdentityRecovery;
|
|
22291
|
+
const signDidAuthVp = params.signDidAuthVp;
|
|
22292
|
+
if (!pending) throw new Error("No identity recovery is waiting for a new sign-in");
|
|
22293
|
+
if (!signDidAuthVp) {
|
|
22294
|
+
throw new Error("DID proof signing is required to bind a new sign-in");
|
|
22295
|
+
}
|
|
22296
|
+
const storageId = activeStorageId;
|
|
22297
|
+
const { shares, shareVersion } = await withShareUpdateLock(
|
|
22298
|
+
storage2,
|
|
22299
|
+
storageId,
|
|
22300
|
+
() => withRotationLock(async () => {
|
|
22301
|
+
const assertActive = /* @__PURE__ */ __name(() => {
|
|
22302
|
+
if (activeStorageId !== storageId || pendingIdentityRecovery !== pending || storageGeneration !== generation) {
|
|
22303
|
+
throw new Error("This recovery request was cancelled.");
|
|
22304
|
+
}
|
|
22305
|
+
}, "assertActive");
|
|
22306
|
+
assertActive();
|
|
22307
|
+
const candidates = await readPendingShareCandidates(storage2, storageId);
|
|
22308
|
+
if (pending.rebind && pending.rebind.storageId !== storageId) {
|
|
22309
|
+
throw new Error("Active account changed during identity recovery");
|
|
22310
|
+
}
|
|
22311
|
+
if (candidates.some(
|
|
22312
|
+
(candidate) => candidate.share !== pending.rebind?.shares.deviceShare
|
|
22313
|
+
)) {
|
|
22314
|
+
throw new Error(
|
|
22315
|
+
"Reconcile the pending share update before binding a sign-in"
|
|
22316
|
+
);
|
|
22317
|
+
}
|
|
22318
|
+
let shareVersion2;
|
|
22319
|
+
const retry = pending.rebind?.attempted ?? false;
|
|
22320
|
+
if (retry && pending.rebind) {
|
|
22321
|
+
const current = await fetchAuthShareRaw(
|
|
22322
|
+
serverUrl,
|
|
22323
|
+
params.token,
|
|
22324
|
+
params.providerType,
|
|
22325
|
+
void 0,
|
|
22326
|
+
tenantId
|
|
22327
|
+
);
|
|
22328
|
+
const authShare = authShareToString(current?.authShare);
|
|
22329
|
+
if (authShare && current?.shareVersion != null) {
|
|
22330
|
+
const health = await verifyStoredShares(
|
|
22331
|
+
{
|
|
22332
|
+
getDevice: /* @__PURE__ */ __name(async () => pending.rebind.shares.deviceShare, "getDevice"),
|
|
22333
|
+
getAuth: /* @__PURE__ */ __name(async () => authShare, "getAuth")
|
|
22334
|
+
},
|
|
22335
|
+
pending.primaryDid,
|
|
22336
|
+
async (key) => key === pending.privateKey ? pending.primaryDid : ""
|
|
22337
|
+
);
|
|
22338
|
+
if (health.healthy) shareVersion2 = current.shareVersion;
|
|
22339
|
+
}
|
|
22340
|
+
}
|
|
22341
|
+
assertActive();
|
|
22342
|
+
if (shareVersion2 === void 0) {
|
|
22343
|
+
const didAuthVp = await requestFreshDidAuthVp(
|
|
22344
|
+
serverUrl,
|
|
22345
|
+
pending.privateKey,
|
|
22346
|
+
pending.primaryDid,
|
|
22347
|
+
signDidAuthVp,
|
|
22348
|
+
tenantId
|
|
22349
|
+
);
|
|
22350
|
+
assertActive();
|
|
22351
|
+
pending.rebind ??= {
|
|
22352
|
+
shares: (await splitAndVerify(pending.privateKey)).shares,
|
|
22353
|
+
storageId,
|
|
22354
|
+
attempted: false
|
|
22355
|
+
};
|
|
22356
|
+
const rebind = pending.rebind;
|
|
22357
|
+
if (!candidates.some(
|
|
22358
|
+
(candidate) => candidate.share === rebind.shares.deviceShare
|
|
22359
|
+
)) {
|
|
22360
|
+
await addPendingShareCandidate(
|
|
22361
|
+
storage2,
|
|
22362
|
+
{ share: rebind.shares.deviceShare, createdAt: Date.now() },
|
|
22363
|
+
storageId
|
|
22364
|
+
);
|
|
22365
|
+
}
|
|
22366
|
+
assertActive();
|
|
22367
|
+
rebind.attempted = true;
|
|
22368
|
+
try {
|
|
22369
|
+
const response = await postJson(
|
|
22370
|
+
`${serverUrl}/keys/recovery-session/rebind`,
|
|
22371
|
+
{
|
|
22372
|
+
recoverySessionToken: pending.rebindSessionToken,
|
|
22373
|
+
providerType: params.providerType,
|
|
22374
|
+
primaryDid: pending.primaryDid,
|
|
22375
|
+
authShare: {
|
|
22376
|
+
encryptedData: rebind.shares.authShare,
|
|
22377
|
+
encryptedDek: "",
|
|
22378
|
+
iv: ""
|
|
22379
|
+
}
|
|
22380
|
+
},
|
|
22381
|
+
{
|
|
22382
|
+
...buildHeaders("", didAuthVp, tenantId),
|
|
22383
|
+
"X-Auth-Token": params.token
|
|
22384
|
+
}
|
|
22385
|
+
);
|
|
22386
|
+
shareVersion2 = response.shareVersion;
|
|
22387
|
+
} catch (error) {
|
|
22388
|
+
if (error instanceof ShareWriteRejectedError && !retry) {
|
|
22389
|
+
await removePendingShareCandidate(
|
|
22390
|
+
storage2,
|
|
22391
|
+
rebind.shares.deviceShare,
|
|
22392
|
+
storageId
|
|
22393
|
+
);
|
|
22394
|
+
pending.rebind = void 0;
|
|
22395
|
+
}
|
|
22396
|
+
throw error;
|
|
22397
|
+
}
|
|
22398
|
+
}
|
|
22399
|
+
if (shareVersion2 === void 0 || !pending.rebind) {
|
|
22400
|
+
throw new Error("Server did not confirm the new share");
|
|
22401
|
+
}
|
|
22402
|
+
assertActive();
|
|
22403
|
+
const shares2 = pending.rebind.shares;
|
|
22404
|
+
await storage2.storeDeviceShare(shares2.deviceShare, storageId);
|
|
22405
|
+
await storage2.storeShareVersion(shareVersion2, storageId);
|
|
22406
|
+
await clearPendingShareCandidates(storage2, storageId);
|
|
22407
|
+
assertActive();
|
|
22408
|
+
lastEmailShare = shares2.emailShare;
|
|
22409
|
+
lastShareVersion = shareVersion2;
|
|
22410
|
+
lastServerSnapshot = {
|
|
22411
|
+
currentVersion: shareVersion2,
|
|
22412
|
+
resolvedVersion: shareVersion2,
|
|
22413
|
+
authShare: shares2.authShare,
|
|
22414
|
+
primaryDid: pending.primaryDid
|
|
22415
|
+
};
|
|
22416
|
+
pendingIdentityRecovery = void 0;
|
|
22417
|
+
return { shares: shares2, shareVersion: shareVersion2 };
|
|
22418
|
+
})
|
|
22419
|
+
);
|
|
22420
|
+
await tryEnrollEscrow(
|
|
22421
|
+
params.token,
|
|
22422
|
+
params.providerType,
|
|
22423
|
+
pending.privateKey,
|
|
22424
|
+
pending.primaryDid,
|
|
22425
|
+
shares,
|
|
22426
|
+
shareVersion,
|
|
22427
|
+
signDidAuthVp,
|
|
22428
|
+
void 0,
|
|
22429
|
+
void 0,
|
|
22430
|
+
true
|
|
22431
|
+
);
|
|
22432
|
+
if (activeStorageId !== storageId || storageGeneration !== generation) {
|
|
22433
|
+
throw new Error("This recovery request was cancelled.");
|
|
22434
|
+
}
|
|
22435
|
+
return { privateKey: pending.privateKey, did: pending.primaryDid };
|
|
22436
|
+
},
|
|
19742
22437
|
// --- Contact method management ---
|
|
19743
22438
|
async upgradeContactMethod(token, providerType, previousPhone, email, code) {
|
|
19744
22439
|
const res = await fetch(`${serverUrl}/keys/upgrade-contact-method`, {
|
|
@@ -19761,7 +22456,7 @@ function createSSSStrategy(config) {
|
|
|
19761
22456
|
return { customToken: data?.customToken };
|
|
19762
22457
|
},
|
|
19763
22458
|
// --- Email backup ---
|
|
19764
|
-
async sendEmailBackupShare(token, providerType, _privateKey, email) {
|
|
22459
|
+
async sendEmailBackupShare(token, providerType, _privateKey, email, didAuthVp) {
|
|
19765
22460
|
if (!enableEmailBackupShare) return;
|
|
19766
22461
|
if (!lastEmailShare) {
|
|
19767
22462
|
console.warn(
|
|
@@ -19769,29 +22464,22 @@ function createSSSStrategy(config) {
|
|
|
19769
22464
|
);
|
|
19770
22465
|
return;
|
|
19771
22466
|
}
|
|
19772
|
-
if (
|
|
19773
|
-
await sendEmailShareToRecoveryEmail(
|
|
19774
|
-
serverUrl,
|
|
19775
|
-
token,
|
|
19776
|
-
providerType,
|
|
19777
|
-
lastEmailShare,
|
|
19778
|
-
lastShareVersion,
|
|
19779
|
-
tenantId
|
|
19780
|
-
);
|
|
19781
|
-
} else {
|
|
19782
|
-
if (!email) {
|
|
19783
|
-
console.warn("Cannot send email backup share: no email address");
|
|
19784
|
-
return;
|
|
19785
|
-
}
|
|
22467
|
+
if (email) {
|
|
19786
22468
|
await sendEmailBackupShare(
|
|
19787
22469
|
serverUrl,
|
|
19788
22470
|
token,
|
|
19789
22471
|
providerType,
|
|
19790
22472
|
lastEmailShare,
|
|
19791
22473
|
email,
|
|
22474
|
+
escrowRelayPublicKey,
|
|
22475
|
+
escrowRelayKeyId,
|
|
22476
|
+
emailBranding,
|
|
19792
22477
|
lastShareVersion,
|
|
22478
|
+
didAuthVp,
|
|
19793
22479
|
tenantId
|
|
19794
22480
|
);
|
|
22481
|
+
} else {
|
|
22482
|
+
throw new Error("Enter a recovery email before sending a recovery key");
|
|
19795
22483
|
}
|
|
19796
22484
|
lastEmailShare = void 0;
|
|
19797
22485
|
lastShareVersion = void 0;
|
|
@@ -19808,6 +22496,8 @@ function createSSSStrategy(config) {
|
|
|
19808
22496
|
return [SSS_DB_NAME];
|
|
19809
22497
|
},
|
|
19810
22498
|
async cleanup() {
|
|
22499
|
+
storageGeneration++;
|
|
22500
|
+
await Promise.allSettled(localWrites);
|
|
19811
22501
|
}
|
|
19812
22502
|
};
|
|
19813
22503
|
}
|
|
@@ -19900,13 +22590,7 @@ var decryptShareFromTransfer = /* @__PURE__ */ __name(async (payload, recipientP
|
|
|
19900
22590
|
}, "decryptShareFromTransfer");
|
|
19901
22591
|
|
|
19902
22592
|
// src/qr-login.ts
|
|
19903
|
-
var buildHeaders2 = /* @__PURE__ */ __name((
|
|
19904
|
-
const headers = { "Content-Type": "application/json" };
|
|
19905
|
-
if (token) {
|
|
19906
|
-
headers.Authorization = `Bearer ${token}`;
|
|
19907
|
-
}
|
|
19908
|
-
return headers;
|
|
19909
|
-
}, "buildHeaders");
|
|
22593
|
+
var buildHeaders2 = /* @__PURE__ */ __name(() => ({ "Content-Type": "application/json" }), "buildHeaders");
|
|
19910
22594
|
var createQrLoginSession = /* @__PURE__ */ __name(async (config) => {
|
|
19911
22595
|
const ephemeralKeypair = await generateEphemeralKeypair();
|
|
19912
22596
|
const response = await fetch(`${config.serverUrl}/qr-login/session`, {
|
|
@@ -19974,10 +22658,14 @@ var pollUntilApproved = /* @__PURE__ */ __name(async (config, sessionId, ephemer
|
|
|
19974
22658
|
await new Promise((resolve, reject) => {
|
|
19975
22659
|
const timer = setTimeout(resolve, intervalMs);
|
|
19976
22660
|
if (options?.signal) {
|
|
19977
|
-
options.signal.addEventListener(
|
|
19978
|
-
|
|
19979
|
-
|
|
19980
|
-
|
|
22661
|
+
options.signal.addEventListener(
|
|
22662
|
+
"abort",
|
|
22663
|
+
() => {
|
|
22664
|
+
clearTimeout(timer);
|
|
22665
|
+
reject(new Error("QR login polling aborted"));
|
|
22666
|
+
},
|
|
22667
|
+
{ once: true }
|
|
22668
|
+
);
|
|
19981
22669
|
}
|
|
19982
22670
|
});
|
|
19983
22671
|
}
|
|
@@ -20029,43 +22717,68 @@ var notifyDevicesForQrSession = /* @__PURE__ */ __name(async (config, sessionId,
|
|
|
20029
22717
|
return { sent: false, deviceCount: 0 };
|
|
20030
22718
|
}
|
|
20031
22719
|
}, "notifyDevicesForQrSession");
|
|
20032
|
-
|
|
20033
|
-
// src/types.ts
|
|
20034
|
-
var import_types = __toESM(require_dist());
|
|
20035
22720
|
var export_AuthSessionError = import_types.AuthSessionError;
|
|
20036
22721
|
export {
|
|
20037
22722
|
AtomicUpdateError,
|
|
20038
22723
|
export_AuthSessionError as AuthSessionError,
|
|
20039
22724
|
DEFAULT_KDF_PARAMS,
|
|
22725
|
+
EMAIL_RELAY_ALGORITHM,
|
|
22726
|
+
EMAIL_RELAY_ENVELOPE_VERSION,
|
|
22727
|
+
ESCROW_ALGORITHM,
|
|
22728
|
+
ESCROW_BLOB_INFO,
|
|
22729
|
+
ESCROW_CLIENT_KEY_ID,
|
|
22730
|
+
ESCROW_ENVELOPE_VERSION,
|
|
22731
|
+
ESCROW_PIN_MAX_ATTEMPTS,
|
|
22732
|
+
ESCROW_RELEASE_INFO,
|
|
22733
|
+
EscrowHoldRestartThrottledError,
|
|
22734
|
+
EscrowPinLockedError,
|
|
22735
|
+
EscrowPinMismatchError,
|
|
22736
|
+
EscrowPinThrottledError,
|
|
22737
|
+
EscrowPinUnavailableError,
|
|
22738
|
+
EscrowRequestError,
|
|
22739
|
+
IdentityRecoverySessionConsumedError,
|
|
22740
|
+
PIN_MAX_LENGTH,
|
|
22741
|
+
PIN_MIN_LENGTH,
|
|
20040
22742
|
SSSApiClient,
|
|
20041
22743
|
SSSKeyManager,
|
|
20042
22744
|
SSS_THRESHOLD,
|
|
20043
22745
|
SSS_TOTAL_SHARES,
|
|
20044
22746
|
ShareVerificationError,
|
|
22747
|
+
ShareWriteRejectedError,
|
|
20045
22748
|
approveQrLoginSession,
|
|
20046
22749
|
atomicRecovery,
|
|
20047
22750
|
atomicShareUpdate,
|
|
20048
22751
|
base64ToBuffer,
|
|
20049
22752
|
bufferToBase64,
|
|
22753
|
+
buildRecoveryPhraseChallengeOptions,
|
|
20050
22754
|
bytesToHex,
|
|
20051
22755
|
clearAllShares,
|
|
22756
|
+
constantTimeEqualHex,
|
|
20052
22757
|
countWords,
|
|
20053
22758
|
createAdaptiveStorage,
|
|
20054
22759
|
createPasskeyCredential,
|
|
20055
22760
|
createQrLoginSession,
|
|
20056
22761
|
createSSSKeyManager,
|
|
20057
22762
|
createSSSStrategy,
|
|
22763
|
+
decryptEmailRelayPayload,
|
|
22764
|
+
decryptEscrowBlob,
|
|
20058
22765
|
decryptShareFromTransfer,
|
|
20059
22766
|
decryptShareWithPasskey,
|
|
20060
22767
|
decryptWithPassword,
|
|
20061
22768
|
deleteDeviceShare,
|
|
20062
22769
|
deriveKeyFromPasskey,
|
|
20063
22770
|
deriveKeyFromPassword,
|
|
22771
|
+
derivePinProof,
|
|
22772
|
+
encryptEmailRelayPayload,
|
|
22773
|
+
encryptEscrowBlob,
|
|
20064
22774
|
encryptShareForTransfer,
|
|
20065
22775
|
encryptShareWithPasskey,
|
|
20066
22776
|
encryptWithPassword,
|
|
20067
22777
|
generateEd25519PrivateKey,
|
|
22778
|
+
generateEmailRelayConfirmationCode,
|
|
20068
22779
|
generateEphemeralKeypair,
|
|
22780
|
+
generateEscrowKeyPair,
|
|
22781
|
+
generatePinSalt,
|
|
20069
22782
|
generateRecoveryPhrase,
|
|
20070
22783
|
getDeviceShare,
|
|
20071
22784
|
getQrLoginSessionInfo,
|
|
@@ -20076,18 +22789,28 @@ export {
|
|
|
20076
22789
|
isPublicComputerMode,
|
|
20077
22790
|
isWebAuthnSupported,
|
|
20078
22791
|
listAllDeviceShares,
|
|
22792
|
+
normalizePin,
|
|
20079
22793
|
notifyDevicesForQrSession,
|
|
22794
|
+
openEscrowRelease,
|
|
22795
|
+
parseEmailRelayEnvelope,
|
|
22796
|
+
parseEmailRelayPlaintext,
|
|
22797
|
+
parseEscrowBlobPlaintext,
|
|
22798
|
+
parseEscrowEnvelope,
|
|
22799
|
+
parseEscrowReleasePlaintext,
|
|
20080
22800
|
pollQrLoginSession,
|
|
20081
22801
|
pollUntilApproved,
|
|
20082
22802
|
reconstructFromShares,
|
|
20083
22803
|
reconstructPrivateKey,
|
|
20084
22804
|
recoveryPhraseToShare,
|
|
22805
|
+
sealEscrowRelease,
|
|
20085
22806
|
setPublicComputerMode,
|
|
20086
22807
|
shareToRecoveryPhrase,
|
|
20087
22808
|
splitAndVerify,
|
|
20088
22809
|
splitPrivateKey,
|
|
20089
22810
|
storeDeviceShare,
|
|
20090
22811
|
storeShareVersion,
|
|
22812
|
+
validatePin,
|
|
20091
22813
|
validateRecoveryPhrase,
|
|
22814
|
+
verifyEnclaveAttestation,
|
|
20092
22815
|
verifyStoredShares
|
|
20093
22816
|
};
|