@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/routes/contracts.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
} from '@accesslayer/consentflowcontract/relationships/create';
|
|
78
78
|
import { getProfileByDid, getProfileByProfileId } from '@accesslayer/profile/read';
|
|
79
79
|
import { sendBoost, isDraftBoost } from '@helpers/boost.helpers';
|
|
80
|
+
import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
|
|
80
81
|
import { isRelationshipBlocked } from '@helpers/connection.helpers';
|
|
81
82
|
import { getBoostByUri } from '@accesslayer/boost/read';
|
|
82
83
|
import { canProfileIssueBoost } from '@accesslayer/boost/relationships/read';
|
|
@@ -818,17 +819,7 @@ export const contractsRouter = t.router({
|
|
|
818
819
|
unsignedVc.issuanceDate = new Date().toISOString();
|
|
819
820
|
}
|
|
820
821
|
unsignedVc.issuer = { id: getDidWeb(ctx.domain, profile.profileId) };
|
|
821
|
-
|
|
822
|
-
unsignedVc.credentialSubject = unsignedVc.credentialSubject.map(subject => ({
|
|
823
|
-
...subject,
|
|
824
|
-
id: getDidWeb(ctx.domain, otherProfile.profileId),
|
|
825
|
-
}));
|
|
826
|
-
} else {
|
|
827
|
-
unsignedVc.credentialSubject = {
|
|
828
|
-
...unsignedVc.credentialSubject,
|
|
829
|
-
id: getDidWeb(ctx.domain, otherProfile.profileId),
|
|
830
|
-
};
|
|
831
|
-
}
|
|
822
|
+
setCredentialSubjectIds(unsignedVc, getDidWeb(ctx.domain, otherProfile.profileId));
|
|
832
823
|
if (unsignedVc?.type?.includes('BoostCredential')) unsignedVc.boostId = boostUri;
|
|
833
824
|
// Inject OBv3 skill alignments based on boost's framework/skills
|
|
834
825
|
await injectObv3AlignmentsIntoCredentialForBoost(unsignedVc, boost, ctx.domain);
|
|
@@ -116,7 +116,10 @@ describe('credential refresh issuer route gating', () => {
|
|
|
116
116
|
markConstraintsReady = resolve;
|
|
117
117
|
})
|
|
118
118
|
);
|
|
119
|
-
mocks.sendRefreshableCredential.mockResolvedValue(
|
|
119
|
+
mocks.sendRefreshableCredential.mockResolvedValue({
|
|
120
|
+
uri: 'lc:network:credential:root',
|
|
121
|
+
receipt: { refreshId: 'opaque-refresh-id' },
|
|
122
|
+
});
|
|
120
123
|
|
|
121
124
|
const caller = credentialRefreshesRouter.createCaller({
|
|
122
125
|
domain: 'network.example.com',
|
|
@@ -174,7 +177,10 @@ describe('credential refresh issuer route gating', () => {
|
|
|
174
177
|
|
|
175
178
|
it('forwards initial notification suppression to managed delivery', async () => {
|
|
176
179
|
process.env.CREDENTIAL_REFRESH_ENABLED = 'true';
|
|
177
|
-
mocks.sendRefreshableCredential.mockResolvedValue(
|
|
180
|
+
mocks.sendRefreshableCredential.mockResolvedValue({
|
|
181
|
+
uri: 'lc:network:credential:root',
|
|
182
|
+
receipt: { refreshId: 'opaque-refresh-id' },
|
|
183
|
+
});
|
|
178
184
|
|
|
179
185
|
const caller = credentialRefreshesRouter.createCaller({
|
|
180
186
|
domain: 'network.example.com',
|
|
@@ -124,7 +124,7 @@ export const credentialRefreshesRouter = t.router({
|
|
|
124
124
|
.mutation(async ({ ctx, input }) => {
|
|
125
125
|
const { profile } = ctx.user;
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
const { uri } = await sendRefreshableCredential({
|
|
128
128
|
issuerProfile: profile,
|
|
129
129
|
refreshId: input.refreshId,
|
|
130
130
|
credential: input.credential,
|
|
@@ -132,6 +132,8 @@ export const credentialRefreshesRouter = t.router({
|
|
|
132
132
|
skipNotification: input.skipNotification,
|
|
133
133
|
domain: ctx.domain,
|
|
134
134
|
});
|
|
135
|
+
|
|
136
|
+
return uri;
|
|
135
137
|
}),
|
|
136
138
|
|
|
137
139
|
publishCredentialRefresh: credentialRefreshRoute
|
|
@@ -156,6 +158,7 @@ export const credentialRefreshesRouter = t.router({
|
|
|
156
158
|
issuerProfile: profile,
|
|
157
159
|
input: input as PublishCredentialRefreshInput,
|
|
158
160
|
domain: ctx.domain,
|
|
161
|
+
branding: ctx.tenant?.emailBranding,
|
|
159
162
|
});
|
|
160
163
|
}),
|
|
161
164
|
|
|
@@ -71,6 +71,7 @@ export const credentialsRouter = t.router({
|
|
|
71
71
|
// Log credential activity FIRST to get activityId
|
|
72
72
|
const activityId = await logCredentialSent({
|
|
73
73
|
actorProfileId: profile.profileId,
|
|
74
|
+
onBehalfOf: ctx.user.onBehalfOf,
|
|
74
75
|
recipientType: 'profile',
|
|
75
76
|
recipientIdentifier: targetProfile.profileId,
|
|
76
77
|
recipientProfileId: targetProfile.profileId,
|
|
@@ -118,7 +119,7 @@ export const credentialsRouter = t.router({
|
|
|
118
119
|
const { profile } = ctx.user;
|
|
119
120
|
const { uri } = input;
|
|
120
121
|
|
|
121
|
-
return acceptCredential(profile, uri, input?.options ?? {});
|
|
122
|
+
return acceptCredential(profile, uri, input?.options ?? {}, ctx.tenant?.emailBranding);
|
|
122
123
|
}),
|
|
123
124
|
|
|
124
125
|
receivedCredentials: profileRoute
|
package/src/routes/inbox.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { trace } from '@tracing';
|
|
2
|
+
import { submitInboxBatch, getInboxBatch } from '@helpers/inbox-queue.helpers';
|
|
3
|
+
import {
|
|
4
|
+
assertInboxRefreshEnabled,
|
|
5
|
+
inboxRefreshRequestDigest,
|
|
6
|
+
getInboxRefreshReplay,
|
|
7
|
+
getInboxRefreshReceipt,
|
|
8
|
+
resumeInboxRefreshDelivery,
|
|
9
|
+
} from '@helpers/inbox-refresh.helpers';
|
|
1
10
|
import { getDidWeb } from '@helpers/did.helpers';
|
|
2
11
|
import { TRPCError } from '@trpc/server';
|
|
3
12
|
import { z } from 'zod';
|
|
@@ -6,6 +15,9 @@ import { t, profileRoute, openRoute, verifiedContactRoute, scopedRoute } from '@
|
|
|
6
15
|
import {
|
|
7
16
|
PaginationOptionsValidator,
|
|
8
17
|
IssueInboxCredentialValidator,
|
|
18
|
+
IssueInboxCredentialBatchValidator,
|
|
19
|
+
InboxBatchReceiptValidator,
|
|
20
|
+
InboxBatchStatusValidator,
|
|
9
21
|
IssueInboxCredentialResponseValidator,
|
|
10
22
|
InboxCredentialValidator,
|
|
11
23
|
PaginatedInboxCredentialsValidator,
|
|
@@ -18,15 +30,14 @@ import {
|
|
|
18
30
|
JWEValidator,
|
|
19
31
|
} from '@learncard/types';
|
|
20
32
|
import { getInboxCredentialMeta } from '@helpers/credential-meta.helpers';
|
|
21
|
-
import { claimIntoInbox, issueToInbox } from '@helpers/inbox.helpers';
|
|
22
|
-
import { prepareCredentialFromBoost, getBoostUri } from '@helpers/boost.helpers';
|
|
33
|
+
import { claimIntoInbox, issueToInbox, resolveInboxCredentialInput } from '@helpers/inbox.helpers';
|
|
23
34
|
import {
|
|
24
35
|
hasMustacheVariables,
|
|
25
36
|
renderBoostTemplate,
|
|
26
37
|
parseRenderedTemplate,
|
|
27
38
|
} from '@helpers/template.helpers';
|
|
28
39
|
import { getProfileByVerifiedContactMethod } from '@accesslayer/contact-method/relationships/read';
|
|
29
|
-
import {
|
|
40
|
+
import { getBoostsForProfile } from '@accesslayer/boost/read';
|
|
30
41
|
import {
|
|
31
42
|
generateGuardianApprovalToken,
|
|
32
43
|
generateGuardianApprovalUrl,
|
|
@@ -447,55 +458,36 @@ export const inboxRouter = t.router({
|
|
|
447
458
|
.output(IssueInboxCredentialResponseValidator)
|
|
448
459
|
.mutation(async ({ ctx, input }) => {
|
|
449
460
|
const { profile } = ctx.user;
|
|
450
|
-
const { recipient,
|
|
451
|
-
|
|
452
|
-
// Resolve credential from templateUri if provided
|
|
453
|
-
let credential = inputCredential;
|
|
454
|
-
let resolvedBoostUri: string | undefined;
|
|
455
|
-
|
|
456
|
-
if (templateUri && !credential) {
|
|
457
|
-
const boostInstance = await getBoostByUri(templateUri);
|
|
458
|
-
|
|
459
|
-
if (!boostInstance) {
|
|
460
|
-
throw new TRPCError({
|
|
461
|
-
code: 'NOT_FOUND',
|
|
462
|
-
message: `Boost not found: ${templateUri}`,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (!boostInstance.dataValues.boost) {
|
|
467
|
-
throw new TRPCError({
|
|
468
|
-
code: 'BAD_REQUEST',
|
|
469
|
-
message: `Boost does not contain a credential template: ${templateUri}`,
|
|
470
|
-
});
|
|
471
|
-
}
|
|
461
|
+
const { recipient, configuration } = input;
|
|
472
462
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
463
|
+
const refreshDigest = input.refresh ? inboxRefreshRequestDigest(input) : undefined;
|
|
464
|
+
if (input.refresh) {
|
|
465
|
+
await assertInboxRefreshEnabled(ctx.user.scope);
|
|
466
|
+
const replay = await getInboxRefreshReplay(
|
|
467
|
+
profile.profileId,
|
|
468
|
+
input.idempotencyKey,
|
|
469
|
+
refreshDigest!
|
|
470
|
+
);
|
|
471
|
+
if (replay && (replay.claimUrl || replay.inbox.currentStatus !== 'PENDING')) {
|
|
472
|
+
await resumeInboxRefreshDelivery(replay.inbox.refreshId!, ctx.domain);
|
|
473
|
+
const receipt = await getInboxRefreshReceipt(
|
|
474
|
+
replay.inbox.refreshId!,
|
|
475
|
+
ctx.domain
|
|
483
476
|
);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
477
|
+
return {
|
|
478
|
+
issuanceId: replay.inbox.id,
|
|
479
|
+
status: replay.inbox.currentStatus,
|
|
480
|
+
recipient,
|
|
481
|
+
claimUrl: replay.claimUrl,
|
|
482
|
+
refresh: receipt,
|
|
483
|
+
...(replay.inbox.currentStatus === 'ISSUED'
|
|
484
|
+
? { recipientDid: receipt.holderDid }
|
|
485
|
+
: {}),
|
|
486
|
+
};
|
|
490
487
|
}
|
|
491
488
|
}
|
|
492
489
|
|
|
493
|
-
|
|
494
|
-
throw new TRPCError({
|
|
495
|
-
code: 'BAD_REQUEST',
|
|
496
|
-
message: 'Either credential or templateUri must be provided',
|
|
497
|
-
});
|
|
498
|
-
}
|
|
490
|
+
const { credential, resolvedBoostUri } = await resolveInboxCredentialInput(input, ctx);
|
|
499
491
|
|
|
500
492
|
// Normalize signing authority name if provided
|
|
501
493
|
const normalizedConfiguration = configuration?.signingAuthority
|
|
@@ -513,11 +505,18 @@ export const inboxRouter = t.router({
|
|
|
513
505
|
profile,
|
|
514
506
|
recipient,
|
|
515
507
|
credential,
|
|
516
|
-
|
|
508
|
+
{
|
|
509
|
+
...normalizedConfiguration,
|
|
510
|
+
boostUri: resolvedBoostUri,
|
|
511
|
+
refresh: input.refresh,
|
|
512
|
+
idempotencyKey: input.idempotencyKey,
|
|
513
|
+
refreshRequestDigest: refreshDigest,
|
|
514
|
+
},
|
|
517
515
|
ctx
|
|
518
516
|
);
|
|
519
517
|
|
|
520
518
|
return {
|
|
519
|
+
refresh: result.refresh,
|
|
521
520
|
issuanceId: result.inboxCredential.id,
|
|
522
521
|
status: result.status,
|
|
523
522
|
recipient,
|
|
@@ -536,6 +535,42 @@ export const inboxRouter = t.router({
|
|
|
536
535
|
}
|
|
537
536
|
}),
|
|
538
537
|
|
|
538
|
+
issueBatch: profileRoute
|
|
539
|
+
.meta({
|
|
540
|
+
openapi: {
|
|
541
|
+
protect: true,
|
|
542
|
+
method: 'POST',
|
|
543
|
+
path: '/inbox/issue-batch',
|
|
544
|
+
tags: ['Universal Inbox'],
|
|
545
|
+
summary: 'Issue Credentials to Universal Inbox (Batch)',
|
|
546
|
+
description:
|
|
547
|
+
'Queue 1–100 credentials for background issuance. Returns a durable batch ID; poll GET /inbox/batches/{batchId} for ordered results. Request and item idempotency keys are issuer-scoped for 24 hours. Maximum JSON payload: 4 MiB.',
|
|
548
|
+
},
|
|
549
|
+
requiredScope: 'inbox:write',
|
|
550
|
+
})
|
|
551
|
+
.input(IssueInboxCredentialBatchValidator)
|
|
552
|
+
.output(InboxBatchReceiptValidator)
|
|
553
|
+
.mutation(({ ctx, input }) =>
|
|
554
|
+
trace('route', 'issueBatch', () => submitInboxBatch(ctx.user.profile, input, ctx), {
|
|
555
|
+
itemCount: input.items.length,
|
|
556
|
+
})
|
|
557
|
+
),
|
|
558
|
+
|
|
559
|
+
getBatch: profileRoute
|
|
560
|
+
.meta({
|
|
561
|
+
openapi: {
|
|
562
|
+
protect: true,
|
|
563
|
+
method: 'GET',
|
|
564
|
+
path: '/inbox/batches/{batchId}',
|
|
565
|
+
tags: ['Universal Inbox'],
|
|
566
|
+
summary: 'Get Inbox Batch Progress',
|
|
567
|
+
},
|
|
568
|
+
requiredScope: 'inbox:read',
|
|
569
|
+
})
|
|
570
|
+
.input(z.object({ batchId: z.string() }))
|
|
571
|
+
.output(InboxBatchStatusValidator)
|
|
572
|
+
.query(({ ctx, input }) => getInboxBatch(ctx.user.profile.profileId, input.batchId)),
|
|
573
|
+
|
|
539
574
|
claim: verifiedContactRoute
|
|
540
575
|
.meta({
|
|
541
576
|
openapi: {
|
|
@@ -682,6 +717,7 @@ export const inboxRouter = t.router({
|
|
|
682
717
|
// Log initial activity so embed claims appear in the dashboard
|
|
683
718
|
const activityId = await logCredentialSent({
|
|
684
719
|
actorProfileId: issuerProfile.profileId,
|
|
720
|
+
onBehalfOf: ctx.user?.onBehalfOf,
|
|
685
721
|
recipientType: contactMethod.type as 'email' | 'phone',
|
|
686
722
|
recipientIdentifier: contactMethod.value,
|
|
687
723
|
integrationId: integration.id,
|
|
@@ -825,7 +861,17 @@ export const inboxRouter = t.router({
|
|
|
825
861
|
});
|
|
826
862
|
}
|
|
827
863
|
|
|
828
|
-
return
|
|
864
|
+
return {
|
|
865
|
+
...inboxCredential,
|
|
866
|
+
...(inboxCredential.refreshId
|
|
867
|
+
? {
|
|
868
|
+
refresh: await getInboxRefreshReceipt(
|
|
869
|
+
inboxCredential.refreshId,
|
|
870
|
+
ctx.domain
|
|
871
|
+
),
|
|
872
|
+
}
|
|
873
|
+
: {}),
|
|
874
|
+
};
|
|
829
875
|
}),
|
|
830
876
|
|
|
831
877
|
// ─── Guardian Credential Approval Routes ─────────────────────────────────────
|
package/src/routes/index.ts
CHANGED
|
@@ -9,8 +9,9 @@ import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
|
|
9
9
|
import { OpenApiMeta } from 'trpc-to-openapi';
|
|
10
10
|
import jwtDecode from 'jwt-decode';
|
|
11
11
|
import * as Sentry from '@sentry/serverless';
|
|
12
|
-
import { AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX } from '@learncard/types';
|
|
12
|
+
import { ACT_AS_HEADER, AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX } from '@learncard/types';
|
|
13
13
|
import { ContactMethodType } from '@learncard/types';
|
|
14
|
+
import { MAX_SHARE_LINK_REQUEST_BYTES, utf8ByteLength } from '@learncard/types';
|
|
14
15
|
|
|
15
16
|
import { RegExpTransformer } from '@learncard/helpers';
|
|
16
17
|
import { resolveTenantFromRequest, type ResolvedTenant } from '@learncard/email-templates';
|
|
@@ -48,7 +49,11 @@ export type Context = {
|
|
|
48
49
|
did: string;
|
|
49
50
|
isChallengeValid: boolean;
|
|
50
51
|
scope?: string;
|
|
52
|
+
isAuthGrant?: boolean;
|
|
53
|
+
actAsPolicy?: string;
|
|
54
|
+
onBehalfOf?: string;
|
|
51
55
|
};
|
|
56
|
+
actAs?: string;
|
|
52
57
|
contactMethod?: ContactMethodType;
|
|
53
58
|
domain: string;
|
|
54
59
|
tenant: ResolvedTenant;
|
|
@@ -84,6 +89,14 @@ export const createContext = async (
|
|
|
84
89
|
| { req: { headers: Map<string, string> } }
|
|
85
90
|
): Promise<Context> => {
|
|
86
91
|
const event = 'event' in options ? options.event : options.req;
|
|
92
|
+
const headerEntries =
|
|
93
|
+
'get' in event.headers
|
|
94
|
+
? Array.from(event.headers as Map<string, string>)
|
|
95
|
+
: Object.entries(event.headers);
|
|
96
|
+
const rawActAs = headerEntries.find(
|
|
97
|
+
([name]) => name.toLowerCase() === ACT_AS_HEADER.toLowerCase()
|
|
98
|
+
)?.[1];
|
|
99
|
+
const actAs = Array.isArray(rawActAs) ? rawActAs.join(',') : rawActAs;
|
|
87
100
|
const authHeader =
|
|
88
101
|
'get' in event.headers
|
|
89
102
|
? (event.headers as Map<string, string>).get('authorization')
|
|
@@ -141,6 +154,7 @@ export const createContext = async (
|
|
|
141
154
|
if (!challenge)
|
|
142
155
|
return {
|
|
143
156
|
user: { did, isChallengeValid: false, scope: AUTH_GRANT_NO_ACCESS_SCOPE },
|
|
157
|
+
actAs,
|
|
144
158
|
domain,
|
|
145
159
|
tenant,
|
|
146
160
|
sourceIp,
|
|
@@ -148,6 +162,8 @@ export const createContext = async (
|
|
|
148
162
|
|
|
149
163
|
let isChallengeValid = false;
|
|
150
164
|
let scope = AUTH_GRANT_FULL_ACCESS_SCOPE;
|
|
165
|
+
let isAuthGrant = false;
|
|
166
|
+
let actAsPolicy: string | undefined;
|
|
151
167
|
|
|
152
168
|
// If the user is using a provisional auth token for a contact method:
|
|
153
169
|
if (challenge?.includes(CONTACT_METHOD_SESSION_PREFIX)) {
|
|
@@ -159,6 +175,7 @@ export const createContext = async (
|
|
|
159
175
|
if (!contactMethod) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
160
176
|
return {
|
|
161
177
|
contactMethod,
|
|
178
|
+
actAs,
|
|
162
179
|
domain,
|
|
163
180
|
tenant,
|
|
164
181
|
sourceIp,
|
|
@@ -166,9 +183,14 @@ export const createContext = async (
|
|
|
166
183
|
}
|
|
167
184
|
// If the user is using a real auth grant i.e. an API Token.
|
|
168
185
|
} else if (challenge?.includes(AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX)) {
|
|
169
|
-
const {
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
const {
|
|
187
|
+
isChallengeValid: _isChallengeValid,
|
|
188
|
+
scope: _scope,
|
|
189
|
+
actAs: policy,
|
|
190
|
+
} = await isAuthGrantChallengeValidForDID(challenge, did);
|
|
191
|
+
|
|
192
|
+
isAuthGrant = true;
|
|
193
|
+
actAsPolicy = policy;
|
|
172
194
|
isChallengeValid = _isChallengeValid;
|
|
173
195
|
scope = _scope;
|
|
174
196
|
// If the user is using a real challenge signed by their private key.
|
|
@@ -182,7 +204,8 @@ export const createContext = async (
|
|
|
182
204
|
Sentry.setUser({ id: did });
|
|
183
205
|
|
|
184
206
|
return {
|
|
185
|
-
user: { did, isChallengeValid, scope },
|
|
207
|
+
user: { did, isChallengeValid, scope, isAuthGrant, actAsPolicy },
|
|
208
|
+
actAs,
|
|
186
209
|
domain,
|
|
187
210
|
tenant,
|
|
188
211
|
_guardianApprovalToken,
|
|
@@ -192,17 +215,96 @@ export const createContext = async (
|
|
|
192
215
|
}
|
|
193
216
|
}
|
|
194
217
|
|
|
195
|
-
return { domain, tenant, _guardianApprovalToken, sourceIp };
|
|
218
|
+
return { domain, tenant, _guardianApprovalToken, sourceIp, actAs };
|
|
196
219
|
};
|
|
197
220
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Sentry.configureScope(scope => {
|
|
202
|
-
scope.setTransactionName(`trpc-${path}`);
|
|
203
|
-
});
|
|
204
|
-
return next({ ctx });
|
|
221
|
+
const sentryTransactionNameMiddleware = t.middleware(({ ctx, next, path }) => {
|
|
222
|
+
Sentry.configureScope(scope => {
|
|
223
|
+
scope.setTransactionName(`trpc-${path}`);
|
|
205
224
|
});
|
|
225
|
+
return next({ ctx });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Sentry's tRPC middleware type predates this repo's generic context. Cast
|
|
229
|
+
// through the tRPC builder's expected parameter type instead of `any`.
|
|
230
|
+
type SentryTrpcMiddleware = Parameters<typeof t.middleware>[0];
|
|
231
|
+
|
|
232
|
+
const sentryInputCaptureMiddleware = Sentry.Handlers.trpcMiddleware({
|
|
233
|
+
attachRpcInput: true,
|
|
234
|
+
}) as unknown as SentryTrpcMiddleware;
|
|
235
|
+
|
|
236
|
+
const sentryNoInputCaptureMiddleware = Sentry.Handlers.trpcMiddleware({
|
|
237
|
+
attachRpcInput: false,
|
|
238
|
+
}) as unknown as SentryTrpcMiddleware;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Complete-request byte bound for owner share-link routes, enforced on the RAW
|
|
242
|
+
* input before Zod parsing/stripping. Individual field validators cannot bound a
|
|
243
|
+
* request whose unknown or duplicate fields are stripped first, so this measures
|
|
244
|
+
* the whole serialized body (UTF-8) and fails closed over the shared 1 MiB cap.
|
|
245
|
+
*
|
|
246
|
+
* It is composed into the base so it runs before authentication and before any
|
|
247
|
+
* input parser, and can never be skipped by a malformed or unauthenticated call.
|
|
248
|
+
*/
|
|
249
|
+
const enforceShareLinkRequestByteBound = t.middleware(async ({ ctx, next, getRawInput }) => {
|
|
250
|
+
const raw = await getRawInput();
|
|
251
|
+
|
|
252
|
+
let bytes: number;
|
|
253
|
+
if (raw === undefined || raw === null) {
|
|
254
|
+
bytes = 0;
|
|
255
|
+
} else if (typeof raw === 'string') {
|
|
256
|
+
bytes = utf8ByteLength(raw);
|
|
257
|
+
} else {
|
|
258
|
+
let serialized: string;
|
|
259
|
+
try {
|
|
260
|
+
serialized = JSON.stringify(raw);
|
|
261
|
+
} catch {
|
|
262
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'invalid share-link request' });
|
|
263
|
+
}
|
|
264
|
+
bytes = utf8ByteLength(serialized);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (bytes > MAX_SHARE_LINK_REQUEST_BYTES) {
|
|
268
|
+
throw new TRPCError({
|
|
269
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
270
|
+
message: 'share-link request exceeds the 1 MiB limit',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return next({ ctx });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
export const openRoute = t.procedure
|
|
278
|
+
.use(t.middleware(sentryInputCaptureMiddleware))
|
|
279
|
+
.use(sentryTransactionNameMiddleware);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* LC-2187 public share-link paths must never be cached by an intermediary or a
|
|
283
|
+
* browser. tRPC has no per-procedure header API, so the actual HTTP adapters
|
|
284
|
+
* (Fastify and Lambda, both tRPC and OpenAPI) apply `Cache-Control` from the
|
|
285
|
+
* resolved procedure path through this single helper.
|
|
286
|
+
*/
|
|
287
|
+
export const PUBLIC_SHARE_LINK_ROUTE_PREFIX = 'publicShareLinks.';
|
|
288
|
+
|
|
289
|
+
export const publicShareLinkCacheControlHeaders = (
|
|
290
|
+
paths: readonly string[] | undefined
|
|
291
|
+
): Record<string, string> =>
|
|
292
|
+
(paths ?? []).some(path => path.startsWith(PUBLIC_SHARE_LINK_ROUTE_PREFIX))
|
|
293
|
+
? { 'Cache-Control': 'private, no-store' }
|
|
294
|
+
: {};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Route base for procedures whose input carries owner-private material (titles,
|
|
298
|
+
* notes, ciphertext envelopes, recovery JWEs) and whose error paths must not
|
|
299
|
+
* attach raw request bodies to Sentry. It runs the same Sentry error/transaction
|
|
300
|
+
* handling but with input attachment disabled, so no branch — malformed,
|
|
301
|
+
* unauthenticated or failing — can capture the payload, and it enforces the
|
|
302
|
+
* complete-request byte bound before input parsing.
|
|
303
|
+
*/
|
|
304
|
+
export const openRouteWithoutInputCapture = t.procedure
|
|
305
|
+
.use(t.middleware(sentryNoInputCaptureMiddleware))
|
|
306
|
+
.use(sentryTransactionNameMiddleware)
|
|
307
|
+
.use(enforceShareLinkRequestByteBound);
|
|
206
308
|
|
|
207
309
|
export const resolveProfileFromContextDid = async (
|
|
208
310
|
didFromContext: string | undefined,
|
|
@@ -245,23 +347,82 @@ export const resolveProfileFromContextDid = async (
|
|
|
245
347
|
return getProfileByDid(did);
|
|
246
348
|
};
|
|
247
349
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
350
|
+
type ActingUser = NonNullable<Context['user']> & { profile: ProfileType | null };
|
|
351
|
+
|
|
352
|
+
const resolveActAs = async (
|
|
353
|
+
user: ActingUser,
|
|
354
|
+
actAs: string | undefined,
|
|
355
|
+
domain: string
|
|
356
|
+
): Promise<ActingUser> => {
|
|
357
|
+
const requested = actAs?.trim();
|
|
358
|
+
if (!requested) return user;
|
|
359
|
+
|
|
360
|
+
const { profile } = user;
|
|
361
|
+
const prefix = `did:web:${domain}:users:`;
|
|
362
|
+
const profileId = requested.startsWith(prefix) ? requested.slice(prefix.length) : requested;
|
|
363
|
+
if (!profileId || /[:/?#]/.test(profileId)) {
|
|
364
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid act-as profile identifier' });
|
|
251
365
|
}
|
|
252
366
|
|
|
253
|
-
|
|
367
|
+
// Two DB lookups per request when the act-as header is present (target profile,
|
|
368
|
+
// then its managers) — acceptable at backend issuance volume; not worth caching.
|
|
369
|
+
const target = await getProfileByProfileId(profileId);
|
|
370
|
+
if (!target)
|
|
371
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Act-as target profile not found' });
|
|
254
372
|
|
|
255
|
-
|
|
373
|
+
const managers = await getProfilesThatManageAProfile(target.profileId);
|
|
374
|
+
if (!profile || !managers.some(manager => manager.profileId === profile.profileId)) {
|
|
375
|
+
throw new TRPCError({
|
|
376
|
+
code: 'FORBIDDEN',
|
|
377
|
+
message: `You do not manage profile "${profileId}".`,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
256
380
|
|
|
257
|
-
|
|
258
|
-
|
|
381
|
+
const policy = user.actAsPolicy;
|
|
382
|
+
const permitted =
|
|
383
|
+
policy === '*' ||
|
|
384
|
+
policy
|
|
385
|
+
?.split(',')
|
|
386
|
+
.map(id => id.trim())
|
|
387
|
+
.includes(target.profileId);
|
|
388
|
+
if (user.isAuthGrant && !permitted) {
|
|
389
|
+
throw new TRPCError({
|
|
390
|
+
code: 'FORBIDDEN',
|
|
391
|
+
message: `This API token may not act as "${profileId}". Grant actAs on the token.`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
259
394
|
|
|
260
|
-
|
|
261
|
-
|
|
395
|
+
Sentry.setUser({ id: target.profileId, username: target.displayName });
|
|
396
|
+
return { ...user, profile: target, did: target.did, onBehalfOf: profile.profileId };
|
|
397
|
+
};
|
|
262
398
|
|
|
263
|
-
|
|
264
|
-
})
|
|
399
|
+
const withDid = (base: typeof openRoute) =>
|
|
400
|
+
base.use(async ({ ctx, next }) => {
|
|
401
|
+
if (!ctx.user?.did) {
|
|
402
|
+
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const profile = await resolveProfileFromContextDid(ctx.user.did, ctx.domain);
|
|
406
|
+
|
|
407
|
+
if (profile) Sentry.setUser({ id: profile.profileId, username: profile.displayName });
|
|
408
|
+
|
|
409
|
+
const user = ctx.user.isChallengeValid
|
|
410
|
+
? await resolveActAs({ ...ctx.user, profile }, ctx.actAs, ctx.domain)
|
|
411
|
+
: { ...ctx.user, profile };
|
|
412
|
+
|
|
413
|
+
return next({ ctx: { ...ctx, user } });
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
export const didRoute = withDid(openRoute);
|
|
417
|
+
|
|
418
|
+
const withChallenge = (base: typeof didRoute) =>
|
|
419
|
+
base.use(({ ctx, next }) => {
|
|
420
|
+
if (!ctx.user?.isChallengeValid) throw new TRPCError({ code: 'UNAUTHORIZED' });
|
|
421
|
+
|
|
422
|
+
return next({ ctx: { ...ctx, user: ctx.user } });
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
export const didAndChallengeRoute = withChallenge(didRoute);
|
|
265
426
|
|
|
266
427
|
export const scopedRoute = didAndChallengeRoute.use(({ ctx, next, meta }) => {
|
|
267
428
|
if (!meta?.requiredScope) {
|
|
@@ -295,33 +456,41 @@ export const openProfileRoute = didRoute.use(async ({ ctx, next }) => {
|
|
|
295
456
|
return next({ ctx: { ...ctx, user: { ...ctx.user, profile } } });
|
|
296
457
|
});
|
|
297
458
|
|
|
298
|
-
|
|
299
|
-
|
|
459
|
+
const withProfile = (base: typeof didAndChallengeRoute) =>
|
|
460
|
+
base.use(async ({ ctx, next, meta }) => {
|
|
461
|
+
const { profile } = ctx.user;
|
|
300
462
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
463
|
+
if (!profile) {
|
|
464
|
+
throw new TRPCError({
|
|
465
|
+
code: 'NOT_FOUND',
|
|
466
|
+
message: 'Profile not found. Please make a profile!',
|
|
467
|
+
});
|
|
468
|
+
}
|
|
307
469
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
470
|
+
if (!meta?.requiredScope) {
|
|
471
|
+
return next({ ctx: { ...ctx, user: { ...ctx.user, profile } } });
|
|
472
|
+
}
|
|
311
473
|
|
|
312
|
-
|
|
474
|
+
const userScope = ctx.user?.scope || AUTH_GRANT_NO_ACCESS_SCOPE;
|
|
313
475
|
|
|
314
|
-
|
|
476
|
+
const hasRequiredScope = userHasRequiredScopes(userScope, meta.requiredScope);
|
|
315
477
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
478
|
+
if (!hasRequiredScope) {
|
|
479
|
+
throw new TRPCError({
|
|
480
|
+
code: 'UNAUTHORIZED',
|
|
481
|
+
message: `This operation requires ${meta.requiredScope} scope`,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
322
484
|
|
|
323
|
-
|
|
324
|
-
});
|
|
485
|
+
return next({ ctx: { ...ctx, user: { ...ctx.user, profile } } });
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
export const profileRoute = withProfile(didAndChallengeRoute);
|
|
489
|
+
|
|
490
|
+
/** Same auth chain, composed on a base that never attaches private RPC input to Sentry. */
|
|
491
|
+
export const didRouteWithoutInputCapture = withDid(openRouteWithoutInputCapture);
|
|
492
|
+
export const didAndChallengeRouteWithoutInputCapture = withChallenge(didRouteWithoutInputCapture);
|
|
493
|
+
export const profileRouteWithoutInputCapture = withProfile(didAndChallengeRouteWithoutInputCapture);
|
|
325
494
|
|
|
326
495
|
export const openProfileManagerRoute = openRoute.use(async ({ ctx, next }) => {
|
|
327
496
|
if (!ctx.user?.did) throw new TRPCError({ code: 'UNAUTHORIZED' });
|
|
@@ -385,7 +554,8 @@ const GUARDIAN_APPROVAL_CLOCK_SKEW_SECONDS = 60;
|
|
|
385
554
|
export const guardianGatedRoute = profileRoute.use(async ({ ctx, next }) => {
|
|
386
555
|
const { profile } = ctx.user;
|
|
387
556
|
const guardianApprovalToken = ctx._guardianApprovalToken;
|
|
388
|
-
|
|
557
|
+
// Service profiles (orgs) are managed for admin purposes, not guardianship.
|
|
558
|
+
const isChildAccount = !profile.isServiceProfile && (await isProfileManaged(profile.profileId));
|
|
389
559
|
let guardianIdentity: { profileId: string; did: string } | undefined;
|
|
390
560
|
|
|
391
561
|
if (isChildAccount && guardianApprovalToken) {
|