@learncard/network-brain-service 3.17.0 → 3.19.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 +4 -0
- package/AGENTS.md +177 -159
- package/CHANGELOG.md +148 -0
- package/README.md +26 -8
- package/compose.inbox.yml +7 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/auth-grant/read.d.ts +1 -0
- package/dist/accesslayer/auth-grant/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +5 -3
- 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/create.test.d.ts +2 -0
- package/dist/accesslayer/credential/create.test.d.ts.map +1 -0
- package/dist/accesslayer/credential/relationships/read.d.ts +1 -0
- package/dist/accesslayer/credential/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-activity/create.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts +18 -0
- package/dist/accesslayer/credential-refresh/email-delivery.d.ts.map +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts +1 -0
- package/dist/accesslayer/credential-refresh/index.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/read.d.ts.map +1 -1
- package/dist/accesslayer/credential-refresh/update.d.ts.map +1 -1
- package/dist/accesslayer/inbox-batch/store.d.ts +33 -0
- package/dist/accesslayer/inbox-batch/store.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/create.d.ts +5 -0
- package/dist/accesslayer/inbox-credential/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/refresh.d.ts +71 -0
- package/dist/accesslayer/inbox-credential/refresh.d.ts.map +1 -0
- package/dist/accesslayer/inbox-credential/update.d.ts +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/cache/atomic-counter.test-helpers.d.ts +3 -0
- package/dist/cache/atomic-counter.test-helpers.d.ts.map +1 -0
- package/dist/cache/atomic-counter.test.d.ts +2 -0
- package/dist/cache/atomic-counter.test.d.ts.map +1 -0
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/config/environment.d.ts +46 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/helpers/activity.helpers.d.ts +4 -0
- package/dist/helpers/activity.helpers.d.ts.map +1 -1
- package/dist/helpers/boost-storage.helpers.test.d.ts +2 -0
- package/dist/helpers/boost-storage.helpers.test.d.ts.map +1 -0
- package/dist/helpers/boost.helpers.d.ts +3 -7
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.d.ts.map +1 -1
- package/dist/helpers/contact-method.helpers.test.d.ts +2 -0
- package/dist/helpers/contact-method.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts +44 -0
- package/dist/helpers/credential-refresh-email-content.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-email-content.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts +17 -0
- package/dist/helpers/credential-refresh-email.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts +4 -0
- package/dist/helpers/credential-refresh-proof.helpers.d.ts.map +1 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts +2 -0
- package/dist/helpers/credential-refresh-proof.helpers.test.d.ts.map +1 -0
- package/dist/helpers/credential-refresh.helpers.d.ts +45 -2
- package/dist/helpers/credential-refresh.helpers.d.ts.map +1 -1
- package/dist/helpers/credential.helpers.d.ts +4 -2
- package/dist/helpers/credential.helpers.d.ts.map +1 -1
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox-batch-http.helpers.d.ts +15 -0
- package/dist/helpers/inbox-batch-http.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch-http.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts +3 -0
- package/dist/helpers/inbox-batch-status.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.d.ts +10 -0
- package/dist/helpers/inbox-batch.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-batch.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts +13 -0
- package/dist/helpers/inbox-issuance-error.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.d.ts +11 -0
- package/dist/helpers/inbox-queue.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts +2 -0
- package/dist/helpers/inbox-queue.helpers.test.d.ts.map +1 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts +186 -0
- package/dist/helpers/inbox-refresh.helpers.d.ts.map +1 -0
- package/dist/helpers/inbox.helpers.d.ts +9 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/issuedCredentialStatus.helpers.d.ts +5 -0
- package/dist/helpers/issuedCredentialStatus.helpers.d.ts.map +1 -0
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/rateLimit.helpers.d.ts.map +1 -1
- package/dist/helpers/refresh-send-intent.helpers.d.ts +51 -0
- package/dist/helpers/refresh-send-intent.helpers.d.ts.map +1 -0
- package/dist/helpers/shim.d.ts +1 -1
- package/dist/helpers/shim.d.ts.map +1 -1
- package/dist/helpers/signingAuthority.helpers.d.ts +3 -2
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/signingAuthority.helpers.test.d.ts +2 -0
- package/dist/helpers/signingAuthority.helpers.test.d.ts.map +1 -0
- package/dist/helpers/status-list.helpers.d.ts.map +1 -1
- package/dist/helpers/status-list.helpers.test.d.ts +2 -0
- package/dist/helpers/status-list.helpers.test.d.ts.map +1 -0
- package/dist/inbox-queue-local.d.ts +2 -0
- package/dist/inbox-queue-local.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AuthGrant.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Credential.d.ts +1 -0
- package/dist/models/Credential.d.ts.map +1 -1
- package/dist/models/CredentialActivity.d.ts +1 -0
- package/dist/models/CredentialActivity.d.ts.map +1 -1
- package/dist/models/CredentialRefresh.d.ts +5 -2
- package/dist/models/CredentialRefresh.d.ts.map +1 -1
- package/dist/models/InboxCredential.d.ts +1 -0
- package/dist/models/InboxCredential.d.ts.map +1 -1
- package/dist/models/credential-refresh-constraints.d.ts.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/routes/act-as.test.d.ts +2 -0
- package/dist/routes/act-as.test.d.ts.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/auth-grants.d.ts +10 -0
- package/dist/routes/auth-grants.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +506 -12
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +48 -2
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credential-refreshes.d.ts +4 -4
- package/dist/routes/credential-refreshes.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +8 -2
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/did-metadata.d.ts +8 -8
- package/dist/routes/federation.d.ts +2 -2
- package/dist/routes/inbox.d.ts +973 -31
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +47 -127
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +6 -6
- package/dist/routes/storage.d.ts +63 -63
- package/dist/routes/test.d.ts +6 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts +14 -14
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts +2 -0
- package/dist/services/delivery/adapters/postmark.adapter.test.d.ts.map +1 -0
- package/dist/services/registry/registry.factory.d.ts.map +1 -1
- package/dist/types/activity.d.ts +4 -0
- package/dist/types/activity.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +22 -8
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +10 -1
- package/dist/types/credential-refresh.d.ts.map +1 -1
- package/dist/types/credential.d.ts +7 -0
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/did-metadata.d.ts +2 -2
- package/dist/types/inbox-batch.d.ts +38 -0
- package/dist/types/inbox-batch.d.ts.map +1 -0
- package/inbox-e2e-setup.ts +53 -1
- package/inbox-elasticmq.conf +10 -0
- package/lambda.ts +61 -16
- package/package.json +18 -16
- package/scripts/setup-inbox-queues.ts +60 -0
- package/serverless-local.yml +2 -0
- package/serverless.yml +75 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +4 -3
- package/src/accesslayer/auth-grant/read.ts +2 -1
- package/src/accesslayer/consentflowcontract/relationships/create.ts +15 -5
- package/src/accesslayer/consentflowcontract/relationships/read.ts +161 -63
- package/src/accesslayer/consentflowcontract/relationships/update.ts +43 -10
- package/src/accesslayer/credential/create.test.ts +141 -0
- package/src/accesslayer/credential/create.ts +27 -3
- package/src/accesslayer/credential-activity/create.ts +1 -0
- package/src/accesslayer/credential-refresh/email-delivery.ts +116 -0
- package/src/accesslayer/credential-refresh/index.ts +1 -0
- package/src/accesslayer/credential-refresh/read.ts +8 -2
- package/src/accesslayer/credential-refresh/update.ts +4 -0
- package/src/accesslayer/inbox-batch/store.ts +469 -0
- package/src/accesslayer/inbox-credential/create.ts +51 -19
- package/src/accesslayer/inbox-credential/refresh.ts +221 -0
- package/src/accesslayer/inbox-credential/update.ts +1 -1
- package/src/cache/atomic-counter.test-helpers.ts +27 -0
- package/src/cache/atomic-counter.test.ts +6 -0
- package/src/cache/index.ts +25 -13
- package/src/config/environment.ts +26 -0
- package/src/credential-refresh.ts +5 -1
- package/src/docker-entry.ts +8 -0
- package/src/helpers/activity.helpers.ts +4 -0
- package/src/helpers/app-counter.helpers.ts +1 -1
- package/src/helpers/boost-storage.helpers.test.ts +242 -0
- package/src/helpers/boost.helpers.ts +58 -437
- package/src/helpers/contact-method.helpers.test.ts +43 -0
- package/src/helpers/contact-method.helpers.ts +3 -4
- package/src/helpers/credential-refresh-email-content.helpers.test.ts +222 -0
- package/src/helpers/credential-refresh-email-content.helpers.ts +155 -0
- package/src/helpers/credential-refresh-email.helpers.ts +201 -0
- package/src/helpers/credential-refresh-proof.helpers.test.ts +103 -0
- package/src/helpers/credential-refresh-proof.helpers.ts +43 -0
- package/src/helpers/credential-refresh.helpers.ts +385 -107
- package/src/helpers/credential.helpers.ts +6 -2
- package/src/helpers/finalize-inbox.helpers.ts +39 -22
- package/src/helpers/inbox-batch-http.helpers.test.ts +188 -0
- package/src/helpers/inbox-batch-http.helpers.ts +150 -0
- package/src/helpers/inbox-batch-status.helpers.ts +11 -0
- package/src/helpers/inbox-batch.helpers.test.ts +668 -0
- package/src/helpers/inbox-batch.helpers.ts +374 -0
- package/src/helpers/inbox-issuance-error.helpers.ts +25 -0
- package/src/helpers/inbox-queue.helpers.test.ts +253 -0
- package/src/helpers/inbox-queue.helpers.ts +372 -0
- package/src/helpers/inbox-refresh.helpers.ts +432 -0
- package/src/helpers/inbox.helpers.ts +301 -50
- package/src/helpers/issuedCredentialStatus.helpers.ts +11 -0
- package/src/helpers/notifications.helpers.ts +4 -2
- package/src/helpers/rateLimit.helpers.ts +2 -5
- package/src/helpers/refresh-send-intent.helpers.ts +273 -0
- package/src/helpers/shim.ts +32 -31
- package/src/helpers/signingAuthority.helpers.test.ts +198 -0
- package/src/helpers/signingAuthority.helpers.ts +41 -18
- package/src/helpers/status-list.helpers.test.ts +77 -0
- package/src/helpers/status-list.helpers.ts +31 -8
- package/src/inbox-queue-local.ts +28 -0
- package/src/models/AuthGrant.ts +1 -0
- package/src/models/ConsentFlowTerms.ts +4 -0
- package/src/models/ConsentFlowTransaction.ts +4 -0
- package/src/models/Credential.ts +1 -0
- package/src/models/CredentialActivity.ts +3 -5
- package/src/models/CredentialRefresh.ts +4 -2
- package/src/models/InboxCredential.ts +2 -0
- package/src/models/credential-refresh-constraints.test.ts +8 -3
- package/src/models/credential-refresh-constraints.ts +5 -0
- package/src/openapi.ts +8 -0
- package/src/routes/act-as.test.ts +239 -0
- package/src/routes/app-store.ts +69 -52
- package/src/routes/auth-grants.ts +13 -11
- package/src/routes/boosts.ts +943 -120
- package/src/routes/contracts.ts +55 -7
- package/src/routes/credential-refreshes.test.ts +8 -2
- package/src/routes/credential-refreshes.ts +4 -1
- package/src/routes/credentials.ts +2 -1
- package/src/routes/inbox.ts +95 -49
- package/src/routes/index.ts +164 -88
- package/src/routes/skills.ts +1 -1
- package/src/routes/workflows.ts +80 -31
- package/src/services/delivery/adapters/postmark.adapter.test.ts +54 -0
- package/src/services/delivery/adapters/postmark.adapter.ts +12 -0
- package/src/services/registry/registry.factory.ts +2 -1
- package/src/types/activity.ts +2 -0
- package/src/types/consentflowcontract.ts +21 -3
- package/src/types/credential-refresh.ts +21 -2
- package/src/types/credential.ts +13 -1
- package/src/types/inbox-batch.ts +46 -0
- package/test/app-store-listing.spec.ts +66 -30
- package/test/atomic-counter.e2e.ts +12 -0
- package/test/auth-grant.spec.ts +21 -21
- package/test/bitstring-status-list.spec.ts +12 -9
- package/test/consentflow.spec.ts +453 -26
- package/test/credential-refresh-email.spec.ts +663 -0
- package/test/credential-refresh-publication.spec.ts +13 -5
- package/test/guardian-gated-routes.spec.ts +337 -180
- package/test/helpers/inbox-e2e-environment.ts +6 -0
- package/test/inbox-batch.spec.ts +1338 -0
- package/test/inbox-refresh.spec.ts +534 -0
- package/test/inbox-security.e2e.ts +48 -3
- package/test/openapi.spec.ts +41 -1
- package/test/refresh-send.spec.ts +1799 -0
- package/test/registry.spec.ts +56 -11
- package/test/revoke-suspend-per-instance.spec.ts +9 -1
- package/vitest.config.ts +13 -0
- package/vitest.inbox-e2e.config.ts +5 -1
- package/vitest.integration.config.ts +2 -0
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
} from '@models';
|
|
16
16
|
import {
|
|
17
17
|
DbTermsType,
|
|
18
|
+
DbTermsValidator,
|
|
18
19
|
FlatDbTermsType,
|
|
19
20
|
DbContractType,
|
|
20
21
|
FlatDbContractType,
|
|
22
|
+
DbContractValidator,
|
|
21
23
|
FlatDbTransactionType,
|
|
22
24
|
DbTransactionType,
|
|
23
25
|
} from 'types/consentflowcontract';
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
HolderExportMetadata,
|
|
33
35
|
LCNProfile,
|
|
34
36
|
} from '@learncard/types';
|
|
37
|
+
import { ConsentFlowGuardianApprovalValidator } from '@learncard/types';
|
|
35
38
|
import { FlatBoostType } from 'types/boost';
|
|
36
39
|
import { constructUri, getIdFromUri } from '@helpers/uri.helpers';
|
|
37
40
|
import { flattenObject, inflateObject } from '@helpers/objects.helpers';
|
|
@@ -40,6 +43,8 @@ import { ProfileType } from 'types/profile';
|
|
|
40
43
|
import { CredentialType } from 'types/credential';
|
|
41
44
|
import { getBoostUri } from '@helpers/boost.helpers';
|
|
42
45
|
import { getCredentialUri } from '@helpers/credential.helpers';
|
|
46
|
+
import { getProfilesThatManageAProfile } from '@accesslayer/profile/relationships/read';
|
|
47
|
+
import { getDidWeb } from '@helpers/did.helpers';
|
|
43
48
|
|
|
44
49
|
export const isProfileConsentFlowContractAdmin = async (
|
|
45
50
|
profile: ProfileType,
|
|
@@ -80,6 +85,25 @@ export const hasProfileConsentedToContract = async (
|
|
|
80
85
|
return Number(result.records[0]?.get('count') ?? 0) > 0;
|
|
81
86
|
};
|
|
82
87
|
|
|
88
|
+
export const hasGuardianApprovalHistory = async (terms: DbTermsType): Promise<boolean> => {
|
|
89
|
+
if (terms.guardianApproval) return true;
|
|
90
|
+
|
|
91
|
+
const result = await new QueryBuilder()
|
|
92
|
+
.match({
|
|
93
|
+
related: [
|
|
94
|
+
{ identifier: 'transaction', model: ConsentFlowTransaction },
|
|
95
|
+
ConsentFlowTransaction.getRelationshipByAlias('isFor'),
|
|
96
|
+
{ model: ConsentFlowTerms, where: { id: terms.id } },
|
|
97
|
+
],
|
|
98
|
+
})
|
|
99
|
+
.where('transaction.`guardianApproval.guardianProfileId` IS NOT NULL')
|
|
100
|
+
.return('transaction.id')
|
|
101
|
+
.limit(1)
|
|
102
|
+
.run();
|
|
103
|
+
|
|
104
|
+
return result.records.length > 0;
|
|
105
|
+
};
|
|
106
|
+
|
|
83
107
|
export const getContractDetailsById = async (
|
|
84
108
|
id: string
|
|
85
109
|
): Promise<{ contractOwner: LCNProfile; contract: DbContractType } | null> => {
|
|
@@ -135,7 +159,7 @@ export const getContractTermsForProfile = async (
|
|
|
135
159
|
.run()
|
|
136
160
|
);
|
|
137
161
|
|
|
138
|
-
return result.length > 0 ? inflateObject
|
|
162
|
+
return result.length > 0 ? DbTermsValidator.parse(inflateObject(result[0]!.terms)) : null;
|
|
139
163
|
};
|
|
140
164
|
|
|
141
165
|
export const getContractTermsById = async (
|
|
@@ -516,6 +540,13 @@ all(key IN keys($params) WHERE
|
|
|
516
540
|
}));
|
|
517
541
|
};
|
|
518
542
|
|
|
543
|
+
// Empty expiry values historically mean no expiry. Invalid nonempty values never authorize access.
|
|
544
|
+
const isConsentExpiryActive = (expiresAt: string | undefined, now: number): boolean => {
|
|
545
|
+
if (!expiresAt?.trim()) return true;
|
|
546
|
+
const expiry = Date.parse(expiresAt);
|
|
547
|
+
return Number.isFinite(expiry) && expiry > now;
|
|
548
|
+
};
|
|
549
|
+
|
|
519
550
|
export const getConsentedDataBetweenProfiles = async (
|
|
520
551
|
ownerProfileId: string,
|
|
521
552
|
consenterProfileId: string,
|
|
@@ -530,29 +561,40 @@ export const getConsentedDataBetweenProfiles = async (
|
|
|
530
561
|
const convertedContractQuery = id ? convertObjectRegExpToNeo4j({ id }) : {};
|
|
531
562
|
const { whereClause: contractWhereClause, params: contractQueryParams } =
|
|
532
563
|
buildWhereForQueryBuilder('contract', convertedContractQuery as any);
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
564
|
+
const now = Date.now();
|
|
565
|
+
const managers = await getProfilesThatManageAProfile(consenterProfileId);
|
|
566
|
+
const records: ConsentFlowContractDataForDid[] = [];
|
|
567
|
+
const batchSize = Math.max(limit, 50);
|
|
568
|
+
let offset = 0;
|
|
569
|
+
|
|
570
|
+
// Filter dates after parsing, but fetch bounded batches so invalid or expired grants
|
|
571
|
+
// cannot truncate an otherwise full page or require loading every grant at once.
|
|
572
|
+
while (records.length < limit) {
|
|
573
|
+
const _dbQuery = new QueryBuilder(
|
|
574
|
+
new BindParam({
|
|
575
|
+
params: flattenObject({
|
|
576
|
+
terms: flattenObject(convertDataQueryToNeo4jQuery(params)),
|
|
577
|
+
}),
|
|
578
|
+
cursor,
|
|
579
|
+
...contractQueryParams,
|
|
580
|
+
})
|
|
581
|
+
).match({
|
|
582
|
+
related: [
|
|
583
|
+
{ model: Profile, where: { profileId: consenterProfileId } },
|
|
584
|
+
ConsentFlowTerms.getRelationshipByAlias('createdBy'),
|
|
585
|
+
{ identifier: 'terms', model: ConsentFlowTerms },
|
|
586
|
+
ConsentFlowTerms.getRelationshipByAlias('consentsTo'),
|
|
587
|
+
{ model: ConsentFlowContract, identifier: 'contract' },
|
|
588
|
+
ConsentFlowContract.getRelationshipByAlias('createdBy'),
|
|
589
|
+
{ model: Profile, where: { profileId: ownerProfileId } },
|
|
590
|
+
],
|
|
591
|
+
// The following is custom Cypher logic that has the following behavior:
|
|
592
|
+
// If query key is true, ensure all resulting terms have that key
|
|
593
|
+
// If query key is false, ensure no resulting terms have that key
|
|
594
|
+
// If query key is not present, return all terms whether they have it or not
|
|
595
|
+
}).where(`
|
|
596
|
+
(terms.status = 'live' OR (terms.status = 'stale' AND terms.oneTime = true))
|
|
597
|
+
AND all(key IN keys($params) WHERE
|
|
556
598
|
CASE $params[key]
|
|
557
599
|
WHEN true THEN terms[key] IS NOT NULL AND terms[key] <> []
|
|
558
600
|
WHEN false THEN terms[key] IS NULL OR terms[key] = []
|
|
@@ -561,47 +603,103 @@ all(key IN keys($params) WHERE
|
|
|
561
603
|
AND ${contractWhereClause}
|
|
562
604
|
`);
|
|
563
605
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
.
|
|
573
|
-
.
|
|
574
|
-
.
|
|
575
|
-
|
|
606
|
+
const dbQuery = _dbQuery.match({
|
|
607
|
+
optional: true,
|
|
608
|
+
related: [
|
|
609
|
+
{ identifier: 'transaction', model: ConsentFlowTransaction },
|
|
610
|
+
ConsentFlowTransaction.getRelationshipByAlias('isFor'),
|
|
611
|
+
{ identifier: 'terms' },
|
|
612
|
+
],
|
|
613
|
+
}).with(`terms, contract,
|
|
614
|
+
coalesce(terms.createdAt, min(CASE WHEN transaction.action = 'consent' THEN transaction.date END)) AS createdAt,
|
|
615
|
+
coalesce(terms.updatedAt, max(transaction.date), terms.createdAt) AS date,
|
|
616
|
+
count(transaction.\`guardianApproval.guardianProfileId\`) > 0 AS hadGuardianApproval`);
|
|
617
|
+
|
|
618
|
+
if (cursor) dbQuery.where('date < $cursor');
|
|
619
|
+
|
|
620
|
+
const results = convertQueryResultToPropertiesObjectArray<{
|
|
621
|
+
terms: FlatDbTermsType;
|
|
622
|
+
contract: FlatDbContractType;
|
|
623
|
+
createdAt: string | null;
|
|
624
|
+
date: string;
|
|
625
|
+
hadGuardianApproval: boolean;
|
|
626
|
+
}>(
|
|
627
|
+
await dbQuery
|
|
628
|
+
.return('terms, contract, createdAt, date, hadGuardianApproval')
|
|
629
|
+
.orderBy('date DESC, terms.id ASC')
|
|
630
|
+
.skip(offset)
|
|
631
|
+
.limit(batchSize)
|
|
632
|
+
.run()
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
for (const result of results) {
|
|
636
|
+
const parsedTerm = DbTermsValidator.safeParse(inflateObject(result.terms));
|
|
637
|
+
const parsedContract = DbContractValidator.safeParse(inflateObject(result.contract));
|
|
638
|
+
if (!parsedTerm.success || !parsedContract.success) {
|
|
639
|
+
// Never log stored consent data, identifiers, or raw validation errors.
|
|
640
|
+
console.warn('consented_data: invalid_stored_grant');
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const term = parsedTerm.data;
|
|
644
|
+
const contract = parsedContract.data;
|
|
645
|
+
if (
|
|
646
|
+
!isConsentExpiryActive(term.expiresAt, now) ||
|
|
647
|
+
!isConsentExpiryActive(contract.expiresAt, now)
|
|
648
|
+
)
|
|
649
|
+
continue;
|
|
650
|
+
|
|
651
|
+
const parsedApproval = ConsentFlowGuardianApprovalValidator.safeParse(
|
|
652
|
+
term.guardianApproval
|
|
653
|
+
);
|
|
654
|
+
const approval = parsedApproval.success ? parsedApproval.data : undefined;
|
|
655
|
+
const required =
|
|
656
|
+
managers.length > 0 || !!term.guardianApproval || result.hadGuardianApproval;
|
|
657
|
+
const approved =
|
|
658
|
+
!!approval &&
|
|
659
|
+
approval.contractUpdatedAt === contract.updatedAt &&
|
|
660
|
+
managers.some(
|
|
661
|
+
manager =>
|
|
662
|
+
manager.profileId === approval.guardianProfileId &&
|
|
663
|
+
(manager.did === approval.guardianDid ||
|
|
664
|
+
getDidWeb(domain, manager.profileId) === approval.guardianDid)
|
|
665
|
+
);
|
|
576
666
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
667
|
+
records.push({
|
|
668
|
+
date: result.date,
|
|
669
|
+
...(result.createdAt ? { createdAt: result.createdAt } : {}),
|
|
670
|
+
contractUpdatedAt: contract.updatedAt,
|
|
671
|
+
...(contract.expiresAt?.trim() ? { contractExpiresAt: contract.expiresAt } : {}),
|
|
672
|
+
...(contract.reasonForAccessing !== undefined
|
|
673
|
+
? { reasonForAccessing: contract.reasonForAccessing }
|
|
674
|
+
: {}),
|
|
675
|
+
guardian: { required, approved, ...(approval ? { approval } : {}) },
|
|
676
|
+
contractUri: constructUri('contract', contract.id, domain),
|
|
677
|
+
termsUri: constructUri('terms', term.id, domain),
|
|
678
|
+
status: term.status,
|
|
679
|
+
...(term.expiresAt?.trim() ? { expiresAt: term.expiresAt } : {}),
|
|
680
|
+
terms: term.terms,
|
|
681
|
+
credentials: Object.entries(term.terms.read.credentials.categories).flatMap(
|
|
682
|
+
([category, { shared, sharing, shareUntil }]) => {
|
|
683
|
+
if (
|
|
684
|
+
term.terms.read.credentials.sharing === false ||
|
|
685
|
+
!sharing ||
|
|
686
|
+
!isConsentExpiryActive(shareUntil, now) ||
|
|
687
|
+
!shouldIncludeCategory(params.credentials?.categories, category)
|
|
688
|
+
)
|
|
689
|
+
return [];
|
|
690
|
+
|
|
691
|
+
return shared?.map(uri => ({ category, uri })) ?? [];
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
personal: term.terms.read.personal,
|
|
695
|
+
});
|
|
696
|
+
if (records.length >= limit) return records;
|
|
697
|
+
}
|
|
698
|
+
if (results.length < batchSize) break;
|
|
699
|
+
offset += batchSize;
|
|
700
|
+
}
|
|
581
701
|
|
|
582
|
-
return
|
|
583
|
-
date: term.updatedAt!,
|
|
584
|
-
contractUri: constructUri('contract', contract.id, domain),
|
|
585
|
-
credentials: Object.entries(term.terms.read.credentials.categories)
|
|
586
|
-
.flatMap(([category, { shared, sharing, shareUntil }]) => {
|
|
587
|
-
if (
|
|
588
|
-
!sharing ||
|
|
589
|
-
(shareUntil && new Date(shareUntil) < new Date()) ||
|
|
590
|
-
!shouldIncludeCategory(params.credentials?.categories, category)
|
|
591
|
-
) {
|
|
592
|
-
return false as any as { category: string; uri: string };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return (
|
|
596
|
-
shared?.map(uri => ({
|
|
597
|
-
category: category,
|
|
598
|
-
uri,
|
|
599
|
-
})) ?? []
|
|
600
|
-
);
|
|
601
|
-
})
|
|
602
|
-
.filter(Boolean),
|
|
603
|
-
personal: term.terms.read.personal,
|
|
604
|
-
}));
|
|
702
|
+
return records;
|
|
605
703
|
};
|
|
606
704
|
|
|
607
705
|
export const getTransactionsForTerms = async (
|
|
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
3
3
|
import {
|
|
4
4
|
ConsentFlowTerms as ConsentFlowTermsType,
|
|
5
5
|
ConsentFlowTransaction as ConsentFlowTransactionType,
|
|
6
|
+
ConsentFlowGuardianApproval,
|
|
6
7
|
LCNNotificationTypeEnumValidator,
|
|
7
8
|
LCNProfile,
|
|
8
9
|
VC,
|
|
@@ -34,10 +35,12 @@ export const reconsentTerms = async (
|
|
|
34
35
|
terms,
|
|
35
36
|
expiresAt,
|
|
36
37
|
oneTime,
|
|
38
|
+
guardianApproval,
|
|
37
39
|
}: {
|
|
38
40
|
terms: ConsentFlowTermsType;
|
|
39
41
|
expiresAt?: string;
|
|
40
42
|
oneTime?: boolean;
|
|
43
|
+
guardianApproval?: ConsentFlowGuardianApproval;
|
|
41
44
|
},
|
|
42
45
|
domain: string
|
|
43
46
|
): Promise<boolean> => {
|
|
@@ -49,15 +52,27 @@ export const reconsentTerms = async (
|
|
|
49
52
|
...(typeof oneTime === 'boolean' ? { oneTime } : {}),
|
|
50
53
|
} as const satisfies ConsentFlowTransactionType;
|
|
51
54
|
|
|
55
|
+
const existingFlat = flattenObject({
|
|
56
|
+
terms: relationship.terms.terms,
|
|
57
|
+
guardianApproval: relationship.terms.guardianApproval,
|
|
58
|
+
});
|
|
59
|
+
const newFlat = flattenObject({ terms, ...(guardianApproval ? { guardianApproval } : {}) });
|
|
60
|
+
const removedProperties = Object.fromEntries(
|
|
61
|
+
Object.keys(existingFlat)
|
|
62
|
+
.filter(key => !(key in newFlat))
|
|
63
|
+
.map(key => [key, null])
|
|
64
|
+
);
|
|
65
|
+
|
|
52
66
|
const result = await new QueryBuilder(
|
|
53
67
|
new BindParam({
|
|
54
|
-
params:
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
params: {
|
|
69
|
+
...newFlat,
|
|
70
|
+
...removedProperties,
|
|
71
|
+
updatedAt: transaction.date,
|
|
57
72
|
status: oneTime ? 'stale' : 'live',
|
|
58
73
|
...(typeof expiresAt === 'string' ? { expiresAt } : {}),
|
|
59
74
|
...(typeof oneTime === 'boolean' ? { oneTime } : {}),
|
|
60
|
-
}
|
|
75
|
+
},
|
|
61
76
|
})
|
|
62
77
|
)
|
|
63
78
|
.match({
|
|
@@ -167,7 +182,10 @@ export const reconsentTerms = async (
|
|
|
167
182
|
boostCredential,
|
|
168
183
|
contractOwnerSigningAuthority,
|
|
169
184
|
domain,
|
|
170
|
-
|
|
185
|
+
true,
|
|
186
|
+
undefined,
|
|
187
|
+
true,
|
|
188
|
+
[getDidWeb(domain, relationship.contractOwner.profileId)]
|
|
171
189
|
);
|
|
172
190
|
|
|
173
191
|
// Create transaction to record the boost issuance
|
|
@@ -244,7 +262,13 @@ export const updateTerms = async (
|
|
|
244
262
|
terms,
|
|
245
263
|
expiresAt,
|
|
246
264
|
oneTime,
|
|
247
|
-
|
|
265
|
+
guardianApproval,
|
|
266
|
+
}: {
|
|
267
|
+
terms: ConsentFlowTermsType;
|
|
268
|
+
expiresAt?: string;
|
|
269
|
+
oneTime?: boolean;
|
|
270
|
+
guardianApproval?: ConsentFlowGuardianApproval;
|
|
271
|
+
},
|
|
248
272
|
domain: string
|
|
249
273
|
): Promise<boolean> => {
|
|
250
274
|
const transaction = {
|
|
@@ -260,8 +284,14 @@ export const updateTerms = async (
|
|
|
260
284
|
/* -------------------------------------------------------------------------- */
|
|
261
285
|
|
|
262
286
|
// 1. Flatten both the existing stored terms and the new terms we are saving
|
|
263
|
-
const existingFlat = flattenObject({
|
|
264
|
-
|
|
287
|
+
const existingFlat = flattenObject({
|
|
288
|
+
terms: relationship.terms.terms,
|
|
289
|
+
guardianApproval: relationship.terms.guardianApproval,
|
|
290
|
+
});
|
|
291
|
+
const newFlatInner = flattenObject({
|
|
292
|
+
terms,
|
|
293
|
+
...(guardianApproval ? { guardianApproval } : {}),
|
|
294
|
+
});
|
|
265
295
|
|
|
266
296
|
// 2. Determine keys that are present in the existing node but NOT in the new update
|
|
267
297
|
const keysToRemove = Object.keys(existingFlat).filter(key => !(key in newFlatInner));
|
|
@@ -269,7 +299,7 @@ export const updateTerms = async (
|
|
|
269
299
|
// 3. Build a params object: keys for new/updated properties + keys to delete (set to null)
|
|
270
300
|
const paramsForSet = {
|
|
271
301
|
...newFlatInner,
|
|
272
|
-
updatedAt:
|
|
302
|
+
updatedAt: transaction.date,
|
|
273
303
|
status: oneTime ? 'stale' : 'live',
|
|
274
304
|
...(typeof expiresAt === 'string' ? { expiresAt } : {}),
|
|
275
305
|
...(typeof oneTime === 'boolean' ? { oneTime } : {}),
|
|
@@ -382,7 +412,10 @@ export const updateTerms = async (
|
|
|
382
412
|
boostCredential,
|
|
383
413
|
contractOwnerSigningAuthority,
|
|
384
414
|
domain,
|
|
385
|
-
|
|
415
|
+
true,
|
|
416
|
+
undefined,
|
|
417
|
+
true,
|
|
418
|
+
[getDidWeb(domain, relationship.contractOwner.profileId)]
|
|
386
419
|
);
|
|
387
420
|
|
|
388
421
|
// Create transaction to record the boost issuance
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { JWE, VC } from '@learncard/types';
|
|
3
|
+
import type { IssuedCredential } from '../../types/credential';
|
|
4
|
+
|
|
5
|
+
const mocks = vi.hoisted(() => ({ createOne: vi.fn(async input => input) }));
|
|
6
|
+
vi.mock('@models', () => ({ Credential: { createOne: mocks.createOne } }));
|
|
7
|
+
|
|
8
|
+
import { storeCredential } from './create';
|
|
9
|
+
|
|
10
|
+
const statusEntry = {
|
|
11
|
+
id: 'https://network.example/status/1#42',
|
|
12
|
+
type: 'BitstringStatusListEntry' as const,
|
|
13
|
+
statusPurpose: 'revocation' as const,
|
|
14
|
+
statusListIndex: '42',
|
|
15
|
+
statusListCredential: 'https://network.example/status/1',
|
|
16
|
+
};
|
|
17
|
+
const createJwe = (): JWE => ({
|
|
18
|
+
protected: 'header',
|
|
19
|
+
iv: 'iv',
|
|
20
|
+
ciphertext: 'ciphertext',
|
|
21
|
+
tag: 'tag',
|
|
22
|
+
recipients: [],
|
|
23
|
+
});
|
|
24
|
+
const createVc = (): VC => ({
|
|
25
|
+
'@context': ['https://www.w3.org/2018/credentials/v1'],
|
|
26
|
+
type: ['VerifiableCredential'],
|
|
27
|
+
issuer: 'did:example:issuer',
|
|
28
|
+
issuanceDate: '2026-01-01T00:00:00Z',
|
|
29
|
+
credentialSubject: { id: 'did:example:subject' },
|
|
30
|
+
proof: {
|
|
31
|
+
type: 'Ed25519Signature2020',
|
|
32
|
+
created: '2026-01-01T00:00:00Z',
|
|
33
|
+
proofPurpose: 'assertionMethod',
|
|
34
|
+
verificationMethod: 'did:example:issuer#key-1',
|
|
35
|
+
proofValue: 'existing-signature',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('credential status storage', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.restoreAllMocks();
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('persists public status coordinates after serializing an issuance result', async () => {
|
|
46
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
47
|
+
const jwe = createJwe();
|
|
48
|
+
const issued: IssuedCredential = {
|
|
49
|
+
kind: 'issued-credential',
|
|
50
|
+
credential: jwe,
|
|
51
|
+
statusEntries: [statusEntry],
|
|
52
|
+
};
|
|
53
|
+
await storeCredential(JSON.parse(JSON.stringify(issued)));
|
|
54
|
+
expect(mocks.createOne).toHaveBeenCalledWith(
|
|
55
|
+
expect.objectContaining({
|
|
56
|
+
credential: JSON.stringify(jwe),
|
|
57
|
+
statusEntries: JSON.stringify([statusEntry]),
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
expect(warn).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('warns when a client-encrypted JWE has no metadata, without logging its payload', async () => {
|
|
64
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
65
|
+
const original = createJwe();
|
|
66
|
+
const jwe = { ...original };
|
|
67
|
+
await storeCredential(jwe);
|
|
68
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
69
|
+
expect.stringContaining('Encrypted credential has no status metadata'),
|
|
70
|
+
{ credentialId: expect.any(String) }
|
|
71
|
+
);
|
|
72
|
+
expect(mocks.createOne.mock.calls[0]![0].statusEntries).toBeUndefined();
|
|
73
|
+
expect(mocks.createOne.mock.calls[0]![0].credential).toBe(JSON.stringify(jwe));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('rejects internal issuance results that lost their status metadata before writing', async () => {
|
|
77
|
+
const issued = { kind: 'issued-credential', credential: createJwe() } as IssuedCredential;
|
|
78
|
+
await expect(storeCredential(issued)).rejects.toThrow();
|
|
79
|
+
expect(mocks.createOne).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it.each([undefined, []])(
|
|
83
|
+
'warns for a pre-signed plaintext VC with missing or empty status (%j) and preserves its signature',
|
|
84
|
+
async credentialStatus => {
|
|
85
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
86
|
+
const credential = { ...createVc(), credentialStatus };
|
|
87
|
+
const stored = await storeCredential(credential);
|
|
88
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
89
|
+
expect.stringContaining('Plaintext credential has no status metadata'),
|
|
90
|
+
{ credentialId: stored.id }
|
|
91
|
+
);
|
|
92
|
+
expect(stored.credential).toBe(JSON.stringify(credential));
|
|
93
|
+
expect(stored.statusEntries).toBeUndefined();
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
it.each([statusEntry, [statusEntry]])(
|
|
98
|
+
'does not warn for a plaintext VC with embedded status entries (%j)',
|
|
99
|
+
async credentialStatus => {
|
|
100
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
101
|
+
const credential = { ...createVc(), credentialStatus };
|
|
102
|
+
const stored = await storeCredential(credential);
|
|
103
|
+
expect(warn).not.toHaveBeenCalled();
|
|
104
|
+
expect(stored.credential).toBe(JSON.stringify(credential));
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
it.each([
|
|
109
|
+
['Encrypted', createJwe()],
|
|
110
|
+
['Plaintext', createVc()],
|
|
111
|
+
] as const)(
|
|
112
|
+
'warns for %s VC v1 issuance with empty status metadata',
|
|
113
|
+
async (kind, credential) => {
|
|
114
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
115
|
+
const stored = await storeCredential({
|
|
116
|
+
kind: 'issued-credential',
|
|
117
|
+
credential,
|
|
118
|
+
statusEntries: [],
|
|
119
|
+
});
|
|
120
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
121
|
+
expect.stringContaining(`${kind} credential has no status metadata`),
|
|
122
|
+
{ credentialId: stored.id }
|
|
123
|
+
);
|
|
124
|
+
expect(stored.credential).toBe(JSON.stringify(credential));
|
|
125
|
+
expect(stored.statusEntries).toBeUndefined();
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
it('does not treat a wire JWE with extra envelope-like fields as trusted issuance metadata', async () => {
|
|
130
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
131
|
+
const jwe = {
|
|
132
|
+
...createJwe(),
|
|
133
|
+
kind: 'issued-credential',
|
|
134
|
+
credential: createJwe(),
|
|
135
|
+
statusEntries: [statusEntry],
|
|
136
|
+
};
|
|
137
|
+
await storeCredential(jwe);
|
|
138
|
+
expect(mocks.createOne.mock.calls[0]![0].statusEntries).toBeUndefined();
|
|
139
|
+
expect(mocks.createOne.mock.calls[0]![0].credential).toBe(JSON.stringify(jwe));
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -1,12 +1,36 @@
|
|
|
1
|
-
import { UnsignedVC, VC, JWE } from '@learncard/types';
|
|
1
|
+
import { BitstringStatusListEntryValidator, UnsignedVC, VC, JWE } from '@learncard/types';
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
|
+
import { getBitstringStatusListEntries, isEncrypted } from '@learncard/helpers';
|
|
4
|
+
import { isIssuedCredential } from '@helpers/issuedCredentialStatus.helpers';
|
|
5
|
+
import type { IssuedCredential } from 'types/credential';
|
|
3
6
|
|
|
4
7
|
import { Credential, CredentialInstance } from '@models';
|
|
5
8
|
|
|
6
9
|
export const storeCredential = async (
|
|
7
|
-
|
|
10
|
+
input: UnsignedVC | VC | JWE | IssuedCredential
|
|
8
11
|
): Promise<CredentialInstance> => {
|
|
9
12
|
const id = uuid();
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
const issued = isIssuedCredential(input);
|
|
15
|
+
const credential = issued ? input.credential : input;
|
|
16
|
+
// Fail if an internal issuance result lost its metadata. Pre-signed wire
|
|
17
|
+
// payloads remain supported, with an explicit revocation warning.
|
|
18
|
+
const statusEntries = issued
|
|
19
|
+
? BitstringStatusListEntryValidator.array().parse(input.statusEntries)
|
|
20
|
+
: undefined;
|
|
21
|
+
const encrypted = isEncrypted(credential);
|
|
22
|
+
if (
|
|
23
|
+
!statusEntries?.length &&
|
|
24
|
+
(encrypted || !getBitstringStatusListEntries(credential).length)
|
|
25
|
+
) {
|
|
26
|
+
console.warn(
|
|
27
|
+
`[storeCredential] ${encrypted ? 'Encrypted' : 'Plaintext'} credential has no status metadata; network revocation and suspension cannot update its signed status list.`,
|
|
28
|
+
{ credentialId: id }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return Credential.createOne({
|
|
32
|
+
id,
|
|
33
|
+
credential: JSON.stringify(credential),
|
|
34
|
+
...(statusEntries?.length ? { statusEntries: JSON.stringify(statusEntries) } : {}),
|
|
35
|
+
});
|
|
12
36
|
};
|
|
@@ -23,6 +23,7 @@ export const createCredentialActivity = async (
|
|
|
23
23
|
eventType: params.eventType,
|
|
24
24
|
timestamp,
|
|
25
25
|
actorProfileId: params.actorProfileId,
|
|
26
|
+
onBehalfOf: params.onBehalfOf,
|
|
26
27
|
recipientType: params.recipientType,
|
|
27
28
|
recipientIdentifier: params.recipientIdentifier,
|
|
28
29
|
boostUri: params.boostUri,
|