@learncard/network-brain-service 3.19.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +44 -111
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +120 -0
- package/README.md +9 -2
- package/bootstrapLambda.test.ts +72 -0
- package/bundle.mjs +9 -1
- 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/share-link/abandon.d.ts.map +1 -1
- 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/finalize.d.ts.map +1 -1
- package/dist/accesslayer/share-link/helpers.d.ts +3 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -1
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -1
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -1
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -1
- package/dist/accesslayer/share-link/types.d.ts +8 -3
- package/dist/accesslayer/share-link/types.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 +5 -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/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/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/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/profile.helpers.d.ts +1 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- 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/coordinator.d.ts.map +1 -1
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runner.d.ts +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runtime.d.ts +2 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/types.d.ts +4 -1
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -1
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/production.d.ts +3 -5
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/types.d.ts +8 -2
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -1
- 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/ShareLink.d.ts +2 -0
- package/dist/models/ShareLink.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts.map +1 -1
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/publish-metafile.json +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 +146 -3
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +34 -0
- 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.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 +55 -0
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +175 -14
- package/dist/routes/share-links.d.ts.map +1 -1
- 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/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 +30 -3
- 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 -195
- package/lambdaApp.ts +203 -0
- package/package.json +20 -19
- package/project.json +8 -1
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -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 +29 -74
- package/shareLinkMaintenanceLambda.ts +13 -44
- 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 +116 -93
- 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 +240 -189
- 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/share-link/abandon.ts +20 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/finalize.ts +55 -7
- package/src/accesslayer/share-link/helpers.ts +33 -2
- package/src/accesslayer/share-link/recovery.ts +20 -0
- package/src/accesslayer/share-link/reserve.ts +85 -6
- package/src/accesslayer/share-link/revoke.ts +35 -0
- package/src/accesslayer/share-link/types.ts +9 -4
- 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 +1 -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/docker-entry.ts +24 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- 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/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +51 -66
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +59 -0
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/profile.helpers.ts +6 -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/coordinator.ts +15 -2
- package/src/helpers/share-link-coordinator/recovery-runner.ts +15 -0
- package/src/helpers/share-link-maintenance/runner.ts +28 -0
- package/src/helpers/share-link-maintenance/runtime.ts +6 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +3 -0
- package/src/helpers/share-link-maintenance/types.ts +2 -0
- package/src/helpers/share-link-owner-projection.ts +2 -0
- package/src/helpers/share-link-policy/production.ts +37 -17
- package/src/helpers/share-link-policy/resolver.ts +16 -11
- package/src/helpers/share-link-policy/types.ts +15 -7
- 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/ShareLink.ts +4 -0
- package/src/models/index.ts +23 -0
- package/src/models/share-link-constraints.ts +5 -0
- package/src/openapi.ts +44 -8
- package/src/routes/app-store.ts +1054 -27
- package/src/routes/boosts.ts +12 -14
- package/src/routes/contracts.ts +382 -262
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +4 -1
- package/src/routes/profile-manager.ts +19 -0
- package/src/routes/profiles.ts +12 -0
- package/src/routes/public-share-links.ts +169 -0
- package/src/routes/share-links.ts +69 -0
- package/src/routes/workflows.ts +3 -2
- 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/guardian-gated-routes.spec.ts +36 -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 +24 -2
- package/test/openapi.spec.ts +14 -73
- package/test/profiles.spec.ts +30 -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-lifecycle.neo4j.spec.ts +145 -2
- package/test/share-link-maintenance.unit.spec.ts +32 -5
- package/test/share-link-owner-route.unit.spec.ts +70 -2
- package/test/share-link-policy.unit.spec.ts +18 -4
- package/test/share-link-production-policy.unit.spec.ts +58 -9
- package/test/share-link-public-route.unit.spec.ts +279 -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 +13 -0
- package/vitest.inbox-e2e.config.ts +2 -0
- package/workerLambda.test.ts +100 -0
package/src/routes/contracts.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import { SendContractRequestValidator, ContractRequestFieldsValidator } from '@learncard/types';
|
|
2
|
+
import {
|
|
3
|
+
sendGenericContractRequest,
|
|
4
|
+
decideGenericContractRequest,
|
|
5
|
+
markGenericContractRequestSeen,
|
|
6
|
+
assertGenericRequestWriteScope,
|
|
7
|
+
canManageContractRequests,
|
|
8
|
+
resolveContractRequest,
|
|
9
|
+
} from '@helpers/contract-requests.helpers';
|
|
1
10
|
import { environment } from '@environment';
|
|
11
|
+
import {
|
|
12
|
+
smartResumeFingerprint,
|
|
13
|
+
publishSmartResume,
|
|
14
|
+
} from '@accesslayer/consentflowcontract/relationships/smartResume';
|
|
15
|
+
import { uploadSmartResume } from '@helpers/smartResume.helpers';
|
|
2
16
|
import { TRPCError } from '@trpc/server';
|
|
3
17
|
import { z } from 'zod';
|
|
4
18
|
|
|
5
19
|
import { t, profileRoute, openRoute, guardianGatedRoute } from '@routes';
|
|
6
|
-
import { ConsentFlowContract } from '@models';
|
|
7
20
|
|
|
8
21
|
import {
|
|
9
22
|
ConsentFlowContractValidator,
|
|
@@ -47,10 +60,11 @@ import {
|
|
|
47
60
|
isProfileConsentFlowContractAdmin,
|
|
48
61
|
getWritersForContract,
|
|
49
62
|
} from '@accesslayer/consentflowcontract/relationships/read';
|
|
50
|
-
import {
|
|
63
|
+
import { inflateObject } from '@helpers/objects.helpers';
|
|
64
|
+
import { constructUri, getIdFromUri } from '@helpers/uri.helpers';
|
|
51
65
|
import {
|
|
52
66
|
getContractByUri,
|
|
53
|
-
|
|
67
|
+
getStoredContractRequest,
|
|
54
68
|
getRequestedForList,
|
|
55
69
|
getRequestedForForUser,
|
|
56
70
|
getContractById,
|
|
@@ -64,17 +78,12 @@ import { areTermsValid } from '@helpers/contract.helpers';
|
|
|
64
78
|
import { updateDidForProfile, getProfileIdFromString } from '@helpers/did.helpers';
|
|
65
79
|
import {
|
|
66
80
|
syncCredentialsToContract,
|
|
67
|
-
updateRequestedForStatusIfExists,
|
|
68
81
|
updateTerms,
|
|
69
82
|
pruneDeletedUrisFromConsentTerms,
|
|
70
83
|
upsertRequestedForRelationship,
|
|
71
84
|
withdrawTerms,
|
|
72
85
|
} from '@accesslayer/consentflowcontract/relationships/update';
|
|
73
|
-
import {
|
|
74
|
-
consentToContract,
|
|
75
|
-
setAutoBoostForContract,
|
|
76
|
-
setCreatorForContract,
|
|
77
|
-
} from '@accesslayer/consentflowcontract/relationships/create';
|
|
86
|
+
import { consentToContract } from '@accesslayer/consentflowcontract/relationships/create';
|
|
78
87
|
import { getProfileByDid, getProfileByProfileId } from '@accesslayer/profile/read';
|
|
79
88
|
import { sendBoost, isDraftBoost } from '@helpers/boost.helpers';
|
|
80
89
|
import { setCredentialSubjectIds } from '@helpers/credentialSubject.helpers';
|
|
@@ -105,6 +114,24 @@ import { addNotificationToQueue } from '@helpers/notifications.helpers';
|
|
|
105
114
|
import { getNotificationMessage } from '@helpers/notificationMessages';
|
|
106
115
|
import { resolveRecipientLocale } from '@helpers/getRecipientLocale.helpers';
|
|
107
116
|
import { ProfileType } from 'types/profile';
|
|
117
|
+
import {
|
|
118
|
+
getRecipientsForContract,
|
|
119
|
+
addRecipientToContract,
|
|
120
|
+
removeRecipientFromContract,
|
|
121
|
+
canReadContractData,
|
|
122
|
+
} from '@accesslayer/consentflowcontract/relationships/recipients';
|
|
123
|
+
|
|
124
|
+
const publicContractRecipient = (domain: string, recipient: ProfileType) => {
|
|
125
|
+
const profile = updateDidForProfile(domain, recipient);
|
|
126
|
+
return { ...sanitizeProfileForTier(profile, 'unauthenticated'), did: profile.did };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const resolveRecipientProfileId = async (identifier: string, domain: string): Promise<string> => {
|
|
130
|
+
const profileId = await getProfileIdFromString(identifier, domain);
|
|
131
|
+
if (!profileId)
|
|
132
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid recipient identifier' });
|
|
133
|
+
return profileId;
|
|
134
|
+
};
|
|
108
135
|
|
|
109
136
|
export const contractsRouter = t.router({
|
|
110
137
|
createConsentFlowContract: profileRoute
|
|
@@ -133,6 +160,7 @@ export const contractsRouter = t.router({
|
|
|
133
160
|
expiresAt: z.string().optional(),
|
|
134
161
|
autoboosts: z.array(AutoBoostConfigValidator).optional(),
|
|
135
162
|
writers: z.array(z.string()).optional(),
|
|
163
|
+
recipients: z.array(z.string()).max(50).optional(),
|
|
136
164
|
})
|
|
137
165
|
)
|
|
138
166
|
.output(z.string())
|
|
@@ -150,26 +178,31 @@ export const contractsRouter = t.router({
|
|
|
150
178
|
expiresAt,
|
|
151
179
|
autoboosts,
|
|
152
180
|
writers,
|
|
181
|
+
recipients,
|
|
153
182
|
} = input;
|
|
154
183
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
const recipientIds = [
|
|
185
|
+
...new Set(
|
|
186
|
+
await Promise.all(
|
|
187
|
+
(recipients ?? []).map(id => resolveRecipientProfileId(id, ctx.domain))
|
|
188
|
+
)
|
|
189
|
+
),
|
|
190
|
+
].filter(id => id !== ctx.user.profile.profileId);
|
|
191
|
+
const recipientProfiles = await getProfilesByProfileIds(recipientIds);
|
|
192
|
+
if (recipientProfiles.length !== recipientIds.length) {
|
|
193
|
+
throw new TRPCError({
|
|
194
|
+
code: 'BAD_REQUEST',
|
|
195
|
+
message: 'Could not find all recipient profiles',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
168
198
|
|
|
169
|
-
|
|
170
|
-
|
|
199
|
+
const preparedAutoBoosts: {
|
|
200
|
+
id: string;
|
|
201
|
+
signingAuthorityEndpoint: string;
|
|
202
|
+
signingAuthorityName: string;
|
|
203
|
+
}[] = [];
|
|
171
204
|
|
|
172
|
-
//
|
|
205
|
+
// Validate specified writers before creating the contract.
|
|
173
206
|
if (writers && writers.length > 0) {
|
|
174
207
|
const writerProfiles = await getProfilesByProfileIds(writers);
|
|
175
208
|
|
|
@@ -184,17 +217,6 @@ export const contractsRouter = t.router({
|
|
|
184
217
|
)}`,
|
|
185
218
|
});
|
|
186
219
|
}
|
|
187
|
-
|
|
188
|
-
// Create CAN_WRITE relationship for each writer using static relateTo
|
|
189
|
-
for (const writerProfile of writerProfiles) {
|
|
190
|
-
await ConsentFlowContract.relateTo({
|
|
191
|
-
alias: 'canWrite',
|
|
192
|
-
where: {
|
|
193
|
-
source: { id: createdContract.id }, // Specify source contract by id
|
|
194
|
-
target: { profileId: writerProfile.profileId }, // Specify target profile by profileId
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
if (autoboosts && autoboosts.length > 0) {
|
|
@@ -243,18 +265,94 @@ export const contractsRouter = t.router({
|
|
|
243
265
|
});
|
|
244
266
|
}
|
|
245
267
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
normalizedSigningAuthority,
|
|
250
|
-
|
|
251
|
-
);
|
|
268
|
+
preparedAutoBoosts.push({
|
|
269
|
+
id: boost.id,
|
|
270
|
+
signingAuthorityEndpoint: normalizedSigningAuthority.endpoint,
|
|
271
|
+
signingAuthorityName: normalizedSigningAuthority.name,
|
|
272
|
+
});
|
|
252
273
|
}
|
|
253
274
|
}
|
|
254
275
|
|
|
276
|
+
// All validation precedes the single atomic creation statement.
|
|
277
|
+
const createdContract = await createConsentFlowContract({
|
|
278
|
+
contract,
|
|
279
|
+
ownerProfileId: ctx.user.profile.profileId,
|
|
280
|
+
recipientIds,
|
|
281
|
+
writerIds: writers,
|
|
282
|
+
autoboosts: preparedAutoBoosts,
|
|
283
|
+
name,
|
|
284
|
+
subtitle,
|
|
285
|
+
description,
|
|
286
|
+
reasonForAccessing,
|
|
287
|
+
needsGuardianConsent,
|
|
288
|
+
redirectUrl,
|
|
289
|
+
frontDoorBoostUri,
|
|
290
|
+
image,
|
|
291
|
+
expiresAt,
|
|
292
|
+
});
|
|
293
|
+
|
|
255
294
|
return constructUri('contract', createdContract.id, ctx.domain);
|
|
256
295
|
}),
|
|
257
296
|
|
|
297
|
+
addContractRecipient: profileRoute
|
|
298
|
+
.meta({
|
|
299
|
+
requiredScope: 'contracts:write',
|
|
300
|
+
openapi: {
|
|
301
|
+
protect: true,
|
|
302
|
+
method: 'POST',
|
|
303
|
+
path: '/consent-flow-contract/recipient/add',
|
|
304
|
+
tags: ['Contracts'],
|
|
305
|
+
summary: 'Add a data recipient before first consent',
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
.input(z.object({ contractUri: z.string(), recipient: z.string() }))
|
|
309
|
+
.output(z.boolean())
|
|
310
|
+
.mutation(async ({ ctx, input }) => {
|
|
311
|
+
const contract = await getContractByUri(input.contractUri);
|
|
312
|
+
if (!contract)
|
|
313
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
|
|
314
|
+
if (!(await isProfileConsentFlowContractAdmin(ctx.user.profile, contract)))
|
|
315
|
+
throw new TRPCError({
|
|
316
|
+
code: 'UNAUTHORIZED',
|
|
317
|
+
message: 'Profile does not own contract',
|
|
318
|
+
});
|
|
319
|
+
const profileId = await resolveRecipientProfileId(input.recipient, ctx.domain);
|
|
320
|
+
if (!(await getProfileByProfileId(profileId)))
|
|
321
|
+
throw new TRPCError({
|
|
322
|
+
code: 'BAD_REQUEST',
|
|
323
|
+
message: 'Could not find recipient profile',
|
|
324
|
+
});
|
|
325
|
+
return addRecipientToContract(contract.id, profileId);
|
|
326
|
+
}),
|
|
327
|
+
|
|
328
|
+
removeContractRecipient: profileRoute
|
|
329
|
+
.meta({
|
|
330
|
+
requiredScope: 'contracts:write',
|
|
331
|
+
openapi: {
|
|
332
|
+
protect: true,
|
|
333
|
+
method: 'POST',
|
|
334
|
+
path: '/consent-flow-contract/recipient/remove',
|
|
335
|
+
tags: ['Contracts'],
|
|
336
|
+
summary: 'Remove a data recipient',
|
|
337
|
+
},
|
|
338
|
+
})
|
|
339
|
+
.input(z.object({ contractUri: z.string(), recipient: z.string() }))
|
|
340
|
+
.output(z.boolean())
|
|
341
|
+
.mutation(async ({ ctx, input }) => {
|
|
342
|
+
const contract = await getContractByUri(input.contractUri);
|
|
343
|
+
if (!contract)
|
|
344
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
|
|
345
|
+
if (!(await isProfileConsentFlowContractAdmin(ctx.user.profile, contract)))
|
|
346
|
+
throw new TRPCError({
|
|
347
|
+
code: 'UNAUTHORIZED',
|
|
348
|
+
message: 'Profile does not own contract',
|
|
349
|
+
});
|
|
350
|
+
return removeRecipientFromContract(
|
|
351
|
+
contract.id,
|
|
352
|
+
await resolveRecipientProfileId(input.recipient, ctx.domain)
|
|
353
|
+
);
|
|
354
|
+
}),
|
|
355
|
+
|
|
258
356
|
getConsentFlowContract: openRoute
|
|
259
357
|
.meta({
|
|
260
358
|
openapi: {
|
|
@@ -280,6 +378,7 @@ export const contractsRouter = t.router({
|
|
|
280
378
|
}
|
|
281
379
|
|
|
282
380
|
const autoboosts = await getAutoBoostsForContract(result.contract.id, ctx.domain);
|
|
381
|
+
const recipients = await getRecipientsForContract(result.contract.id);
|
|
283
382
|
|
|
284
383
|
return {
|
|
285
384
|
owner: updateDidForProfile(ctx.domain, result.contractOwner),
|
|
@@ -297,6 +396,10 @@ export const contractsRouter = t.router({
|
|
|
297
396
|
uri: constructUri('contract', result.contract.id, ctx.domain),
|
|
298
397
|
...(result.contract.expiresAt ? { expiresAt: result.contract.expiresAt } : {}),
|
|
299
398
|
autoBoosts: autoboosts,
|
|
399
|
+
recipients: recipients.map(recipient =>
|
|
400
|
+
publicContractRecipient(ctx.domain, recipient)
|
|
401
|
+
),
|
|
402
|
+
audienceVersion: Number(result.contract.audienceVersion ?? 0),
|
|
300
403
|
};
|
|
301
404
|
}),
|
|
302
405
|
|
|
@@ -426,16 +529,16 @@ export const contractsRouter = t.router({
|
|
|
426
529
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
|
|
427
530
|
}
|
|
428
531
|
|
|
429
|
-
if (!(await
|
|
532
|
+
if (!(await canReadContractData(contract.id, profile.profileId))) {
|
|
430
533
|
throw new TRPCError({
|
|
431
534
|
code: 'UNAUTHORIZED',
|
|
432
|
-
message: 'Profile
|
|
535
|
+
message: 'Profile is not in the contract data audience',
|
|
433
536
|
});
|
|
434
537
|
}
|
|
435
538
|
|
|
436
539
|
const contractId = getIdFromUri(uri);
|
|
437
540
|
|
|
438
|
-
const results = await getConsentedDataForContract(contractId, {
|
|
541
|
+
const results = await getConsentedDataForContract(contractId, profile.profileId, {
|
|
439
542
|
query,
|
|
440
543
|
limit: limit + 1,
|
|
441
544
|
cursor,
|
|
@@ -892,6 +995,8 @@ export const contractsRouter = t.router({
|
|
|
892
995
|
contractUri: z.string(),
|
|
893
996
|
expiresAt: z.string().optional(),
|
|
894
997
|
oneTime: z.boolean().optional(),
|
|
998
|
+
audienceVersion: z.number().int().nonnegative().optional(),
|
|
999
|
+
expectedRequestId: z.string().min(1).optional(),
|
|
895
1000
|
recipientToken: z.string().optional(), // SmartResume recipientToken needed for API call
|
|
896
1001
|
})
|
|
897
1002
|
)
|
|
@@ -907,7 +1012,15 @@ export const contractsRouter = t.router({
|
|
|
907
1012
|
});
|
|
908
1013
|
}
|
|
909
1014
|
|
|
910
|
-
const {
|
|
1015
|
+
const {
|
|
1016
|
+
terms,
|
|
1017
|
+
contractUri,
|
|
1018
|
+
expiresAt,
|
|
1019
|
+
oneTime,
|
|
1020
|
+
recipientToken,
|
|
1021
|
+
audienceVersion,
|
|
1022
|
+
expectedRequestId,
|
|
1023
|
+
} = input;
|
|
911
1024
|
|
|
912
1025
|
const contractDetails = await getContractDetailsByUri(contractUri);
|
|
913
1026
|
|
|
@@ -915,7 +1028,37 @@ export const contractsRouter = t.router({
|
|
|
915
1028
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Could not find contract' });
|
|
916
1029
|
}
|
|
917
1030
|
|
|
918
|
-
if (
|
|
1031
|
+
if (
|
|
1032
|
+
contractDetails.contract.expiresAt?.trim() &&
|
|
1033
|
+
(!Number.isFinite(Date.parse(contractDetails.contract.expiresAt)) ||
|
|
1034
|
+
Date.parse(contractDetails.contract.expiresAt) <= Date.now())
|
|
1035
|
+
) {
|
|
1036
|
+
throw new TRPCError({ code: 'CONFLICT', message: 'This contract has expired.' });
|
|
1037
|
+
}
|
|
1038
|
+
const isSmartResume =
|
|
1039
|
+
contractUri === environment.SMART_RESUME_CONTRACT_URI ||
|
|
1040
|
+
contractUri ===
|
|
1041
|
+
'lc:network:network.learncard.com/trpc:contract:55b738f0-49f4-4b33-b6c1-afa99b605cd6';
|
|
1042
|
+
if (isSmartResume && !recipientToken) {
|
|
1043
|
+
throw new TRPCError({
|
|
1044
|
+
code: 'BAD_REQUEST',
|
|
1045
|
+
message: 'Missing recipientToken for SmartResume',
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
const previousTerms = await getContractTermsForProfile(
|
|
1049
|
+
profile,
|
|
1050
|
+
contractDetails.contract
|
|
1051
|
+
);
|
|
1052
|
+
const retrySmartResume =
|
|
1053
|
+
isSmartResume &&
|
|
1054
|
+
Boolean(previousTerms?.smartResumeFingerprint) &&
|
|
1055
|
+
(previousTerms?.status === 'live' ||
|
|
1056
|
+
(previousTerms?.status === 'stale' && previousTerms.oneTime));
|
|
1057
|
+
|
|
1058
|
+
if (
|
|
1059
|
+
!retrySmartResume &&
|
|
1060
|
+
(await hasProfileConsentedToContract(profile, contractDetails.contract))
|
|
1061
|
+
) {
|
|
919
1062
|
try {
|
|
920
1063
|
await upsertRequestedForRelationship(
|
|
921
1064
|
contractDetails.contract.id,
|
|
@@ -930,10 +1073,6 @@ export const contractsRouter = t.router({
|
|
|
930
1073
|
}
|
|
931
1074
|
|
|
932
1075
|
if (!guardianIdentity) {
|
|
933
|
-
const previousTerms = await getContractTermsForProfile(
|
|
934
|
-
profile,
|
|
935
|
-
contractDetails.contract
|
|
936
|
-
);
|
|
937
1076
|
if (previousTerms && (await hasGuardianApprovalHistory(previousTerms))) {
|
|
938
1077
|
throw new TRPCError({
|
|
939
1078
|
code: 'FORBIDDEN',
|
|
@@ -954,156 +1093,46 @@ export const contractsRouter = t.router({
|
|
|
954
1093
|
);
|
|
955
1094
|
}
|
|
956
1095
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const categoryValues = Object.values(categories) as Array<{ shared?: string[] }>;
|
|
995
|
-
|
|
996
|
-
const allSharedCredentialUris = [
|
|
997
|
-
// filter out duplicates
|
|
998
|
-
...new Set(categoryValues.flatMap(({ shared }) => shared ?? [])),
|
|
999
|
-
];
|
|
1000
|
-
|
|
1001
|
-
const resolvedCredentials = await Promise.all(
|
|
1002
|
-
allSharedCredentialUris.map(async uri => {
|
|
1003
|
-
try {
|
|
1004
|
-
return await resolveUri(uri);
|
|
1005
|
-
} catch (error) {
|
|
1006
|
-
console.error(`Error resolving URI ${uri}:`, error);
|
|
1007
|
-
return undefined;
|
|
1008
|
-
}
|
|
1009
|
-
})
|
|
1010
|
-
);
|
|
1011
|
-
|
|
1012
|
-
type ResolvedCredential = {
|
|
1013
|
-
issuer?: string | { id: string };
|
|
1014
|
-
id?: string;
|
|
1015
|
-
boostCredential?: Record<string, unknown>;
|
|
1016
|
-
} & Record<string, unknown>;
|
|
1017
|
-
|
|
1018
|
-
const credentials = resolvedCredentials
|
|
1019
|
-
.filter(
|
|
1020
|
-
(cred): cred is ResolvedCredential =>
|
|
1021
|
-
typeof cred === 'object' && cred !== null
|
|
1022
|
-
)
|
|
1023
|
-
.map(cred =>
|
|
1024
|
-
cred.boostCredential && typeof cred.boostCredential === 'object'
|
|
1025
|
-
? ({ ...cred.boostCredential, id: cred.id } as ResolvedCredential) // unwrap credential, preserve id
|
|
1026
|
-
: cred
|
|
1027
|
-
);
|
|
1028
|
-
|
|
1029
|
-
const transformedCredentials = credentials.map(cred => {
|
|
1030
|
-
const issuer =
|
|
1031
|
-
typeof cred.issuer === 'string'
|
|
1032
|
-
? { id: cred.issuer }
|
|
1033
|
-
: cred.issuer || { id: '' };
|
|
1034
|
-
|
|
1035
|
-
return {
|
|
1036
|
-
...cred,
|
|
1037
|
-
issuer,
|
|
1038
|
-
};
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
const { name, email } = parsedTerms.read.personal;
|
|
1042
|
-
|
|
1043
|
-
const body = JSON.stringify({
|
|
1044
|
-
'@context': [
|
|
1045
|
-
'https://www.w3.org/ns/credentials/v2',
|
|
1046
|
-
'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json',
|
|
1047
|
-
'https://w3id.org/security/suites/ed25519-2020/v1',
|
|
1048
|
-
],
|
|
1049
|
-
recipienttoken: recipientToken,
|
|
1050
|
-
recipient: {
|
|
1051
|
-
id: ctx.user.did,
|
|
1052
|
-
givenName: name && name !== 'Anonymous' ? name : '',
|
|
1053
|
-
familyName: '', // this is neecessary in order for givenName to be respected
|
|
1054
|
-
email: email && email !== 'anonymous@hidden.com' ? email : '',
|
|
1096
|
+
const fingerprint = isSmartResume
|
|
1097
|
+
? smartResumeFingerprint({
|
|
1098
|
+
terms,
|
|
1099
|
+
expiresAt,
|
|
1100
|
+
oneTime,
|
|
1101
|
+
audienceVersion,
|
|
1102
|
+
recipientToken,
|
|
1103
|
+
expectedRequestId,
|
|
1104
|
+
})
|
|
1105
|
+
: undefined;
|
|
1106
|
+
// A completed one-time snapshot can be followed by a new reviewed decision.
|
|
1107
|
+
// Identical submissions still replay the completed upload instead.
|
|
1108
|
+
const newDecisionAfterOneTime =
|
|
1109
|
+
retrySmartResume &&
|
|
1110
|
+
previousTerms?.status === 'stale' &&
|
|
1111
|
+
previousTerms.oneTime &&
|
|
1112
|
+
previousTerms.smartResumePublicationStatus === 'succeeded' &&
|
|
1113
|
+
fingerprint !== previousTerms.smartResumeFingerprint;
|
|
1114
|
+
if (!retrySmartResume || newDecisionAfterOneTime)
|
|
1115
|
+
await consentToContract(
|
|
1116
|
+
profile,
|
|
1117
|
+
contractDetails,
|
|
1118
|
+
{
|
|
1119
|
+
terms,
|
|
1120
|
+
expiresAt,
|
|
1121
|
+
oneTime,
|
|
1122
|
+
audienceVersion,
|
|
1123
|
+
expectedRequestId,
|
|
1124
|
+
smartResumeFingerprint: fingerprint,
|
|
1125
|
+
guardianApproval: guardianIdentity
|
|
1126
|
+
? {
|
|
1127
|
+
guardianProfileId: guardianIdentity.profileId,
|
|
1128
|
+
guardianDid: guardianIdentity.did,
|
|
1129
|
+
approvedAt: new Date().toISOString(),
|
|
1130
|
+
contractUpdatedAt: contractDetails.contract.updatedAt,
|
|
1131
|
+
}
|
|
1132
|
+
: undefined,
|
|
1055
1133
|
},
|
|
1056
|
-
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
try {
|
|
1060
|
-
const response = await fetch(`${srUrl}api/v1/credentials`, {
|
|
1061
|
-
method: 'POST',
|
|
1062
|
-
headers: {
|
|
1063
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1064
|
-
'Content-Type': 'application/json',
|
|
1065
|
-
},
|
|
1066
|
-
body,
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
if (!response.ok) {
|
|
1070
|
-
throw new Error(`Error (${response.status}): ${await response.text()}`);
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const result = (await response.json()) as { redirect_url?: string };
|
|
1074
|
-
redirectUrl = result.redirect_url;
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
console.error('Error uploading credentials to SmartResume:', error);
|
|
1077
|
-
throw error;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
await consentToContract(
|
|
1082
|
-
profile,
|
|
1083
|
-
contractDetails,
|
|
1084
|
-
{
|
|
1085
|
-
terms,
|
|
1086
|
-
expiresAt,
|
|
1087
|
-
oneTime,
|
|
1088
|
-
guardianApproval: guardianIdentity
|
|
1089
|
-
? {
|
|
1090
|
-
guardianProfileId: guardianIdentity.profileId,
|
|
1091
|
-
guardianDid: guardianIdentity.did,
|
|
1092
|
-
approvedAt: new Date().toISOString(),
|
|
1093
|
-
contractUpdatedAt: contractDetails.contract.updatedAt,
|
|
1094
|
-
}
|
|
1095
|
-
: undefined,
|
|
1096
|
-
},
|
|
1097
|
-
ctx.domain
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
try {
|
|
1101
|
-
await updateRequestedForStatusIfExists(
|
|
1102
|
-
contractDetails.contract.id,
|
|
1103
|
-
profile.profileId,
|
|
1104
|
-
'accepted'
|
|
1134
|
+
ctx.domain
|
|
1105
1135
|
);
|
|
1106
|
-
} catch {}
|
|
1107
1136
|
|
|
1108
1137
|
const relationship = await getContractTermsForProfile(
|
|
1109
1138
|
profile,
|
|
@@ -1117,6 +1146,18 @@ export const contractsRouter = t.router({
|
|
|
1117
1146
|
});
|
|
1118
1147
|
}
|
|
1119
1148
|
|
|
1149
|
+
const redirectUrl = isSmartResume
|
|
1150
|
+
? await publishSmartResume({
|
|
1151
|
+
contractId: contractDetails.contract.id,
|
|
1152
|
+
profileId: profile.profileId,
|
|
1153
|
+
termsId: relationship.id,
|
|
1154
|
+
fingerprint: fingerprint!,
|
|
1155
|
+
audienceVersion,
|
|
1156
|
+
expectedRequestId,
|
|
1157
|
+
upload: acceptedTerms =>
|
|
1158
|
+
uploadSmartResume(acceptedTerms, ctx.user.did, recipientToken!),
|
|
1159
|
+
})
|
|
1160
|
+
: undefined;
|
|
1120
1161
|
return { termsUri: constructUri('terms', relationship.id, ctx.domain), redirectUrl };
|
|
1121
1162
|
}),
|
|
1122
1163
|
|
|
@@ -1178,9 +1219,14 @@ export const contractsRouter = t.router({
|
|
|
1178
1219
|
? { expiresAt: record.contract.expiresAt }
|
|
1179
1220
|
: {}),
|
|
1180
1221
|
autoBoosts: record.autoBoosts,
|
|
1222
|
+
audienceVersion: Number(record.contract.audienceVersion ?? 0),
|
|
1223
|
+
recipients: record.recipients.map(recipient =>
|
|
1224
|
+
publicContractRecipient(ctx.domain, recipient)
|
|
1225
|
+
),
|
|
1181
1226
|
},
|
|
1182
1227
|
uri: constructUri('terms', record.terms.id, ctx.domain),
|
|
1183
1228
|
terms: record.terms.terms,
|
|
1229
|
+
...(record.terms.referral ? { referral: record.terms.referral } : {}),
|
|
1184
1230
|
...(record.terms.expiresAt ? { expiresAt: record.terms.expiresAt } : {}),
|
|
1185
1231
|
...(record.terms.oneTime ? { oneTime: record.terms.oneTime } : {}),
|
|
1186
1232
|
consenter: updateDidForProfile(ctx.domain, profile),
|
|
@@ -1207,6 +1253,7 @@ export const contractsRouter = t.router({
|
|
|
1207
1253
|
terms: ConsentFlowTermsValidator,
|
|
1208
1254
|
expiresAt: z.string().optional(),
|
|
1209
1255
|
oneTime: z.boolean().optional(),
|
|
1256
|
+
audienceVersion: z.number().int().nonnegative().optional(),
|
|
1210
1257
|
})
|
|
1211
1258
|
)
|
|
1212
1259
|
.output(z.boolean())
|
|
@@ -1222,7 +1269,7 @@ export const contractsRouter = t.router({
|
|
|
1222
1269
|
});
|
|
1223
1270
|
}
|
|
1224
1271
|
|
|
1225
|
-
const { uri, terms, expiresAt, oneTime } = input;
|
|
1272
|
+
const { uri, terms, expiresAt, oneTime, audienceVersion } = input;
|
|
1226
1273
|
|
|
1227
1274
|
const decodedUri = decodeURIComponent(uri);
|
|
1228
1275
|
const relationship = await getContractTermsByUri(decodedUri);
|
|
@@ -1271,6 +1318,7 @@ export const contractsRouter = t.router({
|
|
|
1271
1318
|
terms,
|
|
1272
1319
|
expiresAt,
|
|
1273
1320
|
oneTime,
|
|
1321
|
+
audienceVersion,
|
|
1274
1322
|
guardianApproval: guardianIdentity
|
|
1275
1323
|
? {
|
|
1276
1324
|
guardianProfileId: guardianIdentity.profileId,
|
|
@@ -1396,7 +1444,7 @@ export const contractsRouter = t.router({
|
|
|
1396
1444
|
});
|
|
1397
1445
|
}
|
|
1398
1446
|
|
|
1399
|
-
await Promise.all([withdrawTerms(relationship), deleteStorageForUri(uri)]);
|
|
1447
|
+
await Promise.all([withdrawTerms(relationship, ctx.domain), deleteStorageForUri(uri)]);
|
|
1400
1448
|
|
|
1401
1449
|
return true;
|
|
1402
1450
|
}),
|
|
@@ -1528,13 +1576,14 @@ export const contractsRouter = t.router({
|
|
|
1528
1576
|
.input(
|
|
1529
1577
|
z.object({
|
|
1530
1578
|
termsUri: z.string(),
|
|
1579
|
+
audienceVersion: z.number().int().nonnegative().optional(),
|
|
1531
1580
|
categories: z.record(z.string(), z.string().array()),
|
|
1532
1581
|
})
|
|
1533
1582
|
)
|
|
1534
1583
|
.output(z.boolean())
|
|
1535
1584
|
.mutation(async ({ ctx, input }) => {
|
|
1536
1585
|
const { profile } = ctx.user;
|
|
1537
|
-
const { termsUri, categories } = input;
|
|
1586
|
+
const { termsUri, categories, audienceVersion } = input;
|
|
1538
1587
|
|
|
1539
1588
|
// Verify the terms exist and belong to this profile
|
|
1540
1589
|
const relationship = await getContractTermsByUri(termsUri);
|
|
@@ -1604,7 +1653,12 @@ export const contractsRouter = t.router({
|
|
|
1604
1653
|
}
|
|
1605
1654
|
|
|
1606
1655
|
// Sync the credentials
|
|
1607
|
-
const result = await syncCredentialsToContract(
|
|
1656
|
+
const result = await syncCredentialsToContract(
|
|
1657
|
+
relationship,
|
|
1658
|
+
categories,
|
|
1659
|
+
audienceVersion,
|
|
1660
|
+
ctx.domain
|
|
1661
|
+
);
|
|
1608
1662
|
|
|
1609
1663
|
return result;
|
|
1610
1664
|
}),
|
|
@@ -1824,6 +1878,49 @@ export const contractsRouter = t.router({
|
|
|
1824
1878
|
return true;
|
|
1825
1879
|
}),
|
|
1826
1880
|
|
|
1881
|
+
sendContractRequest: profileRoute
|
|
1882
|
+
.meta({
|
|
1883
|
+
requiredScope: 'contracts:write',
|
|
1884
|
+
openapi: {
|
|
1885
|
+
protect: true,
|
|
1886
|
+
method: 'POST',
|
|
1887
|
+
path: '/consent-flow-contracts/request',
|
|
1888
|
+
tags: ['Contracts'],
|
|
1889
|
+
summary: 'Send an attributed contract request',
|
|
1890
|
+
},
|
|
1891
|
+
})
|
|
1892
|
+
.input(SendContractRequestValidator)
|
|
1893
|
+
.output(z.boolean())
|
|
1894
|
+
.mutation(async ({ ctx, input }) =>
|
|
1895
|
+
sendGenericContractRequest(input, ctx.user.profile, ctx.domain)
|
|
1896
|
+
),
|
|
1897
|
+
|
|
1898
|
+
denyContractRequest: profileRoute
|
|
1899
|
+
.meta({
|
|
1900
|
+
requiredScope: 'contracts:write',
|
|
1901
|
+
openapi: {
|
|
1902
|
+
protect: true,
|
|
1903
|
+
method: 'POST',
|
|
1904
|
+
path: '/consent-flow-contracts/deny-request',
|
|
1905
|
+
tags: ['Contracts'],
|
|
1906
|
+
summary: 'Deny a pending contract request as its target',
|
|
1907
|
+
},
|
|
1908
|
+
})
|
|
1909
|
+
.input(z.object({ contractUri: z.string() }))
|
|
1910
|
+
.output(z.boolean())
|
|
1911
|
+
.mutation(async ({ ctx, input }) => {
|
|
1912
|
+
const contract = await getContractByUri(input.contractUri);
|
|
1913
|
+
if (!contract)
|
|
1914
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
1915
|
+
return decideGenericContractRequest({
|
|
1916
|
+
contract,
|
|
1917
|
+
targetProfileId: ctx.user.profile.profileId,
|
|
1918
|
+
actorProfileId: ctx.user.profile.profileId,
|
|
1919
|
+
status: 'denied',
|
|
1920
|
+
domain: ctx.domain,
|
|
1921
|
+
});
|
|
1922
|
+
}),
|
|
1923
|
+
|
|
1827
1924
|
sendAiInsightsContractRequest: profileRoute
|
|
1828
1925
|
.meta({
|
|
1829
1926
|
openapi: {
|
|
@@ -1848,43 +1945,14 @@ export const contractsRouter = t.router({
|
|
|
1848
1945
|
const { profile } = ctx.user;
|
|
1849
1946
|
const { contractUri, targetProfileId, shareLink } = input;
|
|
1850
1947
|
|
|
1851
|
-
const
|
|
1948
|
+
const { contract: contractByUri, target: targetProfile } = await resolveContractRequest(
|
|
1949
|
+
contractUri,
|
|
1852
1950
|
targetProfileId,
|
|
1853
|
-
|
|
1951
|
+
profile,
|
|
1952
|
+
ctx.domain,
|
|
1953
|
+
false
|
|
1854
1954
|
);
|
|
1855
|
-
|
|
1856
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Profile not found' });
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
const contractByUri = await getContractByUri(contractUri);
|
|
1860
|
-
|
|
1861
|
-
if (!contractByUri)
|
|
1862
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
1863
|
-
|
|
1864
|
-
const contract = await getConsentFlowContractById(contractByUri.id); // model instance
|
|
1865
|
-
|
|
1866
|
-
if (!contract) {
|
|
1867
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
const writers = await getWritersForContract(contractByUri);
|
|
1871
|
-
const isAuthorized = writers.some(writer => writer.profileId === profile.profileId);
|
|
1872
|
-
|
|
1873
|
-
if (!isAuthorized) {
|
|
1874
|
-
throw new TRPCError({
|
|
1875
|
-
code: 'UNAUTHORIZED',
|
|
1876
|
-
message: 'You do not have permission to send requests for this contract.',
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
const targetProfile = await getProfileByProfileId(resolvedTargetProfileId);
|
|
1881
|
-
|
|
1882
|
-
if (!targetProfile) {
|
|
1883
|
-
throw new TRPCError({
|
|
1884
|
-
code: 'NOT_FOUND',
|
|
1885
|
-
message: 'Target profile not found.',
|
|
1886
|
-
});
|
|
1887
|
-
}
|
|
1955
|
+
const resolvedTargetProfileId = targetProfile.profileId;
|
|
1888
1956
|
|
|
1889
1957
|
try {
|
|
1890
1958
|
await upsertRequestedForRelationship(
|
|
@@ -1894,6 +1962,7 @@ export const contractsRouter = t.router({
|
|
|
1894
1962
|
null
|
|
1895
1963
|
);
|
|
1896
1964
|
} catch (error) {
|
|
1965
|
+
if (error instanceof TRPCError && error.code === 'CONFLICT') throw error;
|
|
1897
1966
|
throw new TRPCError({
|
|
1898
1967
|
code: 'BAD_REQUEST',
|
|
1899
1968
|
message: 'Unable to send request',
|
|
@@ -2026,9 +2095,9 @@ export const contractsRouter = t.router({
|
|
|
2026
2095
|
)
|
|
2027
2096
|
.output(
|
|
2028
2097
|
z.array(
|
|
2029
|
-
|
|
2098
|
+
ContractRequestFieldsValidator.extend({
|
|
2030
2099
|
profile: LCNProfileValidator,
|
|
2031
|
-
status: z.enum(['pending', 'accepted', 'denied']).nullable(),
|
|
2100
|
+
status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
|
|
2032
2101
|
readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
|
|
2033
2102
|
})
|
|
2034
2103
|
)
|
|
@@ -2041,17 +2110,19 @@ export const contractsRouter = t.router({
|
|
|
2041
2110
|
|
|
2042
2111
|
if (!contractByUri)
|
|
2043
2112
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
2044
|
-
const
|
|
2045
|
-
const isAuthorized = writers.some(w => w.profileId === profile.profileId);
|
|
2113
|
+
const isAuthorized = await canManageContractRequests(contractByUri, profile.profileId);
|
|
2046
2114
|
|
|
2047
|
-
if (
|
|
2115
|
+
if (
|
|
2116
|
+
!isAuthorized &&
|
|
2117
|
+
!(await canReadContractData(contractByUri.id, profile.profileId))
|
|
2118
|
+
) {
|
|
2048
2119
|
throw new TRPCError({
|
|
2049
2120
|
code: 'UNAUTHORIZED',
|
|
2050
2121
|
message: 'You do not have permissions to view requests made for this contract.',
|
|
2051
2122
|
});
|
|
2052
2123
|
}
|
|
2053
2124
|
|
|
2054
|
-
const requests = await getRequestedForList(contractByUri.id);
|
|
2125
|
+
const requests = await getRequestedForList(contractByUri.id, profile.profileId);
|
|
2055
2126
|
|
|
2056
2127
|
return Promise.all(
|
|
2057
2128
|
requests.map(async request => {
|
|
@@ -2086,13 +2157,11 @@ export const contractsRouter = t.router({
|
|
|
2086
2157
|
})
|
|
2087
2158
|
)
|
|
2088
2159
|
.output(
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
})
|
|
2095
|
-
.nullable()
|
|
2160
|
+
ContractRequestFieldsValidator.extend({
|
|
2161
|
+
profile: LCNProfileValidator,
|
|
2162
|
+
status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
|
|
2163
|
+
readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
|
|
2164
|
+
}).nullable()
|
|
2096
2165
|
)
|
|
2097
2166
|
.query(async ({ ctx, input }) => {
|
|
2098
2167
|
const { profile } = ctx.user;
|
|
@@ -2119,10 +2188,9 @@ export const contractsRouter = t.router({
|
|
|
2119
2188
|
const isCheckingOwnStatus = profile.profileId === resolvedTargetProfileId;
|
|
2120
2189
|
|
|
2121
2190
|
if (!isCheckingOwnStatus) {
|
|
2122
|
-
const
|
|
2123
|
-
const isAuthorized = writers.some(w => w.profileId === profile.profileId);
|
|
2191
|
+
const isAuthorized = await canManageContractRequests(contract, profile.profileId);
|
|
2124
2192
|
|
|
2125
|
-
if (!isAuthorized) {
|
|
2193
|
+
if (!isAuthorized && !(await canReadContractData(contract.id, profile.profileId))) {
|
|
2126
2194
|
throw new TRPCError({
|
|
2127
2195
|
code: 'UNAUTHORIZED',
|
|
2128
2196
|
message:
|
|
@@ -2131,7 +2199,11 @@ export const contractsRouter = t.router({
|
|
|
2131
2199
|
}
|
|
2132
2200
|
}
|
|
2133
2201
|
|
|
2134
|
-
const requests = await getRequestedForForUser(
|
|
2202
|
+
const requests = await getRequestedForForUser(
|
|
2203
|
+
contract.id,
|
|
2204
|
+
resolvedTargetProfileId,
|
|
2205
|
+
profile.profileId
|
|
2206
|
+
);
|
|
2135
2207
|
|
|
2136
2208
|
if (!requests?.[0]) return null;
|
|
2137
2209
|
|
|
@@ -2154,7 +2226,7 @@ export const contractsRouter = t.router({
|
|
|
2154
2226
|
tags: ['Contracts'],
|
|
2155
2227
|
summary: 'Marks a contract request as seen',
|
|
2156
2228
|
description:
|
|
2157
|
-
'Updates the read status of a contract request to "seen" for the specified target profile.
|
|
2229
|
+
'Updates the read status of a contract request to "seen" for the specified target profile. Targets can mark their existing requests seen. Legacy writer behavior is retained for legacy requests.',
|
|
2158
2230
|
},
|
|
2159
2231
|
})
|
|
2160
2232
|
.input(
|
|
@@ -2181,6 +2253,31 @@ export const contractsRouter = t.router({
|
|
|
2181
2253
|
if (!contractByUri)
|
|
2182
2254
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
2183
2255
|
|
|
2256
|
+
const request = await getStoredContractRequest(
|
|
2257
|
+
contractByUri.id,
|
|
2258
|
+
resolvedTargetProfileId
|
|
2259
|
+
);
|
|
2260
|
+
if (request?.requestId) {
|
|
2261
|
+
assertGenericRequestWriteScope(ctx.user.scope);
|
|
2262
|
+
if (profile.profileId !== resolvedTargetProfileId)
|
|
2263
|
+
throw new TRPCError({
|
|
2264
|
+
code: 'UNAUTHORIZED',
|
|
2265
|
+
message: 'Only the target can mark this request seen',
|
|
2266
|
+
});
|
|
2267
|
+
return markGenericContractRequestSeen(contractByUri.id, resolvedTargetProfileId);
|
|
2268
|
+
}
|
|
2269
|
+
if (profile.profileId === resolvedTargetProfileId) {
|
|
2270
|
+
if (!request)
|
|
2271
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Request not found' });
|
|
2272
|
+
await upsertRequestedForRelationship(
|
|
2273
|
+
contractByUri.id,
|
|
2274
|
+
resolvedTargetProfileId,
|
|
2275
|
+
undefined,
|
|
2276
|
+
'seen'
|
|
2277
|
+
);
|
|
2278
|
+
return true;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2184
2281
|
const writers = await getWritersForContract(contractByUri);
|
|
2185
2282
|
const isAuthorized = writers.some(w => w.profileId === profile.profileId);
|
|
2186
2283
|
|
|
@@ -2217,7 +2314,7 @@ export const contractsRouter = t.router({
|
|
|
2217
2314
|
tags: ['Contracts'],
|
|
2218
2315
|
summary: 'Cancels/removes a contract request',
|
|
2219
2316
|
description:
|
|
2220
|
-
'
|
|
2317
|
+
'Cancels a pending generic request while retaining its history. Owner/writers, the requesting recipient, or the target may cancel. Legacy requests retain deletion behavior.',
|
|
2221
2318
|
},
|
|
2222
2319
|
})
|
|
2223
2320
|
.input(
|
|
@@ -2244,6 +2341,21 @@ export const contractsRouter = t.router({
|
|
|
2244
2341
|
if (!contractByUri)
|
|
2245
2342
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Contract not found' });
|
|
2246
2343
|
|
|
2344
|
+
const request = await getStoredContractRequest(
|
|
2345
|
+
contractByUri.id,
|
|
2346
|
+
resolvedTargetProfileId
|
|
2347
|
+
);
|
|
2348
|
+
if (request?.requestId) {
|
|
2349
|
+
assertGenericRequestWriteScope(ctx.user.scope);
|
|
2350
|
+
return decideGenericContractRequest({
|
|
2351
|
+
contract: contractByUri,
|
|
2352
|
+
targetProfileId: resolvedTargetProfileId,
|
|
2353
|
+
actorProfileId: profile.profileId,
|
|
2354
|
+
status: 'cancelled',
|
|
2355
|
+
domain: ctx.domain,
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2247
2359
|
const isDenyingRequest = profile.profileId === resolvedTargetProfileId;
|
|
2248
2360
|
|
|
2249
2361
|
if (!isDenyingRequest) {
|
|
@@ -2302,10 +2414,15 @@ export const contractsRouter = t.router({
|
|
|
2302
2414
|
)
|
|
2303
2415
|
.output(
|
|
2304
2416
|
z.array(
|
|
2305
|
-
|
|
2306
|
-
contract: ConsentFlowContractValidator.extend({
|
|
2417
|
+
ContractRequestFieldsValidator.extend({
|
|
2418
|
+
contract: ConsentFlowContractValidator.extend({
|
|
2419
|
+
uri: z.string(),
|
|
2420
|
+
name: z.string().optional(),
|
|
2421
|
+
image: z.string().optional(),
|
|
2422
|
+
description: z.string().optional(),
|
|
2423
|
+
}),
|
|
2307
2424
|
profile: LCNProfileValidator,
|
|
2308
|
-
status: z.enum(['pending', 'accepted', 'denied']).nullable(),
|
|
2425
|
+
status: z.enum(['pending', 'accepted', 'denied', 'cancelled']).nullable(),
|
|
2309
2426
|
readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
|
|
2310
2427
|
})
|
|
2311
2428
|
)
|
|
@@ -2345,7 +2462,10 @@ export const contractsRouter = t.router({
|
|
|
2345
2462
|
sanitizeProfileForTier(updatedProfile, 'authenticated')
|
|
2346
2463
|
),
|
|
2347
2464
|
contract: {
|
|
2348
|
-
...request.contract,
|
|
2465
|
+
...inflateObject(request.contract).contract,
|
|
2466
|
+
name: request.contract.name,
|
|
2467
|
+
image: request.contract.image,
|
|
2468
|
+
description: request.contract.description,
|
|
2349
2469
|
uri: constructUri('contract', request.contract.id, ctx.domain),
|
|
2350
2470
|
},
|
|
2351
2471
|
};
|
|
@@ -2372,7 +2492,7 @@ export const contractsRouter = t.router({
|
|
|
2372
2492
|
)
|
|
2373
2493
|
.output(
|
|
2374
2494
|
z.array(
|
|
2375
|
-
|
|
2495
|
+
ContractRequestFieldsValidator.extend({
|
|
2376
2496
|
profile: LCNProfileValidator,
|
|
2377
2497
|
status: z.enum(['pending', 'accepted', 'denied']).nullable(),
|
|
2378
2498
|
readStatus: z.enum(['unseen', 'seen']).nullable().optional(),
|