@learncard/network-brain-service 3.19.0 → 4.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 +47 -82
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +147 -0
- package/README.md +51 -2
- package/bootstrapLambda.test.ts +72 -0
- package/config/config.dev.json +29 -0
- package/config/config.example.json +29 -0
- package/config/config.json +1 -0
- package/config/config.production.json +29 -0
- package/config/config.scouts.dev.json +24 -0
- package/config/config.scouts.production.json +24 -0
- package/contractEventsLambda.ts +18 -0
- package/contractEventsLambdaApp.ts +19 -0
- package/didWebLambda.ts +13 -18
- package/didWebLambdaApp.ts +22 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
- package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
- package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
- package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
- package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.d.ts +2 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/accesslayer/presentation/read.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
- package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts +3 -0
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
- package/dist/accesslayer/share-link/attachment.d.ts +37 -0
- package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
- package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
- package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
- package/dist/accesslayer/share-link/errors.d.ts +8 -0
- package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts +3 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
- package/dist/accesslayer/share-link/helpers.d.ts +59 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
- package/dist/accesslayer/share-link/index.d.ts +12 -0
- package/dist/accesslayer/share-link/index.d.ts.map +1 -0
- package/dist/accesslayer/share-link/list.d.ts +5 -0
- package/dist/accesslayer/share-link/list.d.ts.map +1 -0
- package/dist/accesslayer/share-link/read.d.ts +5 -0
- package/dist/accesslayer/share-link/read.d.ts.map +1 -0
- package/dist/accesslayer/share-link/receipt.d.ts +11 -0
- package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
- package/dist/accesslayer/share-link/recovery.d.ts +6 -0
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
- package/dist/accesslayer/share-link/reserve.d.ts +4 -0
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
- package/dist/accesslayer/share-link/revoke.d.ts +3 -0
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction.d.ts +15 -0
- package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
- package/dist/accesslayer/share-link/types.d.ts +375 -0
- package/dist/accesslayer/share-link/types.d.ts.map +1 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +49 -0
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/config/applyDockerStageConfig.d.ts +2 -0
- package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
- package/dist/config/environment.d.ts +14 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
- package/dist/config/stageConfig.d.ts +112 -0
- package/dist/config/stageConfig.d.ts.map +1 -0
- package/dist/config/stageConfig.test.d.ts +2 -0
- package/dist/config/stageConfig.test.d.ts.map +1 -0
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/docker-entry.d.ts.map +1 -1
- package/dist/helpers/app-manifest.helpers.d.ts +5 -0
- package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
- package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
- package/dist/helpers/consent-referral.helpers.d.ts +2 -0
- package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
- package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
- package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts +25 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-requests.helpers.d.ts +19 -0
- package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
- package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
- package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inline-template.helpers.d.ts +45 -0
- package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts +6 -0
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/presentation-metadata.d.ts +4 -0
- package/dist/helpers/presentation-metadata.d.ts.map +1 -0
- package/dist/helpers/presentation.helpers.d.ts +1 -1
- package/dist/helpers/presentation.helpers.d.ts.map +1 -1
- package/dist/helpers/profile.helpers.d.ts +2 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/share-content-client/adapters.d.ts +10 -0
- package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-client/canonical.d.ts +7 -0
- package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-client/client.d.ts +5 -0
- package/dist/helpers/share-content-client/client.d.ts.map +1 -0
- package/dist/helpers/share-content-client/config.d.ts +11 -0
- package/dist/helpers/share-content-client/config.d.ts.map +1 -0
- package/dist/helpers/share-content-client/index.d.ts +6 -0
- package/dist/helpers/share-content-client/index.d.ts.map +1 -0
- package/dist/helpers/share-content-client/responses.d.ts +16 -0
- package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
- package/dist/helpers/share-content-client/types.d.ts +145 -0
- package/dist/helpers/share-content-client/types.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment-access.d.ts +4 -0
- package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment.d.ts +18 -0
- package/dist/helpers/share-link-attachment.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
- package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
- package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
- package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts +20 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +38 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/types.d.ts +56 -0
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/config.d.ts +16 -0
- package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
- package/dist/helpers/share-link-owner-projection.d.ts +4 -0
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode.d.ts +6 -0
- package/dist/helpers/share-link-passcode.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/index.d.ts +4 -0
- package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/production.d.ts +11 -0
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/types.d.ts +22 -0
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
- package/dist/helpers/share-link-public/config.d.ts +13 -0
- package/dist/helpers/share-link-public/config.d.ts.map +1 -0
- package/dist/helpers/share-link-view-notification.d.ts +2 -0
- package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/smartResume.helpers.d.ts +3 -0
- package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AppManifestVersion.d.ts +20 -0
- package/dist/models/AppManifestVersion.d.ts.map +1 -0
- package/dist/models/AppStoreListing.d.ts +13 -0
- package/dist/models/AppStoreListing.d.ts.map +1 -1
- package/dist/models/ConsentFlowContract.d.ts +13 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +2 -0
- package/dist/models/Integration.d.ts.map +1 -1
- package/dist/models/Presentation.d.ts +2 -0
- package/dist/models/Presentation.d.ts.map +1 -1
- package/dist/models/Profile.d.ts +2 -0
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +46 -0
- package/dist/models/ShareLink.d.ts.map +1 -0
- package/dist/models/ShareViewReceipt.d.ts +22 -0
- package/dist/models/ShareViewReceipt.d.ts.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts +3 -0
- package/dist/models/share-link-constraints.d.ts.map +1 -0
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/public-share-link-http.d.ts +4 -0
- package/dist/public-share-link-http.d.ts.map +1 -0
- package/dist/routes/app-store.d.ts +619 -0
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +2 -2
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +148 -5
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +35 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +52 -2
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +153 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +1 -0
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +285 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +1005 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/routes/test.d.ts +5 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/types/app-manifest.d.ts +339 -0
- package/dist/types/app-manifest.d.ts.map +1 -0
- package/dist/types/app-store-listing.d.ts +34 -0
- package/dist/types/app-store-listing.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +31 -4
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +1 -1
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +49 -192
- package/lambdaApp.ts +203 -0
- package/package.json +25 -17
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -0
- package/scripts/seed-demo-persona.ts +425 -0
- package/scripts/verify-python-contracts.ts +72 -0
- package/serverless-local.yml +3 -6
- package/serverless.function-env.cjs +50 -0
- package/serverless.local.yml +0 -1
- package/serverless.yml +44 -55
- package/shareLinkMaintenanceLambda.ts +18 -0
- package/shareLinkMaintenanceLambdaApp.ts +50 -0
- package/src/accesslayer/app-manifest-version/create.ts +57 -0
- package/src/accesslayer/app-manifest-version/read.ts +129 -0
- package/src/accesslayer/app-manifest-version/update.ts +61 -0
- package/src/accesslayer/app-store-listing/read.ts +6 -0
- package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
- package/src/accesslayer/app-store-listing/update.ts +15 -0
- package/src/accesslayer/boost/relationships/create.ts +8 -2
- package/src/accesslayer/consentflowcontract/create.ts +81 -8
- package/src/accesslayer/consentflowcontract/read.ts +84 -10
- package/src/accesslayer/consentflowcontract/relationships/create.ts +125 -109
- package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
- package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
- package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/update.ts +255 -217
- package/src/accesslayer/credential/create.test.ts +2 -115
- package/src/accesslayer/credential/create.ts +11 -6
- package/src/accesslayer/credential/relationships/create.ts +17 -10
- package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
- package/src/accesslayer/inbox-credential/update.ts +4 -2
- package/src/accesslayer/presentation/read.ts +57 -25
- package/src/accesslayer/presentation/relationships/create.ts +54 -11
- package/src/accesslayer/presentation/relationships/read.ts +21 -6
- package/src/accesslayer/share-link/abandon.ts +120 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/cleanup.ts +271 -0
- package/src/accesslayer/share-link/errors.ts +32 -0
- package/src/accesslayer/share-link/finalize.ts +313 -0
- package/src/accesslayer/share-link/helpers.ts +450 -0
- package/src/accesslayer/share-link/index.ts +11 -0
- package/src/accesslayer/share-link/list.ts +162 -0
- package/src/accesslayer/share-link/read.ts +107 -0
- package/src/accesslayer/share-link/receipt.ts +548 -0
- package/src/accesslayer/share-link/recovery.ts +565 -0
- package/src/accesslayer/share-link/reserve.ts +763 -0
- package/src/accesslayer/share-link/revoke.ts +232 -0
- package/src/accesslayer/share-link/transaction-execution.ts +72 -0
- package/src/accesslayer/share-link/transaction.ts +126 -0
- package/src/accesslayer/share-link/types.ts +606 -0
- package/src/app.ts +6 -0
- package/src/cache/app-store.caches.ts +35 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/environment.test.ts +17 -12
- package/src/config/environment.ts +15 -0
- package/src/config/serverlessFunctionEnv.test.ts +105 -0
- package/src/config/stageConfig.test.ts +25 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/docker-entry.ts +81 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/claim-hooks.helpers.ts +20 -7
- package/src/helpers/consent-referral.helpers.ts +6 -0
- package/src/helpers/consent-scopes.helpers.ts +58 -0
- package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
- package/src/helpers/contract-events.helpers.ts +540 -0
- package/src/helpers/contract-requests.helpers.ts +211 -0
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +58 -70
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +77 -1
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +13 -0
- package/src/helpers/share-content-client/adapters.ts +55 -0
- package/src/helpers/share-content-client/canonical.ts +25 -0
- package/src/helpers/share-content-client/client.ts +440 -0
- package/src/helpers/share-content-client/config.ts +189 -0
- package/src/helpers/share-content-client/index.ts +27 -0
- package/src/helpers/share-content-client/responses.ts +273 -0
- package/src/helpers/share-content-client/types.ts +253 -0
- package/src/helpers/share-link-attachment-access.ts +87 -0
- package/src/helpers/share-link-attachment.ts +115 -0
- package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
- package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
- package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +727 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +443 -0
- package/src/helpers/share-link-coordinator/runtime.ts +88 -0
- package/src/helpers/share-link-coordinator/stat.ts +30 -0
- package/src/helpers/share-link-coordinator/types.ts +353 -0
- package/src/helpers/share-link-lifecycle/index.ts +4 -0
- package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
- package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
- package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
- package/src/helpers/share-link-lifecycle/types.ts +58 -0
- package/src/helpers/share-link-maintenance/budget.ts +63 -0
- package/src/helpers/share-link-maintenance/config.ts +312 -0
- package/src/helpers/share-link-maintenance/index.ts +24 -0
- package/src/helpers/share-link-maintenance/runner.ts +251 -0
- package/src/helpers/share-link-maintenance/runtime.ts +340 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +73 -0
- package/src/helpers/share-link-maintenance/types.ts +91 -0
- package/src/helpers/share-link-owner/config.ts +48 -0
- package/src/helpers/share-link-owner/diagnostics.ts +89 -0
- package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
- package/src/helpers/share-link-owner-projection.ts +54 -0
- package/src/helpers/share-link-passcode-abuse.ts +36 -0
- package/src/helpers/share-link-passcode.ts +47 -0
- package/src/helpers/share-link-policy/index.ts +7 -0
- package/src/helpers/share-link-policy/production.ts +108 -0
- package/src/helpers/share-link-policy/resolver.ts +113 -0
- package/src/helpers/share-link-policy/types.ts +56 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/helpers/signingAuthority.helpers.ts +23 -14
- package/src/helpers/smartResume.helpers.ts +123 -0
- package/src/models/AppManifestVersion.ts +43 -0
- package/src/models/AppStoreListing.ts +32 -0
- package/src/models/ConsentFlowContract.ts +34 -3
- package/src/models/ConsentFlowTerms.ts +10 -0
- package/src/models/ConsentFlowTransaction.ts +3 -0
- package/src/models/Integration.ts +2 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +134 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/index.ts +25 -0
- package/src/models/share-link-constraints.ts +98 -0
- package/src/openapi.ts +44 -8
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/app-store.ts +1056 -38
- package/src/routes/boosts.ts +14 -25
- package/src/routes/contracts.ts +384 -273
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +142 -44
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +29 -2
- package/src/routes/profiles.ts +29 -6
- package/src/routes/public-share-links.ts +983 -0
- package/src/routes/share-links.ts +709 -0
- package/src/routes/workflows.ts +3 -2
- package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
- package/src/types/app-manifest.ts +28 -0
- package/src/types/app-store-listing.ts +13 -0
- package/src/types/consentflowcontract.ts +25 -4
- package/test/app-manifest.helpers.spec.ts +170 -0
- package/test/app-manifest.routes.spec.ts +299 -0
- package/test/app-store-listing-updates.spec.ts +236 -0
- package/test/app-store-listing.spec.ts +65 -3
- package/test/consent-data-boundaries.spec.ts +352 -0
- package/test/consent-recipients.spec.ts +513 -0
- package/test/consent-scopes.helpers.spec.ts +67 -0
- package/test/consentflow.spec.ts +63 -2
- package/test/contract-requests.spec.ts +1588 -0
- package/test/credential-storage.neo4j.spec.ts +122 -0
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +49 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/inbox-security.e2e.ts +26 -10
- package/test/inbox.spec.ts +184 -8
- package/test/inline-template.helpers.spec.ts +49 -0
- package/test/managed-recipient-inbox.spec.ts +152 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +14 -53
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +97 -1
- package/test/share-content-client.contract.spec.ts +365 -0
- package/test/share-content-client.unit.spec.ts +462 -0
- package/test/share-link-abuse.unit.spec.ts +65 -0
- package/test/share-link-attachment.neo4j.spec.ts +262 -0
- package/test/share-link-attachment.unit.spec.ts +244 -0
- package/test/share-link-coordinator.neo4j.spec.ts +684 -0
- package/test/share-link-coordinator.unit.spec.ts +990 -0
- package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +1411 -0
- package/test/share-link-lifecycle.unit.spec.ts +205 -0
- package/test/share-link-list-cursor.unit.spec.ts +86 -0
- package/test/share-link-list.neo4j.spec.ts +324 -0
- package/test/share-link-maintenance.neo4j.spec.ts +355 -0
- package/test/share-link-maintenance.unit.spec.ts +1140 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +820 -0
- package/test/share-link-owner-runtime.unit.spec.ts +102 -0
- package/test/share-link-passcode.unit.spec.ts +29 -0
- package/test/share-link-policy.unit.spec.ts +149 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +126 -0
- package/test/share-link-public-config.unit.spec.ts +64 -0
- package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
- package/test/share-link-public-route.unit.spec.ts +1079 -0
- package/test/share-link-receipt.neo4j.spec.ts +943 -0
- package/test/share-link-recovery.neo4j.spec.ts +935 -0
- package/test/share-link-recovery.unit.spec.ts +726 -0
- package/test/share-link-service-policy.neo4j.spec.ts +403 -0
- package/test/signing-authority-encryption.spec.ts +75 -0
- package/test/smart-resume-consent.spec.ts +233 -0
- package/test/smart-resume-upload.spec.ts +65 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +14 -0
- package/vitest.inbox-e2e.config.ts +2 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
- package/workerLambda.test.ts +100 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { BindParam, QueryBuilder } from 'neogma';
|
|
3
|
+
import { TRPCError } from '@trpc/server';
|
|
4
|
+
import type { SendContractRequest } from '@learncard/types';
|
|
5
|
+
import {
|
|
6
|
+
getContractByUri,
|
|
7
|
+
getContractRequestAccess,
|
|
8
|
+
getStoredContractRequest,
|
|
9
|
+
} from '@accesslayer/consentflowcontract/read';
|
|
10
|
+
import { getProfileByProfileId } from '@accesslayer/profile/read';
|
|
11
|
+
import { getWritersForContract } from '@accesslayer/consentflowcontract/relationships/read';
|
|
12
|
+
import {
|
|
13
|
+
canReadContractData,
|
|
14
|
+
lockContractAudience,
|
|
15
|
+
runAudienceMutation,
|
|
16
|
+
} from '@accesslayer/consentflowcontract/relationships/recipients';
|
|
17
|
+
import { getProfileIdFromString } from './did.helpers';
|
|
18
|
+
import { userHasRequiredScopes } from './auth-grant.helpers';
|
|
19
|
+
import { enforceRateLimits } from './rateLimit.helpers';
|
|
20
|
+
import { requestEventCypher, tryDispatchContractEvent } from './contract-events.helpers';
|
|
21
|
+
import type { ProfileType } from 'types/profile';
|
|
22
|
+
import type { DbContractType } from 'types/consentflowcontract';
|
|
23
|
+
|
|
24
|
+
/** Resolve shared request inputs without granting writers access to consented data. */
|
|
25
|
+
export const resolveContractRequest = async (
|
|
26
|
+
contractUri: string,
|
|
27
|
+
targetIdentifier: string,
|
|
28
|
+
profile: ProfileType,
|
|
29
|
+
domain: string,
|
|
30
|
+
allowRecipients = true
|
|
31
|
+
): Promise<{ contract: DbContractType; target: ProfileType }> => {
|
|
32
|
+
const contract = await getContractByUri(contractUri);
|
|
33
|
+
if (!contract) throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
34
|
+
if (!(await canManageContractRequests(contract, profile.profileId, allowRecipients)))
|
|
35
|
+
throw new TRPCError({
|
|
36
|
+
code: 'UNAUTHORIZED',
|
|
37
|
+
message: 'You do not have permission to send requests for this contract.',
|
|
38
|
+
});
|
|
39
|
+
const targetId = await getProfileIdFromString(targetIdentifier, domain);
|
|
40
|
+
const target = targetId ? await getProfileByProfileId(targetId) : null;
|
|
41
|
+
if (!target) throw new TRPCError({ code: 'NOT_FOUND', message: 'Target profile not found' });
|
|
42
|
+
return { contract, target };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const canManageContractRequests = async (
|
|
46
|
+
contract: DbContractType,
|
|
47
|
+
profileId: string,
|
|
48
|
+
allowRecipients = false
|
|
49
|
+
): Promise<boolean> => {
|
|
50
|
+
const access = await getContractRequestAccess(contract.id, profileId);
|
|
51
|
+
return access.isManager || (allowRecipients && access.isRecipient);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** One request per contract/target in v1. Exact pending retries do not create another event. */
|
|
55
|
+
export const sendGenericContractRequest = async (
|
|
56
|
+
input: SendContractRequest,
|
|
57
|
+
profile: ProfileType,
|
|
58
|
+
domain: string
|
|
59
|
+
): Promise<boolean> => {
|
|
60
|
+
const { contract, target } = await resolveContractRequest(
|
|
61
|
+
input.contractUri,
|
|
62
|
+
input.targetProfileId,
|
|
63
|
+
profile,
|
|
64
|
+
domain
|
|
65
|
+
);
|
|
66
|
+
if (target.profileId === profile.profileId)
|
|
67
|
+
throw new TRPCError({
|
|
68
|
+
code: 'BAD_REQUEST',
|
|
69
|
+
message: 'You cannot send a request to yourself.',
|
|
70
|
+
});
|
|
71
|
+
await enforceRateLimits([
|
|
72
|
+
{
|
|
73
|
+
key: `contract-request-rate:${contract.id}:${profile.profileId}`,
|
|
74
|
+
limit: 500,
|
|
75
|
+
windowSeconds: 3600,
|
|
76
|
+
description: '500 contract requests per hour',
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
const requestId = randomUUID();
|
|
80
|
+
const query = lockContractAudience(
|
|
81
|
+
new QueryBuilder(
|
|
82
|
+
new BindParam({
|
|
83
|
+
targetProfileId: target.profileId,
|
|
84
|
+
senderProfileId: profile.profileId,
|
|
85
|
+
requestId,
|
|
86
|
+
externalReferenceId: input.externalReferenceId ?? null,
|
|
87
|
+
message: input.message ?? null,
|
|
88
|
+
now: new Date().toISOString(),
|
|
89
|
+
domain,
|
|
90
|
+
eventFrom: profile.profileId,
|
|
91
|
+
eventKind: 'request_sent',
|
|
92
|
+
})
|
|
93
|
+
),
|
|
94
|
+
contract.id
|
|
95
|
+
).raw(`
|
|
96
|
+
MATCH (target:Profile {profileId: $targetProfileId})
|
|
97
|
+
WITH contract, target WHERE $targetProfileId <> $senderProfileId AND EXISTS {
|
|
98
|
+
MATCH (contract)-[:CREATED_BY|CAN_WRITE|SHARES_DATA_WITH]->(:Profile {profileId: $senderProfileId})
|
|
99
|
+
} AND CASE WHEN contract.expiresAt IS NULL OR trim(contract.expiresAt) = '' THEN true ELSE datetime(contract.expiresAt) > datetime($now) END
|
|
100
|
+
AND NOT EXISTS {
|
|
101
|
+
MATCH (target)-[:CREATED_BY]->(terms:ConsentFlowTerms)-[:CONSENTS_TO]->(contract)
|
|
102
|
+
WHERE (terms.status = 'live' OR (terms.status = 'stale' AND terms.oneTime = true)) AND
|
|
103
|
+
CASE WHEN terms.expiresAt IS NULL OR trim(terms.expiresAt) = '' THEN true ELSE datetime(terms.expiresAt) > datetime($now) END
|
|
104
|
+
}
|
|
105
|
+
OPTIONAL MATCH (contract)-[existing:REQUESTED_FOR]->(target)
|
|
106
|
+
WITH contract, target, existing WHERE existing IS NULL OR
|
|
107
|
+
(existing.status = 'pending' AND existing.requestId IS NOT NULL AND
|
|
108
|
+
existing.requestedBy = $senderProfileId AND
|
|
109
|
+
coalesce(existing.externalReferenceId, '') = coalesce($externalReferenceId, '') AND
|
|
110
|
+
coalesce(existing.message, '') = coalesce($message, ''))
|
|
111
|
+
MERGE (contract)-[request:REQUESTED_FOR]->(target)
|
|
112
|
+
ON CREATE SET request += {requestId: $requestId, requestedBy: $senderProfileId,
|
|
113
|
+
externalReferenceId: $externalReferenceId, message: $message,
|
|
114
|
+
requestedAt: $now, status: 'pending', readStatus: 'unseen'}
|
|
115
|
+
${requestEventCypher}
|
|
116
|
+
`);
|
|
117
|
+
const result = await runAudienceMutation(query);
|
|
118
|
+
if (!result.records.length)
|
|
119
|
+
throw new TRPCError({
|
|
120
|
+
code: 'CONFLICT',
|
|
121
|
+
message:
|
|
122
|
+
'Active consent or a conflicting/terminal request already exists, or the contract changed.',
|
|
123
|
+
});
|
|
124
|
+
await tryDispatchContractEvent(result.records[0]!.get('eventId'));
|
|
125
|
+
return true;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Target-only denial; cancellation also permits owner/writers and the requesting recipient. */
|
|
129
|
+
export const decideGenericContractRequest = async ({
|
|
130
|
+
contract,
|
|
131
|
+
targetProfileId,
|
|
132
|
+
actorProfileId,
|
|
133
|
+
status,
|
|
134
|
+
domain,
|
|
135
|
+
}: {
|
|
136
|
+
contract: DbContractType;
|
|
137
|
+
targetProfileId: string;
|
|
138
|
+
actorProfileId: string;
|
|
139
|
+
status: 'denied' | 'cancelled';
|
|
140
|
+
domain: string;
|
|
141
|
+
}): Promise<boolean> => {
|
|
142
|
+
const request = await getStoredContractRequest(contract.id, targetProfileId);
|
|
143
|
+
if (!request?.requestId)
|
|
144
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract request not found' });
|
|
145
|
+
const isTarget = actorProfileId === targetProfileId;
|
|
146
|
+
const writers = await getWritersForContract(contract);
|
|
147
|
+
const authorized =
|
|
148
|
+
isTarget ||
|
|
149
|
+
(status === 'cancelled' &&
|
|
150
|
+
(writers.some(profile => profile.profileId === actorProfileId) ||
|
|
151
|
+
(request.requestedBy === actorProfileId &&
|
|
152
|
+
(await canReadContractData(contract.id, actorProfileId)))));
|
|
153
|
+
if (!authorized)
|
|
154
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You cannot change this request' });
|
|
155
|
+
const result = await runAudienceMutation(
|
|
156
|
+
lockContractAudience(
|
|
157
|
+
new QueryBuilder(
|
|
158
|
+
new BindParam({
|
|
159
|
+
targetProfileId,
|
|
160
|
+
actorProfileId,
|
|
161
|
+
status,
|
|
162
|
+
domain,
|
|
163
|
+
now: new Date().toISOString(),
|
|
164
|
+
eventKind: status === 'denied' ? 'request_denied' : 'request_cancelled',
|
|
165
|
+
eventFrom: actorProfileId,
|
|
166
|
+
})
|
|
167
|
+
),
|
|
168
|
+
contract.id
|
|
169
|
+
).raw(`
|
|
170
|
+
MATCH (contract)-[request:REQUESTED_FOR]->(target:Profile {profileId: $targetProfileId})
|
|
171
|
+
WHERE request.requestId IS NOT NULL AND request.status IN ['pending', $status]
|
|
172
|
+
AND ($actorProfileId = $targetProfileId OR ($status = 'cancelled' AND
|
|
173
|
+
(EXISTS { MATCH (contract)-[:CREATED_BY|CAN_WRITE]->(:Profile {profileId: $actorProfileId}) } OR
|
|
174
|
+
(request.requestedBy = $actorProfileId AND EXISTS {
|
|
175
|
+
MATCH (contract)-[:SHARES_DATA_WITH]->(:Profile {profileId: $actorProfileId}) }))))
|
|
176
|
+
SET request.status = $status
|
|
177
|
+
${requestEventCypher}
|
|
178
|
+
`)
|
|
179
|
+
);
|
|
180
|
+
if (!result.records.length)
|
|
181
|
+
throw new TRPCError({
|
|
182
|
+
code: 'CONFLICT',
|
|
183
|
+
message: 'Request already decided or permission changed',
|
|
184
|
+
});
|
|
185
|
+
await tryDispatchContractEvent(result.records[0]!.get('eventId'));
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const markGenericContractRequestSeen = async (
|
|
190
|
+
contractId: string,
|
|
191
|
+
targetProfileId: string
|
|
192
|
+
): Promise<boolean> => {
|
|
193
|
+
const result = await runAudienceMutation(
|
|
194
|
+
lockContractAudience(new QueryBuilder(new BindParam({ targetProfileId })), contractId)
|
|
195
|
+
.raw(`MATCH (contract)-[request:REQUESTED_FOR]->(:Profile {profileId: $targetProfileId})
|
|
196
|
+
WHERE request.requestId IS NOT NULL
|
|
197
|
+
SET request.readStatus = 'seen' RETURN request`)
|
|
198
|
+
);
|
|
199
|
+
if (!result.records.length)
|
|
200
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Request not found' });
|
|
201
|
+
return true;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Legacy request mutations retain their scope behavior; attributed requests require write. */
|
|
205
|
+
export const assertGenericRequestWriteScope = (scope?: string): void => {
|
|
206
|
+
if (!userHasRequiredScopes(scope ?? '', 'contracts:write'))
|
|
207
|
+
throw new TRPCError({
|
|
208
|
+
code: 'UNAUTHORIZED',
|
|
209
|
+
message: 'This operation requires contracts:write scope',
|
|
210
|
+
});
|
|
211
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { UnsignedVC } from '@learncard/types';
|
|
3
|
+
|
|
4
|
+
import { setCredentialSubjectIds } from './credentialSubject.helpers';
|
|
5
|
+
|
|
6
|
+
const collectSubjectIds = (credential: Record<string, unknown>): string[] => {
|
|
7
|
+
const subject = credential.credentialSubject;
|
|
8
|
+
const subjects = Array.isArray(subject) ? subject : [subject];
|
|
9
|
+
|
|
10
|
+
return subjects.flatMap(currentSubject => {
|
|
11
|
+
if (!currentSubject || typeof currentSubject !== 'object') return [];
|
|
12
|
+
|
|
13
|
+
const subjectRecord = currentSubject as Record<string, unknown>;
|
|
14
|
+
const embedded = subjectRecord.verifiableCredential;
|
|
15
|
+
const embeddedCredentials = Array.isArray(embedded) ? embedded : embedded ? [embedded] : [];
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
subjectRecord.id as string,
|
|
19
|
+
...embeddedCredentials.flatMap(nested =>
|
|
20
|
+
collectSubjectIds(nested as Record<string, unknown>)
|
|
21
|
+
),
|
|
22
|
+
];
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('setCredentialSubjectIds', () => {
|
|
27
|
+
it('patches every CLR learner subject without rewriting achievement identities', () => {
|
|
28
|
+
const credential = {
|
|
29
|
+
credentialSubject: {
|
|
30
|
+
id: 'did:example:old-root',
|
|
31
|
+
achievement: {
|
|
32
|
+
id: 'https://example.edu/transcript',
|
|
33
|
+
creator: { id: 'https://example.edu' },
|
|
34
|
+
},
|
|
35
|
+
verifiableCredential: [
|
|
36
|
+
{
|
|
37
|
+
credentialSubject: {
|
|
38
|
+
id: 'did:example:old-course',
|
|
39
|
+
achievement: {
|
|
40
|
+
id: 'https://example.edu/courses/imd-120',
|
|
41
|
+
creator: { id: 'https://example.edu/departments/design' },
|
|
42
|
+
},
|
|
43
|
+
verifiableCredential: {
|
|
44
|
+
credentialSubject: { id: 'did:example:old-assessment' },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
} as unknown as UnsignedVC;
|
|
51
|
+
|
|
52
|
+
setCredentialSubjectIds(credential, 'did:example:learner');
|
|
53
|
+
|
|
54
|
+
expect(collectSubjectIds(credential as unknown as Record<string, unknown>)).toEqual([
|
|
55
|
+
'did:example:learner',
|
|
56
|
+
'did:example:learner',
|
|
57
|
+
'did:example:learner',
|
|
58
|
+
]);
|
|
59
|
+
expect(credential.credentialSubject).toMatchObject({
|
|
60
|
+
achievement: {
|
|
61
|
+
id: 'https://example.edu/transcript',
|
|
62
|
+
creator: { id: 'https://example.edu' },
|
|
63
|
+
},
|
|
64
|
+
verifiableCredential: [
|
|
65
|
+
{
|
|
66
|
+
credentialSubject: {
|
|
67
|
+
achievement: {
|
|
68
|
+
id: 'https://example.edu/courses/imd-120',
|
|
69
|
+
creator: { id: 'https://example.edu/departments/design' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('preserves signed embedded credentials while patching unsigned subjects', () => {
|
|
78
|
+
const credential = {
|
|
79
|
+
credentialSubject: {
|
|
80
|
+
id: 'did:example:old-root',
|
|
81
|
+
verifiableCredential: [
|
|
82
|
+
{
|
|
83
|
+
credentialSubject: { id: 'did:example:signed-subject' },
|
|
84
|
+
proof: { type: 'DataIntegrityProof', proofValue: 'signed-value' },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
credentialSubject: { id: 'did:example:unsigned-subject' },
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
} as unknown as UnsignedVC;
|
|
92
|
+
|
|
93
|
+
setCredentialSubjectIds(credential, 'did:example:learner');
|
|
94
|
+
|
|
95
|
+
const subject = credential.credentialSubject as Record<string, unknown>;
|
|
96
|
+
const embedded = subject.verifiableCredential as Array<Record<string, unknown>>;
|
|
97
|
+
|
|
98
|
+
expect(subject.id).toBe('did:example:learner');
|
|
99
|
+
expect(embedded[0]?.credentialSubject).toEqual({
|
|
100
|
+
id: 'did:example:signed-subject',
|
|
101
|
+
});
|
|
102
|
+
expect(embedded[1]?.credentialSubject).toEqual({
|
|
103
|
+
id: 'did:example:learner',
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('creates an outer credential subject when a legacy template omits it', () => {
|
|
108
|
+
const credential = {} as UnsignedVC;
|
|
109
|
+
|
|
110
|
+
setCredentialSubjectIds(credential, 'did:example:learner');
|
|
111
|
+
|
|
112
|
+
expect(credential.credentialSubject).toEqual({ id: 'did:example:learner' });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UnsignedVC, VC } from '@learncard/types';
|
|
2
|
+
|
|
3
|
+
const setSubjectIds = (subject: unknown, subjectDid: string): void => {
|
|
4
|
+
if (Array.isArray(subject)) {
|
|
5
|
+
subject.forEach(nestedSubject => setSubjectIds(nestedSubject, subjectDid));
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!subject || typeof subject !== 'object') return;
|
|
10
|
+
|
|
11
|
+
const mutableSubject = subject as Record<string, unknown>;
|
|
12
|
+
mutableSubject.id = subjectDid;
|
|
13
|
+
|
|
14
|
+
const embeddedCredentials = mutableSubject.verifiableCredential;
|
|
15
|
+
const credentials = Array.isArray(embeddedCredentials)
|
|
16
|
+
? embeddedCredentials
|
|
17
|
+
: [embeddedCredentials];
|
|
18
|
+
|
|
19
|
+
credentials.forEach(credential => {
|
|
20
|
+
if (!credential || typeof credential !== 'object' || Array.isArray(credential)) return;
|
|
21
|
+
|
|
22
|
+
const credentialRecord = credential as Record<string, unknown>;
|
|
23
|
+
if (credentialRecord.proof !== undefined) return;
|
|
24
|
+
|
|
25
|
+
setSubjectIds(credentialRecord.credentialSubject, subjectDid);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sets the recipient DID on the outer credential and unsigned credentials nested in a CLR.
|
|
31
|
+
* Signed embedded credentials are immutable because changing their subject invalidates the proof.
|
|
32
|
+
*/
|
|
33
|
+
export const setCredentialSubjectIds = (credential: UnsignedVC | VC, subjectDid: string): void => {
|
|
34
|
+
if (!credential.credentialSubject) {
|
|
35
|
+
credential.credentialSubject = { id: subjectDid };
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setSubjectIds(credential.credentialSubject, subjectDid);
|
|
40
|
+
};
|
|
@@ -3,7 +3,11 @@ import {
|
|
|
3
3
|
LCNNotificationTypeEnumValidator,
|
|
4
4
|
LCNInboxStatusEnumValidator,
|
|
5
5
|
VC,
|
|
6
|
+
JWE,
|
|
6
7
|
UnsignedVC,
|
|
8
|
+
VCValidator,
|
|
9
|
+
JWEValidator,
|
|
10
|
+
InboxCredentialType,
|
|
7
11
|
} from '@learncard/types';
|
|
8
12
|
import { ProfileType } from 'types/profile';
|
|
9
13
|
import { getContactMethodsForProfile } from '@accesslayer/contact-method/read';
|
|
@@ -21,6 +25,113 @@ import { getLearnCard, getEmptyLearnCard } from '@helpers/learnCard.helpers';
|
|
|
21
25
|
import { logCredentialClaimed, logCredentialFailed } from '@helpers/activity.helpers';
|
|
22
26
|
import { handleConnectionPromptsForCredentialClaim } from '@helpers/connectionPrompt.helpers';
|
|
23
27
|
import { decryptInboxCredential } from '@helpers/inbox-encryption.helpers';
|
|
28
|
+
import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
|
|
29
|
+
import { getBitstringStatusListEntries } from '@learncard/helpers';
|
|
30
|
+
import type { IssuedCredential } from 'types/credential';
|
|
31
|
+
import { getBoostByUri } from '@accesslayer/boost/read';
|
|
32
|
+
import { storeCredential } from '@accesslayer/credential/create';
|
|
33
|
+
import { createBoostInstanceOfRelationship } from '@accesslayer/boost/relationships/create';
|
|
34
|
+
import {
|
|
35
|
+
createSentCredentialRelationship,
|
|
36
|
+
createReceivedCredentialRelationship,
|
|
37
|
+
setDefaultClaimedRole,
|
|
38
|
+
} from '@accesslayer/credential/relationships/create';
|
|
39
|
+
import { neogma } from '@instance';
|
|
40
|
+
import { processClaimHooks } from '@helpers/claim-hooks.helpers';
|
|
41
|
+
import { clearDidWebCacheForChildProfileManagers } from '@accesslayer/boost/relationships/update';
|
|
42
|
+
|
|
43
|
+
/** Graph-only completion: a failed write retains pending escrow for the next attempt. */
|
|
44
|
+
const finalizeAcceptedDelivery = async (
|
|
45
|
+
inboxCredential: InboxCredentialType,
|
|
46
|
+
issued: IssuedCredential,
|
|
47
|
+
recipient: ProfileType,
|
|
48
|
+
sender: ProfileType
|
|
49
|
+
): Promise<boolean> => {
|
|
50
|
+
const boost = inboxCredential.boostUri
|
|
51
|
+
? await getBoostByUri(inboxCredential.boostUri)
|
|
52
|
+
: undefined;
|
|
53
|
+
const session = neogma.driver.session();
|
|
54
|
+
const transaction = session.beginTransaction({ timeout: 30_000 });
|
|
55
|
+
// Signing and status-index allocation happen before this bounded graph transaction;
|
|
56
|
+
// a retry or lost claim race may leave unused indices, intentionally, rather than
|
|
57
|
+
// hold Neo4j locks across external signing.
|
|
58
|
+
try {
|
|
59
|
+
const finalized = await finalizeAndWipeInboxCredential(
|
|
60
|
+
inboxCredential.id,
|
|
61
|
+
{ recipientDid: recipient.did, credential: JWEValidator.parse(issued.credential) },
|
|
62
|
+
{},
|
|
63
|
+
transaction
|
|
64
|
+
);
|
|
65
|
+
if (!finalized) {
|
|
66
|
+
// Another claim or expiry won the lock. It is not a new issuance failure.
|
|
67
|
+
await transaction.rollback();
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const instance = await storeCredential(issued, transaction);
|
|
71
|
+
if (boost) await createBoostInstanceOfRelationship(instance, boost, transaction);
|
|
72
|
+
await createSentCredentialRelationship(
|
|
73
|
+
{ type: 'profile', profile: sender },
|
|
74
|
+
recipient,
|
|
75
|
+
instance,
|
|
76
|
+
undefined,
|
|
77
|
+
inboxCredential.activityId,
|
|
78
|
+
inboxCredential.integrationId,
|
|
79
|
+
transaction
|
|
80
|
+
);
|
|
81
|
+
await createReceivedCredentialRelationship(
|
|
82
|
+
recipient,
|
|
83
|
+
sender,
|
|
84
|
+
instance,
|
|
85
|
+
undefined,
|
|
86
|
+
transaction
|
|
87
|
+
);
|
|
88
|
+
await processClaimHooks(recipient, instance, transaction);
|
|
89
|
+
await setDefaultClaimedRole(recipient, instance, transaction);
|
|
90
|
+
if (
|
|
91
|
+
!(await createClaimedRelationship(
|
|
92
|
+
recipient.profileId,
|
|
93
|
+
inboxCredential.id,
|
|
94
|
+
'finalize',
|
|
95
|
+
transaction
|
|
96
|
+
))
|
|
97
|
+
) {
|
|
98
|
+
throw new Error('Inbox claim recipient no longer exists');
|
|
99
|
+
}
|
|
100
|
+
await transaction.commit();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
await transaction
|
|
103
|
+
.rollback()
|
|
104
|
+
.catch(rollbackError =>
|
|
105
|
+
console.error(
|
|
106
|
+
'Failed to roll back inbox finalization',
|
|
107
|
+
inboxCredential.id,
|
|
108
|
+
rollbackError
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
throw error;
|
|
112
|
+
} finally {
|
|
113
|
+
// Closing the session cannot turn a committed delivery into a pending failure.
|
|
114
|
+
await session
|
|
115
|
+
.close()
|
|
116
|
+
.catch(error =>
|
|
117
|
+
console.error(
|
|
118
|
+
'Failed to close inbox finalization session',
|
|
119
|
+
inboxCredential.id,
|
|
120
|
+
error
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (boost) {
|
|
125
|
+
await clearDidWebCacheForChildProfileManagers(boost.id).catch(error =>
|
|
126
|
+
console.error(
|
|
127
|
+
'Could not clear did:web cache after inbox claim',
|
|
128
|
+
inboxCredential.id,
|
|
129
|
+
error
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
24
135
|
|
|
25
136
|
export async function finalizeInboxCredentialsForProfile(
|
|
26
137
|
profile: ProfileType,
|
|
@@ -30,8 +141,8 @@ export async function finalizeInboxCredentialsForProfile(
|
|
|
30
141
|
claimed: number;
|
|
31
142
|
errors: number;
|
|
32
143
|
guardianPending: number;
|
|
33
|
-
verifiableCredentials: VC[];
|
|
34
|
-
deliveries: { id: string; credential: VC }[];
|
|
144
|
+
verifiableCredentials: (VC | JWE)[];
|
|
145
|
+
deliveries: { id: string; credential: VC | JWE }[];
|
|
35
146
|
}> {
|
|
36
147
|
const contactMethods = await getContactMethodsForProfile(profile.did);
|
|
37
148
|
const verifiedContacts = contactMethods.filter(cm => cm.isVerified);
|
|
@@ -48,8 +159,8 @@ export async function finalizeInboxCredentialsForProfile(
|
|
|
48
159
|
lcDid = lc.id.did();
|
|
49
160
|
} catch {}
|
|
50
161
|
|
|
51
|
-
const verifiableCredentials: VC[] = [];
|
|
52
|
-
const deliveries: { id: string; credential: VC }[] = [];
|
|
162
|
+
const verifiableCredentials: (VC | JWE)[] = [];
|
|
163
|
+
const deliveries: { id: string; credential: VC | JWE }[] = [];
|
|
53
164
|
|
|
54
165
|
for (const cm of verifiedContacts) {
|
|
55
166
|
const pending = await getAcceptedPendingInboxCredentialsForContactMethodId(cm.id);
|
|
@@ -79,7 +190,8 @@ export async function finalizeInboxCredentialsForProfile(
|
|
|
79
190
|
}
|
|
80
191
|
|
|
81
192
|
try {
|
|
82
|
-
let finalCredential: VC;
|
|
193
|
+
let finalCredential: VC | JWE;
|
|
194
|
+
let issued: IssuedCredential | undefined;
|
|
83
195
|
const credentialPayload = inboxCredential.refreshId
|
|
84
196
|
? undefined
|
|
85
197
|
: await decryptInboxCredential(inboxCredential.credential);
|
|
@@ -102,29 +214,16 @@ export async function finalizeInboxCredentialsForProfile(
|
|
|
102
214
|
if (!endpoint || !name)
|
|
103
215
|
throw new Error('Inbox credential missing signing authority info');
|
|
104
216
|
|
|
105
|
-
|
|
106
|
-
if (!issuerProfile) throw new Error('Issuer profile not found');
|
|
217
|
+
if (!senderProfile) throw new Error('Issuer profile not found');
|
|
107
218
|
|
|
108
219
|
const signingAuthorityForUser = await getSigningAuthorityForUserByName(
|
|
109
|
-
|
|
220
|
+
senderProfile,
|
|
110
221
|
endpoint,
|
|
111
222
|
name
|
|
112
223
|
);
|
|
113
224
|
if (!signingAuthorityForUser) throw new Error('Signing authority not found');
|
|
114
225
|
|
|
115
|
-
|
|
116
|
-
if (Array.isArray(unsignedCredential.credentialSubject)) {
|
|
117
|
-
unsignedCredential.credentialSubject =
|
|
118
|
-
unsignedCredential.credentialSubject.map(sub => ({
|
|
119
|
-
...sub,
|
|
120
|
-
id: (sub as any).did || (sub as any).id || profile.did,
|
|
121
|
-
}));
|
|
122
|
-
} else {
|
|
123
|
-
(unsignedCredential.credentialSubject as any).id =
|
|
124
|
-
(unsignedCredential as any).credentialSubject?.did ||
|
|
125
|
-
(unsignedCredential as any).credentialSubject?.id ||
|
|
126
|
-
profile.did;
|
|
127
|
-
}
|
|
226
|
+
setCredentialSubjectIds(unsignedCredential, profile.did);
|
|
128
227
|
|
|
129
228
|
// Set issuer from signing authority
|
|
130
229
|
unsignedCredential.issuer = signingAuthorityForUser.relationship.did;
|
|
@@ -136,36 +235,56 @@ export async function finalizeInboxCredentialsForProfile(
|
|
|
136
235
|
? getAppDidWeb(domain, listingSlug)
|
|
137
236
|
: undefined;
|
|
138
237
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
238
|
+
issued = await issueCredentialWithSigningAuthority(
|
|
239
|
+
{ type: 'profile', profile: senderProfile },
|
|
240
|
+
unsignedCredential,
|
|
241
|
+
signingAuthorityForUser,
|
|
242
|
+
domain,
|
|
243
|
+
undefined,
|
|
244
|
+
ownerDidOverride,
|
|
245
|
+
undefined,
|
|
246
|
+
// Preserve issuer did:key access alongside the managed/app did:web reader.
|
|
247
|
+
[senderProfile.did]
|
|
248
|
+
);
|
|
249
|
+
finalCredential = issued.credential;
|
|
149
250
|
} else {
|
|
150
251
|
finalCredential = JSON.parse(credentialPayload!) as VC;
|
|
151
252
|
}
|
|
152
253
|
|
|
153
254
|
if (!inboxCredential.refreshId) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
255
|
+
if (!senderProfile) throw new Error('Issuer profile not found');
|
|
256
|
+
if (!issued) {
|
|
257
|
+
const signedCredential = VCValidator.parse(finalCredential);
|
|
258
|
+
const learnCard = await getEmptyLearnCard();
|
|
259
|
+
issued = {
|
|
260
|
+
kind: 'issued-credential',
|
|
261
|
+
credential: await learnCard.invoke.createDagJwe(signedCredential, [
|
|
262
|
+
profile.did,
|
|
263
|
+
inboxCredential.issuerDid,
|
|
264
|
+
]),
|
|
265
|
+
statusEntries: getBitstringStatusListEntries(signedCredential),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (
|
|
269
|
+
!(await finalizeAcceptedDelivery(
|
|
270
|
+
inboxCredential,
|
|
271
|
+
issued,
|
|
272
|
+
profile,
|
|
273
|
+
senderProfile
|
|
274
|
+
))
|
|
275
|
+
)
|
|
276
|
+
continue;
|
|
277
|
+
finalCredential = issued.credential;
|
|
165
278
|
}
|
|
166
279
|
|
|
167
|
-
//
|
|
168
|
-
|
|
280
|
+
// Managed refresh retains its separate lifecycle; ordinary claims audit in the transaction.
|
|
281
|
+
if (inboxCredential.refreshId) {
|
|
282
|
+
await createClaimedRelationship(
|
|
283
|
+
profile.profileId,
|
|
284
|
+
inboxCredential.id,
|
|
285
|
+
'finalize'
|
|
286
|
+
);
|
|
287
|
+
}
|
|
169
288
|
|
|
170
289
|
if (
|
|
171
290
|
senderProfile &&
|