@learncard/network-brain-service 4.1.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +14 -0
- package/CHANGELOG.md +143 -0
- package/README.md +27 -1
- package/bootstrapLambda.test.ts +70 -32
- package/config/config.dev.json +1 -0
- package/config/config.example.json +2 -1
- package/config/config.production.json +1 -0
- package/config/config.scouts.dev.json +1 -0
- package/config/config.scouts.production.json +1 -0
- package/consentFlowDeadlineLambda.test.ts +108 -0
- package/consentFlowDeadlineLambda.ts +18 -0
- package/consentFlowDeadlineLambdaApp.ts +14 -0
- package/dist/accesslayer/boost/create.d.ts +8 -2
- package/dist/accesslayer/boost/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/audience.d.ts +4 -4
- package/dist/accesslayer/campaign/audience.d.ts.map +1 -1
- package/dist/accesslayer/campaign/create.d.ts +1 -1
- package/dist/accesslayer/campaign/create.d.ts.map +1 -1
- package/dist/accesslayer/campaign/delivery.d.ts +5 -0
- package/dist/accesslayer/campaign/delivery.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.d.ts +5 -0
- package/dist/accesslayer/campaign/dispatch.d.ts.map +1 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts +2 -0
- package/dist/accesslayer/campaign/dispatch.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.d.ts +10 -0
- package/dist/accesslayer/campaign/email.d.ts.map +1 -0
- package/dist/accesslayer/campaign/email.test.d.ts +2 -0
- package/dist/accesslayer/campaign/email.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/preferences.d.ts +5 -0
- package/dist/accesslayer/campaign/preferences.d.ts.map +1 -0
- package/dist/accesslayer/campaign/read.d.ts.map +1 -1
- package/dist/accesslayer/campaign/test.d.ts +5 -0
- package/dist/accesslayer/campaign/test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/test.test.d.ts +2 -0
- package/dist/accesslayer/campaign/test.test.d.ts.map +1 -0
- package/dist/accesslayer/campaign/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts +1 -1
- package/dist/accesslayer/consentflowcontract/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +2 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +19 -4
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +6 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/versions.d.ts +68 -0
- package/dist/accesslayer/consentflowcontract/versions.d.ts.map +1 -0
- package/dist/accesslayer/credential/create.d.ts +3 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/delete.d.ts +1 -0
- package/dist/accesslayer/credential/delete.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +15 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/read.d.ts +2 -0
- package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts +1 -0
- 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-credential/create.d.ts +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +7 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -1
- package/dist/app.d.ts +11 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +61 -1
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/campaign-unsubscribe.d.ts +7 -0
- package/dist/campaign-unsubscribe.d.ts.map +1 -0
- package/dist/campaign-unsubscribe.test.d.ts +2 -0
- package/dist/campaign-unsubscribe.test.d.ts.map +1 -0
- package/dist/config/environment.d.ts +42 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/stageConfig.d.ts +4 -0
- package/dist/config/stageConfig.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/helpers/boost.helpers.d.ts +4 -1
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/campaign-app-origin.helpers.d.ts +2 -0
- package/dist/helpers/campaign-app-origin.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-email.helpers.d.ts +13 -0
- package/dist/helpers/campaign-email.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts +7 -0
- package/dist/helpers/campaign-notifications.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.d.ts +7 -0
- package/dist/helpers/campaign-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts +2 -0
- package/dist/helpers/campaign-unsubscribe-page.d.ts.map +1 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts +7 -0
- package/dist/helpers/campaign-unsubscribe.helpers.d.ts.map +1 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts +2 -0
- package/dist/helpers/campaign-webhook.helpers.test.d.ts.map +1 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts +5 -0
- package/dist/helpers/consentflow-deadline.helpers.d.ts.map +1 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts +2 -0
- package/dist/helpers/consentflow-legacy-migration.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -1
- package/dist/helpers/contract.helpers.d.ts +1 -1
- package/dist/helpers/contract.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-display.helpers.d.ts +5 -0
- package/dist/helpers/credential-display.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-display.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-display.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-materiality.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-refresh.helpers.d.ts +30 -1
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential-status-mutation.helpers.d.ts +5 -0
- package/dist/helpers/credential-status-mutation.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-status-mutation.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential.helpers.d.ts +1 -1
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-refresh.helpers.d.ts +4 -2
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts +1 -0
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/learnCard.helpers.d.ts +1 -0
- package/dist/helpers/learnCard.helpers.d.ts.map +1 -1
- package/dist/helpers/managed-status.helpers.d.ts +7 -0
- package/dist/helpers/managed-status.helpers.d.ts.map +1 -0
- package/dist/helpers/managed-status.helpers.test.d.ts +2 -0
- package/dist/helpers/managed-status.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-delivery-boundary.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts +13 -0
- package/dist/helpers/notification-webhook-fetch.helpers.d.ts.map +1 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts +2 -0
- package/dist/helpers/notification-webhook-fetch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +13 -5
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts +2 -0
- package/dist/helpers/refresh-send-intent.helpers.test.d.ts.map +1 -0
- package/dist/helpers/send-contract.helpers.d.ts +11 -0
- package/dist/helpers/send-contract.helpers.d.ts.map +1 -0
- package/dist/helpers/status-list.helpers.d.ts +12 -1
- package/dist/helpers/status-list.helpers.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowContractVersion.d.ts +12 -0
- package/dist/models/ConsentFlowContractVersion.d.ts.map +1 -0
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/Credential.d.ts +2 -0
- package/dist/models/Credential.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -0
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +1 -1
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/campaign-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/openapi.d.ts.map +1 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/activity.d.ts +15 -5
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts +15 -5
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +12 -2
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/bench.d.ts +11 -1
- package/dist/routes/bench.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +70 -15
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/campaigns.d.ts +183 -51
- package/dist/routes/campaigns.d.ts.map +1 -1
- package/dist/routes/claim-hooks.d.ts +11 -1
- package/dist/routes/claim-hooks.d.ts.map +1 -1
- package/dist/routes/contact-methods.d.ts +11 -1
- package/dist/routes/contact-methods.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +374 -8
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-input-privacy.test.d.ts +2 -0
- package/dist/routes/credential-input-privacy.test.d.ts.map +1 -0
- package/dist/routes/credential-refreshes.d.ts +210 -1
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credential-share-previews.d.ts +11 -1
- package/dist/routes/credential-share-previews.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +132 -3
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/did-metadata.d.ts +11 -1
- package/dist/routes/did-metadata.d.ts.map +1 -1
- package/dist/routes/federation.d.ts +11 -1
- package/dist/routes/federation.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +54 -19
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +100 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/integrations.d.ts +14 -4
- package/dist/routes/integrations.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +11 -1
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts +11 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts +11 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +22 -2
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +36 -16
- package/dist/routes/share-links.d.ts.map +1 -1
- package/dist/routes/skill-frameworks.d.ts +11 -1
- package/dist/routes/skill-frameworks.d.ts.map +1 -1
- package/dist/routes/skills.d.ts +11 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/storage.d.ts +37 -23
- package/dist/routes/storage.d.ts.map +1 -1
- package/dist/routes/test.d.ts +79 -3
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/utilities.d.ts +11 -1
- package/dist/routes/utilities.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts +11 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts +3 -0
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/delivery.service.d.ts +10 -0
- package/dist/services/delivery/delivery.service.d.ts.map +1 -1
- package/dist/types/activity.d.ts +3 -3
- package/dist/types/app-manifest.d.ts +10 -10
- package/dist/types/campaign.d.ts +13 -1
- package/dist/types/campaign.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +175 -7
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +27 -0
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/credential.d.ts +5 -0
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/integration.d.ts +3 -3
- package/lambda.ts +44 -14
- package/lambdaApp.ts +20 -0
- package/package.json +20 -19
- package/project.json +5 -0
- package/scripts/migrate-consent-flow-versions.ts +9 -0
- package/scripts/report-send-template-exposure.ts +47 -0
- package/scripts/verify-python-contracts.ts +16 -1
- package/serverless-local.yml +15 -2
- package/serverless.function-env.cjs +4 -0
- package/serverless.local.yml +12 -0
- package/serverless.yml +48 -3
- package/src/accesslayer/boost/create.ts +62 -7
- package/src/accesslayer/campaign/README.md +275 -6
- package/src/accesslayer/campaign/audience.ts +39 -7
- package/src/accesslayer/campaign/create.ts +4 -2
- package/src/accesslayer/campaign/delivery.ts +66 -0
- package/src/accesslayer/campaign/dispatch.test.ts +59 -0
- package/src/accesslayer/campaign/dispatch.ts +172 -0
- package/src/accesslayer/campaign/email.test.ts +73 -0
- package/src/accesslayer/campaign/email.ts +91 -0
- package/src/accesslayer/campaign/preferences.ts +53 -0
- package/src/accesslayer/campaign/read.ts +3 -1
- package/src/accesslayer/campaign/state.ts +1 -1
- package/src/accesslayer/campaign/test.test.ts +31 -0
- package/src/accesslayer/campaign/test.ts +75 -0
- package/src/accesslayer/campaign/update.ts +15 -4
- package/src/accesslayer/consentflowcontract/create.ts +20 -1
- package/src/accesslayer/consentflowcontract/delete.ts +121 -3
- package/src/accesslayer/consentflowcontract/read.ts +15 -9
- package/src/accesslayer/consentflowcontract/relationships/create.ts +70 -36
- package/src/accesslayer/consentflowcontract/relationships/read.ts +305 -73
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +1 -1
- package/src/accesslayer/consentflowcontract/relationships/update.ts +269 -98
- package/src/accesslayer/consentflowcontract/versions.ts +790 -0
- package/src/accesslayer/credential/create.ts +33 -9
- package/src/accesslayer/credential/delete.ts +45 -0
- package/src/accesslayer/credential/relationships/create.ts +102 -47
- package/src/accesslayer/credential/relationships/update.ts +8 -116
- package/src/accesslayer/credential-refresh/read.ts +26 -2
- package/src/accesslayer/credential-refresh/update.ts +6 -2
- package/src/accesslayer/inbox-credential/create.ts +7 -1
- package/src/accesslayer/inbox-credential/refresh.ts +14 -4
- package/src/campaign-unsubscribe.test.ts +158 -0
- package/src/campaign-unsubscribe.ts +86 -0
- package/src/config/environment.test.ts +52 -0
- package/src/config/environment.ts +58 -0
- package/src/config/serverlessFunctionEnv.test.ts +59 -0
- package/src/credential-refresh.ts +1 -0
- package/src/docker-entry.ts +26 -1
- package/src/helpers/boost-storage.helpers.test.ts +107 -41
- package/src/helpers/boost.helpers.ts +88 -40
- package/src/helpers/campaign-app-origin.helpers.ts +25 -0
- package/src/helpers/campaign-email.helpers.ts +113 -0
- package/src/helpers/campaign-notifications.helpers.ts +127 -0
- package/src/helpers/campaign-queue.helpers.test.ts +442 -0
- package/src/helpers/campaign-queue.helpers.ts +122 -0
- package/src/helpers/campaign-unsubscribe-page.ts +107 -0
- package/src/helpers/campaign-unsubscribe.helpers.ts +56 -0
- package/src/helpers/campaign-webhook.helpers.test.ts +137 -0
- package/src/helpers/consentflow-deadline.helpers.ts +22 -0
- package/src/helpers/consentflow-legacy-migration.helpers.ts +179 -0
- package/src/helpers/contract-events.helpers.ts +35 -3
- package/src/helpers/contract-requests.helpers.ts +1 -1
- package/src/helpers/contract.helpers.ts +5 -1
- package/src/helpers/credential-display.helpers.test.ts +68 -0
- package/src/helpers/credential-display.helpers.ts +15 -0
- package/src/helpers/credential-refresh-email.helpers.ts +5 -12
- package/src/helpers/credential-refresh-materiality.helpers.test.ts +138 -1
- package/src/helpers/credential-refresh-materiality.helpers.ts +62 -1
- package/src/helpers/credential-refresh-proof.helpers.test.ts +1 -1
- package/src/helpers/credential-refresh-proof.helpers.ts +2 -2
- package/src/helpers/credential-refresh.helpers.ts +1166 -376
- package/src/helpers/credential-status-mutation.helpers.test.ts +155 -0
- package/src/helpers/credential-status-mutation.helpers.ts +166 -0
- package/src/helpers/credential.helpers.ts +19 -11
- package/src/helpers/finalize-inbox.helpers.ts +1 -1
- package/src/helpers/inbox-refresh.helpers.ts +34 -8
- package/src/helpers/inbox.helpers.ts +2 -0
- package/src/helpers/learnCard.helpers.ts +23 -0
- package/src/helpers/managed-status.helpers.test.ts +101 -0
- package/src/helpers/managed-status.helpers.ts +147 -0
- package/src/helpers/notification-delivery-boundary.helpers.test.ts +114 -0
- package/src/helpers/notification-webhook-fetch.helpers.test.ts +228 -0
- package/src/helpers/notification-webhook-fetch.helpers.ts +166 -0
- package/src/helpers/notificationQueue.helpers.ts +5 -0
- package/src/helpers/notifications.helpers.ts +19 -12
- package/src/helpers/refresh-send-intent.helpers.test.ts +60 -0
- package/src/helpers/refresh-send-intent.helpers.ts +81 -18
- package/src/helpers/send-contract.helpers.ts +36 -0
- package/src/helpers/status-list.helpers.test.ts +29 -12
- package/src/helpers/status-list.helpers.ts +329 -20
- package/src/models/ConsentFlowContract.ts +3 -0
- package/src/models/ConsentFlowContractVersion.ts +50 -0
- package/src/models/ConsentFlowTerms.ts +4 -0
- package/src/models/Credential.ts +2 -0
- package/src/models/CredentialRefresh.ts +2 -0
- package/src/models/Profile.ts +2 -0
- package/src/models/campaign-constraints.ts +13 -8
- package/src/models/index.ts +2 -0
- package/src/openapi.ts +21 -0
- package/src/routes/app-store.ts +2 -1
- package/src/routes/boosts.ts +219 -137
- package/src/routes/campaigns.ts +98 -19
- package/src/routes/contracts.ts +433 -115
- package/src/routes/credential-input-privacy.test.ts +92 -0
- package/src/routes/credential-refreshes.ts +273 -2
- package/src/routes/federation.ts +7 -2
- package/src/routes/inbox.ts +6 -1
- package/src/routes/index.ts +31 -2
- package/src/routes/workflows.ts +61 -46
- package/src/services/delivery/adapters/postmark.adapter.test.ts +70 -1
- package/src/services/delivery/adapters/postmark.adapter.ts +84 -11
- package/src/services/delivery/delivery.service.ts +4 -0
- package/src/types/campaign.ts +14 -1
- package/src/types/consentflowcontract.ts +43 -2
- package/src/types/credential-refresh.ts +11 -1
- package/src/types/credential.ts +2 -0
- package/test/boost-status-cleanup.spec.ts +123 -0
- package/test/campaigns-email.unit.spec.ts +268 -0
- package/test/campaigns.spec.ts +804 -30
- package/test/consent-autoboost-idempotency.spec.ts +432 -0
- package/test/consent-data-boundaries.spec.ts +322 -26
- package/test/consent-legacy-migration.spec.ts +315 -0
- package/test/consent-recipients.spec.ts +7 -7
- package/test/consent-version-updates.spec.ts +761 -0
- package/test/consentflow.spec.ts +698 -226
- package/test/contract-requests.spec.ts +55 -9
- package/test/credential-refresh-allocation.spec.ts +212 -14
- package/test/credential-refresh-email.spec.ts +6 -10
- package/test/credential-refresh-endpoint.spec.ts +3 -0
- package/test/credential-refresh-publication.spec.ts +52 -15
- package/test/credential-storage.neo4j.spec.ts +33 -6
- package/test/helpers/legacy-autoboost.ts +48 -0
- package/test/holder-mode-send.spec.ts +710 -0
- package/test/inbox-refresh.spec.ts +105 -0
- package/test/inbox-security.e2e.ts +18 -22
- package/test/managed-status-revocation.spec.ts +970 -0
- package/test/notifications-worker.spec.ts +31 -25
- package/test/refresh-send.spec.ts +334 -5
- package/test/smart-resume-consent.spec.ts +1 -1
- package/test/verification/campaigns.ts +40 -0
- package/vitest.campaigns.config.ts +2 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { resolveTenantFromRequest } from '@learncard/email-templates';
|
|
3
|
+
import type {
|
|
4
|
+
PrepareRefreshableSendInput,
|
|
5
|
+
SendBoostInput,
|
|
6
|
+
PublishCredentialRefreshInput,
|
|
7
|
+
} from '@learncard/types';
|
|
8
|
+
|
|
9
|
+
const transport = vi.hoisted(() => ({ send: vi.fn(), lookup: vi.fn() }));
|
|
10
|
+
vi.mock('@sentry/serverless', () => ({
|
|
11
|
+
Handlers: {
|
|
12
|
+
trpcMiddleware:
|
|
13
|
+
({ attachRpcInput }: { attachRpcInput: boolean }) =>
|
|
14
|
+
async (opts: { next: () => Promise<unknown>; getRawInput: () => Promise<unknown> }) => {
|
|
15
|
+
const input = attachRpcInput ? await opts.getRawInput() : undefined;
|
|
16
|
+
try {
|
|
17
|
+
return await opts.next();
|
|
18
|
+
} finally {
|
|
19
|
+
transport.send({ input });
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
configureScope: vi.fn(),
|
|
24
|
+
setUser: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
vi.mock('@accesslayer/profile/read', () => ({
|
|
27
|
+
getProfileByDid: transport.lookup,
|
|
28
|
+
getProfileByProfileId: transport.lookup,
|
|
29
|
+
}));
|
|
30
|
+
vi.mock('../models/credential-refresh-constraints', () => ({
|
|
31
|
+
ensureCredentialRefreshConstraints: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import { boostsRouter } from './boosts';
|
|
35
|
+
import { credentialRefreshesRouter } from './credential-refreshes';
|
|
36
|
+
import type { Context } from './index';
|
|
37
|
+
|
|
38
|
+
describe('credential route input telemetry', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
process.env.CREDENTIAL_REFRESH_ENABLED = 'true';
|
|
41
|
+
transport.send.mockClear();
|
|
42
|
+
transport.lookup.mockResolvedValue({ profileId: 'issuer', did: 'did:key:issuer' });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it.each(['prepare', 'send', 'publish'] as const)(
|
|
46
|
+
'%s never captures malformed, unauthenticated or failing request input',
|
|
47
|
+
async route => {
|
|
48
|
+
const privateInput = {
|
|
49
|
+
recipient: 42,
|
|
50
|
+
signedCredential: 'PRIVATE CLAIM',
|
|
51
|
+
templateData: { private: 'PRIVATE CLAIM' },
|
|
52
|
+
};
|
|
53
|
+
const invoke = (ctx: Context): Promise<unknown> => {
|
|
54
|
+
if (route === 'prepare')
|
|
55
|
+
return boostsRouter
|
|
56
|
+
.createCaller(ctx)
|
|
57
|
+
.prepareRefreshableSend(
|
|
58
|
+
privateInput as unknown as PrepareRefreshableSendInput
|
|
59
|
+
);
|
|
60
|
+
if (route === 'send')
|
|
61
|
+
return boostsRouter
|
|
62
|
+
.createCaller(ctx)
|
|
63
|
+
.send(privateInput as unknown as SendBoostInput);
|
|
64
|
+
return credentialRefreshesRouter
|
|
65
|
+
.createCaller(ctx)
|
|
66
|
+
.publishCredentialRefresh(
|
|
67
|
+
privateInput as unknown as PublishCredentialRefreshInput
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
const authenticated: Context = {
|
|
71
|
+
domain: 'example.com',
|
|
72
|
+
tenant: resolveTenantFromRequest({}),
|
|
73
|
+
user: { did: 'did:key:issuer', isChallengeValid: true, scope: '*:*' },
|
|
74
|
+
};
|
|
75
|
+
await expect(invoke(authenticated)).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
|
76
|
+
await expect(
|
|
77
|
+
invoke({ domain: 'example.com', tenant: resolveTenantFromRequest({}) })
|
|
78
|
+
).rejects.toMatchObject({
|
|
79
|
+
code: 'UNAUTHORIZED',
|
|
80
|
+
});
|
|
81
|
+
transport.lookup.mockRejectedValueOnce(new Error('LOOKUP_FAILED'));
|
|
82
|
+
await expect(invoke(authenticated)).rejects.toThrow('LOOKUP_FAILED');
|
|
83
|
+
expect(transport.send).toHaveBeenCalledTimes(3);
|
|
84
|
+
expect(transport.send.mock.calls).toEqual([
|
|
85
|
+
[{ input: undefined }],
|
|
86
|
+
[{ input: undefined }],
|
|
87
|
+
[{ input: undefined }],
|
|
88
|
+
]);
|
|
89
|
+
expect(JSON.stringify(transport.send.mock.calls)).not.toContain('PRIVATE CLAIM');
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
});
|
|
@@ -2,32 +2,50 @@ import { TRPCError } from '@trpc/server';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import {
|
|
4
4
|
AllocateCredentialRefreshInputValidator,
|
|
5
|
+
AttachCredentialRefreshStatusEntriesInputValidator,
|
|
5
6
|
AllocateCredentialRefreshResultValidator,
|
|
7
|
+
EncryptionRecipientsResultValidator,
|
|
6
8
|
GetCredentialRefreshHistoryInputValidator,
|
|
7
9
|
GetCredentialRefreshHistoryResultValidator,
|
|
10
|
+
GetEncryptionRecipientsInputValidator,
|
|
8
11
|
PublishCredentialRefreshInputValidator,
|
|
9
12
|
PublishCredentialRefreshResultValidator,
|
|
13
|
+
PublishHolderEncryptedRefreshInputValidator,
|
|
14
|
+
SendHolderEncryptedRefreshableInputValidator,
|
|
10
15
|
VCValidator,
|
|
16
|
+
GetCredentialRefreshReceiptInputValidator,
|
|
17
|
+
CredentialRefreshReceiptValidator,
|
|
11
18
|
} from '@learncard/types';
|
|
12
19
|
import type { PublishCredentialRefreshInput } from '@learncard/types';
|
|
13
20
|
|
|
14
|
-
import { t,
|
|
21
|
+
import { t, credentialProfileRoute } from '@routes';
|
|
15
22
|
import { getCredentialRefreshRuntimeEnvironment } from '@environment';
|
|
16
23
|
import { getProfileByDid, getProfileByProfileId } from '@accesslayer/profile/read';
|
|
17
24
|
import {
|
|
18
25
|
getCredentialRefresh,
|
|
19
26
|
getCredentialRefreshVersions,
|
|
27
|
+
getCredentialRefreshReceiptBinding,
|
|
20
28
|
} from '@accesslayer/credential-refresh';
|
|
21
29
|
import {
|
|
22
30
|
allocateCredentialRefresh,
|
|
31
|
+
getCurrentEncryptionRecipients,
|
|
23
32
|
publishCredentialRefresh,
|
|
33
|
+
publishHolderEncryptedRefreshableCredential,
|
|
34
|
+
sendHolderEncryptedRefreshableCredential,
|
|
24
35
|
sendRefreshableCredential,
|
|
25
36
|
} from '@helpers/credential-refresh.helpers';
|
|
26
37
|
import { getDidWeb } from '@helpers/did.helpers';
|
|
27
38
|
import { isRelationshipBlocked } from '@helpers/connection.helpers';
|
|
28
39
|
import { ensureCredentialRefreshConstraints } from '../models/credential-refresh-constraints';
|
|
40
|
+
import { getBitstringStatusListEntries } from '@learncard/helpers';
|
|
41
|
+
import { neogma } from '@instance';
|
|
42
|
+
import { matchesCredentialStatusDigest } from '@helpers/credential-refresh-materiality.helpers';
|
|
43
|
+
import { bindRefreshStatusEntries } from '@helpers/managed-status.helpers';
|
|
44
|
+
import { getStatusManagement } from '@helpers/status-list.helpers';
|
|
45
|
+
import { constructUri, getUriParts } from '@helpers/uri.helpers';
|
|
46
|
+
import { getCredentialRefreshServiceUrl } from '@helpers/credential-refresh.helpers';
|
|
29
47
|
|
|
30
|
-
const credentialRefreshRoute =
|
|
48
|
+
const credentialRefreshRoute = credentialProfileRoute.use(async ({ ctx, next }) => {
|
|
31
49
|
if (!getCredentialRefreshRuntimeEnvironment().CREDENTIAL_REFRESH_ENABLED) {
|
|
32
50
|
throw new TRPCError({
|
|
33
51
|
code: 'NOT_FOUND',
|
|
@@ -41,6 +59,149 @@ const credentialRefreshRoute = profileRoute.use(async ({ ctx, next }) => {
|
|
|
41
59
|
});
|
|
42
60
|
|
|
43
61
|
export const credentialRefreshesRouter = t.router({
|
|
62
|
+
getReceipt: credentialRefreshRoute
|
|
63
|
+
.meta({ requiredScope: 'credentials:read' })
|
|
64
|
+
.input(GetCredentialRefreshReceiptInputValidator)
|
|
65
|
+
.output(CredentialRefreshReceiptValidator.nullable())
|
|
66
|
+
.query(async ({ ctx, input }) => {
|
|
67
|
+
const { id, type } = getUriParts(input.uri);
|
|
68
|
+
if (type !== 'credential' || input.uri !== constructUri('credential', id, ctx.domain)) {
|
|
69
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid delivery URI' });
|
|
70
|
+
}
|
|
71
|
+
const refresh = await getCredentialRefreshReceiptBinding(
|
|
72
|
+
id,
|
|
73
|
+
ctx.user.profile.profileId
|
|
74
|
+
);
|
|
75
|
+
if (refresh === null) return null;
|
|
76
|
+
if (
|
|
77
|
+
!refresh ||
|
|
78
|
+
!refresh.holderDid ||
|
|
79
|
+
refresh.holderProfileId !== ctx.user.profile.profileId ||
|
|
80
|
+
!['awaiting_claim', 'active'].includes(refresh.state)
|
|
81
|
+
) {
|
|
82
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Delivery not found' });
|
|
83
|
+
}
|
|
84
|
+
// Coordinates alone cannot reconstruct the exact signed descriptor of legacy sends.
|
|
85
|
+
let binding = refresh.credentialStatusBinding ?? refresh.inboxCredentialStatus;
|
|
86
|
+
if (binding === undefined) {
|
|
87
|
+
binding = await neogma.getTransaction(null, async tx => {
|
|
88
|
+
const result = await tx.run(
|
|
89
|
+
`MATCH (refresh:CredentialRefresh {refreshId: $refreshId})
|
|
90
|
+
SET refresh._statusBindingLock = true
|
|
91
|
+
REMOVE refresh._statusBindingLock
|
|
92
|
+
RETURN refresh.credentialStatusBinding AS binding,
|
|
93
|
+
refresh.inboxCredentialStatus AS inboxBinding,
|
|
94
|
+
refresh.credentialStatusDigest AS digest`,
|
|
95
|
+
{ refreshId: refresh.refreshId }
|
|
96
|
+
);
|
|
97
|
+
const row = result.records[0];
|
|
98
|
+
const existing: unknown = row?.get('binding') ?? row?.get('inboxBinding');
|
|
99
|
+
if (typeof existing === 'string') return existing;
|
|
100
|
+
const digest: unknown = row?.get('digest');
|
|
101
|
+
if (
|
|
102
|
+
typeof digest !== 'string' ||
|
|
103
|
+
!matchesCredentialStatusDigest(null, digest)
|
|
104
|
+
) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
// The JSON string "null" authenticates absence; an unset property
|
|
108
|
+
// means an unknown legacy descriptor. Never store Neo4j null.
|
|
109
|
+
await tx.run(
|
|
110
|
+
`MATCH (refresh:CredentialRefresh {refreshId: $refreshId})
|
|
111
|
+
SET refresh.credentialStatusBinding = 'null'`,
|
|
112
|
+
{ refreshId: refresh.refreshId }
|
|
113
|
+
);
|
|
114
|
+
return 'null';
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (binding === undefined) {
|
|
118
|
+
throw new TRPCError({
|
|
119
|
+
code: 'PRECONDITION_FAILED',
|
|
120
|
+
message: 'Delivery binding unavailable',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const status: unknown = JSON.parse(binding);
|
|
124
|
+
return CredentialRefreshReceiptValidator.parse({
|
|
125
|
+
refreshId: refresh.refreshId,
|
|
126
|
+
credentialId: refresh.credentialId,
|
|
127
|
+
issuerDid: refresh.issuerDid,
|
|
128
|
+
holderDid: refresh.holderDid,
|
|
129
|
+
...(refresh.refreshServiceBinding
|
|
130
|
+
? {}
|
|
131
|
+
: { refreshServiceBinding: 'managed-entry' }),
|
|
132
|
+
refreshService: refresh.refreshServiceBinding
|
|
133
|
+
? JSON.parse(refresh.refreshServiceBinding)
|
|
134
|
+
: {
|
|
135
|
+
id: getCredentialRefreshServiceUrl(refresh.refreshId, ctx.domain),
|
|
136
|
+
type: 'LearnCardCredentialRefresh2026',
|
|
137
|
+
authorization: { type: 'LearnCardDIDAuth' },
|
|
138
|
+
},
|
|
139
|
+
...(status === null ? {} : { credentialStatus: status }),
|
|
140
|
+
encryptionMode: refresh.encryptionMode ?? 'network',
|
|
141
|
+
});
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
attachStatusEntries: credentialRefreshRoute
|
|
145
|
+
.meta({ requiredScope: 'credentials:write' })
|
|
146
|
+
.input(AttachCredentialRefreshStatusEntriesInputValidator)
|
|
147
|
+
.output(z.boolean())
|
|
148
|
+
.mutation(async ({ ctx, input }) => {
|
|
149
|
+
const refresh = await getCredentialRefresh(input.refreshId);
|
|
150
|
+
if (!refresh || refresh.issuerProfileId !== ctx.user.profile.profileId) {
|
|
151
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Credential refresh not found' });
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
!matchesCredentialStatusDigest(
|
|
155
|
+
input.credentialStatus,
|
|
156
|
+
refresh.credentialStatusDigest
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
throw new TRPCError({
|
|
160
|
+
code: 'BAD_REQUEST',
|
|
161
|
+
message: 'Credential status does not match the original credential',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const entries = getBitstringStatusListEntries({
|
|
165
|
+
credentialStatus: input.credentialStatus ?? undefined,
|
|
166
|
+
});
|
|
167
|
+
if (entries.length === 0 && input.credentialStatus !== null) {
|
|
168
|
+
throw new TRPCError({
|
|
169
|
+
code: 'BAD_REQUEST',
|
|
170
|
+
message: 'Credential has no supported public status entries',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
await neogma.getTransaction(null, async tx => {
|
|
174
|
+
const result = await tx.run(
|
|
175
|
+
`MATCH (refresh:CredentialRefresh {refreshId: $refreshId})-[:ROOT]->(root:Credential)
|
|
176
|
+
SET root._statusMutationLock = true REMOVE root._statusMutationLock
|
|
177
|
+
RETURN root.id AS id`,
|
|
178
|
+
{ refreshId: input.refreshId }
|
|
179
|
+
);
|
|
180
|
+
const root = result.records[0];
|
|
181
|
+
if (!root)
|
|
182
|
+
throw new TRPCError({
|
|
183
|
+
code: 'CONFLICT',
|
|
184
|
+
message: 'Credential has no original delivery',
|
|
185
|
+
});
|
|
186
|
+
await bindRefreshStatusEntries(
|
|
187
|
+
input.refreshId,
|
|
188
|
+
entries,
|
|
189
|
+
refresh.issuerProfileId,
|
|
190
|
+
ctx.domain,
|
|
191
|
+
tx
|
|
192
|
+
);
|
|
193
|
+
await tx.run(
|
|
194
|
+
`MATCH (refresh:CredentialRefresh {refreshId: $refreshId})
|
|
195
|
+
SET refresh.credentialStatusBinding = $credentialStatusBinding`,
|
|
196
|
+
{
|
|
197
|
+
refreshId: input.refreshId,
|
|
198
|
+
credentialStatusBinding: JSON.stringify(input.credentialStatus),
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
return true;
|
|
203
|
+
}),
|
|
204
|
+
|
|
44
205
|
allocateCredentialRefresh: credentialRefreshRoute
|
|
45
206
|
.meta({
|
|
46
207
|
openapi: {
|
|
@@ -136,6 +297,83 @@ export const credentialRefreshesRouter = t.router({
|
|
|
136
297
|
return uri;
|
|
137
298
|
}),
|
|
138
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Holder-mode counterpart of `sendRefreshableCredential` (LC-2345): a separate
|
|
302
|
+
* route rather than a branch of `send`/`sendRefreshableCredential`, so the
|
|
303
|
+
* ciphertext-only input shape (`HolderEncryptedHandoffValidator`) never has to
|
|
304
|
+
* share a validator with the plaintext `credential`/`signedCredential` paths.
|
|
305
|
+
*/
|
|
306
|
+
sendHolderEncryptedRefreshable: credentialRefreshRoute
|
|
307
|
+
.meta({
|
|
308
|
+
openapi: {
|
|
309
|
+
protect: true,
|
|
310
|
+
method: 'POST',
|
|
311
|
+
path: '/credential-refresh/send-holder-encrypted',
|
|
312
|
+
tags: ['Credential Refresh'],
|
|
313
|
+
summary: 'Send a holder-mode (student-only encrypted) refreshable credential',
|
|
314
|
+
description:
|
|
315
|
+
'Binds an already-encrypted handoff to its allocated refresh aggregate. The server never decrypts or processes the credential.',
|
|
316
|
+
},
|
|
317
|
+
requiredScope: 'credentials:write',
|
|
318
|
+
})
|
|
319
|
+
.input(SendHolderEncryptedRefreshableInputValidator)
|
|
320
|
+
.output(
|
|
321
|
+
z.object({
|
|
322
|
+
uri: z.string(),
|
|
323
|
+
activityId: z.string(),
|
|
324
|
+
statusManagement: z.enum(['network', 'external', 'mixed', 'none']),
|
|
325
|
+
})
|
|
326
|
+
)
|
|
327
|
+
.mutation(async ({ ctx, input }) => {
|
|
328
|
+
const { profile } = ctx.user;
|
|
329
|
+
|
|
330
|
+
const { uri, activityId } = await sendHolderEncryptedRefreshableCredential({
|
|
331
|
+
issuerProfile: profile,
|
|
332
|
+
handoff: input.handoff,
|
|
333
|
+
idempotencyKey: input.idempotencyKey,
|
|
334
|
+
boostUri: input.boostUri,
|
|
335
|
+
skipNotification: input.skipNotification,
|
|
336
|
+
domain: ctx.domain,
|
|
337
|
+
onBehalfOf: ctx.user.onBehalfOf,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
uri,
|
|
342
|
+
activityId,
|
|
343
|
+
statusManagement: getStatusManagement(
|
|
344
|
+
getBitstringStatusListEntries(input.handoff),
|
|
345
|
+
ctx.domain
|
|
346
|
+
),
|
|
347
|
+
};
|
|
348
|
+
}),
|
|
349
|
+
|
|
350
|
+
/** Issuer-only: the current recipient set a holder-mode handoff must encrypt to. */
|
|
351
|
+
getEncryptionRecipients: credentialRefreshRoute
|
|
352
|
+
.meta({
|
|
353
|
+
openapi: {
|
|
354
|
+
protect: true,
|
|
355
|
+
method: 'GET',
|
|
356
|
+
path: '/credential-refresh/encryption-recipients',
|
|
357
|
+
tags: ['Credential Refresh'],
|
|
358
|
+
summary: 'Get the current holder-mode encryption recipients',
|
|
359
|
+
description:
|
|
360
|
+
'Returns the current key-agreement verification-method ids (holder + managers) a holder-mode handoff must encrypt to. Used after a RECIPIENTS_CHANGED conflict.',
|
|
361
|
+
},
|
|
362
|
+
requiredScope: 'credentials:read',
|
|
363
|
+
})
|
|
364
|
+
.input(GetEncryptionRecipientsInputValidator)
|
|
365
|
+
.output(EncryptionRecipientsResultValidator)
|
|
366
|
+
.query(async ({ ctx, input }) => {
|
|
367
|
+
const { profile } = ctx.user;
|
|
368
|
+
|
|
369
|
+
const recipientKeyIds = await getCurrentEncryptionRecipients({
|
|
370
|
+
issuerProfile: profile,
|
|
371
|
+
refreshId: input.refreshId,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
return { recipientKeyIds };
|
|
375
|
+
}),
|
|
376
|
+
|
|
139
377
|
publishCredentialRefresh: credentialRefreshRoute
|
|
140
378
|
.meta({
|
|
141
379
|
openapi: {
|
|
@@ -162,6 +400,38 @@ export const credentialRefreshesRouter = t.router({
|
|
|
162
400
|
});
|
|
163
401
|
}),
|
|
164
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Holder-mode counterpart of `publishCredentialRefresh`: a separate route, for the
|
|
405
|
+
* same reason as `sendHolderEncryptedRefreshable`. `PublishCredentialRefreshInputValidator`
|
|
406
|
+
* has no signing-authority equivalent for a handoff — a holder aggregate rejects
|
|
407
|
+
* signing-authority publication outright (`publishCredentialRefresh` enforces this).
|
|
408
|
+
*/
|
|
409
|
+
publishHolderEncryptedRefreshable: credentialRefreshRoute
|
|
410
|
+
.meta({
|
|
411
|
+
openapi: {
|
|
412
|
+
protect: true,
|
|
413
|
+
method: 'POST',
|
|
414
|
+
path: '/credential-refresh/publish-holder-encrypted',
|
|
415
|
+
tags: ['Credential Refresh'],
|
|
416
|
+
summary: 'Publish a holder-mode (student-only encrypted) refresh version',
|
|
417
|
+
description:
|
|
418
|
+
'Publishes a new immutable version from an already-encrypted handoff and atomically advances the refresh head. `notify` replaces server-inferred materiality.',
|
|
419
|
+
},
|
|
420
|
+
requiredScope: 'credentials:write',
|
|
421
|
+
})
|
|
422
|
+
.input(PublishHolderEncryptedRefreshInputValidator)
|
|
423
|
+
.output(PublishCredentialRefreshResultValidator)
|
|
424
|
+
.mutation(async ({ ctx, input }) => {
|
|
425
|
+
const { profile } = ctx.user;
|
|
426
|
+
|
|
427
|
+
return publishHolderEncryptedRefreshableCredential({
|
|
428
|
+
issuerProfile: profile,
|
|
429
|
+
input,
|
|
430
|
+
domain: ctx.domain,
|
|
431
|
+
branding: ctx.tenant?.emailBranding,
|
|
432
|
+
});
|
|
433
|
+
}),
|
|
434
|
+
|
|
165
435
|
getCredentialRefreshHistory: credentialRefreshRoute
|
|
166
436
|
.meta({
|
|
167
437
|
openapi: {
|
|
@@ -212,6 +482,7 @@ export const credentialRefreshesRouter = t.router({
|
|
|
212
482
|
etag: record.etag,
|
|
213
483
|
signingMode: record.signingMode,
|
|
214
484
|
updateSummary: record.updateSummary,
|
|
485
|
+
updateReason: record.updateReason,
|
|
215
486
|
})),
|
|
216
487
|
hasMore,
|
|
217
488
|
cursor,
|
package/src/routes/federation.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
getServerDidWebDID,
|
|
10
10
|
getOwningServiceDid,
|
|
11
11
|
} from '@helpers/federation.helpers';
|
|
12
|
-
import { getProfileByProfileId } from '@accesslayer/profile/read';
|
|
12
|
+
import { getProfileByDid, getProfileByProfileId } from '@accesslayer/profile/read';
|
|
13
13
|
import { getProfileIdFromDid } from '@helpers/did.helpers';
|
|
14
14
|
|
|
15
15
|
import { getOrCreateFederatedProfile } from '@helpers/profile.helpers';
|
|
@@ -99,13 +99,18 @@ export const federationRouter = t.router({
|
|
|
99
99
|
senderDid,
|
|
100
100
|
issuerDisplayName
|
|
101
101
|
);
|
|
102
|
+
// A local sender's status lists belong to its real profile, not the federated alias.
|
|
103
|
+
const localIssuer = await getProfileByDid(issuerDid);
|
|
102
104
|
|
|
103
105
|
const credentialUri = await sendCredential(
|
|
104
106
|
federatedSender,
|
|
105
107
|
recipientProfile,
|
|
106
108
|
credential,
|
|
107
109
|
ctx.domain,
|
|
108
|
-
configuration
|
|
110
|
+
configuration,
|
|
111
|
+
undefined,
|
|
112
|
+
undefined,
|
|
113
|
+
localIssuer?.profileId ?? federatedSender.profileId
|
|
109
114
|
);
|
|
110
115
|
|
|
111
116
|
return {
|
package/src/routes/inbox.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { submitInboxBatch, getInboxBatch } from '@helpers/inbox-queue.helpers';
|
|
|
3
3
|
import {
|
|
4
4
|
assertInboxRefreshEnabled,
|
|
5
5
|
inboxRefreshRequestDigest,
|
|
6
|
+
getInboxRefreshRequestDigestCandidates,
|
|
6
7
|
getInboxRefreshReplay,
|
|
7
8
|
getInboxRefreshReceipt,
|
|
8
9
|
resumeInboxRefreshDelivery,
|
|
@@ -466,7 +467,8 @@ export const inboxRouter = t.router({
|
|
|
466
467
|
const replay = await getInboxRefreshReplay(
|
|
467
468
|
profile.profileId,
|
|
468
469
|
input.idempotencyKey,
|
|
469
|
-
refreshDigest
|
|
470
|
+
refreshDigest!,
|
|
471
|
+
getInboxRefreshRequestDigestCandidates(input)
|
|
470
472
|
);
|
|
471
473
|
if (replay && (replay.claimUrl || replay.inbox.currentStatus !== 'PENDING')) {
|
|
472
474
|
await resumeInboxRefreshDelivery(replay.inbox.refreshId!, ctx.domain);
|
|
@@ -511,6 +513,9 @@ export const inboxRouter = t.router({
|
|
|
511
513
|
refresh: input.refresh,
|
|
512
514
|
idempotencyKey: input.idempotencyKey,
|
|
513
515
|
refreshRequestDigest: refreshDigest,
|
|
516
|
+
refreshRequestDigestCandidates: input.refresh
|
|
517
|
+
? getInboxRefreshRequestDigestCandidates(input)
|
|
518
|
+
: undefined,
|
|
514
519
|
},
|
|
515
520
|
ctx
|
|
516
521
|
);
|
package/src/routes/index.ts
CHANGED
|
@@ -9,7 +9,11 @@ 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 {
|
|
12
|
+
import {
|
|
13
|
+
ACT_AS_HEADER,
|
|
14
|
+
AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX,
|
|
15
|
+
CONTRACT_APPROVAL_REQUIRED,
|
|
16
|
+
} from '@learncard/types';
|
|
13
17
|
import { ContactMethodType } from '@learncard/types';
|
|
14
18
|
import { MAX_SHARE_LINK_REQUEST_BYTES, utf8ByteLength } from '@learncard/types';
|
|
15
19
|
|
|
@@ -80,6 +84,18 @@ export const t = initTRPC
|
|
|
80
84
|
input: RegExpTransformer,
|
|
81
85
|
output: { serialize: o => o, deserialize: o => o },
|
|
82
86
|
},
|
|
87
|
+
errorFormatter: ({ shape, error }) => ({
|
|
88
|
+
...shape,
|
|
89
|
+
data: {
|
|
90
|
+
...shape.data,
|
|
91
|
+
reason:
|
|
92
|
+
error.cause &&
|
|
93
|
+
'code' in error.cause &&
|
|
94
|
+
error.cause.code === CONTRACT_APPROVAL_REQUIRED
|
|
95
|
+
? CONTRACT_APPROVAL_REQUIRED
|
|
96
|
+
: undefined,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
83
99
|
});
|
|
84
100
|
|
|
85
101
|
export const createContext = async (
|
|
@@ -290,7 +306,9 @@ export const PUBLIC_SHARE_LINK_ROUTE_PREFIX = 'publicShareLinks.';
|
|
|
290
306
|
export const publicShareLinkCacheControlHeaders = (
|
|
291
307
|
paths: readonly string[] | undefined
|
|
292
308
|
): Record<string, string> =>
|
|
293
|
-
(paths ?? []).some(
|
|
309
|
+
(paths ?? []).some(
|
|
310
|
+
path => path.startsWith(PUBLIC_SHARE_LINK_ROUTE_PREFIX) || path === 'campaigns.unsubscribe'
|
|
311
|
+
)
|
|
294
312
|
? { 'Cache-Control': 'private, no-store' }
|
|
295
313
|
: {};
|
|
296
314
|
|
|
@@ -493,6 +511,17 @@ export const didRouteWithoutInputCapture = withDid(openRouteWithoutInputCapture)
|
|
|
493
511
|
export const didAndChallengeRouteWithoutInputCapture = withChallenge(didRouteWithoutInputCapture);
|
|
494
512
|
export const profileRouteWithoutInputCapture = withProfile(didAndChallengeRouteWithoutInputCapture);
|
|
495
513
|
|
|
514
|
+
/** Credential payloads need the same privacy/auth chain, not the share-link byte limit. */
|
|
515
|
+
export const credentialProfileRoute = withProfile(
|
|
516
|
+
withChallenge(
|
|
517
|
+
withDid(
|
|
518
|
+
t.procedure
|
|
519
|
+
.use(t.middleware(sentryNoInputCaptureMiddleware))
|
|
520
|
+
.use(sentryTransactionNameMiddleware)
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
);
|
|
524
|
+
|
|
496
525
|
export const openProfileManagerRoute = openRoute.use(async ({ ctx, next }) => {
|
|
497
526
|
if (!ctx.user?.did) throw new TRPCError({ code: 'UNAUTHORIZED' });
|
|
498
527
|
|