@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
package/src/cache/index.ts
CHANGED
|
@@ -85,11 +85,11 @@ export type Cache = {
|
|
|
85
85
|
lrange: (key: RedisKey, start: number, stop: number) => Promise<string[] | undefined>;
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Atomically increments a key by 1 and returns the new value.
|
|
89
|
-
* If the key does not exist it is created with value
|
|
90
|
-
* When ttl is provided
|
|
88
|
+
* Atomically increments a key by amount (default 1) and returns the new value.
|
|
89
|
+
* If the key does not exist it is created with value amount.
|
|
90
|
+
* When ttl is provided, increment and missing-expiry repair happen atomically.
|
|
91
91
|
*/
|
|
92
|
-
incr: (key: RedisKey, ttl?: number) => Promise<number | undefined>;
|
|
92
|
+
incr: (key: RedisKey, ttl?: number, amount?: number) => Promise<number | undefined>;
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
/** Evict all keys after one hour by default */
|
|
@@ -239,19 +239,31 @@ export const getCache = (): Cache => {
|
|
|
239
239
|
|
|
240
240
|
return undefined;
|
|
241
241
|
},
|
|
242
|
-
incr: async (key, ttl) => {
|
|
242
|
+
incr: async (key, ttl, amount = 1) => {
|
|
243
243
|
try {
|
|
244
244
|
const redis = cache?.redis ?? cache?.node;
|
|
245
245
|
if (!redis) return undefined;
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
247
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) return undefined;
|
|
248
|
+
// Increment and expiry must be atomic: a crash between commands must not
|
|
249
|
+
// leave a permanent quota. Also repair counters created without an expiry.
|
|
250
|
+
if (ttl)
|
|
251
|
+
return Number(
|
|
252
|
+
await redis.eval(
|
|
253
|
+
`
|
|
254
|
+
local count = redis.call('INCRBY', KEYS[1], ARGV[1])
|
|
255
|
+
if redis.call('TTL', KEYS[1]) < 0 then
|
|
256
|
+
redis.call('EXPIRE', KEYS[1], ARGV[2])
|
|
257
|
+
end
|
|
258
|
+
return count
|
|
259
|
+
`,
|
|
260
|
+
1,
|
|
261
|
+
key,
|
|
262
|
+
amount,
|
|
263
|
+
ttl
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
return await redis.incrby(key, amount);
|
|
255
267
|
} catch (e) {
|
|
256
268
|
console.error('Cache incr error', e);
|
|
257
269
|
}
|
|
@@ -36,6 +36,9 @@ export const brainServiceEnvironmentShape = {
|
|
|
36
36
|
OIDC_EXPECTED_AUDIENCE: optionalEnvironmentString,
|
|
37
37
|
AWS_REGION: optionalEnvironmentString,
|
|
38
38
|
NOTIFICATIONS_QUEUE_URL: optionalEnvironmentUrl,
|
|
39
|
+
INBOX_QUEUE_URL: optionalEnvironmentUrl,
|
|
40
|
+
INBOX_DEAD_LETTER_QUEUE_URL: optionalEnvironmentUrl,
|
|
41
|
+
INBOX_QUEUE_ENDPOINT: optionalEnvironmentUrl,
|
|
39
42
|
NOTIFICATIONS_QUEUE_POLL_URL: optionalEnvironmentUrl,
|
|
40
43
|
NOTIFICATIONS_SERVICE_WEBHOOK_URL: optionalEnvironmentUrl.or(z.literal('false')),
|
|
41
44
|
NOTIFICATIONS_SERVICE_PORT: optionalEnvironmentPort,
|
|
@@ -74,12 +77,15 @@ export const brainServiceEnvironmentShape = {
|
|
|
74
77
|
SKILL_SEMANTIC_SEARCH_RATE_LIMIT_PER_MIN: optionalEnvironmentPort,
|
|
75
78
|
BITSTRING_STATUS_LIST_SIZE: optionalEnvironmentPort,
|
|
76
79
|
CREDENTIAL_REFRESH_DIGEST_SECRET: optionalEnvironmentString,
|
|
80
|
+
SHARE_LINK_REQUEST_HASH_SECRET: optionalEnvironmentString,
|
|
77
81
|
CREDENTIAL_REFRESH_ENABLED: optionalEnvironmentBoolean.default(false),
|
|
78
82
|
CREDENTIAL_REFRESH_NOTIFICATION_WINDOW_HOURS: credentialRefreshNotificationWindowHours,
|
|
79
83
|
IS_OFFLINE: optionalEnvironmentBoolean.default(false),
|
|
80
84
|
IS_CI: optionalEnvironmentBoolean.default(false),
|
|
81
85
|
IS_E2E_TEST: optionalEnvironmentBoolean.default(false),
|
|
82
86
|
INBOX_DELETE_EXPIRED_RECORDS: optionalEnvironmentBoolean.default(false),
|
|
87
|
+
INBOX_BATCH_ITEMS_PER_HOUR: optionalEnvironmentString,
|
|
88
|
+
AWS_LAMBDA_FUNCTION_NAME: optionalEnvironmentString,
|
|
83
89
|
ENABLE_BENCH_ROUTES: optionalEnvironmentBoolean.default(false),
|
|
84
90
|
ENABLE_SEND_CREDENTIAL_TELEMETRY: optionalEnvironmentBoolean.default(false),
|
|
85
91
|
LC_PERF_LOG: optionalEnvironmentBoolean.default(false),
|
|
@@ -206,6 +212,40 @@ export const getCredentialRefreshRuntimeEnvironment = (): CredentialRefreshRunti
|
|
|
206
212
|
examplePath: 'services/learn-card-network/brain-service/.env.example',
|
|
207
213
|
});
|
|
208
214
|
|
|
215
|
+
const shareLinkRequestHashRuntimeEnvironmentSchema = z.object({
|
|
216
|
+
NODE_ENV: brainServiceEnvironmentShape.NODE_ENV,
|
|
217
|
+
SHARE_LINK_REQUEST_HASH_SECRET: brainServiceEnvironmentShape.SHARE_LINK_REQUEST_HASH_SECRET,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/** Reads the dedicated fingerprint secret at call time for enabled share-link APIs. */
|
|
221
|
+
export const getShareLinkRequestHashRuntimeEnvironment = () =>
|
|
222
|
+
parseEnvironment(shareLinkRequestHashRuntimeEnvironmentSchema, process.env, {
|
|
223
|
+
project: 'brain-service share links',
|
|
224
|
+
source: 'process environment',
|
|
225
|
+
examplePath: 'services/learn-card-network/brain-service/.env.example',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const inboxBatchRuntimeEnvironmentSchema = z.object({
|
|
229
|
+
INBOX_QUEUE_URL: brainServiceEnvironmentShape.INBOX_QUEUE_URL,
|
|
230
|
+
INBOX_DEAD_LETTER_QUEUE_URL: brainServiceEnvironmentShape.INBOX_DEAD_LETTER_QUEUE_URL,
|
|
231
|
+
INBOX_QUEUE_ENDPOINT: brainServiceEnvironmentShape.INBOX_QUEUE_ENDPOINT,
|
|
232
|
+
AWS_REGION: brainServiceEnvironmentShape.AWS_REGION,
|
|
233
|
+
NODE_ENV: brainServiceEnvironmentShape.NODE_ENV,
|
|
234
|
+
IS_OFFLINE: brainServiceEnvironmentShape.IS_OFFLINE,
|
|
235
|
+
AWS_LAMBDA_FUNCTION_NAME: brainServiceEnvironmentShape.AWS_LAMBDA_FUNCTION_NAME,
|
|
236
|
+
INBOX_BATCH_ITEMS_PER_HOUR: brainServiceEnvironmentShape.INBOX_BATCH_ITEMS_PER_HOUR,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export type InboxBatchRuntimeEnvironment = z.output<typeof inboxBatchRuntimeEnvironmentSchema>;
|
|
240
|
+
|
|
241
|
+
/** Reads batch settings at call time so local invocations and tests can override them safely. */
|
|
242
|
+
export const getInboxBatchRuntimeEnvironment = (): InboxBatchRuntimeEnvironment =>
|
|
243
|
+
parseEnvironment(inboxBatchRuntimeEnvironmentSchema, process.env, {
|
|
244
|
+
project: 'brain-service inbox batch',
|
|
245
|
+
source: 'process environment',
|
|
246
|
+
examplePath: 'services/learn-card-network/brain-service/.env.example',
|
|
247
|
+
});
|
|
248
|
+
|
|
209
249
|
export type BrainServiceEnvironment = z.output<typeof brainServiceEnvironmentSchema>;
|
|
210
250
|
|
|
211
251
|
export const parseBrainServiceEnvironment = (
|
|
@@ -45,3 +45,8 @@ export const AUTH_GRANT_CREDENTIAL_MANAGEMENT_SCOPE = 'credential:* presentation
|
|
|
45
45
|
export const AUTH_GRANT_CONTRACTS_SCOPE = 'contracts:*';
|
|
46
46
|
export const AUTH_GRANT_DID_METADATA_SCOPE = 'didMetadata:*';
|
|
47
47
|
export const AUTH_GRANT_AUTH_GRANTS_SCOPE = 'authGrants:*';
|
|
48
|
+
|
|
49
|
+
// LC-2187 owner share-link APIs. Least privilege: owner metadata/recovery reads
|
|
50
|
+
// use `shareLinks:read`; create/update/revoke/retry use `shareLinks:write`.
|
|
51
|
+
export const AUTH_GRANT_SHARE_LINKS_READ_SCOPE = 'shareLinks:read';
|
|
52
|
+
export const AUTH_GRANT_SHARE_LINKS_WRITE_SCOPE = 'shareLinks:write';
|
|
@@ -172,7 +172,11 @@ const authenticateRefreshRequest = async (
|
|
|
172
172
|
|
|
173
173
|
// Missing aggregates, wrong holders, and awaiting-claim aggregates deliberately
|
|
174
174
|
// share one response so a valid DID cannot use the endpoint as an existence oracle.
|
|
175
|
-
if (
|
|
175
|
+
if (
|
|
176
|
+
!aggregate.holderDid ||
|
|
177
|
+
aggregate.state === 'pending_holder' ||
|
|
178
|
+
aggregate.holderDid !== auth.holderDid
|
|
179
|
+
) {
|
|
176
180
|
logRefreshRequest(refreshId, 'unauthorized', startedAt);
|
|
177
181
|
await reply.status(403).send({ code: 'CREDENTIAL_REFRESH_UNAUTHORIZED' });
|
|
178
182
|
return null;
|
package/src/docker-entry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { configureInboxBatchBodyLimit } from '@helpers/inbox-batch-http.helpers';
|
|
1
2
|
import { environment } from '@environment';
|
|
2
3
|
import Fastify from 'fastify';
|
|
3
4
|
import fastifyCors from '@fastify/cors';
|
|
@@ -14,17 +15,41 @@ import {
|
|
|
14
15
|
|
|
15
16
|
import { neogma } from '@instance';
|
|
16
17
|
import { appRouter, type AppRouter, createContext } from './app';
|
|
18
|
+
import { publicShareLinkCacheControlHeaders } from './routes';
|
|
19
|
+
import { registerPublicShareLinkNoStore } from './public-share-link-http';
|
|
17
20
|
import { openApiDocument } from './openapi';
|
|
21
|
+
import { inboxBatchResponseMeta } from './helpers/inbox-batch-http.helpers';
|
|
18
22
|
import { didFastifyPlugin } from './dids';
|
|
19
23
|
import { skillsViewerFastifyPlugin } from './skills-viewer';
|
|
20
24
|
import { statusListsFastifyPlugin } from './status-lists';
|
|
21
25
|
import { credentialRefreshFastifyPlugin } from './credential-refresh';
|
|
22
26
|
import { deliverQueuedNotification } from '@helpers/notificationQueue.helpers';
|
|
23
27
|
import { startSkillEmbeddingBackfill } from '@helpers/skill-embedding.helpers';
|
|
28
|
+
import {
|
|
29
|
+
createConsoleMaintenanceLogger,
|
|
30
|
+
createShareLinkMaintenanceRuntime,
|
|
31
|
+
createShareLinkMaintenanceScheduler,
|
|
32
|
+
type ShareLinkMaintenanceScheduler,
|
|
33
|
+
} from '@helpers/share-link-maintenance';
|
|
24
34
|
import { maybeAutoSeedSkillFrameworks } from './seed/seedSkillFrameworks';
|
|
25
35
|
|
|
26
36
|
const server = Fastify({ routerOptions: { maxParamLength: 5000 } });
|
|
27
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The scheduled maintenance runtime is created only after the server is ready.
|
|
40
|
+
* When its config is disabled/invalid it is inert, so no interval and no
|
|
41
|
+
* graph/remote/signing dependency is initialized at all.
|
|
42
|
+
*/
|
|
43
|
+
let shareLinkMaintenanceScheduler: ShareLinkMaintenanceScheduler | null = null;
|
|
44
|
+
|
|
45
|
+
server.addHook('onClose', async () => {
|
|
46
|
+
await shareLinkMaintenanceScheduler?.stop();
|
|
47
|
+
});
|
|
48
|
+
// Register before either OpenAPI or tRPC registers its wildcard route. The hook raises the
|
|
49
|
+
// parser ceiling for those shared routes, then narrows it back to 4 MiB only for batch issuance.
|
|
50
|
+
// Registering this later would leave the already-created adapter routes at Fastify's 1 MiB limit.
|
|
51
|
+
configureInboxBatchBodyLimit(server);
|
|
52
|
+
|
|
28
53
|
server.addHook('onRequest', (request, _reply, done) => {
|
|
29
54
|
type RawWithEmitter = typeof request.raw & {
|
|
30
55
|
on?: (...args: unknown[]) => unknown;
|
|
@@ -71,6 +96,9 @@ server.register(fastifyTRPCPlugin, {
|
|
|
71
96
|
allowMethodOverride: true,
|
|
72
97
|
router: appRouter,
|
|
73
98
|
createContext,
|
|
99
|
+
responseMeta: ({ paths }) => ({
|
|
100
|
+
headers: publicShareLinkCacheControlHeaders(paths),
|
|
101
|
+
}),
|
|
74
102
|
onError({ path, error }) {
|
|
75
103
|
// report to error monitoring
|
|
76
104
|
console.error(`Error in tRPC handler on path '${path}':`, error);
|
|
@@ -79,6 +107,7 @@ server.register(fastifyTRPCPlugin, {
|
|
|
79
107
|
});
|
|
80
108
|
|
|
81
109
|
server.register(fastifyTRPCOpenApiPlugin, {
|
|
110
|
+
responseMeta: inboxBatchResponseMeta,
|
|
82
111
|
basePath: '/api',
|
|
83
112
|
router: appRouter,
|
|
84
113
|
createContext,
|
|
@@ -88,6 +117,13 @@ server.register(fastifyTRPCOpenApiPlugin, {
|
|
|
88
117
|
},
|
|
89
118
|
} satisfies CreateOpenApiFastifyPluginOptions<AppRouter>);
|
|
90
119
|
|
|
120
|
+
/**
|
|
121
|
+
* LC-2187: the Fastify OpenAPI adapter has no `responseMeta` hook, so the
|
|
122
|
+
* no-store contract for the anonymous public share-link routes is applied at
|
|
123
|
+
* the adapter boundary itself. It never depends on a cache layer above.
|
|
124
|
+
*/
|
|
125
|
+
registerPublicShareLinkNoStore(server);
|
|
126
|
+
|
|
91
127
|
server.get('/docs/openapi.json', () => openApiDocument);
|
|
92
128
|
|
|
93
129
|
const SCALAR_HTML = `<!DOCTYPE html>
|
|
@@ -130,12 +166,41 @@ server.register(credentialRefreshFastifyPlugin);
|
|
|
130
166
|
} catch (error) {
|
|
131
167
|
console.error('Skill embedding backfill failed', error);
|
|
132
168
|
}
|
|
169
|
+
|
|
170
|
+
// Explicit startup after readiness. Disabled/invalid config stays inert.
|
|
171
|
+
const maintenanceRuntime = createShareLinkMaintenanceRuntime();
|
|
172
|
+
|
|
173
|
+
if (maintenanceRuntime.resolution.status === 'enabled') {
|
|
174
|
+
shareLinkMaintenanceScheduler = createShareLinkMaintenanceScheduler({
|
|
175
|
+
runOnce: () => maintenanceRuntime.runOnce(),
|
|
176
|
+
intervalMs: maintenanceRuntime.resolution.config.intervalMs,
|
|
177
|
+
logger: createConsoleMaintenanceLogger(),
|
|
178
|
+
});
|
|
179
|
+
shareLinkMaintenanceScheduler.start();
|
|
180
|
+
}
|
|
133
181
|
} catch (err) {
|
|
134
182
|
console.error(err);
|
|
135
183
|
process.exit(1);
|
|
136
184
|
}
|
|
137
185
|
})();
|
|
138
186
|
|
|
187
|
+
const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
|
|
188
|
+
console.log(`Received ${signal}; closing server and draining maintenance`);
|
|
189
|
+
try {
|
|
190
|
+
await server.close();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Server shutdown failed', error);
|
|
193
|
+
}
|
|
194
|
+
process.exit(0);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
process.once('SIGTERM', () => {
|
|
198
|
+
void shutdown('SIGTERM');
|
|
199
|
+
});
|
|
200
|
+
process.once('SIGINT', () => {
|
|
201
|
+
void shutdown('SIGINT');
|
|
202
|
+
});
|
|
203
|
+
|
|
139
204
|
const pollUrl = environment.NOTIFICATIONS_QUEUE_POLL_URL;
|
|
140
205
|
|
|
141
206
|
if (pollUrl) {
|
|
@@ -20,6 +20,7 @@ export const logCredentialActivity = async (
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export const logCredentialSent = async (params: {
|
|
23
|
+
onBehalfOf?: string;
|
|
23
24
|
actorProfileId?: string;
|
|
24
25
|
recipientType: CredentialActivityRecipientType;
|
|
25
26
|
recipientIdentifier: string;
|
|
@@ -45,6 +46,7 @@ export const logCredentialSent = async (params: {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
export const logCredentialDelivered = async (params: {
|
|
49
|
+
onBehalfOf?: string;
|
|
48
50
|
activityId: string;
|
|
49
51
|
actorProfileId: string;
|
|
50
52
|
recipientType: CredentialActivityRecipientType;
|
|
@@ -64,6 +66,7 @@ export const logCredentialDelivered = async (params: {
|
|
|
64
66
|
};
|
|
65
67
|
|
|
66
68
|
export const logCredentialClaimed = async (params: {
|
|
69
|
+
onBehalfOf?: string;
|
|
67
70
|
activityId?: string;
|
|
68
71
|
actorProfileId: string;
|
|
69
72
|
recipientType: CredentialActivityRecipientType;
|
|
@@ -98,6 +101,7 @@ export const logCredentialExpired = async (params: {
|
|
|
98
101
|
};
|
|
99
102
|
|
|
100
103
|
export const logCredentialFailed = async (params: {
|
|
104
|
+
onBehalfOf?: string;
|
|
101
105
|
activityId?: string;
|
|
102
106
|
actorProfileId: string;
|
|
103
107
|
recipientType: CredentialActivityRecipientType;
|
|
@@ -44,7 +44,7 @@ export const handleIncrementCounterEvent = async (
|
|
|
44
44
|
// tRPC does not support HTTP 429 natively, so we cast to BAD_REQUEST
|
|
45
45
|
// while keeping the semantic code in the message for clients.
|
|
46
46
|
throw new TRPCError({
|
|
47
|
-
code: 'TOO_MANY_REQUESTS'
|
|
47
|
+
code: 'TOO_MANY_REQUESTS',
|
|
48
48
|
message: 'Rate limit exceeded: max 100 counter writes per user per app per minute',
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
parseRenderedTemplate,
|
|
23
23
|
shouldAutoAppendTemplateEvidence,
|
|
24
24
|
} from '@helpers/template.helpers';
|
|
25
|
+
import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
|
|
25
26
|
|
|
26
27
|
import { getBoostOwner } from '@accesslayer/boost/relationships/read';
|
|
27
28
|
import { BoostInstance } from '@models';
|
|
@@ -95,15 +96,7 @@ export const convertCredentialToBoostTemplateJSON = (
|
|
|
95
96
|
delete template.proof;
|
|
96
97
|
template.issuer = defaultIssuerDid ?? 'did:example:123';
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
template.credentialSubject = template.credentialSubject.map(subject => {
|
|
100
|
-
subject.id = 'did:example:123';
|
|
101
|
-
|
|
102
|
-
return subject;
|
|
103
|
-
});
|
|
104
|
-
} else {
|
|
105
|
-
template.credentialSubject.id = 'did:example:123';
|
|
106
|
-
}
|
|
99
|
+
setCredentialSubjectIds(template, 'did:example:123');
|
|
107
100
|
return JSON.stringify(template);
|
|
108
101
|
};
|
|
109
102
|
|
|
@@ -262,14 +255,7 @@ export const issueClaimLinkBoost = async (
|
|
|
262
255
|
|
|
263
256
|
boostCredential.issuer = signingAuthorityForUser.relationship.did;
|
|
264
257
|
|
|
265
|
-
|
|
266
|
-
boostCredential.credentialSubject = boostCredential.credentialSubject.map(subject => ({
|
|
267
|
-
...subject,
|
|
268
|
-
id: getDidWeb(domain, to.profileId),
|
|
269
|
-
}));
|
|
270
|
-
} else {
|
|
271
|
-
boostCredential.credentialSubject.id = getDidWeb(domain, to.profileId);
|
|
272
|
-
}
|
|
258
|
+
setCredentialSubjectIds(boostCredential, getDidWeb(domain, to.profileId));
|
|
273
259
|
|
|
274
260
|
// Embed the boostURI into the boost credential for verification purposes.
|
|
275
261
|
if (boostCredential.type.includes('BoostCredential')) {
|
|
@@ -362,20 +348,7 @@ export const prepareCredentialFromBoost = async (
|
|
|
362
348
|
credential.issuer = issuerDid;
|
|
363
349
|
}
|
|
364
350
|
|
|
365
|
-
|
|
366
|
-
if (recipientDid) {
|
|
367
|
-
if (Array.isArray(credential.credentialSubject)) {
|
|
368
|
-
credential.credentialSubject = credential.credentialSubject.map(subject => ({
|
|
369
|
-
...subject,
|
|
370
|
-
id: recipientDid,
|
|
371
|
-
}));
|
|
372
|
-
} else {
|
|
373
|
-
credential.credentialSubject = {
|
|
374
|
-
...(credential.credentialSubject || {}),
|
|
375
|
-
id: recipientDid,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
351
|
+
if (recipientDid) setCredentialSubjectIds(credential, recipientDid);
|
|
379
352
|
|
|
380
353
|
// Embed boostId for BoostCredential types
|
|
381
354
|
if (credential?.type?.includes('BoostCredential')) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const environment = vi.hoisted(() => ({
|
|
4
|
+
CLIENT_APP_DOMAIN_NAME: undefined as string | undefined,
|
|
5
|
+
CLIENT_APP_PORT: undefined as number | undefined,
|
|
6
|
+
PORT: 4000,
|
|
7
|
+
IS_OFFLINE: true,
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('@environment', () => ({ environment }));
|
|
10
|
+
vi.mock('@cache', () => ({ default: {} }));
|
|
11
|
+
|
|
12
|
+
import { generateClaimUrl } from './contact-method.helpers';
|
|
13
|
+
|
|
14
|
+
describe('inbox claim links', () => {
|
|
15
|
+
it('opens the default frontend instead of the backend port', () => {
|
|
16
|
+
expect(generateClaimUrl('test-token')).toBe(
|
|
17
|
+
'http://localhost:3000/interactions/inbox-claim/test-token?iuv=1'
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('uses the configured frontend port locally', () => {
|
|
22
|
+
environment.CLIENT_APP_PORT = 3100;
|
|
23
|
+
try {
|
|
24
|
+
expect(generateClaimUrl('test-token')).toContain('http://localhost:3100/');
|
|
25
|
+
} finally {
|
|
26
|
+
environment.CLIENT_APP_PORT = undefined;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('honors a configured app domain locally and keeps production HTTPS', () => {
|
|
31
|
+
environment.CLIENT_APP_DOMAIN_NAME = 'app.example.org';
|
|
32
|
+
try {
|
|
33
|
+
expect(generateClaimUrl('test-token')).toContain('http://app.example.org/');
|
|
34
|
+
environment.IS_OFFLINE = false;
|
|
35
|
+
expect(generateClaimUrl('test-token')).toBe(
|
|
36
|
+
'https://app.example.org/interactions/inbox-claim/test-token?iuv=1'
|
|
37
|
+
);
|
|
38
|
+
} finally {
|
|
39
|
+
environment.IS_OFFLINE = true;
|
|
40
|
+
environment.CLIENT_APP_DOMAIN_NAME = undefined;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -110,10 +110,9 @@ export const markInboxClaimTokenAsUsed = async (token: string): Promise<boolean>
|
|
|
110
110
|
|
|
111
111
|
export const generateClaimUrl = (token: string): string => {
|
|
112
112
|
const domainName = environment.CLIENT_APP_DOMAIN_NAME;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
: domainName;
|
|
113
|
+
// Claim links open the frontend, not the brain-service HTTP port. Honor an
|
|
114
|
+
// explicitly configured app domain in local development as well.
|
|
115
|
+
const domain = domainName || `localhost:${environment.CLIENT_APP_PORT || 3000}`;
|
|
117
116
|
|
|
118
117
|
const protocol = environment.IS_OFFLINE ? 'http' : 'https';
|
|
119
118
|
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildCredentialUpdatedEmailModel,
|
|
5
|
+
chooseCredentialRefreshEmailTarget,
|
|
6
|
+
extractBoundedCredentialDisplayTitle,
|
|
7
|
+
MAX_CREDENTIAL_DISPLAY_TITLE_LENGTH,
|
|
8
|
+
} from './credential-refresh-email-content.helpers';
|
|
9
|
+
|
|
10
|
+
const email = (value: string, { verified = true, primary = false } = {}) => ({
|
|
11
|
+
type: 'email',
|
|
12
|
+
value,
|
|
13
|
+
isVerified: verified,
|
|
14
|
+
isPrimary: primary,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const phone = (value: string) => ({
|
|
18
|
+
type: 'phone',
|
|
19
|
+
value,
|
|
20
|
+
isVerified: true,
|
|
21
|
+
isPrimary: true,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('extractBoundedCredentialDisplayTitle', () => {
|
|
25
|
+
it('returns a trimmed, whitespace-collapsed title', () => {
|
|
26
|
+
expect(extractBoundedCredentialDisplayTitle(' Introduction \n to\tBiology ')).toBe(
|
|
27
|
+
'Introduction to Biology'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('removes control characters', () => {
|
|
32
|
+
expect(extractBoundedCredentialDisplayTitle('Biology\u0000\u0007 101')).toBe('Biology 101');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('truncates to the bounded maximum length', () => {
|
|
36
|
+
const long = 'x'.repeat(MAX_CREDENTIAL_DISPLAY_TITLE_LENGTH + 50);
|
|
37
|
+
const result = extractBoundedCredentialDisplayTitle(long);
|
|
38
|
+
|
|
39
|
+
expect(result).toHaveLength(MAX_CREDENTIAL_DISPLAY_TITLE_LENGTH);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('honors an explicit maximum length', () => {
|
|
43
|
+
expect(extractBoundedCredentialDisplayTitle('abcdef', 3)).toBe('abc');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it.each([undefined, null, '', ' ', 42, {}, []])(
|
|
47
|
+
'returns undefined for a non-title value (%s)',
|
|
48
|
+
value => {
|
|
49
|
+
expect(extractBoundedCredentialDisplayTitle(value)).toBeUndefined();
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
it('returns undefined when only whitespace remains after sanitization', () => {
|
|
54
|
+
expect(extractBoundedCredentialDisplayTitle('\u0000 \n\u0007')).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('chooseCredentialRefreshEmailTarget', () => {
|
|
59
|
+
it('resolves a verified holder email for an unmanaged holder', () => {
|
|
60
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
61
|
+
holder: { locale: 'es' },
|
|
62
|
+
holderContactMethods: [email('holder@example.com')],
|
|
63
|
+
managers: [],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
status: 'resolved',
|
|
68
|
+
kind: 'holder',
|
|
69
|
+
locale: 'es',
|
|
70
|
+
contactMethod: { type: 'email', value: 'holder@example.com' },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('prefers the verified primary holder email', () => {
|
|
75
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
76
|
+
holder: {},
|
|
77
|
+
holderContactMethods: [
|
|
78
|
+
email('secondary@example.com'),
|
|
79
|
+
email('primary@example.com', { primary: true }),
|
|
80
|
+
],
|
|
81
|
+
managers: [],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(result).toMatchObject({
|
|
85
|
+
status: 'resolved',
|
|
86
|
+
contactMethod: { value: 'primary@example.com' },
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('never uses an unverified holder email', () => {
|
|
91
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
92
|
+
holder: {},
|
|
93
|
+
holderContactMethods: [
|
|
94
|
+
email('unverified@example.com', { verified: false }),
|
|
95
|
+
email('verified@example.com'),
|
|
96
|
+
],
|
|
97
|
+
managers: [],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(result).toMatchObject({
|
|
101
|
+
status: 'resolved',
|
|
102
|
+
contactMethod: { value: 'verified@example.com' },
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('ignores non-email contact methods', () => {
|
|
107
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
108
|
+
holder: {},
|
|
109
|
+
holderContactMethods: [phone('+15555550100')],
|
|
110
|
+
managers: [],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result).toEqual({ status: 'skipped', reason: 'no-verified-holder-email' });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('skips an unmanaged holder with no verified email', () => {
|
|
117
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
118
|
+
holder: {},
|
|
119
|
+
holderContactMethods: [],
|
|
120
|
+
managers: [],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result).toEqual({ status: 'skipped', reason: 'no-verified-holder-email' });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('routes a managed child to the verified guardian email, never the child', () => {
|
|
127
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
128
|
+
holder: { locale: 'en' },
|
|
129
|
+
holderContactMethods: [email('child@example.com')],
|
|
130
|
+
managers: [
|
|
131
|
+
{
|
|
132
|
+
locale: 'fr',
|
|
133
|
+
contactMethods: [email('guardian@example.com')],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual({
|
|
139
|
+
status: 'resolved',
|
|
140
|
+
kind: 'guardian',
|
|
141
|
+
locale: 'fr',
|
|
142
|
+
contactMethod: { type: 'email', value: 'guardian@example.com' },
|
|
143
|
+
});
|
|
144
|
+
expect(JSON.stringify(result)).not.toContain('child@example.com');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('skips a managed child when no guardian has a verified email', () => {
|
|
148
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
149
|
+
holder: {},
|
|
150
|
+
holderContactMethods: [email('child@example.com')],
|
|
151
|
+
managers: [
|
|
152
|
+
{ contactMethods: [email('guardian@example.com', { verified: false })] },
|
|
153
|
+
{ contactMethods: [] },
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual({ status: 'skipped', reason: 'no-verified-guardian-email' });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('skips a managed child even when the child has a verified email', () => {
|
|
161
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
162
|
+
holder: {},
|
|
163
|
+
holderContactMethods: [email('child@example.com')],
|
|
164
|
+
managers: [{ contactMethods: [] }],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(result).toEqual({ status: 'skipped', reason: 'no-verified-guardian-email' });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('prefers a primary verified guardian email across managers', () => {
|
|
171
|
+
const result = chooseCredentialRefreshEmailTarget({
|
|
172
|
+
holder: {},
|
|
173
|
+
holderContactMethods: [],
|
|
174
|
+
managers: [
|
|
175
|
+
{ contactMethods: [email('guardian-a@example.com')] },
|
|
176
|
+
{
|
|
177
|
+
contactMethods: [
|
|
178
|
+
email('guardian-b@example.com', { primary: true }),
|
|
179
|
+
email('guardian-b2@example.com'),
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(result).toMatchObject({
|
|
186
|
+
status: 'resolved',
|
|
187
|
+
kind: 'guardian',
|
|
188
|
+
contactMethod: { value: 'guardian-b@example.com' },
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('buildCredentialUpdatedEmailModel', () => {
|
|
194
|
+
it('includes the issuer display name and credential title', () => {
|
|
195
|
+
expect(
|
|
196
|
+
buildCredentialUpdatedEmailModel({
|
|
197
|
+
issuerDisplayName: 'Inbox Demo School',
|
|
198
|
+
credentialTitle: 'Introduction to Biology',
|
|
199
|
+
})
|
|
200
|
+
).toEqual({
|
|
201
|
+
issuer: { name: 'Inbox Demo School' },
|
|
202
|
+
credential: { name: 'Introduction to Biology' },
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('omits the credential title when unavailable', () => {
|
|
207
|
+
expect(
|
|
208
|
+
buildCredentialUpdatedEmailModel({
|
|
209
|
+
issuerDisplayName: 'Inbox Demo School',
|
|
210
|
+
})
|
|
211
|
+
).toEqual({ issuer: { name: 'Inbox Demo School' } });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('does not include any other credential content', () => {
|
|
215
|
+
const model = buildCredentialUpdatedEmailModel({
|
|
216
|
+
issuerDisplayName: 'Inbox Demo School',
|
|
217
|
+
credentialTitle: 'Introduction to Biology',
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(Object.keys(model).sort()).toEqual(['credential', 'issuer']);
|
|
221
|
+
});
|
|
222
|
+
});
|