@learncard/network-brain-service 3.18.0 → 3.19.1
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/.env.example +38 -2
- package/CHANGELOG.md +118 -0
- package/README.md +42 -0
- package/compose.inbox.yml +7 -0
- package/dist/accesslayer/auth-grant/read.d.ts +1 -0
- package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
- package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
- package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/accesslayer/presentation/read.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
- package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts +3 -0
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
- package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
- package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
- package/dist/accesslayer/share-link/errors.d.ts +8 -0
- package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts +3 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
- package/dist/accesslayer/share-link/helpers.d.ts +56 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
- package/dist/accesslayer/share-link/index.d.ts +12 -0
- package/dist/accesslayer/share-link/index.d.ts.map +1 -0
- package/dist/accesslayer/share-link/list.d.ts +5 -0
- package/dist/accesslayer/share-link/list.d.ts.map +1 -0
- package/dist/accesslayer/share-link/read.d.ts +5 -0
- package/dist/accesslayer/share-link/read.d.ts.map +1 -0
- package/dist/accesslayer/share-link/receipt.d.ts +11 -0
- package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
- package/dist/accesslayer/share-link/recovery.d.ts +6 -0
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
- package/dist/accesslayer/share-link/reserve.d.ts +4 -0
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
- package/dist/accesslayer/share-link/revoke.d.ts +3 -0
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction.d.ts +15 -0
- package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
- package/dist/accesslayer/share-link/types.d.ts +370 -0
- package/dist/accesslayer/share-link/types.d.ts.map +1 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
- package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
- package/dist/cache/atomic-counter.test.d.ts +2 -0
- package/dist/cache/atomic-counter.test.d.ts.map +1 -0
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/config/environment.d.ts +55 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/helpers/activity.helpers.d.ts +4 -0
- package/dist/helpers/activity.helpers.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
- package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential.helpers.d.ts +2 -1
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
- package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
- package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
- package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
- package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox.helpers.d.ts +9 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/presentation-metadata.d.ts +4 -0
- package/dist/helpers/presentation-metadata.d.ts.map +1 -0
- package/dist/helpers/presentation.helpers.d.ts +1 -1
- package/dist/helpers/presentation.helpers.d.ts.map +1 -1
- package/dist/helpers/profile.helpers.d.ts +1 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
- package/dist/helpers/share-content-client/adapters.d.ts +10 -0
- package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-client/canonical.d.ts +7 -0
- package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-client/client.d.ts +5 -0
- package/dist/helpers/share-content-client/client.d.ts.map +1 -0
- package/dist/helpers/share-content-client/config.d.ts +11 -0
- package/dist/helpers/share-content-client/config.d.ts.map +1 -0
- package/dist/helpers/share-content-client/index.d.ts +6 -0
- package/dist/helpers/share-content-client/index.d.ts.map +1 -0
- package/dist/helpers/share-content-client/responses.d.ts +16 -0
- package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
- package/dist/helpers/share-content-client/types.d.ts +145 -0
- package/dist/helpers/share-content-client/types.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
- package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
- package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
- package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts +19 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +36 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/types.d.ts +53 -0
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/config.d.ts +16 -0
- package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
- package/dist/helpers/share-link-owner-projection.d.ts +4 -0
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode.d.ts +6 -0
- package/dist/helpers/share-link-passcode.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/index.d.ts +4 -0
- package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/production.d.ts +13 -0
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/types.d.ts +16 -0
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
- package/dist/helpers/share-link-public/config.d.ts +13 -0
- package/dist/helpers/share-link-public/config.d.ts.map +1 -0
- package/dist/helpers/share-link-view-notification.d.ts +2 -0
- package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
- package/dist/helpers/shim.d.ts +1 -1
- package/dist/helpers/shim.d.ts.map +1 -1
- package/dist/inbox-queue-local.d.ts +2 -0
- package/dist/inbox-queue-local.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AuthGrant.d.ts.map +1 -1
- package/dist/models/CredentialActivity.d.ts +1 -0
- package/dist/models/CredentialActivity.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -2
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/InboxCredential.d.ts +1 -0
- package/dist/models/InboxCredential.d.ts.map +1 -1
- package/dist/models/Presentation.d.ts +2 -0
- package/dist/models/Presentation.d.ts.map +1 -1
- package/dist/models/Profile.d.ts +2 -0
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +44 -0
- package/dist/models/ShareLink.d.ts.map +1 -0
- package/dist/models/ShareViewReceipt.d.ts +22 -0
- package/dist/models/ShareViewReceipt.d.ts.map +1 -0
- package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts +3 -0
- package/dist/models/share-link-constraints.d.ts.map +1 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/public-share-link-http.d.ts +4 -0
- package/dist/public-share-link-http.d.ts.map +1 -0
- package/dist/routes/act-as.test.d.ts +2 -0
- package/dist/routes/act-as.test.d.ts.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +10 -0
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +494 -0
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +2 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +1 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +953 -11
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +154 -85
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +1 -0
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +230 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +844 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
- package/dist/services/registry/registry.factory.d.ts.map +1 -1
- package/dist/types/activity.d.ts +4 -0
- package/dist/types/activity.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +1 -1
- package/dist/types/credential-refresh.d.ts +10 -1
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/inbox-batch.d.ts +38 -0
- package/dist/types/inbox-batch.d.ts.map +1 -0
- package/inbox-e2e-setup.ts +53 -1
- package/inbox-elasticmq.conf +10 -0
- package/lambda.ts +64 -16
- package/package.json +24 -16
- package/scripts/seed-demo-persona.ts +425 -0
- package/scripts/setup-inbox-queues.ts +60 -0
- package/serverless-local.yml +2 -0
- package/serverless.yml +111 -0
- package/shareLinkMaintenanceLambda.ts +49 -0
- package/src/accesslayer/auth-grant/read.ts +2 -1
- package/src/accesslayer/consentflowcontract/relationships/create.ts +9 -16
- package/src/accesslayer/consentflowcontract/relationships/update.ts +15 -28
- package/src/accesslayer/credential-activity/create.ts +1 -0
- package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
- package/src/accesslayer/credential-refresh/index.ts +1 -0
- package/src/accesslayer/credential-refresh/read.ts +8 -2
- package/src/accesslayer/credential-refresh/update.ts +4 -0
- package/src/accesslayer/inbox-batch/store.ts +469 -0
- package/src/accesslayer/inbox-credential/create.ts +51 -19
- package/src/accesslayer/inbox-credential/refresh.ts +221 -0
- package/src/accesslayer/inbox-credential/update.ts +1 -1
- package/src/accesslayer/presentation/read.ts +57 -25
- package/src/accesslayer/presentation/relationships/create.ts +54 -11
- package/src/accesslayer/presentation/relationships/read.ts +21 -6
- package/src/accesslayer/share-link/abandon.ts +100 -0
- package/src/accesslayer/share-link/cleanup.ts +271 -0
- package/src/accesslayer/share-link/errors.ts +32 -0
- package/src/accesslayer/share-link/finalize.ts +265 -0
- package/src/accesslayer/share-link/helpers.ts +419 -0
- package/src/accesslayer/share-link/index.ts +11 -0
- package/src/accesslayer/share-link/list.ts +162 -0
- package/src/accesslayer/share-link/read.ts +107 -0
- package/src/accesslayer/share-link/receipt.ts +548 -0
- package/src/accesslayer/share-link/recovery.ts +545 -0
- package/src/accesslayer/share-link/reserve.ts +684 -0
- package/src/accesslayer/share-link/revoke.ts +197 -0
- package/src/accesslayer/share-link/transaction-execution.ts +72 -0
- package/src/accesslayer/share-link/transaction.ts +126 -0
- package/src/accesslayer/share-link/types.ts +601 -0
- package/src/app.ts +6 -0
- package/src/cache/atomic-counter.test-helpers.ts +27 -0
- package/src/cache/atomic-counter.test.ts +6 -0
- package/src/cache/index.ts +25 -13
- package/src/config/environment.ts +40 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/credential-refresh.ts +5 -1
- package/src/docker-entry.ts +65 -0
- package/src/helpers/activity.helpers.ts +4 -0
- package/src/helpers/app-counter.helpers.ts +1 -1
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/contact-method.helpers.test.ts +43 -0
- package/src/helpers/contact-method.helpers.ts +3 -4
- package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
- package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
- package/src/helpers/credential-refresh-email.helpers.ts +201 -0
- package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
- package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
- package/src/helpers/credential-refresh.helpers.ts +375 -99
- package/src/helpers/credential.helpers.ts +4 -1
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/finalize-inbox.helpers.ts +29 -14
- package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
- package/src/helpers/inbox-batch-http.helpers.ts +150 -0
- package/src/helpers/inbox-batch-status.helpers.ts +11 -0
- package/src/helpers/inbox-batch.helpers.test.ts +668 -0
- package/src/helpers/inbox-batch.helpers.ts +374 -0
- package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
- package/src/helpers/inbox-queue.helpers.test.ts +253 -0
- package/src/helpers/inbox-queue.helpers.ts +372 -0
- package/src/helpers/inbox-refresh.helpers.ts +432 -0
- package/src/helpers/inbox.helpers.ts +275 -36
- package/src/helpers/notificationMessages.ts +18 -1
- package/src/helpers/notifications.helpers.ts +4 -2
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +7 -0
- package/src/helpers/rateLimit.helpers.ts +2 -5
- package/src/helpers/refresh-send-intent.helpers.ts +273 -0
- package/src/helpers/share-content-client/adapters.ts +55 -0
- package/src/helpers/share-content-client/canonical.ts +25 -0
- package/src/helpers/share-content-client/client.ts +440 -0
- package/src/helpers/share-content-client/config.ts +189 -0
- package/src/helpers/share-content-client/index.ts +27 -0
- package/src/helpers/share-content-client/responses.ts +273 -0
- package/src/helpers/share-content-client/types.ts +253 -0
- package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
- package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +714 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +428 -0
- package/src/helpers/share-link-coordinator/runtime.ts +88 -0
- package/src/helpers/share-link-coordinator/stat.ts +30 -0
- package/src/helpers/share-link-coordinator/types.ts +353 -0
- package/src/helpers/share-link-lifecycle/index.ts +4 -0
- package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
- package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
- package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
- package/src/helpers/share-link-lifecycle/types.ts +58 -0
- package/src/helpers/share-link-maintenance/budget.ts +63 -0
- package/src/helpers/share-link-maintenance/config.ts +312 -0
- package/src/helpers/share-link-maintenance/index.ts +24 -0
- package/src/helpers/share-link-maintenance/runner.ts +223 -0
- package/src/helpers/share-link-maintenance/runtime.ts +334 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +70 -0
- package/src/helpers/share-link-maintenance/types.ts +89 -0
- package/src/helpers/share-link-owner/config.ts +48 -0
- package/src/helpers/share-link-owner/diagnostics.ts +89 -0
- package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
- package/src/helpers/share-link-owner-projection.ts +52 -0
- package/src/helpers/share-link-passcode-abuse.ts +36 -0
- package/src/helpers/share-link-passcode.ts +47 -0
- package/src/helpers/share-link-policy/index.ts +7 -0
- package/src/helpers/share-link-policy/production.ts +88 -0
- package/src/helpers/share-link-policy/resolver.ts +108 -0
- package/src/helpers/share-link-policy/types.ts +48 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/helpers/shim.ts +32 -31
- package/src/inbox-queue-local.ts +28 -0
- package/src/models/AuthGrant.ts +1 -0
- package/src/models/CredentialActivity.ts +3 -5
- package/src/models/CredentialRefresh.ts +4 -2
- package/src/models/InboxCredential.ts +2 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +130 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/credential-refresh-constraints.test.ts +8 -3
- package/src/models/credential-refresh-constraints.ts +5 -0
- package/src/models/index.ts +2 -0
- package/src/models/share-link-constraints.ts +93 -0
- package/src/openapi.ts +8 -0
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/act-as.test.ts +239 -0
- package/src/routes/app-store.ts +4 -13
- package/src/routes/auth-grants.ts +13 -11
- package/src/routes/boosts.ts +916 -103
- package/src/routes/contracts.ts +2 -11
- package/src/routes/credential-refreshes.test.ts +8 -2
- package/src/routes/credential-refreshes.ts +4 -1
- package/src/routes/credentials.ts +2 -1
- package/src/routes/inbox.ts +95 -49
- package/src/routes/index.ts +216 -46
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +10 -2
- package/src/routes/profiles.ts +17 -6
- package/src/routes/public-share-links.ts +814 -0
- package/src/routes/share-links.ts +640 -0
- package/src/routes/skills.ts +1 -1
- package/src/routes/workflows.ts +43 -24
- package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
- package/src/services/delivery/adapters/postmark.adapter.ts +16 -1
- package/src/services/registry/registry.factory.ts +2 -1
- package/src/types/activity.ts +2 -0
- package/src/types/credential-refresh.ts +21 -2
- package/src/types/inbox-batch.ts +46 -0
- package/test/atomic-counter.e2e.ts +12 -0
- package/test/auth-grant.spec.ts +21 -21
- package/test/credential-refresh-email.spec.ts +663 -0
- package/test/credential-refresh-publication.spec.ts +2 -0
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +13 -0
- package/test/helpers/inbox-e2e-environment.ts +6 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/inbox-batch.spec.ts +1338 -0
- package/test/inbox-refresh.spec.ts +534 -0
- package/test/managed-recipient-inbox.spec.ts +130 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +61 -1
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +67 -1
- package/test/refresh-send.spec.ts +1799 -0
- package/test/registry.spec.ts +56 -11
- package/test/share-content-client.contract.spec.ts +365 -0
- package/test/share-content-client.unit.spec.ts +462 -0
- package/test/share-link-abuse.unit.spec.ts +65 -0
- package/test/share-link-coordinator.neo4j.spec.ts +684 -0
- package/test/share-link-coordinator.unit.spec.ts +990 -0
- package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +1268 -0
- package/test/share-link-lifecycle.unit.spec.ts +205 -0
- package/test/share-link-list-cursor.unit.spec.ts +86 -0
- package/test/share-link-list.neo4j.spec.ts +324 -0
- package/test/share-link-maintenance.neo4j.spec.ts +355 -0
- package/test/share-link-maintenance.unit.spec.ts +1113 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +752 -0
- package/test/share-link-owner-runtime.unit.spec.ts +102 -0
- package/test/share-link-passcode.unit.spec.ts +29 -0
- package/test/share-link-policy.unit.spec.ts +135 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +77 -0
- package/test/share-link-public-config.unit.spec.ts +64 -0
- package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
- package/test/share-link-public-route.unit.spec.ts +800 -0
- package/test/share-link-receipt.neo4j.spec.ts +943 -0
- package/test/share-link-recovery.neo4j.spec.ts +935 -0
- package/test/share-link-recovery.unit.spec.ts +726 -0
- package/vitest.config.ts +10 -0
- package/vitest.inbox-e2e.config.ts +5 -1
- package/vitest.integration.config.ts +2 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import { environment } from '@environment';
|
|
2
|
+
import { recordInboxRefreshClaimUrl } from '@accesslayer/inbox-credential/refresh';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import {
|
|
5
|
+
assertInboxRefreshEnabled,
|
|
6
|
+
prepareInboxRefresh,
|
|
7
|
+
getInboxRefreshReceipt,
|
|
8
|
+
finalizeInboxRefresh,
|
|
9
|
+
getInboxRefreshIssueKey,
|
|
10
|
+
resumeInboxRefreshDelivery,
|
|
11
|
+
} from './inbox-refresh.helpers';
|
|
1
12
|
import { TRPCError } from '@trpc/server';
|
|
13
|
+
import {
|
|
14
|
+
InboxDeliveryCheckpointError,
|
|
15
|
+
InboxIssuancePreflightError,
|
|
16
|
+
} from './inbox-issuance-error.helpers';
|
|
2
17
|
import {
|
|
3
18
|
VC,
|
|
4
19
|
UnsignedVC,
|
|
@@ -32,7 +47,7 @@ import { doesProfileManageProfile } from '@accesslayer/profile-manager/relations
|
|
|
32
47
|
import { getProfilesThatManageAProfile } from '@accesslayer/profile/relationships/read';
|
|
33
48
|
import { getProfileForInboxCredential } from '@accesslayer/inbox-credential/read';
|
|
34
49
|
import { sendCredential } from '@helpers/credential.helpers';
|
|
35
|
-
import { sendBoost } from '@helpers/boost.helpers';
|
|
50
|
+
import { prepareCredentialFromBoost, getBoostUri, sendBoost } from '@helpers/boost.helpers';
|
|
36
51
|
import { getBoostByUri } from '@accesslayer/boost/read';
|
|
37
52
|
import { issueCredentialWithSigningAuthority } from '@helpers/signingAuthority.helpers';
|
|
38
53
|
import { getSigningAuthorityForUserByName } from '@accesslayer/signing-authority/relationships/read';
|
|
@@ -192,9 +207,15 @@ export const issueToInbox = async (
|
|
|
192
207
|
activityId?: string;
|
|
193
208
|
integrationId?: string;
|
|
194
209
|
guardianEmail?: string;
|
|
210
|
+
refresh?: boolean;
|
|
211
|
+
idempotencyKey?: string;
|
|
212
|
+
refreshRequestDigest?: string;
|
|
195
213
|
} = {},
|
|
196
|
-
ctx: Context
|
|
214
|
+
ctx: Context,
|
|
215
|
+
/** Queue ownership checkpoint immediately before delivery or inbox persistence. */
|
|
216
|
+
beforeDelivery?: () => Promise<void>
|
|
197
217
|
): Promise<{
|
|
218
|
+
refresh?: import('@learncard/types').InboxCredentialRefreshReceipt;
|
|
198
219
|
status: 'PENDING' | 'ISSUED' | 'EXPIRED' | 'DELIVERED' | 'CLAIMED'; // DELIVERED & CLAIMED are deprecated, use ISSUED
|
|
199
220
|
inboxCredential: InboxCredentialType;
|
|
200
221
|
claimUrl?: string;
|
|
@@ -211,13 +232,35 @@ export const issueToInbox = async (
|
|
|
211
232
|
integrationId,
|
|
212
233
|
guardianEmail,
|
|
213
234
|
} = configuration;
|
|
235
|
+
if (configuration.refresh) {
|
|
236
|
+
await assertInboxRefreshEnabled(ctx.user?.scope);
|
|
237
|
+
if (
|
|
238
|
+
credential.proof ||
|
|
239
|
+
credential.type.includes('VerifiablePresentation') ||
|
|
240
|
+
Array.isArray(credential.credentialSubject)
|
|
241
|
+
) {
|
|
242
|
+
throw new TRPCError({
|
|
243
|
+
code: 'BAD_REQUEST',
|
|
244
|
+
message:
|
|
245
|
+
'Inbox refresh requires an unsigned credential with a single subject and a signing authority.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
214
249
|
const isSigned = !!credential?.proof;
|
|
215
250
|
let signingAuthority: IssueInboxSigningAuthority | undefined = _signingAuthority;
|
|
251
|
+
let deliveryCheckpointReached = beforeDelivery === undefined;
|
|
252
|
+
const checkpointDelivery = async (): Promise<void> => {
|
|
253
|
+
await beforeDelivery?.();
|
|
254
|
+
deliveryCheckpointReached = true;
|
|
255
|
+
};
|
|
256
|
+
const assertDeliveryCheckpoint = (result: InboxDeliveryCheckpointError['result']): void => {
|
|
257
|
+
if (!deliveryCheckpointReached) throw new InboxDeliveryCheckpointError(result);
|
|
258
|
+
};
|
|
216
259
|
|
|
217
260
|
if (recipient.type === 'phone') {
|
|
218
261
|
const isTrusted = await getRegistryService().isTrusted(issuerProfile.did);
|
|
219
262
|
if (!isTrusted) {
|
|
220
|
-
throw new
|
|
263
|
+
throw new InboxIssuancePreflightError({
|
|
221
264
|
code: 'FORBIDDEN',
|
|
222
265
|
message:
|
|
223
266
|
'Sending credentials via phone is a feature reserved for members of the LearnCard Trusted Registry. Email delivery is available for all issuers. To verify your issuer, visit: https://docs.learncard.com/how-to-guides/verify-my-issuer',
|
|
@@ -245,20 +288,35 @@ export const issueToInbox = async (
|
|
|
245
288
|
* By providing this parameter, the developer explicitly takes responsibility for the signing process. We trust them and proceed.
|
|
246
289
|
**/
|
|
247
290
|
if (!(await verifyCredentialCanBeSigned(credential as UnsignedVC))) {
|
|
248
|
-
throw new
|
|
291
|
+
throw new InboxIssuancePreflightError({
|
|
249
292
|
code: 'BAD_REQUEST',
|
|
250
293
|
message:
|
|
251
294
|
'Credential failed to pass a pre-flight issuance test. Please verify that the credential is well-formed and can be issued.',
|
|
252
295
|
});
|
|
253
296
|
}
|
|
254
297
|
} else {
|
|
255
|
-
throw new
|
|
298
|
+
throw new InboxIssuancePreflightError({
|
|
256
299
|
code: 'BAD_REQUEST',
|
|
257
300
|
message: 'Unsigned credentials require a signing authority',
|
|
258
301
|
});
|
|
259
302
|
}
|
|
260
303
|
}
|
|
261
304
|
|
|
305
|
+
if (
|
|
306
|
+
configuration.refresh &&
|
|
307
|
+
(!signingAuthority ||
|
|
308
|
+
!(await getSigningAuthorityForUserByName(
|
|
309
|
+
issuerProfile,
|
|
310
|
+
signingAuthority.endpoint,
|
|
311
|
+
signingAuthority.name
|
|
312
|
+
)))
|
|
313
|
+
) {
|
|
314
|
+
throw new TRPCError({
|
|
315
|
+
code: 'NOT_FOUND',
|
|
316
|
+
message: 'Signing authority not found for issuer',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
262
320
|
// Check if recipient already exists with verified email
|
|
263
321
|
const existingProfile = await getProfileByVerifiedContactMethod(
|
|
264
322
|
recipient.type,
|
|
@@ -267,12 +325,14 @@ export const issueToInbox = async (
|
|
|
267
325
|
|
|
268
326
|
// Check if the recipient is a managed child (has a guardian via MANAGES relationship).
|
|
269
327
|
// Managed children need guardian approval for all inbox credentials, even without explicit guardianEmail.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
328
|
+
// Service profiles (orgs) are managed for admin purposes, not guardianship.
|
|
329
|
+
const recipientManagers =
|
|
330
|
+
existingProfile && !existingProfile.isServiceProfile
|
|
331
|
+
? await getProfilesThatManageAProfile(existingProfile.profileId)
|
|
332
|
+
: [];
|
|
273
333
|
const recipientIsManaged = recipientManagers.length > 0;
|
|
274
334
|
|
|
275
|
-
if (existingProfile && !guardianEmail && !recipientIsManaged) {
|
|
335
|
+
if (existingProfile && !guardianEmail && !recipientIsManaged && !configuration.refresh) {
|
|
276
336
|
// Auto-deliver to existing user
|
|
277
337
|
let finalCredential: VC;
|
|
278
338
|
|
|
@@ -287,7 +347,7 @@ export const issueToInbox = async (
|
|
|
287
347
|
);
|
|
288
348
|
|
|
289
349
|
if (!signingAuthorityForUser) {
|
|
290
|
-
throw new
|
|
350
|
+
throw new InboxIssuancePreflightError({
|
|
291
351
|
code: 'NOT_FOUND',
|
|
292
352
|
message: 'Signing authority not found for issuer',
|
|
293
353
|
});
|
|
@@ -317,31 +377,20 @@ export const issueToInbox = async (
|
|
|
317
377
|
credential: encryptedDelivery,
|
|
318
378
|
statusEntries: getBitstringStatusListEntries(finalCredential),
|
|
319
379
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
} else {
|
|
333
|
-
// Fallback to sendCredential if boost not found
|
|
334
|
-
await sendCredential(
|
|
335
|
-
issuerProfile,
|
|
336
|
-
existingProfile,
|
|
337
|
-
delivery,
|
|
338
|
-
ctx.domain,
|
|
339
|
-
undefined,
|
|
340
|
-
activityId,
|
|
341
|
-
integrationId
|
|
342
|
-
);
|
|
343
|
-
}
|
|
380
|
+
const boost = boostUri ? await getBoostByUri(boostUri) : undefined;
|
|
381
|
+
await checkpointDelivery();
|
|
382
|
+
if (boostUri && boost) {
|
|
383
|
+
await sendBoost({
|
|
384
|
+
from: { type: 'profile', profile: issuerProfile },
|
|
385
|
+
to: existingProfile,
|
|
386
|
+
boost,
|
|
387
|
+
credential: delivery,
|
|
388
|
+
domain: ctx.domain,
|
|
389
|
+
activityId,
|
|
390
|
+
integrationId,
|
|
391
|
+
});
|
|
344
392
|
} else {
|
|
393
|
+
// Fall back to ordinary delivery when no boost exists for the supplied URI.
|
|
345
394
|
await sendCredential(
|
|
346
395
|
issuerProfile,
|
|
347
396
|
existingProfile,
|
|
@@ -372,6 +421,7 @@ export const issueToInbox = async (
|
|
|
372
421
|
await logCredentialDelivered({
|
|
373
422
|
activityId,
|
|
374
423
|
actorProfileId: issuerProfile.profileId,
|
|
424
|
+
onBehalfOf: ctx.user?.onBehalfOf,
|
|
375
425
|
recipientType: recipient.type as 'email' | 'phone',
|
|
376
426
|
recipientIdentifier: recipient.value,
|
|
377
427
|
recipientProfileId: existingProfile.profileId,
|
|
@@ -420,6 +470,11 @@ export const issueToInbox = async (
|
|
|
420
470
|
});
|
|
421
471
|
}
|
|
422
472
|
|
|
473
|
+
assertDeliveryCheckpoint({
|
|
474
|
+
issuanceId: finalizedInboxCredential.id,
|
|
475
|
+
status: LCNInboxStatusEnumValidator.enum.ISSUED,
|
|
476
|
+
recipientDid: existingProfile.did,
|
|
477
|
+
});
|
|
423
478
|
return {
|
|
424
479
|
status: LCNInboxStatusEnumValidator.enum.ISSUED,
|
|
425
480
|
inboxCredential: finalizedInboxCredential,
|
|
@@ -429,7 +484,24 @@ export const issueToInbox = async (
|
|
|
429
484
|
// Store in inbox for claiming
|
|
430
485
|
// Guardian gate if issuer specified guardianEmail OR recipient is a managed child
|
|
431
486
|
const needsGuardianGate = !!guardianEmail || recipientIsManaged;
|
|
487
|
+
const preparedRefresh = configuration.refresh
|
|
488
|
+
? await prepareInboxRefresh(credential, issuerProfile, ctx.domain)
|
|
489
|
+
: undefined;
|
|
490
|
+
if (preparedRefresh) credential = preparedRefresh.credential;
|
|
491
|
+
await checkpointDelivery();
|
|
432
492
|
const inboxCredential = await createInboxCredential({
|
|
493
|
+
...(preparedRefresh
|
|
494
|
+
? {
|
|
495
|
+
refresh: {
|
|
496
|
+
aggregate: preparedRefresh.aggregate,
|
|
497
|
+
issueKey: getInboxRefreshIssueKey(
|
|
498
|
+
issuerProfile.profileId,
|
|
499
|
+
configuration.idempotencyKey ?? randomUUID()
|
|
500
|
+
),
|
|
501
|
+
requestDigest: configuration.refreshRequestDigest!,
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
: {}),
|
|
433
505
|
credential: JSON.stringify(credential),
|
|
434
506
|
isSigned,
|
|
435
507
|
recipient,
|
|
@@ -444,6 +516,101 @@ export const issueToInbox = async (
|
|
|
444
516
|
...(needsGuardianGate ? { guardianStatus: 'AWAITING_GUARDIAN' as const } : {}),
|
|
445
517
|
});
|
|
446
518
|
|
|
519
|
+
const refresh = inboxCredential.refreshId
|
|
520
|
+
? await getInboxRefreshReceipt(inboxCredential.refreshId, ctx.domain)
|
|
521
|
+
: undefined;
|
|
522
|
+
if (refresh && existingProfile && !needsGuardianGate) {
|
|
523
|
+
await finalizeInboxRefresh({
|
|
524
|
+
inboxId: inboxCredential.id,
|
|
525
|
+
holderDid: existingProfile.did,
|
|
526
|
+
holderProfile: existingProfile,
|
|
527
|
+
domain: ctx.domain,
|
|
528
|
+
accepted: false,
|
|
529
|
+
});
|
|
530
|
+
await resumeInboxRefreshDelivery(refresh.refreshId, ctx.domain);
|
|
531
|
+
await createDeliveredRelationship(
|
|
532
|
+
issuerProfile.profileId,
|
|
533
|
+
inboxCredential.id,
|
|
534
|
+
existingProfile.did,
|
|
535
|
+
'auto-delivery'
|
|
536
|
+
);
|
|
537
|
+
if (activityId)
|
|
538
|
+
await logCredentialDelivered({
|
|
539
|
+
activityId,
|
|
540
|
+
actorProfileId: issuerProfile.profileId,
|
|
541
|
+
onBehalfOf: ctx.user?.onBehalfOf,
|
|
542
|
+
recipientType: recipient.type,
|
|
543
|
+
recipientIdentifier: recipient.value,
|
|
544
|
+
recipientProfileId: existingProfile.profileId,
|
|
545
|
+
boostUri,
|
|
546
|
+
inboxCredentialId: inboxCredential.id,
|
|
547
|
+
integrationId,
|
|
548
|
+
source: 'send',
|
|
549
|
+
});
|
|
550
|
+
if (webhookUrl)
|
|
551
|
+
await addNotificationToQueue({
|
|
552
|
+
webhookUrl,
|
|
553
|
+
type: LCNNotificationTypeEnumValidator.enum.ISSUANCE_DELIVERED,
|
|
554
|
+
from: { did: (await getLearnCard()).id.did() },
|
|
555
|
+
to: issuerProfile,
|
|
556
|
+
message: getNotificationMessage(
|
|
557
|
+
'issuanceDelivered',
|
|
558
|
+
resolveRecipientLocale(issuerProfile),
|
|
559
|
+
{
|
|
560
|
+
issuer: issuerProfile.displayName,
|
|
561
|
+
recipientType: recipient.type,
|
|
562
|
+
recipientValue: recipient.value,
|
|
563
|
+
}
|
|
564
|
+
),
|
|
565
|
+
data: {
|
|
566
|
+
inbox: {
|
|
567
|
+
issuanceId: inboxCredential.id,
|
|
568
|
+
status: LCNInboxStatusEnumValidator.enum.ISSUED,
|
|
569
|
+
recipient: {
|
|
570
|
+
contactMethod: recipient,
|
|
571
|
+
learnCardId: existingProfile.did,
|
|
572
|
+
},
|
|
573
|
+
timestamp: new Date().toISOString(),
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
// Existing accounts receive the signed credential in their app inbox.
|
|
578
|
+
// Send the email there too, without a claim token for an already-bound delivery.
|
|
579
|
+
if (recipient.type === 'email' && !delivery?.suppress) {
|
|
580
|
+
try {
|
|
581
|
+
const appUrl = environment.IS_OFFLINE
|
|
582
|
+
? generateClaimUrl('')
|
|
583
|
+
: ctx.tenant?.emailBranding?.appUrl || generateClaimUrl('');
|
|
584
|
+
await getDeliveryService(recipient).send({
|
|
585
|
+
contactMethod: recipient,
|
|
586
|
+
templateId: 'universal-inbox-claim',
|
|
587
|
+
templateModel: {
|
|
588
|
+
claimUrl: new URL('/notifications', appUrl).href,
|
|
589
|
+
issuer: { name: issuerProfile.displayName },
|
|
590
|
+
credential: { name: credential.name },
|
|
591
|
+
},
|
|
592
|
+
branding: ctx.tenant?.emailBranding,
|
|
593
|
+
locale: resolveRecipientLocale(existingProfile),
|
|
594
|
+
messageStream: 'universal-inbox',
|
|
595
|
+
});
|
|
596
|
+
} catch {
|
|
597
|
+
// The credential and in-app notification are already durable. An
|
|
598
|
+
// email transport failure must not turn that delivery into an error.
|
|
599
|
+
console.warn('Inbox refresh: initial email failed after auto-delivery.');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
status: 'ISSUED',
|
|
604
|
+
inboxCredential: {
|
|
605
|
+
...inboxCredential,
|
|
606
|
+
currentStatus: 'ISSUED',
|
|
607
|
+
credential: undefined,
|
|
608
|
+
},
|
|
609
|
+
recipientDid: existingProfile.did,
|
|
610
|
+
refresh: { ...refresh, holderDid: existingProfile.did },
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
447
614
|
// Generate claim token
|
|
448
615
|
let recipientContactMethod = await getContactMethodByValue(recipient.type, recipient.value);
|
|
449
616
|
if (!recipientContactMethod) {
|
|
@@ -712,8 +879,9 @@ export const issueToInbox = async (
|
|
|
712
879
|
|
|
713
880
|
await deliveryService.send({
|
|
714
881
|
contactMethod: recipient,
|
|
715
|
-
templateId: delivery?.template?.id
|
|
882
|
+
templateId: delivery?.template?.id || 'universal-inbox-claim',
|
|
716
883
|
templateModel: {
|
|
884
|
+
claimUrl: emailClaimUrl,
|
|
717
885
|
emailClaimUrl,
|
|
718
886
|
claimToken: emailClaimToken,
|
|
719
887
|
...injectedTemplateFields,
|
|
@@ -817,13 +985,84 @@ export const issueToInbox = async (
|
|
|
817
985
|
expiresInDays ? 24 * expiresInDays : 24,
|
|
818
986
|
false
|
|
819
987
|
);
|
|
820
|
-
const claimUrl =
|
|
988
|
+
const claimUrl = refresh
|
|
989
|
+
? await recordInboxRefreshClaimUrl(inboxCredential.id, generateClaimUrl(claimToken))
|
|
990
|
+
: generateClaimUrl(claimToken);
|
|
821
991
|
|
|
992
|
+
assertDeliveryCheckpoint({
|
|
993
|
+
issuanceId: inboxCredential.id,
|
|
994
|
+
status: LCNInboxStatusEnumValidator.enum.PENDING,
|
|
995
|
+
claimUrl,
|
|
996
|
+
...(guardianEmail ? { guardianStatus: 'AWAITING_GUARDIAN' as const } : {}),
|
|
997
|
+
});
|
|
822
998
|
return {
|
|
823
999
|
status: LCNInboxStatusEnumValidator.enum.PENDING,
|
|
824
1000
|
inboxCredential,
|
|
825
1001
|
claimUrl,
|
|
1002
|
+
...(refresh ? { refresh } : {}),
|
|
826
1003
|
...(guardianEmail ? { guardianStatus: 'AWAITING_GUARDIAN' as const } : {}),
|
|
827
1004
|
};
|
|
828
1005
|
}
|
|
829
1006
|
};
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Resolves an input into the credential that issueToInbox consumes. Keeping this outside either
|
|
1010
|
+
* route makes direct credentials and Boost templates behave identically for single and batch
|
|
1011
|
+
* issuance, including template rendering, Boost metadata, and standard client-facing errors.
|
|
1012
|
+
*/
|
|
1013
|
+
export const resolveInboxCredentialInput = async (
|
|
1014
|
+
input: IssueInboxCredentialType,
|
|
1015
|
+
ctx: Context
|
|
1016
|
+
): Promise<{ credential: VC | UnsignedVC | VP; resolvedBoostUri?: string }> => {
|
|
1017
|
+
const { templateUri, configuration } = input;
|
|
1018
|
+
// A direct credential wins when both fields are present, matching the existing single-route
|
|
1019
|
+
// behavior. A template is only loaded when the caller supplied no credential.
|
|
1020
|
+
let credential = input.credential;
|
|
1021
|
+
let resolvedBoostUri: string | undefined;
|
|
1022
|
+
|
|
1023
|
+
if (templateUri && !credential) {
|
|
1024
|
+
const boostInstance = await getBoostByUri(templateUri);
|
|
1025
|
+
|
|
1026
|
+
if (!boostInstance) {
|
|
1027
|
+
throw new TRPCError({
|
|
1028
|
+
code: 'NOT_FOUND',
|
|
1029
|
+
message: `Boost not found: ${templateUri}`,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (!boostInstance.dataValues.boost) {
|
|
1034
|
+
throw new TRPCError({
|
|
1035
|
+
code: 'BAD_REQUEST',
|
|
1036
|
+
message: `Boost does not contain a credential template: ${templateUri}`,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
try {
|
|
1041
|
+
// Use shared helper to prepare credential with templateData rendering,
|
|
1042
|
+
// issuance date, boostId injection, and OBv3 alignments
|
|
1043
|
+
resolvedBoostUri = getBoostUri(boostInstance.id, ctx.domain);
|
|
1044
|
+
|
|
1045
|
+
credential = await prepareCredentialFromBoost(
|
|
1046
|
+
boostInstance,
|
|
1047
|
+
resolvedBoostUri,
|
|
1048
|
+
ctx.domain,
|
|
1049
|
+
{ templateData: configuration?.templateData as Record<string, unknown> }
|
|
1050
|
+
);
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
console.error('Failed to prepare boost credential', e);
|
|
1053
|
+
throw new TRPCError({
|
|
1054
|
+
code: 'BAD_REQUEST',
|
|
1055
|
+
message: `Failed to prepare boost credential template: ${templateUri}`,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (!credential) {
|
|
1061
|
+
throw new TRPCError({
|
|
1062
|
+
code: 'BAD_REQUEST',
|
|
1063
|
+
message: 'Either credential or templateUri must be provided',
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return { credential, resolvedBoostUri };
|
|
1068
|
+
};
|
|
@@ -57,7 +57,8 @@ export type NotificationMessageKey =
|
|
|
57
57
|
| 'credentialRestoredUnnamed'
|
|
58
58
|
// --- Managed credential refresh (LC-2136) — generic copy only: never name the
|
|
59
59
|
// credential, the holder's claims, or implementation terms (refresh/sync/etc.) ---
|
|
60
|
-
| 'credentialRefreshed'
|
|
60
|
+
| 'credentialRefreshed'
|
|
61
|
+
| 'shareViewed';
|
|
61
62
|
|
|
62
63
|
type MessageTemplate = { title: string; body: string };
|
|
63
64
|
type LocaleCatalog = Record<NotificationMessageKey, MessageTemplate>;
|
|
@@ -216,6 +217,10 @@ const en: LocaleCatalog = {
|
|
|
216
217
|
title: 'Credential updated',
|
|
217
218
|
body: '{from} updated one of your credentials.',
|
|
218
219
|
},
|
|
220
|
+
shareViewed: {
|
|
221
|
+
title: 'Share viewed',
|
|
222
|
+
body: 'Your share “{title} ({count})” was viewed.',
|
|
223
|
+
},
|
|
219
224
|
};
|
|
220
225
|
|
|
221
226
|
const es: LocaleCatalog = {
|
|
@@ -367,6 +372,10 @@ const es: LocaleCatalog = {
|
|
|
367
372
|
title: 'Credencial actualizada',
|
|
368
373
|
body: '{from} actualizó una de tus credenciales.',
|
|
369
374
|
},
|
|
375
|
+
shareViewed: {
|
|
376
|
+
title: 'Enlace visto',
|
|
377
|
+
body: 'Se vio tu enlace «{title} ({count})».',
|
|
378
|
+
},
|
|
370
379
|
};
|
|
371
380
|
|
|
372
381
|
const fr: LocaleCatalog = {
|
|
@@ -518,6 +527,10 @@ const fr: LocaleCatalog = {
|
|
|
518
527
|
title: 'Titre mis à jour',
|
|
519
528
|
body: "{from} a mis à jour l'un de vos titres.",
|
|
520
529
|
},
|
|
530
|
+
shareViewed: {
|
|
531
|
+
title: 'Partage consulté',
|
|
532
|
+
body: 'Votre partage « {title} ({count}) » a été consulté.',
|
|
533
|
+
},
|
|
521
534
|
};
|
|
522
535
|
|
|
523
536
|
const ar: LocaleCatalog = {
|
|
@@ -669,6 +682,10 @@ const ar: LocaleCatalog = {
|
|
|
669
682
|
title: 'تم تحديث الشهادة',
|
|
670
683
|
body: 'قام {from} بتحديث إحدى شهاداتك.',
|
|
671
684
|
},
|
|
685
|
+
shareViewed: {
|
|
686
|
+
title: 'تمت مشاهدة المشاركة',
|
|
687
|
+
body: 'تمت مشاهدة مشاركتك «{title} ({count})».',
|
|
688
|
+
},
|
|
672
689
|
};
|
|
673
690
|
|
|
674
691
|
const CATALOGS: Record<NotificationLocale, LocaleCatalog> = { en, es, fr, ar };
|
|
@@ -223,8 +223,10 @@ export const buildCredentialRefreshedNotification = (
|
|
|
223
223
|
return {
|
|
224
224
|
notification: {
|
|
225
225
|
type: LCNNotificationTypeEnumValidator.enum.CREDENTIAL_REFRESHED,
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
// Local webhook delivery normalizes these DIDs in place. Keep the
|
|
227
|
+
// database profiles intact for the subsequent verified-email lookup.
|
|
228
|
+
to: { ...holderProfile },
|
|
229
|
+
from: { ...issuerProfile },
|
|
228
230
|
message: getNotificationMessage(
|
|
229
231
|
'credentialRefreshed',
|
|
230
232
|
resolveRecipientLocale(holderProfile),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { ShareLinkIdValidator } from '@learncard/types';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
// Relationship metadata is not signed VP content. Bound generic metadata and
|
|
6
|
+
// reserve the share-link shape for the recipient's own send/accept workflow.
|
|
7
|
+
const ShareLinkPresentationMetadataValidator = z
|
|
8
|
+
.object({
|
|
9
|
+
type: z.literal('learncard.share-link.v1'),
|
|
10
|
+
shareId: ShareLinkIdValidator,
|
|
11
|
+
title: z.string().min(1).max(120),
|
|
12
|
+
note: z.string().max(500).optional(),
|
|
13
|
+
sharer: z
|
|
14
|
+
.object({
|
|
15
|
+
profileId: z.string().min(1).max(128),
|
|
16
|
+
displayName: z.string().min(1).max(120),
|
|
17
|
+
})
|
|
18
|
+
.strict(),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
|
|
22
|
+
export const PresentationMetadataValidator = z
|
|
23
|
+
.record(z.string().min(1).max(64), z.unknown())
|
|
24
|
+
.refine(value => JSON.stringify(value).length <= 4096, 'Metadata is too large')
|
|
25
|
+
.superRefine((value, ctx) => {
|
|
26
|
+
if (value.type !== 'learncard.share-link.v1') return;
|
|
27
|
+
if (!ShareLinkPresentationMetadataValidator.safeParse(value).success)
|
|
28
|
+
ctx.addIssue({ code: 'custom', message: 'Invalid share-link metadata' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const assertPresentationMetadataRecipient = (
|
|
32
|
+
metadata: Record<string, unknown> | undefined,
|
|
33
|
+
senderProfileId: string,
|
|
34
|
+
recipientProfileId: string
|
|
35
|
+
): void => {
|
|
36
|
+
if (metadata?.type === 'learncard.share-link.v1' && senderProfileId !== recipientProfileId)
|
|
37
|
+
throw new TRPCError({
|
|
38
|
+
code: 'BAD_REQUEST',
|
|
39
|
+
message: 'Share-link metadata requires a self-save',
|
|
40
|
+
});
|
|
41
|
+
};
|
|
@@ -23,23 +23,29 @@ export const sendPresentation = async (
|
|
|
23
23
|
from: ProfileType,
|
|
24
24
|
to: ProfileType,
|
|
25
25
|
presentation: VP | JWE,
|
|
26
|
-
domain: string
|
|
26
|
+
domain: string,
|
|
27
|
+
metadata?: Record<string, unknown>
|
|
27
28
|
): Promise<string> => {
|
|
28
29
|
const presentationInstance = await storePresentation(presentation);
|
|
29
30
|
|
|
30
|
-
await createSentPresentationRelationship(from, to, presentationInstance);
|
|
31
|
+
await createSentPresentationRelationship(from, to, presentationInstance, metadata);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
const uri = getPresentationUri(presentationInstance.id, domain);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
// Saving a shared collection reuses the presentation send/accept pipeline by
|
|
36
|
+
// sending it to the current profile. Keep that storage behavior without
|
|
37
|
+
// creating a meaningless "you sent this to yourself" recipient alert.
|
|
38
|
+
if (from.profileId !== to.profileId) {
|
|
39
|
+
await addNotificationToQueue({
|
|
40
|
+
type: LCNNotificationTypeEnumValidator.enum.PRESENTATION_RECEIVED,
|
|
41
|
+
to,
|
|
42
|
+
from,
|
|
43
|
+
message: getNotificationMessage('presentationReceived', resolveRecipientLocale(to), {
|
|
44
|
+
from: from.displayName,
|
|
45
|
+
}),
|
|
46
|
+
data: { vpUris: [uri] },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
43
49
|
|
|
44
50
|
return uri;
|
|
45
51
|
};
|
|
@@ -63,16 +69,17 @@ export const acceptPresentation = async (profile: ProfileType, uri: string): Pro
|
|
|
63
69
|
});
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
//
|
|
72
|
+
// Acceptance is idempotent so a client can safely recover from a response
|
|
73
|
+
// interruption without producing a second received relationship.
|
|
67
74
|
const alreadyReceived = await getPresentationReceivedByProfile(id, profile);
|
|
68
|
-
if (alreadyReceived) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
if (!alreadyReceived) {
|
|
76
|
+
await createReceivedPresentationRelationship(
|
|
77
|
+
profile,
|
|
78
|
+
pendingVp.source,
|
|
79
|
+
pendingVp.target,
|
|
80
|
+
pendingVp.relationship.metadata
|
|
81
|
+
);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
await createReceivedPresentationRelationship(profile, pendingVp.source, pendingVp.target);
|
|
76
|
-
|
|
77
84
|
return true;
|
|
78
85
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LCNProfileValidator } from '@learncard/types';
|
|
2
|
+
|
|
1
3
|
import { getProfileByProfileId } from '@accesslayer/profile/read';
|
|
2
4
|
import { createProfile } from '@accesslayer/profile/create';
|
|
3
5
|
import { getProfileIdFromDid } from './did.helpers';
|
|
@@ -6,6 +8,11 @@ import { ProfileType } from 'types/profile';
|
|
|
6
8
|
export const transformProfileId = (rawInput: string): string =>
|
|
7
9
|
rawInput.toLowerCase().replaceAll(':', '%3A');
|
|
8
10
|
|
|
11
|
+
export const PublicProfileIdValidator = LCNProfileValidator.shape.profileId.refine(
|
|
12
|
+
profileId => !transformProfileId(profileId).startsWith('sample-'),
|
|
13
|
+
{ message: 'Profile IDs beginning with "sample-" are reserved.' }
|
|
14
|
+
);
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Gets or creates a federated profile for cross-instance credential sending.
|
|
11
18
|
* Federated profiles use email-like format: profileId@service-domain to avoid collisions.
|
|
@@ -24,7 +24,7 @@ export type RateLimitWindow = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Consume
|
|
27
|
+
* Consume the requested units against each window, in order, and throw once any is
|
|
28
28
|
* exhausted.
|
|
29
29
|
*
|
|
30
30
|
* Fails CLOSED: if the cache is unavailable, `cache.incr` returns `undefined`
|
|
@@ -48,11 +48,8 @@ export const enforceRateLimits = async (windows: RateLimitWindow[]): Promise<voi
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (count > limit) {
|
|
51
|
-
// tRPC has no native 429; the existing convention in this service is
|
|
52
|
-
// to cast to BAD_REQUEST while keeping the semantic code, so clients
|
|
53
|
-
// can still discriminate on it.
|
|
54
51
|
throw new TRPCError({
|
|
55
|
-
code: 'TOO_MANY_REQUESTS'
|
|
52
|
+
code: 'TOO_MANY_REQUESTS',
|
|
56
53
|
message: `Rate limit exceeded: ${description}`,
|
|
57
54
|
});
|
|
58
55
|
}
|