@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
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import {
|
|
3
|
+
getConsentFlowContractVersionRecords,
|
|
4
|
+
getCumulativeContractUpdateById,
|
|
5
|
+
getCumulativeContractUpdates,
|
|
6
|
+
} from '@accesslayer/consentflowcontract/versions';
|
|
2
7
|
import { QueryBuilder, BindParam } from 'neogma';
|
|
3
8
|
import mapValues from 'lodash/mapValues';
|
|
4
9
|
import {
|
|
@@ -34,12 +39,14 @@ import {
|
|
|
34
39
|
ConsentFlowTransactionsQuery,
|
|
35
40
|
HolderExportMetadata,
|
|
36
41
|
LCNProfile,
|
|
42
|
+
ConsentFlowContractDetails,
|
|
37
43
|
} from '@learncard/types';
|
|
38
44
|
import { ConsentFlowGuardianApprovalValidator } from '@learncard/types';
|
|
39
45
|
import { FlatBoostType } from 'types/boost';
|
|
40
46
|
import { constructUri, getIdFromUri } from '@helpers/uri.helpers';
|
|
41
47
|
import { flattenObject, inflateObject } from '@helpers/objects.helpers';
|
|
42
|
-
import { shouldIncludeCategory } from '@helpers/contract.helpers';
|
|
48
|
+
import { areTermsValid, shouldIncludeCategory } from '@helpers/contract.helpers';
|
|
49
|
+
import { isServiceProfileExemptFromGuardianship } from '@helpers/profile.helpers';
|
|
43
50
|
import { ProfileType } from 'types/profile';
|
|
44
51
|
import { CredentialType } from 'types/credential';
|
|
45
52
|
import { getBoostUri } from '@helpers/boost.helpers';
|
|
@@ -123,9 +130,16 @@ export const getContractDetailsById = async (
|
|
|
123
130
|
profile: LCNProfile;
|
|
124
131
|
}>(await query.return('contract, profile').run());
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
if (!result.length) return null;
|
|
134
|
+
const rawContract = DbContractValidator.parse(inflateObject(result[0]!.contract));
|
|
135
|
+
return {
|
|
136
|
+
contract: {
|
|
137
|
+
...rawContract,
|
|
138
|
+
version: Number(rawContract.currentVersion ?? 1),
|
|
139
|
+
status: rawContract.status ?? 'active',
|
|
140
|
+
},
|
|
141
|
+
contractOwner: result[0]!.profile,
|
|
142
|
+
};
|
|
129
143
|
};
|
|
130
144
|
|
|
131
145
|
export const getContractDetailsByUri = async (
|
|
@@ -172,6 +186,7 @@ export const getContractTermsById = async (
|
|
|
172
186
|
consenter: LCNProfile;
|
|
173
187
|
contract: DbContractType;
|
|
174
188
|
contractOwner: LCNProfile;
|
|
189
|
+
cumulativeUpdate?: DbContractType['latestUpdate'];
|
|
175
190
|
} | null> => {
|
|
176
191
|
const result = convertQueryResultToPropertiesObjectArray<{
|
|
177
192
|
consenter: LCNProfile;
|
|
@@ -195,14 +210,28 @@ export const getContractTermsById = async (
|
|
|
195
210
|
.run()
|
|
196
211
|
);
|
|
197
212
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
if (!result.length) return null;
|
|
214
|
+
const terms = DbTermsValidator.parse(inflateObject(result[0]!.terms));
|
|
215
|
+
const contract = DbContractValidator.parse(inflateObject(result[0]!.contract));
|
|
216
|
+
const acceptedVersion = Number(terms.lastReviewedVersion ?? terms.consentedVersion ?? 1);
|
|
217
|
+
const currentVersion = Number(contract.currentVersion ?? 1);
|
|
218
|
+
const cumulativeUpdate =
|
|
219
|
+
['live', 'needs_reconsent', 'suspended'].includes(terms.status) &&
|
|
220
|
+
acceptedVersion < currentVersion
|
|
221
|
+
? await getCumulativeContractUpdateById(
|
|
222
|
+
contract.id,
|
|
223
|
+
acceptedVersion,
|
|
224
|
+
terms.status,
|
|
225
|
+
terms.reconsentDeadline
|
|
226
|
+
)
|
|
227
|
+
: undefined;
|
|
228
|
+
return {
|
|
229
|
+
consenter: result[0]!.consenter,
|
|
230
|
+
terms,
|
|
231
|
+
contract,
|
|
232
|
+
contractOwner: result[0]!.contractOwner,
|
|
233
|
+
...(cumulativeUpdate ? { cumulativeUpdate } : {}),
|
|
234
|
+
};
|
|
206
235
|
};
|
|
207
236
|
|
|
208
237
|
export const getContractTermsByUri = async (
|
|
@@ -212,6 +241,7 @@ export const getContractTermsByUri = async (
|
|
|
212
241
|
consenter: LCNProfile;
|
|
213
242
|
contract: DbContractType;
|
|
214
243
|
contractOwner: LCNProfile;
|
|
244
|
+
cumulativeUpdate?: DbContractType['latestUpdate'];
|
|
215
245
|
} | null> => {
|
|
216
246
|
return getContractTermsById(getIdFromUri(uri));
|
|
217
247
|
};
|
|
@@ -262,10 +292,17 @@ export const getConsentFlowContractsForProfile = async (
|
|
|
262
292
|
.run()
|
|
263
293
|
);
|
|
264
294
|
|
|
265
|
-
return results.map(({ contract, boost }) =>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
295
|
+
return results.map(({ contract, boost }) => {
|
|
296
|
+
const details = DbContractValidator.parse(inflateObject(contract));
|
|
297
|
+
return {
|
|
298
|
+
contract: {
|
|
299
|
+
...details,
|
|
300
|
+
version: Number(details.currentVersion ?? 1),
|
|
301
|
+
status: details.status ?? 'active',
|
|
302
|
+
},
|
|
303
|
+
autoBoosts: boost.map(boost => getBoostUri(boost.properties.id, domain)),
|
|
304
|
+
};
|
|
305
|
+
});
|
|
269
306
|
};
|
|
270
307
|
|
|
271
308
|
export const getConsentedContractsForProfile = async (
|
|
@@ -283,10 +320,16 @@ export const getConsentedContractsForProfile = async (
|
|
|
283
320
|
terms: DbTermsType;
|
|
284
321
|
autoBoosts: string[];
|
|
285
322
|
recipients: ProfileType[];
|
|
323
|
+
cumulativeUpdate?: ConsentFlowContractDetails['latestUpdate'];
|
|
286
324
|
}[]
|
|
287
325
|
> => {
|
|
326
|
+
const decodedCursor = decodeConsentedDataCursor(cursor);
|
|
288
327
|
let query = new QueryBuilder(
|
|
289
|
-
new BindParam({
|
|
328
|
+
new BindParam({
|
|
329
|
+
params: flattenObject({ terms: matchQuery }),
|
|
330
|
+
cursorDate: decodedCursor?.updatedAt,
|
|
331
|
+
cursorId: decodedCursor?.id,
|
|
332
|
+
})
|
|
290
333
|
)
|
|
291
334
|
.match({
|
|
292
335
|
related: [
|
|
@@ -301,7 +344,11 @@ export const getConsentedContractsForProfile = async (
|
|
|
301
344
|
})
|
|
302
345
|
.where('all(key IN keys($params) WHERE terms[key] = $params[key])');
|
|
303
346
|
|
|
304
|
-
query =
|
|
347
|
+
query = decodedCursor
|
|
348
|
+
? query.raw(
|
|
349
|
+
"AND (coalesce(terms.updatedAt, terms.createdAt, '') > $cursorDate OR (coalesce(terms.updatedAt, terms.createdAt, '') = $cursorDate AND terms.id > $cursorId))"
|
|
350
|
+
)
|
|
351
|
+
: query;
|
|
305
352
|
|
|
306
353
|
query = query.with('contract, owner, terms').match({
|
|
307
354
|
optional: true,
|
|
@@ -330,18 +377,54 @@ export const getConsentedContractsForProfile = async (
|
|
|
330
377
|
],
|
|
331
378
|
})
|
|
332
379
|
.return('contract, owner, terms, boost, COLLECT(DISTINCT recipient) as recipients')
|
|
333
|
-
.orderBy(
|
|
380
|
+
.orderBy("coalesce(terms.updatedAt, terms.createdAt, '') ASC, terms.id ASC")
|
|
334
381
|
.limit(limit)
|
|
335
382
|
.run()
|
|
336
383
|
);
|
|
337
384
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
terms: inflateObject(result.terms),
|
|
343
|
-
autoBoosts: result.boost.map(boost => getBoostUri(boost.properties.id, domain)),
|
|
385
|
+
const parsedResults = results.map(result => ({
|
|
386
|
+
result,
|
|
387
|
+
details: DbContractValidator.parse(inflateObject(result.contract)),
|
|
388
|
+
terms: DbTermsValidator.parse(inflateObject(result.terms)),
|
|
344
389
|
}));
|
|
390
|
+
const updates = await getCumulativeContractUpdates(
|
|
391
|
+
parsedResults.flatMap(({ details, terms }, index) => {
|
|
392
|
+
const acceptedVersion = Number(
|
|
393
|
+
terms.lastReviewedVersion ?? terms.consentedVersion ?? 1
|
|
394
|
+
);
|
|
395
|
+
const currentVersion = Number(details.currentVersion ?? 1);
|
|
396
|
+
if (
|
|
397
|
+
!['live', 'needs_reconsent', 'suspended'].includes(terms.status) ||
|
|
398
|
+
acceptedVersion >= currentVersion
|
|
399
|
+
)
|
|
400
|
+
return [];
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
key: String(index),
|
|
404
|
+
contractId: details.id,
|
|
405
|
+
acceptedVersion,
|
|
406
|
+
status: terms.status,
|
|
407
|
+
reconsentDeadline: terms.reconsentDeadline,
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
})
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return parsedResults.map(({ result, details, terms }, index) => {
|
|
414
|
+
const cumulativeUpdate = updates.get(String(index));
|
|
415
|
+
return {
|
|
416
|
+
...result,
|
|
417
|
+
recipients: result.recipients.map(recipient => recipient.properties),
|
|
418
|
+
contract: {
|
|
419
|
+
...details,
|
|
420
|
+
version: Number(details.currentVersion ?? 1),
|
|
421
|
+
status: details.status ?? 'active',
|
|
422
|
+
},
|
|
423
|
+
terms,
|
|
424
|
+
...(cumulativeUpdate ? { cumulativeUpdate } : {}),
|
|
425
|
+
autoBoosts: result.boost.map(boost => getBoostUri(boost.properties.id, domain)),
|
|
426
|
+
};
|
|
427
|
+
});
|
|
345
428
|
};
|
|
346
429
|
|
|
347
430
|
const HOLDER_EXPORT_PAGE_SIZE = 100;
|
|
@@ -397,8 +480,11 @@ export const getHolderExportMetadataForProfile = async (
|
|
|
397
480
|
);
|
|
398
481
|
|
|
399
482
|
transactionCursor =
|
|
400
|
-
transactionResults.length > transactionLimit
|
|
401
|
-
?
|
|
483
|
+
transactionResults.length > transactionLimit && transactionPage.length
|
|
484
|
+
? encodeConsentedDataCursor(
|
|
485
|
+
transactionPage.at(-1)!.date,
|
|
486
|
+
transactionPage.at(-1)!.id
|
|
487
|
+
)
|
|
402
488
|
: undefined;
|
|
403
489
|
|
|
404
490
|
if (!transactionCursor) break;
|
|
@@ -411,11 +497,21 @@ export const getHolderExportMetadataForProfile = async (
|
|
|
411
497
|
);
|
|
412
498
|
}
|
|
413
499
|
|
|
500
|
+
const cumulativeUpdate = record.cumulativeUpdate;
|
|
414
501
|
consentRecords.push({
|
|
502
|
+
...(cumulativeUpdate ? { cumulativeUpdate } : {}),
|
|
415
503
|
termsUri: constructUri('terms', record.terms.id, domain),
|
|
416
504
|
status: record.terms.status,
|
|
505
|
+
consentedVersion: record.terms.consentedVersion!,
|
|
506
|
+
...(record.terms.reconsentDeadline
|
|
507
|
+
? { reconsentDeadline: record.terms.reconsentDeadline }
|
|
508
|
+
: {}),
|
|
509
|
+
...(record.terms.newOptionsUntil
|
|
510
|
+
? { newOptionsUntil: record.terms.newOptionsUntil }
|
|
511
|
+
: {}),
|
|
417
512
|
contract: {
|
|
418
513
|
contract: record.contract.contract,
|
|
514
|
+
version: Number(record.contract.currentVersion ?? 1),
|
|
419
515
|
name: record.contract.name,
|
|
420
516
|
subtitle: record.contract.subtitle,
|
|
421
517
|
description: record.contract.description,
|
|
@@ -446,7 +542,13 @@ export const getHolderExportMetadataForProfile = async (
|
|
|
446
542
|
});
|
|
447
543
|
}
|
|
448
544
|
|
|
449
|
-
cursor =
|
|
545
|
+
cursor =
|
|
546
|
+
records.length > pageLimit && page.length
|
|
547
|
+
? encodeConsentedDataCursor(
|
|
548
|
+
page.at(-1)!.terms.updatedAt ?? page.at(-1)!.terms.createdAt ?? '',
|
|
549
|
+
page.at(-1)!.terms.id
|
|
550
|
+
)
|
|
551
|
+
: undefined;
|
|
450
552
|
|
|
451
553
|
if (!cursor) break;
|
|
452
554
|
}
|
|
@@ -493,7 +595,7 @@ const projectConsentedData = (
|
|
|
493
595
|
query: ConsentFlowDataQuery,
|
|
494
596
|
now: number
|
|
495
597
|
): ConsentFlowContractData => ({
|
|
496
|
-
date: term.updatedAt
|
|
598
|
+
date: term.updatedAt ?? term.createdAt ?? '',
|
|
497
599
|
credentials: {
|
|
498
600
|
categories: mapValues(term.terms.read.credentials?.categories ?? {}, (category, name) =>
|
|
499
601
|
term.terms.read.credentials.sharing !== false &&
|
|
@@ -511,11 +613,49 @@ const projectConsentedData = (
|
|
|
511
613
|
* Do not cap scanned candidates: doing so would silently hide valid matches later in a page.
|
|
512
614
|
* Expiry and negative category filters still require the authoritative JS permission check.
|
|
513
615
|
*/
|
|
616
|
+
const CONSENT_DATA_CURSOR_PREFIX = 'v1.';
|
|
617
|
+
|
|
618
|
+
const decodeConsentedDataCursor = (
|
|
619
|
+
cursor?: string
|
|
620
|
+
): { updatedAt: string; id: string } | undefined => {
|
|
621
|
+
if (!cursor) return undefined;
|
|
622
|
+
try {
|
|
623
|
+
if (!cursor.startsWith(CONSENT_DATA_CURSOR_PREFIX)) throw new Error('Invalid cursor');
|
|
624
|
+
const encoded = cursor.slice(CONSENT_DATA_CURSOR_PREFIX.length);
|
|
625
|
+
const decoded = JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
626
|
+
if (
|
|
627
|
+
!Array.isArray(decoded) ||
|
|
628
|
+
decoded.length !== 2 ||
|
|
629
|
+
typeof decoded[0] !== 'string' ||
|
|
630
|
+
typeof decoded[1] !== 'string' ||
|
|
631
|
+
(!Number.isFinite(Date.parse(decoded[0])) && decoded[0] !== '') ||
|
|
632
|
+
!decoded[1] ||
|
|
633
|
+
Buffer.from(JSON.stringify(decoded), 'utf8').toString('base64url') !== encoded
|
|
634
|
+
)
|
|
635
|
+
throw new Error('Invalid cursor');
|
|
636
|
+
return { updatedAt: decoded[0], id: decoded[1] };
|
|
637
|
+
} catch {
|
|
638
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid consent cursor' });
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
export const encodeConsentedDataCursor = (updatedAt: string, id: string): string =>
|
|
643
|
+
`${CONSENT_DATA_CURSOR_PREFIX}${Buffer.from(JSON.stringify([updatedAt, id]), 'utf8').toString(
|
|
644
|
+
'base64url'
|
|
645
|
+
)}`;
|
|
646
|
+
|
|
647
|
+
type ConsentedDataPage = {
|
|
648
|
+
records: ConsentFlowContractData[];
|
|
649
|
+
hasMore: boolean;
|
|
650
|
+
nextCursor?: string;
|
|
651
|
+
};
|
|
652
|
+
|
|
514
653
|
const getPermittedContractData = async (
|
|
515
654
|
viewerProfileId: string,
|
|
516
655
|
{ query = {}, limit, cursor }: { query?: ConsentFlowDataQuery; limit: number; cursor?: string },
|
|
517
656
|
contractId?: string
|
|
518
|
-
): Promise<
|
|
657
|
+
): Promise<ConsentedDataPage> => {
|
|
658
|
+
const decodedCursor = decodeConsentedDataCursor(cursor);
|
|
519
659
|
const now = Date.now();
|
|
520
660
|
const records: ConsentFlowContractData[] = [];
|
|
521
661
|
const personalFilters = Object.entries(query.personal ?? {}).map(([name, required]) => ({
|
|
@@ -532,7 +672,12 @@ const getPermittedContractData = async (
|
|
|
532
672
|
let offset = 0;
|
|
533
673
|
while (records.length < limit) {
|
|
534
674
|
const dbQuery = new QueryBuilder(
|
|
535
|
-
new BindParam({
|
|
675
|
+
new BindParam({
|
|
676
|
+
cursorDate: decodedCursor?.updatedAt,
|
|
677
|
+
cursorId: decodedCursor?.id,
|
|
678
|
+
personalFilters,
|
|
679
|
+
sharedCategories,
|
|
680
|
+
})
|
|
536
681
|
)
|
|
537
682
|
.match({
|
|
538
683
|
related: [
|
|
@@ -548,8 +693,11 @@ const getPermittedContractData = async (
|
|
|
548
693
|
],
|
|
549
694
|
})
|
|
550
695
|
.where(
|
|
551
|
-
"(
|
|
552
|
-
(
|
|
696
|
+
"(coalesce(contract.status, 'active') <> 'retired') AND " +
|
|
697
|
+
"(terms.status IN ['live', 'needs_reconsent', 'suspended'] OR (terms.status = 'consumed' AND terms.oneTime = true))" +
|
|
698
|
+
(decodedCursor
|
|
699
|
+
? " AND (coalesce(terms.updatedAt, terms.createdAt, '') < $cursorDate OR (coalesce(terms.updatedAt, terms.createdAt, '') = $cursorDate AND terms.id > $cursorId))"
|
|
700
|
+
: '') +
|
|
553
701
|
' AND all(filter IN $personalFilters WHERE filter.required = (terms[filter.key] IS NOT NULL))' +
|
|
554
702
|
' AND (size($sharedCategories) = 0 OR coalesce(terms.`terms.read.credentials.sharing`, true) = true)' +
|
|
555
703
|
' AND all(category IN $sharedCategories WHERE terms[category.sharingKey] = true AND' +
|
|
@@ -561,41 +709,79 @@ const getPermittedContractData = async (
|
|
|
561
709
|
}>(
|
|
562
710
|
await dbQuery
|
|
563
711
|
.return('DISTINCT terms, contract')
|
|
564
|
-
.orderBy(
|
|
712
|
+
.orderBy("coalesce(terms.updatedAt, terms.createdAt, '') DESC, terms.id ASC")
|
|
565
713
|
.skip(offset)
|
|
566
714
|
.limit(batchSize)
|
|
567
715
|
.run()
|
|
568
716
|
);
|
|
569
|
-
|
|
717
|
+
const validResults = results.flatMap((result, index) => {
|
|
570
718
|
const term = DbTermsValidator.safeParse(inflateObject(result.terms));
|
|
571
719
|
const contract = DbContractValidator.safeParse(inflateObject(result.contract));
|
|
572
720
|
if (
|
|
573
721
|
!term.success ||
|
|
574
722
|
!contract.success ||
|
|
575
723
|
!isConsentExpiryActive(term.data.expiresAt, now) ||
|
|
576
|
-
!isConsentExpiryActive(contract.data.expiresAt, now) ||
|
|
577
724
|
!matchesConsentedDataQuery(term.data, query, now)
|
|
578
725
|
)
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
726
|
+
return [];
|
|
727
|
+
return [{ term: term.data, contract: contract.data, index }];
|
|
728
|
+
});
|
|
729
|
+
const acceptedVersions = await getConsentFlowContractVersionRecords(
|
|
730
|
+
validResults.map(({ term, contract }) => ({
|
|
731
|
+
contractId: contract.id,
|
|
732
|
+
version: Number(term.consentedVersion ?? 1),
|
|
733
|
+
}))
|
|
734
|
+
);
|
|
735
|
+
const validByIndex = new Map(validResults.map(result => [result.index, result]));
|
|
736
|
+
for (let index = 0; index < results.length; index++) {
|
|
737
|
+
const raw = results[index]!;
|
|
738
|
+
const scanned = inflateObject(raw.terms) as FlatDbTermsType;
|
|
739
|
+
const candidate = validByIndex.get(index);
|
|
740
|
+
if (candidate) {
|
|
741
|
+
const { term, contract } = candidate;
|
|
742
|
+
const acceptedVersion = acceptedVersions.get(
|
|
743
|
+
`${contract.id}:${Number(term.consentedVersion ?? 1)}`
|
|
744
|
+
);
|
|
745
|
+
if (
|
|
746
|
+
acceptedVersion &&
|
|
747
|
+
isConsentExpiryActive(acceptedVersion.expiresAt, now) &&
|
|
748
|
+
areTermsValid(term.terms, acceptedVersion.contract)
|
|
749
|
+
)
|
|
750
|
+
records.push(projectConsentedData(term, query, now));
|
|
751
|
+
}
|
|
752
|
+
if (records.length >= limit) {
|
|
753
|
+
const moreInBatch = index + 1 < results.length;
|
|
754
|
+
const hasMore = moreInBatch || results.length === batchSize;
|
|
755
|
+
return {
|
|
756
|
+
records,
|
|
757
|
+
hasMore,
|
|
758
|
+
...(records.length
|
|
759
|
+
? {
|
|
760
|
+
nextCursor: encodeConsentedDataCursor(
|
|
761
|
+
scanned.updatedAt ?? scanned.createdAt ?? '',
|
|
762
|
+
scanned.id
|
|
763
|
+
),
|
|
764
|
+
}
|
|
765
|
+
: {}),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
582
768
|
}
|
|
583
769
|
if (results.length < batchSize) break;
|
|
584
770
|
offset += batchSize;
|
|
585
771
|
}
|
|
586
|
-
return records;
|
|
772
|
+
return { records, hasMore: false };
|
|
587
773
|
};
|
|
588
774
|
|
|
589
775
|
export const getConsentedDataForProfile = async (
|
|
590
776
|
profileId: string,
|
|
591
777
|
options: { query?: ConsentFlowDataQuery; limit: number; cursor?: string }
|
|
592
|
-
): Promise<
|
|
778
|
+
): Promise<ConsentedDataPage> => getPermittedContractData(profileId, options);
|
|
593
779
|
|
|
594
780
|
export const getConsentedDataForContract = async (
|
|
595
781
|
id: string,
|
|
596
782
|
viewerProfileId: string,
|
|
597
783
|
options: { query?: ConsentFlowDataQuery; limit: number; cursor?: string }
|
|
598
|
-
): Promise<
|
|
784
|
+
): Promise<ConsentedDataPage> => getPermittedContractData(viewerProfileId, options, id);
|
|
599
785
|
|
|
600
786
|
// Empty expiry values historically mean no expiry. Invalid nonempty values never authorize access.
|
|
601
787
|
const isConsentExpiryActive = (expiresAt: string | undefined, now: number): boolean => {
|
|
@@ -604,6 +790,12 @@ const isConsentExpiryActive = (expiresAt: string | undefined, now: number): bool
|
|
|
604
790
|
return Number.isFinite(expiry) && expiry > now;
|
|
605
791
|
};
|
|
606
792
|
|
|
793
|
+
export type ConsentedDataForDidPage = {
|
|
794
|
+
records: ConsentFlowContractDataForDid[];
|
|
795
|
+
hasMore: boolean;
|
|
796
|
+
nextCursor?: string;
|
|
797
|
+
};
|
|
798
|
+
|
|
607
799
|
export const getConsentedDataBetweenProfiles = async (
|
|
608
800
|
ownerProfileId: string,
|
|
609
801
|
consenterProfileId: string,
|
|
@@ -613,7 +805,8 @@ export const getConsentedDataBetweenProfiles = async (
|
|
|
613
805
|
cursor,
|
|
614
806
|
domain,
|
|
615
807
|
}: { query?: ConsentFlowDataForDidQuery; limit: number; cursor?: string; domain: string }
|
|
616
|
-
): Promise<
|
|
808
|
+
): Promise<ConsentedDataForDidPage> => {
|
|
809
|
+
const decodedCursor = decodeConsentedDataCursor(cursor);
|
|
617
810
|
const { id, ...params } = query;
|
|
618
811
|
const convertedContractQuery = id ? convertObjectRegExpToNeo4j({ id }) : {};
|
|
619
812
|
const { whereClause: contractWhereClause, params: contractQueryParams } =
|
|
@@ -639,7 +832,8 @@ export const getConsentedDataBetweenProfiles = async (
|
|
|
639
832
|
while (records.length < limit) {
|
|
640
833
|
const _dbQuery = new QueryBuilder(
|
|
641
834
|
new BindParam({
|
|
642
|
-
|
|
835
|
+
cursorDate: decodedCursor?.updatedAt,
|
|
836
|
+
cursorId: decodedCursor?.id,
|
|
643
837
|
...contractQueryParams,
|
|
644
838
|
})
|
|
645
839
|
).match({
|
|
@@ -652,12 +846,9 @@ export const getConsentedDataBetweenProfiles = async (
|
|
|
652
846
|
'-[:CREATED_BY|SHARES_DATA_WITH]->',
|
|
653
847
|
{ model: Profile, where: { profileId: ownerProfileId } },
|
|
654
848
|
],
|
|
655
|
-
// The following is custom Cypher logic that has the following behavior:
|
|
656
|
-
// If query key is true, ensure all resulting terms have that key
|
|
657
|
-
// If query key is false, ensure no resulting terms have that key
|
|
658
|
-
// If query key is not present, return all terms whether they have it or not
|
|
659
849
|
}).where(`
|
|
660
|
-
(
|
|
850
|
+
(coalesce(contract.status, 'active') <> 'retired') AND
|
|
851
|
+
(terms.status IN ['live', 'needs_reconsent', 'suspended'] OR (terms.status = 'consumed' AND terms.oneTime = true))
|
|
661
852
|
AND ${contractWhereClause}
|
|
662
853
|
`);
|
|
663
854
|
|
|
@@ -670,10 +861,11 @@ AND ${contractWhereClause}
|
|
|
670
861
|
],
|
|
671
862
|
}).with(`terms, contract,
|
|
672
863
|
coalesce(terms.createdAt, min(CASE WHEN transaction.action = 'consent' THEN transaction.date END)) AS createdAt,
|
|
673
|
-
coalesce(terms.updatedAt, max(transaction.date), terms.createdAt) AS date,
|
|
864
|
+
coalesce(terms.updatedAt, max(transaction.date), terms.createdAt, '') AS date,
|
|
674
865
|
count(transaction.\`guardianApproval.guardianProfileId\`) > 0 AS hadGuardianApproval`);
|
|
675
866
|
|
|
676
|
-
if (
|
|
867
|
+
if (decodedCursor)
|
|
868
|
+
dbQuery.where('(date < $cursorDate OR (date = $cursorDate AND terms.id > $cursorId))');
|
|
677
869
|
|
|
678
870
|
const results = convertQueryResultToPropertiesObjectArray<{
|
|
679
871
|
terms: FlatDbTermsType;
|
|
@@ -690,24 +882,39 @@ AND ${contractWhereClause}
|
|
|
690
882
|
.run()
|
|
691
883
|
);
|
|
692
884
|
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
if (!
|
|
885
|
+
const validResults = results.flatMap(result => {
|
|
886
|
+
const term = DbTermsValidator.safeParse(inflateObject(result.terms));
|
|
887
|
+
const contract = DbContractValidator.safeParse(inflateObject(result.contract));
|
|
888
|
+
if (!term.success || !contract.success) {
|
|
697
889
|
// Never log stored consent data, identifiers, or raw validation errors.
|
|
698
890
|
console.warn('consented_data: invalid_stored_grant');
|
|
699
|
-
|
|
891
|
+
return [];
|
|
700
892
|
}
|
|
701
|
-
const term = parsedTerm.data;
|
|
702
|
-
const contract = parsedContract.data;
|
|
703
893
|
if (
|
|
704
|
-
!isConsentExpiryActive(term.expiresAt, now) ||
|
|
705
|
-
!
|
|
894
|
+
!isConsentExpiryActive(term.data.expiresAt, now) ||
|
|
895
|
+
!matchesConsentedDataQuery(term.data, params, now)
|
|
896
|
+
)
|
|
897
|
+
return [];
|
|
898
|
+
return [{ result, term: term.data, contract: contract.data }];
|
|
899
|
+
});
|
|
900
|
+
const acceptedVersions = await getConsentFlowContractVersionRecords(
|
|
901
|
+
validResults.map(({ term, contract }) => ({
|
|
902
|
+
contractId: contract.id,
|
|
903
|
+
version: Number(term.consentedVersion ?? 1),
|
|
904
|
+
}))
|
|
905
|
+
);
|
|
906
|
+
for (let resultIndex = 0; resultIndex < validResults.length; resultIndex++) {
|
|
907
|
+
const { result, term, contract } = validResults[resultIndex]!;
|
|
908
|
+
const acceptedVersion = acceptedVersions.get(
|
|
909
|
+
`${contract.id}:${Number(term.consentedVersion ?? 1)}`
|
|
910
|
+
);
|
|
911
|
+
if (
|
|
912
|
+
!acceptedVersion ||
|
|
913
|
+
!isConsentExpiryActive(acceptedVersion.expiresAt, now) ||
|
|
914
|
+
!areTermsValid(term.terms, acceptedVersion.contract)
|
|
706
915
|
)
|
|
707
916
|
continue;
|
|
708
917
|
|
|
709
|
-
if (!matchesConsentedDataQuery(term, params, now)) continue;
|
|
710
|
-
|
|
711
918
|
const parsedApproval = ConsentFlowGuardianApprovalValidator.safeParse(
|
|
712
919
|
term.guardianApproval
|
|
713
920
|
);
|
|
@@ -716,7 +923,7 @@ AND ${contractWhereClause}
|
|
|
716
923
|
requiresManagerApproval || !!term.guardianApproval || result.hadGuardianApproval;
|
|
717
924
|
const approved =
|
|
718
925
|
!!approval &&
|
|
719
|
-
approval.contractUpdatedAt ===
|
|
926
|
+
approval.contractUpdatedAt === acceptedVersion.createdAt &&
|
|
720
927
|
managers.some(
|
|
721
928
|
manager =>
|
|
722
929
|
manager.profileId === approval.guardianProfileId &&
|
|
@@ -728,10 +935,12 @@ AND ${contractWhereClause}
|
|
|
728
935
|
records.push({
|
|
729
936
|
date: result.date,
|
|
730
937
|
...(result.createdAt ? { createdAt: result.createdAt } : {}),
|
|
731
|
-
contractUpdatedAt:
|
|
732
|
-
...(
|
|
733
|
-
|
|
734
|
-
|
|
938
|
+
contractUpdatedAt: acceptedVersion.createdAt,
|
|
939
|
+
...(acceptedVersion.expiresAt?.trim()
|
|
940
|
+
? { contractExpiresAt: acceptedVersion.expiresAt }
|
|
941
|
+
: {}),
|
|
942
|
+
...(acceptedVersion.reasonForAccessing !== undefined
|
|
943
|
+
? { reasonForAccessing: acceptedVersion.reasonForAccessing }
|
|
735
944
|
: {}),
|
|
736
945
|
guardian: { required, approved, ...(approval ? { approval } : {}) },
|
|
737
946
|
contractUri: constructUri('contract', contract.id, domain),
|
|
@@ -769,13 +978,23 @@ AND ${contractWhereClause}
|
|
|
769
978
|
),
|
|
770
979
|
personal: term.terms.read.personal,
|
|
771
980
|
});
|
|
772
|
-
if (records.length >= limit)
|
|
981
|
+
if (records.length >= limit) {
|
|
982
|
+
const hasMore =
|
|
983
|
+
resultIndex + 1 < validResults.length || results.length === batchSize;
|
|
984
|
+
return {
|
|
985
|
+
records,
|
|
986
|
+
hasMore,
|
|
987
|
+
...(records.length
|
|
988
|
+
? { nextCursor: encodeConsentedDataCursor(result.date, term.id) }
|
|
989
|
+
: {}),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
773
992
|
}
|
|
774
993
|
if (results.length < batchSize) break;
|
|
775
994
|
offset += batchSize;
|
|
776
995
|
}
|
|
777
996
|
|
|
778
|
-
return records;
|
|
997
|
+
return { records, hasMore: false };
|
|
779
998
|
};
|
|
780
999
|
|
|
781
1000
|
export const getTransactionsForTerms = async (
|
|
@@ -792,7 +1011,14 @@ export const getTransactionsForTerms = async (
|
|
|
792
1011
|
convertedQuery as any
|
|
793
1012
|
);
|
|
794
1013
|
|
|
795
|
-
const
|
|
1014
|
+
const decodedCursor = decodeConsentedDataCursor(cursor);
|
|
1015
|
+
const query = new QueryBuilder(
|
|
1016
|
+
new BindParam({
|
|
1017
|
+
cursorDate: decodedCursor?.updatedAt,
|
|
1018
|
+
cursorId: decodedCursor?.id,
|
|
1019
|
+
...transactionQueryParams,
|
|
1020
|
+
})
|
|
1021
|
+
)
|
|
796
1022
|
.match({
|
|
797
1023
|
related: [
|
|
798
1024
|
{ identifier: 'transaction', model: ConsentFlowTransaction },
|
|
@@ -800,7 +1026,13 @@ export const getTransactionsForTerms = async (
|
|
|
800
1026
|
{ model: ConsentFlowTerms, where: { id: termsId } },
|
|
801
1027
|
],
|
|
802
1028
|
})
|
|
803
|
-
.where(
|
|
1029
|
+
.where(
|
|
1030
|
+
`${whereClause}${
|
|
1031
|
+
decodedCursor
|
|
1032
|
+
? ' AND (transaction.date < $cursorDate OR (transaction.date = $cursorDate AND transaction.id < $cursorId))'
|
|
1033
|
+
: ''
|
|
1034
|
+
}`
|
|
1035
|
+
)
|
|
804
1036
|
.with('transaction')
|
|
805
1037
|
.match({
|
|
806
1038
|
optional: true,
|
|
@@ -818,7 +1050,7 @@ export const getTransactionsForTerms = async (
|
|
|
818
1050
|
}>(
|
|
819
1051
|
await query
|
|
820
1052
|
.return('DISTINCT transaction, [node in credentials | properties(node)] AS credentials')
|
|
821
|
-
.orderBy('transaction.date DESC')
|
|
1053
|
+
.orderBy('transaction.date DESC, transaction.id DESC')
|
|
822
1054
|
.limit(limit)
|
|
823
1055
|
.run()
|
|
824
1056
|
);
|
|
@@ -80,7 +80,7 @@ export const publishSmartResume = async ({
|
|
|
80
80
|
WHERE request.requestId = $expectedRequestId AND request.status = 'accepted'
|
|
81
81
|
})
|
|
82
82
|
AND terms.smartResumeMutationVersion = coalesce(terms.mutationVersion, 0)
|
|
83
|
-
AND (terms.status = 'live' OR (terms.status = '
|
|
83
|
+
AND (terms.status = 'live' OR (terms.status = 'consumed' AND terms.oneTime = true))
|
|
84
84
|
AND CASE WHEN terms.expiresAt IS NULL OR trim(terms.expiresAt) = '' THEN true ELSE datetime(terms.expiresAt) > datetime.realtime() END
|
|
85
85
|
AND CASE WHEN contract.expiresAt IS NULL OR trim(contract.expiresAt) = '' THEN true ELSE datetime(contract.expiresAt) > datetime.realtime() END
|
|
86
86
|
AND (terms.smartResumePublicationStatus IN ['pending', 'failed', 'succeeded']
|