@verana-labs/vs-agent-sdk 2.0.0-dev.7 → 2.0.0-dev.70
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/README.md +0 -2
- package/build/adminApi/AdminApiError.d.ts +27 -0
- package/build/adminApi/AdminApiError.js +59 -0
- package/build/adminApi/AdminApiError.js.map +1 -0
- package/build/adminApi/didcommModules.d.ts +2 -0
- package/build/adminApi/didcommModules.js +8 -0
- package/build/adminApi/didcommModules.js.map +1 -0
- package/build/adminApi/index.d.ts +4 -0
- package/build/adminApi/index.js +21 -0
- package/build/adminApi/index.js.map +1 -0
- package/build/adminApi/moduleEndpoint.d.ts +8 -0
- package/build/adminApi/moduleEndpoint.js +28 -0
- package/build/adminApi/moduleEndpoint.js.map +1 -0
- package/build/adminApi/pagination/PageDto.d.ts +7 -0
- package/build/adminApi/pagination/PageDto.js +50 -0
- package/build/adminApi/pagination/PageDto.js.map +1 -0
- package/build/adminApi/pagination/PaginationQueryDto.d.ts +11 -0
- package/build/adminApi/pagination/PaginationQueryDto.js +46 -0
- package/build/adminApi/pagination/PaginationQueryDto.js.map +1 -0
- package/build/adminApi/pagination/cursor.d.ts +28 -0
- package/build/adminApi/pagination/cursor.js +64 -0
- package/build/adminApi/pagination/cursor.js.map +1 -0
- package/build/adminApi/pagination/index.d.ts +4 -0
- package/build/adminApi/pagination/index.js +21 -0
- package/build/adminApi/pagination/index.js.map +1 -0
- package/build/adminApi/pagination/paginate.d.ts +27 -0
- package/build/adminApi/pagination/paginate.js +77 -0
- package/build/adminApi/pagination/paginate.js.map +1 -0
- package/build/agent/VsAgent.d.ts +17 -34
- package/build/agent/VsAgent.js +75 -205
- package/build/agent/VsAgent.js.map +1 -1
- package/build/agent/createVsAgent.d.ts +5 -6
- package/build/agent/createVsAgent.js +3 -5
- package/build/agent/createVsAgent.js.map +1 -1
- package/build/blockchain/AnonCredsTrustService.d.ts +62 -0
- package/build/blockchain/AnonCredsTrustService.js +256 -0
- package/build/blockchain/AnonCredsTrustService.js.map +1 -0
- package/build/blockchain/AuthorizationService.d.ts +5 -3
- package/build/blockchain/AuthorizationService.js +17 -29
- package/build/blockchain/AuthorizationService.js.map +1 -1
- package/build/blockchain/IndexerWebSocketService.d.ts +7 -1
- package/build/blockchain/IndexerWebSocketService.js +68 -36
- package/build/blockchain/IndexerWebSocketService.js.map +1 -1
- package/build/blockchain/VeranaChainService.d.ts +25 -14
- package/build/blockchain/VeranaChainService.js +123 -113
- package/build/blockchain/VeranaChainService.js.map +1 -1
- package/build/blockchain/VeranaIndexerService.d.ts +14 -1
- package/build/blockchain/VeranaIndexerService.js +122 -8
- package/build/blockchain/VeranaIndexerService.js.map +1 -1
- package/build/blockchain/handlers/authorizationHandlers.js +2 -2
- package/build/blockchain/handlers/authorizationHandlers.js.map +1 -1
- package/build/blockchain/handlers/defaultHandlers.js +2 -0
- package/build/blockchain/handlers/defaultHandlers.js.map +1 -1
- package/build/blockchain/handlers/index.d.ts +1 -0
- package/build/blockchain/handlers/index.js +1 -0
- package/build/blockchain/handlers/index.js.map +1 -1
- package/build/blockchain/handlers/selfIssuanceHandlers.d.ts +13 -0
- package/build/blockchain/handlers/selfIssuanceHandlers.js +71 -0
- package/build/blockchain/handlers/selfIssuanceHandlers.js.map +1 -0
- package/build/blockchain/handlers/stateMutations.d.ts +21 -2
- package/build/blockchain/handlers/stateMutations.js +285 -37
- package/build/blockchain/handlers/stateMutations.js.map +1 -1
- package/build/blockchain/index.d.ts +2 -0
- package/build/blockchain/index.js +2 -0
- package/build/blockchain/index.js.map +1 -1
- package/build/blockchain/indexerMappers.d.ts +3 -0
- package/build/blockchain/indexerMappers.js +53 -0
- package/build/blockchain/indexerMappers.js.map +1 -0
- package/build/blockchain/types.d.ts +13 -58
- package/build/blockchain/types.js.map +1 -1
- package/build/bootstrap/EcsBootstrapService.d.ts +14 -3
- package/build/bootstrap/EcsBootstrapService.js +166 -76
- package/build/bootstrap/EcsBootstrapService.js.map +1 -1
- package/build/connections/ParentConnectionModule.d.ts +20 -0
- package/build/connections/ParentConnectionModule.js +56 -0
- package/build/connections/ParentConnectionModule.js.map +1 -0
- package/build/credentials/FullTailsFileService.d.ts +0 -1
- package/build/credentials/FullTailsFileService.js +0 -49
- package/build/credentials/FullTailsFileService.js.map +1 -1
- package/build/did/CachedDocumentLoader.d.ts +3 -0
- package/build/did/CachedDocumentLoader.js +7 -1
- package/build/did/CachedDocumentLoader.js.map +1 -1
- package/build/did/CachedWebDidResolver.d.ts +0 -4
- package/build/did/CachedWebDidResolver.js +1 -5
- package/build/did/CachedWebDidResolver.js.map +1 -1
- package/build/did/artifactServices.d.ts +12 -0
- package/build/did/artifactServices.js +48 -0
- package/build/did/artifactServices.js.map +1 -0
- package/build/did/legacyDidWeb.d.ts +6 -4
- package/build/did/legacyDidWeb.js +17 -14
- package/build/did/legacyDidWeb.js.map +1 -1
- package/build/did/publicDid.d.ts +4 -0
- package/build/did/publicDid.js +19 -0
- package/build/did/publicDid.js.map +1 -0
- package/build/events/BaseMessageEvents.js +139 -5
- package/build/events/BaseMessageEvents.js.map +1 -1
- package/build/events/ConnectionEvents.js +129 -82
- package/build/events/ConnectionEvents.js.map +1 -1
- package/build/events/VsAgentEvents.d.ts +11 -9
- package/build/events/VsAgentEvents.js +8 -1
- package/build/events/VsAgentEvents.js.map +1 -1
- package/build/events/VtFlowEvents.d.ts +1 -1
- package/build/events/VtFlowEvents.js +27 -18
- package/build/events/VtFlowEvents.js.map +1 -1
- package/build/index.d.ts +7 -1
- package/build/index.js +15 -2
- package/build/index.js.map +1 -1
- package/build/plugins/setupBaseDidComm.js +9 -12
- package/build/plugins/setupBaseDidComm.js.map +1 -1
- package/build/transports/VsAgentWsInboundTransport.d.ts +1 -1
- package/build/transports/VsAgentWsInboundTransport.js +20 -14
- package/build/transports/VsAgentWsInboundTransport.js.map +1 -1
- package/build/types.d.ts +7 -0
- package/build/types.js.map +1 -1
- package/build/utils/agent.d.ts +29 -4
- package/build/utils/agent.js +91 -22
- package/build/utils/agent.js.map +1 -1
- package/build/utils/didReadiness.d.ts +9 -0
- package/build/utils/didReadiness.js +42 -0
- package/build/utils/didReadiness.js.map +1 -0
- package/build/utils/ecsClaims.d.ts +47 -0
- package/build/utils/ecsClaims.js +120 -0
- package/build/utils/ecsClaims.js.map +1 -0
- package/build/utils/ecsService.d.ts +8 -0
- package/build/utils/ecsService.js +23 -0
- package/build/utils/ecsService.js.map +1 -0
- package/build/utils/index.d.ts +4 -0
- package/build/utils/index.js +4 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/selfIssuedEcsCredential.d.ts +8 -0
- package/build/utils/selfIssuedEcsCredential.js +151 -0
- package/build/utils/selfIssuedEcsCredential.js.map +1 -0
- package/build/utils/setupSelfTr.d.ts +34 -66
- package/build/utils/setupSelfTr.js +89 -295
- package/build/utils/setupSelfTr.js.map +1 -1
- package/build/utils/trustCredentialStore.d.ts +67 -9
- package/build/utils/trustCredentialStore.js +296 -111
- package/build/utils/trustCredentialStore.js.map +1 -1
- package/build/utils/util.d.ts +10 -0
- package/build/utils/util.js +16 -0
- package/build/utils/util.js.map +1 -1
- package/build/utils/vcdm2.d.ts +34 -0
- package/build/utils/vcdm2.js +62 -0
- package/build/utils/vcdm2.js.map +1 -0
- package/build/utils/vtjscResolver.d.ts +18 -0
- package/build/utils/vtjscResolver.js +54 -0
- package/build/utils/vtjscResolver.js.map +1 -0
- package/build/vtFlow/VtFlowOrchestrator.d.ts +84 -13
- package/build/vtFlow/VtFlowOrchestrator.js +505 -107
- package/build/vtFlow/VtFlowOrchestrator.js.map +1 -1
- package/build/vtFlow/assertVerifiableService.d.ts +1 -0
- package/build/vtFlow/assertVerifiableService.js +45 -5
- package/build/vtFlow/assertVerifiableService.js.map +1 -1
- package/build/vtFlow/ecsIssuanceExemption.d.ts +12 -0
- package/build/vtFlow/ecsIssuanceExemption.js +84 -0
- package/build/vtFlow/ecsIssuanceExemption.js.map +1 -0
- package/build/vtFlow/index.d.ts +1 -0
- package/build/vtFlow/index.js +1 -0
- package/build/vtFlow/index.js.map +1 -1
- package/package.json +17 -12
- package/build/did/migrateWebVhLog.d.ts +0 -34
- package/build/did/migrateWebVhLog.js +0 -230
- package/build/did/migrateWebVhLog.js.map +0 -1
- package/build/did/migrateWebVhVersionTime.d.ts +0 -9
- package/build/did/migrateWebVhVersionTime.js +0 -191
- package/build/did/migrateWebVhVersionTime.js.map +0 -1
|
@@ -3,10 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.VtFlowOrchestrator = void 0;
|
|
4
4
|
const core_1 = require("@credo-ts/core");
|
|
5
5
|
const credo_ts_didcomm_vt_flow_1 = require("@verana-labs/credo-ts-didcomm-vt-flow");
|
|
6
|
+
const verana_types_1 = require("@verana-labs/verana-types");
|
|
6
7
|
const verre_1 = require("@verana-labs/verre");
|
|
8
|
+
const vs_agent_model_1 = require("@verana-labs/vs-agent-model");
|
|
9
|
+
const adminApi_1 = require("../adminApi");
|
|
10
|
+
const ecsIssuanceExemption_1 = require("./ecsIssuanceExemption");
|
|
7
11
|
const types_1 = require("../blockchain/types");
|
|
8
12
|
const types_2 = require("../types");
|
|
9
13
|
const utils_1 = require("../utils");
|
|
14
|
+
// The chain carries a fee discount as an integer from 0 to 10000. The API and the indexer carry it
|
|
15
|
+
// as a decimal from 0 to 1, so only the message is scaled.
|
|
16
|
+
const DISCOUNT_SCALE = 10000;
|
|
17
|
+
const TX_LOOKUP_TIMEOUT_MS = 60000;
|
|
18
|
+
const TX_LOOKUP_INTERVAL_MS = 3000;
|
|
19
|
+
const FEE_DENOM = 'uvna';
|
|
20
|
+
const FEE_KEYS = ['validationFees', 'issuanceFees', 'verificationFees'];
|
|
21
|
+
const DISCOUNT_KEYS = ['issuanceFeeDiscount', 'verificationFeeDiscount'];
|
|
22
|
+
function invalidInput(message) {
|
|
23
|
+
return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidInput, 400, message);
|
|
24
|
+
}
|
|
25
|
+
function invalidState(message) {
|
|
26
|
+
return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidState, 409, message);
|
|
27
|
+
}
|
|
28
|
+
// AUTHZ-CHECK-3 step 1. A record's expiration is only its budget clock, not a validity window.
|
|
29
|
+
function isActiveParticipant(participant) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (!participant.effective_from || Date.parse(participant.effective_from) > now)
|
|
32
|
+
return false;
|
|
33
|
+
if (participant.effective_until && Date.parse(participant.effective_until) <= now)
|
|
34
|
+
return false;
|
|
35
|
+
return !participant.revoked && !participant.slashed;
|
|
36
|
+
}
|
|
37
|
+
function sameTerm(key, given, onEntry) {
|
|
38
|
+
const scale = DISCOUNT_KEYS.includes(key) ? DISCOUNT_SCALE : 1;
|
|
39
|
+
return Math.round(given * scale) === Math.round(onEntry * scale);
|
|
40
|
+
}
|
|
41
|
+
function errorMessage(error) {
|
|
42
|
+
return error instanceof Error ? error.message : String(error);
|
|
43
|
+
}
|
|
10
44
|
class VtFlowOrchestrator {
|
|
11
45
|
constructor(agent, options = {}) {
|
|
12
46
|
this.agent = agent;
|
|
@@ -14,10 +48,10 @@ class VtFlowOrchestrator {
|
|
|
14
48
|
}
|
|
15
49
|
async startOnboardingProcess(input) {
|
|
16
50
|
var _a, _b, _c, _d;
|
|
17
|
-
|
|
51
|
+
this.requireChain();
|
|
18
52
|
if (!this.agent.did)
|
|
19
53
|
throw new Error('Agent has no public DID');
|
|
20
|
-
const holderParticipant = await
|
|
54
|
+
const holderParticipant = await this.agent.indexer.findParticipant(input.applicantParticipantId);
|
|
21
55
|
if (!holderParticipant) {
|
|
22
56
|
throw new Error(`Applicant participant ${input.applicantParticipantId} not found on chain`);
|
|
23
57
|
}
|
|
@@ -37,7 +71,7 @@ class VtFlowOrchestrator {
|
|
|
37
71
|
if (!holderParticipant.validatorParticipantId) {
|
|
38
72
|
throw new Error(`Applicant participant ${input.applicantParticipantId} has no validator_participant_id`);
|
|
39
73
|
}
|
|
40
|
-
const validatorParticipant = await
|
|
74
|
+
const validatorParticipant = await this.agent.indexer.findParticipant(Number(holderParticipant.validatorParticipantId));
|
|
41
75
|
if (!(validatorParticipant === null || validatorParticipant === void 0 ? void 0 : validatorParticipant.did)) {
|
|
42
76
|
throw new Error(`Validator participant ${holderParticipant.validatorParticipantId} not resolvable`);
|
|
43
77
|
}
|
|
@@ -47,46 +81,60 @@ class VtFlowOrchestrator {
|
|
|
47
81
|
// Renewals resend the OR with the same session id so the validator re-attaches the finished flow.
|
|
48
82
|
const vtFlowApi = this.resolveVtFlowApi();
|
|
49
83
|
const [latest] = (await vtFlowApi.findAllByQuery({
|
|
50
|
-
|
|
84
|
+
applicantParticipantId: String(holderParticipant.id),
|
|
51
85
|
role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Applicant,
|
|
52
86
|
flowVariant: credo_ts_didcomm_vt_flow_1.VtFlowVariant.OnboardingProcess,
|
|
53
87
|
}))
|
|
54
88
|
.filter(record => !(0, credo_ts_didcomm_vt_flow_1.isVtFlowTerminalState)(record.state))
|
|
55
89
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const existing = latest;
|
|
90
|
+
const running = latest !== undefined &&
|
|
91
|
+
latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Completed &&
|
|
92
|
+
latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.CredRevoked;
|
|
61
93
|
let connectionId;
|
|
62
|
-
if (
|
|
63
|
-
const connection = await this.agent.didcomm.connections.findById(
|
|
64
|
-
if (connection
|
|
94
|
+
if (latest) {
|
|
95
|
+
const connection = await this.agent.didcomm.connections.findById(latest.connectionId);
|
|
96
|
+
if (connection && (0, utils_1.isUsableConnectionTo)(connection, validatorParticipant.did))
|
|
65
97
|
connectionId = connection.id;
|
|
66
98
|
}
|
|
99
|
+
const undelivered = running && latest.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OrSent && holderParticipant.opState === types_1.ValidationState.PENDING;
|
|
100
|
+
if (running && connectionId && !undelivered) {
|
|
101
|
+
this.agent.config.logger.info(`[vt-flow] onboarding flow ${latest.id} for participant ${holderParticipant.id} is in progress (state ${latest.state}) on an open connection, not resending`);
|
|
102
|
+
return latest;
|
|
103
|
+
}
|
|
67
104
|
if (!connectionId) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
if (!connectionRecord)
|
|
75
|
-
throw new Error('Failed to establish DIDComm connection to validator');
|
|
76
|
-
const ready = await this.agent.didcomm.connections.returnWhenIsConnected(connectionRecord.id);
|
|
77
|
-
connectionId = ready.id;
|
|
105
|
+
connectionId = await (0, utils_1.connectToPublicDid)(this.agent, validatorParticipant.did);
|
|
106
|
+
}
|
|
107
|
+
if (running) {
|
|
108
|
+
this.agent.config.logger.info(`[vt-flow] resending the onboarding request of flow ${latest.id} (state ${latest.state}) on ${connectionId === latest.connectionId ? 'its' : 'a new'} connection`);
|
|
109
|
+
return vtFlowApi.resendOnboardingRequest({ vtFlowRecordId: latest.id, connectionId });
|
|
78
110
|
}
|
|
79
111
|
return vtFlowApi.sendOnboardingRequest({
|
|
80
112
|
connectionId,
|
|
81
|
-
participantSessionId: (_b = (_a = input.participantSessionId) !== null && _a !== void 0 ? _a :
|
|
82
|
-
|
|
113
|
+
participantSessionId: (_b = (_a = input.participantSessionId) !== null && _a !== void 0 ? _a : latest === null || latest === void 0 ? void 0 : latest.participantSessionId) !== null && _b !== void 0 ? _b : core_1.utils.uuid(),
|
|
114
|
+
applicantParticipantId: String(holderParticipant.id),
|
|
115
|
+
applicantParticipantRole: Number(holderParticipant.role),
|
|
116
|
+
validatorParticipantId: String(holderParticipant.validatorParticipantId),
|
|
117
|
+
schemaId: String(holderParticipant.schemaId),
|
|
83
118
|
agentParticipantId: String((_c = this.options.agentParticipantId) !== null && _c !== void 0 ? _c : 0),
|
|
84
119
|
walletAgentParticipantId: String((_d = this.options.walletAgentParticipantId) !== null && _d !== void 0 ? _d : 0),
|
|
85
120
|
claims: input.claims,
|
|
86
121
|
});
|
|
87
122
|
}
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Validate an onboarding process: record the outcome on-chain, accept the onboarding request and
|
|
125
|
+
* move the record to VALIDATED. It sends no credential. Only a HOLDER takes part in a credential
|
|
126
|
+
* exchange; an ISSUER, a VERIFIER or a grantor joins the Ecosystem, and SetParticipantOPToValidated
|
|
127
|
+
* is the whole of it. The caller decides what follows, from the role of the returned participant.
|
|
128
|
+
*
|
|
129
|
+
* For a HOLDER it builds the credential before the chain write, and returns it for
|
|
130
|
+
* offerOnboardingCredential. Claims that do not satisfy the schema, or a schema fetch that fails,
|
|
131
|
+
* must stop the process while the chain holds no outcome yet and the caller can repeat the call.
|
|
132
|
+
*
|
|
133
|
+
* A record that is already VALIDATED and has no credential exchange keeps the chain outcome and
|
|
134
|
+
* only builds the credential again. A repeat call thus re-drives an offer that did not complete.
|
|
135
|
+
*/
|
|
136
|
+
async validateOnboardingProcess(input) {
|
|
137
|
+
var _a, _b;
|
|
90
138
|
const chain = this.requireChain();
|
|
91
139
|
if (!this.agent.did)
|
|
92
140
|
throw new Error('Agent has no public DID');
|
|
@@ -99,43 +147,387 @@ class VtFlowOrchestrator {
|
|
|
99
147
|
if (record.variant !== credo_ts_didcomm_vt_flow_1.VtFlowVariant.OnboardingProcess) {
|
|
100
148
|
throw new Error(`Record variant is '${record.variant}', expected OnboardingProcess`);
|
|
101
149
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!
|
|
112
|
-
throw new Error(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
const resumeOffer = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.Validated && !record.credentialExchangeRecordId;
|
|
151
|
+
if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr && !resumeOffer) {
|
|
152
|
+
throw new Error(`Record state is '${record.state}', expected '${credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr}', or ` +
|
|
153
|
+
`'${credo_ts_didcomm_vt_flow_1.VtFlowState.Validated}' with no credential exchange`);
|
|
154
|
+
}
|
|
155
|
+
if (!record.applicantParticipantId)
|
|
156
|
+
throw new Error('Record has no applicantParticipantId');
|
|
157
|
+
const participantId = Number(record.applicantParticipantId);
|
|
158
|
+
const participant = await this.agent.indexer.findParticipant(participantId);
|
|
159
|
+
if (!participant)
|
|
160
|
+
throw new Error(`Applicant participant ${participantId} not found on chain`);
|
|
161
|
+
if (!participant.did)
|
|
162
|
+
throw new Error('Applicant participant has no DID');
|
|
163
|
+
const credential = Number(participant.role) === types_2.HOLDER_PARTICIPANT_TYPE
|
|
164
|
+
? await this.buildCredential({
|
|
165
|
+
credentialSchemaId: (_a = input.credentialSchemaId) !== null && _a !== void 0 ? _a : String(participant.schemaId),
|
|
166
|
+
subjectDid: participant.did,
|
|
167
|
+
claims: ((_b = record.claims) !== null && _b !== void 0 ? _b : {}),
|
|
168
|
+
credentialType: input.credentialType,
|
|
169
|
+
credentialContext: input.credentialContext,
|
|
170
|
+
})
|
|
171
|
+
: undefined;
|
|
172
|
+
if (resumeOffer)
|
|
173
|
+
return { record, participant, credential };
|
|
120
174
|
// op_summary_digest (MOD-PP-MSG-3) digests the applicant's submission, not the credential
|
|
121
|
-
await chain.setParticipantOPToValidated({
|
|
122
|
-
id: holderParticipantId,
|
|
123
|
-
corporation: holderParticipant.corporation,
|
|
124
|
-
});
|
|
175
|
+
await chain.setParticipantOPToValidated({ id: participantId, corporation: participant.corporation });
|
|
125
176
|
await vtFlowApi.acceptOnboardingRequest(record.id);
|
|
126
|
-
await vtFlowApi.markValidated(record.id);
|
|
177
|
+
const validated = await vtFlowApi.markValidated(record.id);
|
|
178
|
+
return { record: validated, participant, credential };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Offer the credential of an onboarding process to its applicant. Call it only for a HOLDER, and
|
|
182
|
+
* only after validateOnboardingProcess has moved the record to VALIDATED. Give it the credential
|
|
183
|
+
* that validateOnboardingProcess built, so that the process does not build the credential twice.
|
|
184
|
+
*/
|
|
185
|
+
async offerOnboardingCredential(input) {
|
|
186
|
+
var _a, _b, _c, _d;
|
|
187
|
+
this.requireChain();
|
|
188
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
189
|
+
const record = await vtFlowApi.findById(input.vtFlowRecordId);
|
|
190
|
+
if (!record)
|
|
191
|
+
throw new Error(`vt-flow record ${input.vtFlowRecordId} not found`);
|
|
192
|
+
if (!record.applicantParticipantId)
|
|
193
|
+
throw new Error('Record has no applicantParticipantId');
|
|
194
|
+
const participant = (_a = input.participant) !== null && _a !== void 0 ? _a : (await this.agent.indexer.findParticipant(Number(record.applicantParticipantId)));
|
|
195
|
+
if (!(participant === null || participant === void 0 ? void 0 : participant.did))
|
|
196
|
+
throw new Error('Applicant participant has no DID');
|
|
197
|
+
const unsignedCredentialJson = (_b = input.credential) !== null && _b !== void 0 ? _b : (await this.buildCredential({
|
|
198
|
+
credentialSchemaId: (_c = input.credentialSchemaId) !== null && _c !== void 0 ? _c : String(participant.schemaId),
|
|
199
|
+
subjectDid: participant.did,
|
|
200
|
+
claims: ((_d = record.claims) !== null && _d !== void 0 ? _d : {}),
|
|
201
|
+
credentialType: input.credentialType,
|
|
202
|
+
credentialContext: input.credentialContext,
|
|
203
|
+
}));
|
|
204
|
+
// W3C Data Integrity attachment format (RFC 0809). The applicant DID is the credential subject
|
|
205
|
+
// and the exchange runs over its authenticated connection, so no extra binding is required. The
|
|
206
|
+
// cryptosuite is the issuer's choice and is applied when the credential is issued.
|
|
127
207
|
const { record: offered } = await vtFlowApi.offerCredentialForSession({
|
|
128
208
|
vtFlowRecordId: record.id,
|
|
129
|
-
issuerParticipantId: Number(
|
|
209
|
+
issuerParticipantId: Number(participant.validatorParticipantId),
|
|
130
210
|
credentialFormats: {
|
|
131
|
-
|
|
132
|
-
credential: unsignedCredentialJson,
|
|
133
|
-
options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
|
|
134
|
-
},
|
|
211
|
+
dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
|
|
135
212
|
},
|
|
136
213
|
});
|
|
137
214
|
return offered;
|
|
138
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Close an onboarding process that carries no credential exchange, on the validator side. The
|
|
218
|
+
* applicant reaches the same state from the SetParticipantOPToValidated chain event.
|
|
219
|
+
*/
|
|
220
|
+
async completeOnboardingProcess(vtFlowRecordId) {
|
|
221
|
+
return this.resolveVtFlowApi().markCompleted(vtFlowRecordId);
|
|
222
|
+
}
|
|
223
|
+
/** [VSA-ADM-VT-FL-VALIDATE] */
|
|
224
|
+
async validateFlow(input) {
|
|
225
|
+
this.requireChain();
|
|
226
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
227
|
+
const record = await vtFlowApi.findById(input.vtFlowRecordId);
|
|
228
|
+
if (!record) {
|
|
229
|
+
throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.UnknownId, 404, `no flow with id "${input.vtFlowRecordId}"`);
|
|
230
|
+
}
|
|
231
|
+
if (record.role !== credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator)
|
|
232
|
+
throw invalidState('validate is a validator action');
|
|
233
|
+
if (record.variant === credo_ts_didcomm_vt_flow_1.VtFlowVariant.DirectIssuance)
|
|
234
|
+
return this.validateDirectIssuance(record);
|
|
235
|
+
if (!record.applicantParticipantId)
|
|
236
|
+
throw invalidState('the flow names no applicant participant');
|
|
237
|
+
const applicant = await this.agent.indexer.getParticipant(record.applicantParticipantId);
|
|
238
|
+
const entryValidated = applicant.op_state === 'VALIDATED';
|
|
239
|
+
this.assertValidateState(record, entryValidated);
|
|
240
|
+
const terms = this.validationTerms(input, applicant, record);
|
|
241
|
+
await this.assertClaimsAndTerm(record, applicant, terms);
|
|
242
|
+
if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr)
|
|
243
|
+
await vtFlowApi.acceptOnboardingRequest(record.id);
|
|
244
|
+
if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
|
|
245
|
+
await this.sendValidating(record);
|
|
246
|
+
if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims)
|
|
247
|
+
return this.continueAfterValidated(record.id);
|
|
248
|
+
if (entryValidated) {
|
|
249
|
+
if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated)
|
|
250
|
+
await vtFlowApi.markValidated(record.id);
|
|
251
|
+
return this.continueAfterValidated(record.id);
|
|
252
|
+
}
|
|
253
|
+
const validation = Object.assign({ decidedAt: new Date().toISOString(), submission: await this.submissionPath(applicant) }, terms);
|
|
254
|
+
if (validation.submission === credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator) {
|
|
255
|
+
return vtFlowApi.recordValidation(record.id, validation, credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx);
|
|
256
|
+
}
|
|
257
|
+
return this.submitValidation(record.id, applicant, validation);
|
|
258
|
+
}
|
|
259
|
+
// A Direct Issuance flow has no entry to validate, so no transaction: check the claims, then offer.
|
|
260
|
+
async validateDirectIssuance(record) {
|
|
261
|
+
if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validating && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending) {
|
|
262
|
+
throw invalidState(`the flow is '${record.state}', which validate does not accept`);
|
|
263
|
+
}
|
|
264
|
+
if (!record.schemaId)
|
|
265
|
+
throw invalidState('the flow names no credential schema');
|
|
266
|
+
const connection = await this.agent.didcomm.connections.findById(record.connectionId);
|
|
267
|
+
if (!(connection === null || connection === void 0 ? void 0 : connection.theirDid))
|
|
268
|
+
throw invalidState('the flow connection has no peer DID');
|
|
269
|
+
const schema = await this.agent.indexer.getCredentialSchema(record.schemaId);
|
|
270
|
+
this.assertClaims(schema.json_schema, connection.theirDid, record.claims);
|
|
271
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
272
|
+
if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
|
|
273
|
+
await this.sendValidating(record);
|
|
274
|
+
const offer = await this.buildDirectIssuanceOffer(record.id);
|
|
275
|
+
if (!offer)
|
|
276
|
+
throw invalidState('the agent holds no active ISSUER participant for the schema of the flow');
|
|
277
|
+
const { record: offered } = await vtFlowApi.offerCredentialForSession(Object.assign({ vtFlowRecordId: record.id }, offer));
|
|
278
|
+
return offered;
|
|
279
|
+
}
|
|
280
|
+
// [VSA-ADM-VT-FL-START]: checked before the flow moves, as a failed send would leave it in VALIDATING
|
|
281
|
+
async sendValidating(record) {
|
|
282
|
+
const connection = await this.agent.didcomm.connections.findById(record.connectionId);
|
|
283
|
+
if (!(connection === null || connection === void 0 ? void 0 : connection.isReady))
|
|
284
|
+
throw invalidState('the flow connection is not ESTABLISHED');
|
|
285
|
+
await this.resolveVtFlowApi().sendValidating(record.id);
|
|
286
|
+
}
|
|
287
|
+
assertClaims(jsonSchema, subjectDid, claims) {
|
|
288
|
+
const violations = (0, utils_1.schemaViolations)(JSON.parse(jsonSchema), Object.assign({ id: subjectDid }, (claims !== null && claims !== void 0 ? claims : {})));
|
|
289
|
+
if (violations.length > 0) {
|
|
290
|
+
throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidClaims, 422, 'the claim set does not satisfy the json_schema', { violations });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
assertValidateState(record, entryValidated) {
|
|
294
|
+
const accepted = [
|
|
295
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr,
|
|
296
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.Validating,
|
|
297
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending,
|
|
298
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed,
|
|
299
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims,
|
|
300
|
+
];
|
|
301
|
+
// Once the entry is VALIDATED on chain any validator-side state resumes into issuance, which is
|
|
302
|
+
// how a backend that disabled the default notification handler drives the flow ([VSA-ADM-VT-FL-VALIDATE-11]).
|
|
303
|
+
const acceptedOnceValidated = [
|
|
304
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx,
|
|
305
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
|
|
306
|
+
credo_ts_didcomm_vt_flow_1.VtFlowState.Validated,
|
|
307
|
+
];
|
|
308
|
+
if (accepted.includes(record.state))
|
|
309
|
+
return;
|
|
310
|
+
if (entryValidated && acceptedOnceValidated.includes(record.state))
|
|
311
|
+
return;
|
|
312
|
+
throw invalidState(`the flow is '${record.state}', which validate does not accept`);
|
|
313
|
+
}
|
|
314
|
+
validationTerms(input, applicant, record) {
|
|
315
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
316
|
+
for (const key of FEE_KEYS) {
|
|
317
|
+
const fee = input[key];
|
|
318
|
+
if (fee !== undefined && !(Number.isInteger(fee) && fee >= 0)) {
|
|
319
|
+
throw invalidInput(`${key} must be a non-negative integer`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const key of DISCOUNT_KEYS) {
|
|
323
|
+
const discount = input[key];
|
|
324
|
+
if (discount !== undefined && !(discount >= 0 && discount <= 1)) {
|
|
325
|
+
throw invalidInput(`${key} must be between 0 and 1`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (input.effectiveUntil !== undefined && Number.isNaN(Date.parse(input.effectiveUntil))) {
|
|
329
|
+
throw invalidInput('effectiveUntil must be an ISO 8601 datetime');
|
|
330
|
+
}
|
|
331
|
+
const onEntry = {
|
|
332
|
+
validationFees: (_a = applicant.validation_fees) !== null && _a !== void 0 ? _a : 0,
|
|
333
|
+
issuanceFees: (_b = applicant.issuance_fees) !== null && _b !== void 0 ? _b : 0,
|
|
334
|
+
verificationFees: (_c = applicant.verification_fees) !== null && _c !== void 0 ? _c : 0,
|
|
335
|
+
issuanceFeeDiscount: (_d = applicant.issuance_fee_discount) !== null && _d !== void 0 ? _d : 0,
|
|
336
|
+
verificationFeeDiscount: (_e = applicant.verification_fee_discount) !== null && _e !== void 0 ? _e : 0,
|
|
337
|
+
};
|
|
338
|
+
const renewal = !!applicant.effective_from;
|
|
339
|
+
const recorded = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed ? record.validation : undefined;
|
|
340
|
+
const terms = {
|
|
341
|
+
effectiveUntil: (_f = input.effectiveUntil) !== null && _f !== void 0 ? _f : recorded === null || recorded === void 0 ? void 0 : recorded.effectiveUntil,
|
|
342
|
+
opSummaryDigest: (_g = input.opSummaryDigest) !== null && _g !== void 0 ? _g : recorded === null || recorded === void 0 ? void 0 : recorded.opSummaryDigest,
|
|
343
|
+
};
|
|
344
|
+
for (const key of [...FEE_KEYS, ...DISCOUNT_KEYS]) {
|
|
345
|
+
const given = input[key];
|
|
346
|
+
if (renewal) {
|
|
347
|
+
if (given !== undefined && !sameTerm(key, given, onEntry[key])) {
|
|
348
|
+
throw invalidInput(`${key} differs from the entry, and a renewal keeps the agreed value`);
|
|
349
|
+
}
|
|
350
|
+
terms[key] = onEntry[key];
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
terms[key] = (_h = given !== null && given !== void 0 ? given : recorded === null || recorded === void 0 ? void 0 : recorded[key]) !== null && _h !== void 0 ? _h : 0;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return terms;
|
|
357
|
+
}
|
|
358
|
+
async assertClaimsAndTerm(record, applicant, terms) {
|
|
359
|
+
var _a;
|
|
360
|
+
if (applicant.role !== types_1.ParticipantRole.Holder)
|
|
361
|
+
return;
|
|
362
|
+
if (!applicant.did)
|
|
363
|
+
throw invalidState('the applicant entry has no DID to issue to');
|
|
364
|
+
const schema = await this.agent.indexer.getCredentialSchema(applicant.schema_id);
|
|
365
|
+
this.assertClaims(schema.json_schema, applicant.did, record.claims);
|
|
366
|
+
// An ECS Organization or Persona credential needs a validUntil. With a validity period of 0 the
|
|
367
|
+
// VPR sets no effective_until, so the call has to carry one.
|
|
368
|
+
const ecsKey = await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema);
|
|
369
|
+
const needsValidUntil = ecsKey === vs_agent_model_1.ECS.ORG || ecsKey === vs_agent_model_1.ECS.PERSONA;
|
|
370
|
+
if (needsValidUntil && ((_a = schema.holder_validation_validity_period) !== null && _a !== void 0 ? _a : 0) === 0 && !terms.effectiveUntil) {
|
|
371
|
+
throw invalidInput('this schema has no validity period, so effectiveUntil is required');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async submissionPath(applicant) {
|
|
375
|
+
const authorization = this.agent.authorizationService;
|
|
376
|
+
if (!authorization || applicant.validator_participant_id == null)
|
|
377
|
+
return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
|
|
378
|
+
const validator = await this.agent.indexer
|
|
379
|
+
.getParticipant(applicant.validator_participant_id)
|
|
380
|
+
.catch(() => undefined);
|
|
381
|
+
if (!validator || !isActiveParticipant(validator))
|
|
382
|
+
return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
|
|
383
|
+
await authorization.refreshForOperator().catch(() => undefined);
|
|
384
|
+
const grant = authorization.getVsOperatorAuthorizationRecord(validator.id);
|
|
385
|
+
return (grant === null || grant === void 0 ? void 0 : grant.msgTypes.includes(verana_types_1.veranaTypeUrls.MsgSetParticipantOPToValidated))
|
|
386
|
+
? credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Agent
|
|
387
|
+
: credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
|
|
388
|
+
}
|
|
389
|
+
async submitValidation(recordId, applicant, validation) {
|
|
390
|
+
var _a, _b, _c, _d, _e;
|
|
391
|
+
const chain = this.requireChain();
|
|
392
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
393
|
+
const fail = (reason, error) => vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: { status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason, error } }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed);
|
|
394
|
+
const grant = applicant.validator_participant_id == null
|
|
395
|
+
? undefined
|
|
396
|
+
: (_a = this.agent.authorizationService) === null || _a === void 0 ? void 0 : _a.getVsOperatorAuthorizationRecord(applicant.validator_participant_id);
|
|
397
|
+
const granter = (grant === null || grant === void 0 ? void 0 : grant.withFeegrant) ? chain.corporation : undefined;
|
|
398
|
+
const message = chain.setParticipantOPToValidatedMsg({
|
|
399
|
+
id: applicant.id,
|
|
400
|
+
effectiveUntil: validation.effectiveUntil ? new Date(validation.effectiveUntil) : undefined,
|
|
401
|
+
validationFees: validation.validationFees,
|
|
402
|
+
issuanceFees: validation.issuanceFees,
|
|
403
|
+
verificationFees: validation.verificationFees,
|
|
404
|
+
issuanceFeeDiscount: Math.round(((_b = validation.issuanceFeeDiscount) !== null && _b !== void 0 ? _b : 0) * DISCOUNT_SCALE),
|
|
405
|
+
verificationFeeDiscount: Math.round(((_c = validation.verificationFeeDiscount) !== null && _c !== void 0 ? _c : 0) * DISCOUNT_SCALE),
|
|
406
|
+
opSummaryDigest: validation.opSummaryDigest,
|
|
407
|
+
});
|
|
408
|
+
let allowance;
|
|
409
|
+
try {
|
|
410
|
+
allowance = granter ? await chain.feeAllowance(granter, FEE_DENOM) : undefined;
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
|
|
414
|
+
}
|
|
415
|
+
if (granter && !allowance) {
|
|
416
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.FeegrantExpired, 'the Corporation grants the agent no active fee allowance');
|
|
417
|
+
}
|
|
418
|
+
let fee;
|
|
419
|
+
try {
|
|
420
|
+
fee = await chain.estimateFee([message], granter);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
|
|
424
|
+
}
|
|
425
|
+
const amount = BigInt((_e = (_d = fee.amount.find(coin => coin.denom === FEE_DENOM)) === null || _d === void 0 ? void 0 : _d.amount) !== null && _e !== void 0 ? _e : '0');
|
|
426
|
+
if (granter && allowance) {
|
|
427
|
+
if (!allowance.unlimited && allowance.remaining < amount) {
|
|
428
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.FeegrantExhausted, `the fee allowance has ${allowance.remaining}${FEE_DENOM} left`);
|
|
429
|
+
}
|
|
430
|
+
let corporation;
|
|
431
|
+
try {
|
|
432
|
+
corporation = BigInt((await chain.getAccountBalance(granter, FEE_DENOM)).amount);
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
|
|
436
|
+
}
|
|
437
|
+
if (corporation < amount) {
|
|
438
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.InsufficientFundsCorporation, `the Corporation holds ${corporation}${FEE_DENOM}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
let own;
|
|
443
|
+
try {
|
|
444
|
+
own = BigInt((await chain.getBalance(FEE_DENOM)).amount);
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
|
|
448
|
+
}
|
|
449
|
+
if (own < amount) {
|
|
450
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.InsufficientFundsAgent, `the agent account holds ${own}${FEE_DENOM}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
let hash;
|
|
454
|
+
try {
|
|
455
|
+
hash = await chain.broadcastWithoutWaiting([message], fee);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.BroadcastError, errorMessage(error));
|
|
459
|
+
}
|
|
460
|
+
const submitted = await vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: { hash, submittedAt: new Date().toISOString(), status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Submitted } }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted);
|
|
461
|
+
void this.resolveValidationTx(recordId).catch(error => this.agent.config.logger.error(`[vt-flow] resolving the validation of ${recordId} failed`, {
|
|
462
|
+
error: errorMessage(error),
|
|
463
|
+
}));
|
|
464
|
+
return submitted;
|
|
465
|
+
}
|
|
466
|
+
// [VSA-ADM-VT-FL-VALIDATE-8]. Leaving VALIDATION_TX_SUBMITTED ends the loop, so the notification
|
|
467
|
+
// handler moving the flow first wins the race.
|
|
468
|
+
async resolveValidationTx(recordId) {
|
|
469
|
+
var _a, _b, _c;
|
|
470
|
+
const chain = this.requireChain();
|
|
471
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
472
|
+
for (;;) {
|
|
473
|
+
const record = await vtFlowApi.findById(recordId);
|
|
474
|
+
const validation = record === null || record === void 0 ? void 0 : record.validation;
|
|
475
|
+
const hash = (_a = validation === null || validation === void 0 ? void 0 : validation.tx) === null || _a === void 0 ? void 0 : _a.hash;
|
|
476
|
+
if (!record || !validation || !hash || record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted)
|
|
477
|
+
return;
|
|
478
|
+
const tx = await chain.findTx(hash).catch(() => undefined);
|
|
479
|
+
if (tx && tx.code === 0) {
|
|
480
|
+
await vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: Object.assign(Object.assign({}, validation.tx), { height: tx.height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Succeeded }) }));
|
|
481
|
+
await vtFlowApi.markValidated(recordId);
|
|
482
|
+
await this.continueAfterValidated(recordId);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (tx)
|
|
486
|
+
return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed, tx.rawLog, tx.height);
|
|
487
|
+
const submittedAt = (_c = (_b = validation.tx) === null || _b === void 0 ? void 0 : _b.submittedAt) !== null && _c !== void 0 ? _c : validation.decidedAt;
|
|
488
|
+
if (Date.now() - Date.parse(submittedAt) >= TX_LOOKUP_TIMEOUT_MS) {
|
|
489
|
+
return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxNotFound, 'not found 60 seconds after the broadcast');
|
|
490
|
+
}
|
|
491
|
+
await new Promise(resolve => setTimeout(resolve, TX_LOOKUP_INTERVAL_MS));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// The VPR has no distinct error for a second SetParticipantOPtoValidated, so the entry tells a
|
|
495
|
+
// rejected duplicate apart from a real failure.
|
|
496
|
+
async failUnlessValidated(record, validation, reason, error, height) {
|
|
497
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
498
|
+
const applicant = record.applicantParticipantId
|
|
499
|
+
? await this.agent.indexer.getParticipant(record.applicantParticipantId)
|
|
500
|
+
: undefined;
|
|
501
|
+
if ((applicant === null || applicant === void 0 ? void 0 : applicant.op_state) === 'VALIDATED') {
|
|
502
|
+
await vtFlowApi.markValidated(record.id);
|
|
503
|
+
await this.continueAfterValidated(record.id);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
await vtFlowApi.recordValidation(record.id, Object.assign(Object.assign({}, validation), { tx: Object.assign(Object.assign({}, validation.tx), { height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason, error }) }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed);
|
|
507
|
+
}
|
|
508
|
+
async continueAfterValidated(recordId) {
|
|
509
|
+
const vtFlowApi = this.resolveVtFlowApi();
|
|
510
|
+
const record = await vtFlowApi.findById(recordId);
|
|
511
|
+
if (!record)
|
|
512
|
+
throw new Error(`vt-flow record ${recordId} not found`);
|
|
513
|
+
if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims) {
|
|
514
|
+
return record;
|
|
515
|
+
}
|
|
516
|
+
const participant = await this.agent.indexer.findParticipant(Number(record.applicantParticipantId));
|
|
517
|
+
if (!participant)
|
|
518
|
+
throw new Error(`Applicant participant ${record.applicantParticipantId} not found`);
|
|
519
|
+
if (Number(participant.role) !== types_2.HOLDER_PARTICIPANT_TYPE)
|
|
520
|
+
return this.completeOnboardingProcess(recordId);
|
|
521
|
+
return this.offerOnboardingCredential({ vtFlowRecordId: recordId, participant });
|
|
522
|
+
}
|
|
523
|
+
/** [VSA-ADM-VT-FL-VALIDATE-8]: at startup, resume every flow left in VALIDATION_TX_SUBMITTED. */
|
|
524
|
+
async resumeValidationSubmissions() {
|
|
525
|
+
const pending = await this.resolveVtFlowApi().findAllByQuery({
|
|
526
|
+
role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator,
|
|
527
|
+
flowState: credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
|
|
528
|
+
});
|
|
529
|
+
await Promise.all(pending.map(record => this.resolveValidationTx(record.id)));
|
|
530
|
+
}
|
|
139
531
|
async acceptCredential(input) {
|
|
140
532
|
const vtFlowApi = this.resolveVtFlowApi();
|
|
141
533
|
const record = await vtFlowApi.findById(input.vtFlowRecordId);
|
|
@@ -154,7 +546,7 @@ class VtFlowOrchestrator {
|
|
|
154
546
|
const chain = this.requireChain();
|
|
155
547
|
if (!this.agent.did)
|
|
156
548
|
throw new Error('Agent has no public DID');
|
|
157
|
-
const indexer = this.
|
|
549
|
+
const indexer = this.agent.indexer;
|
|
158
550
|
const vtFlowApi = this.resolveVtFlowApi();
|
|
159
551
|
const record = await vtFlowApi.findById(vtFlowRecordId);
|
|
160
552
|
if (!record)
|
|
@@ -185,47 +577,48 @@ class VtFlowOrchestrator {
|
|
|
185
577
|
});
|
|
186
578
|
return {
|
|
187
579
|
credentialFormats: {
|
|
188
|
-
|
|
189
|
-
credential: unsignedCredentialJson,
|
|
190
|
-
options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
|
|
191
|
-
},
|
|
580
|
+
dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
|
|
192
581
|
},
|
|
193
582
|
issuerParticipantId: issuer.id,
|
|
194
583
|
};
|
|
195
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Builds the unsigned VC Data Model 2.0 credential that goes into the offer. RFC 0809 derives the
|
|
587
|
+
* advertised data model version from its context, and the issuer secures it with a
|
|
588
|
+
* DataIntegrityProof once the applicant requests it.
|
|
589
|
+
*/
|
|
196
590
|
async buildCredential(input) {
|
|
197
|
-
var _a
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
throw new Error(`No stored VTJSC found for ${schemaRef}`);
|
|
207
|
-
const { data } = entry;
|
|
208
|
-
const unsignedCredential = (0, utils_1.createCredential)({
|
|
591
|
+
var _a;
|
|
592
|
+
const jsonSchemaCredentialId = await this.resolveJsonSchemaCredentialId(input.credentialSchemaId);
|
|
593
|
+
// A credential whose claims don't satisfy the schema's required fields is not a valid
|
|
594
|
+
// instance of that credential type — reject it here rather than issue an empty shell.
|
|
595
|
+
const schema = await this.agent.indexer.getCredentialSchema(input.credentialSchemaId);
|
|
596
|
+
(0, utils_1.validateSchema)(JSON.parse(schema.json_schema), Object.assign({ id: input.subjectDid }, input.claims));
|
|
597
|
+
// Data model 2.0 holds the claims on the credential subject itself; the applicant DID wins over
|
|
598
|
+
// any `id` claim so the credential can never name a different subject
|
|
599
|
+
const unsignedCredential = (0, utils_1.createW3cV2Credential)({
|
|
209
600
|
id: `${this.agent.did}#${core_1.utils.uuid()}`,
|
|
210
601
|
type: (_a = input.credentialType) !== null && _a !== void 0 ? _a : ['VerifiableCredential', 'VerifiableTrustCredential'],
|
|
211
602
|
issuer: this.agent.did,
|
|
212
|
-
|
|
603
|
+
context: input.credentialContext,
|
|
604
|
+
credentialSubject: Object.assign(Object.assign({}, input.claims), { id: input.subjectDid }),
|
|
605
|
+
credentialSchema: { id: jsonSchemaCredentialId, type: 'JsonSchemaCredential' },
|
|
213
606
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
type: 'JsonSchemaCredential',
|
|
219
|
-
};
|
|
220
|
-
return core_1.JsonTransformer.toJSON(unsignedCredential);
|
|
607
|
+
return (0, utils_1.toOfferedCredentialJson)(unsignedCredential);
|
|
608
|
+
}
|
|
609
|
+
async resolveJsonSchemaCredentialId(credentialSchemaId) {
|
|
610
|
+
return (0, utils_1.resolveJsonSchemaCredentialId)(this.agent, this.agent.indexer, credentialSchemaId, this.requireChain().getChainId);
|
|
221
611
|
}
|
|
222
612
|
/** Per the spec the algorithm comes from the schema, never from the digest value. */
|
|
223
613
|
async digestAlgorithmForSchema(schemaId) {
|
|
224
|
-
|
|
614
|
+
return (await this.credentialSchema(schemaId)).digestAlgorithm;
|
|
615
|
+
}
|
|
616
|
+
async credentialSchema(schemaId) {
|
|
617
|
+
const schema = await this.agent.indexer.getCredentialSchema(schemaId);
|
|
225
618
|
if (!schema.digest_algorithm) {
|
|
226
619
|
throw new Error(`Credential schema ${schemaId} has no digest_algorithm`);
|
|
227
620
|
}
|
|
228
|
-
return schema.digest_algorithm;
|
|
621
|
+
return { digestAlgorithm: schema.digest_algorithm, ecsKey: await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema) };
|
|
229
622
|
}
|
|
230
623
|
/** Fired after signing and before delivery, so a failure here must abort the issuance. */
|
|
231
624
|
async onCredentialIssued(vtFlowRecordId, signedCredential) {
|
|
@@ -238,7 +631,7 @@ class VtFlowOrchestrator {
|
|
|
238
631
|
if (record.issuerParticipantId == null) {
|
|
239
632
|
throw new Error(`vt-flow record ${vtFlowRecordId} has no issuerParticipantId to anchor against`);
|
|
240
633
|
}
|
|
241
|
-
const issuer = await this.
|
|
634
|
+
const issuer = await this.agent.indexer.getParticipant(record.issuerParticipantId);
|
|
242
635
|
const algorithm = await this.digestAlgorithmForSchema(issuer.schema_id);
|
|
243
636
|
const digest = (0, verre_1.computeCredentialDigestJCS)(signedCredential, algorithm);
|
|
244
637
|
await chain.createOrUpdateParticipantSession({
|
|
@@ -261,7 +654,7 @@ class VtFlowOrchestrator {
|
|
|
261
654
|
if (!record.credentialExchangeRecordId) {
|
|
262
655
|
throw new Error('Record has no credentialExchangeRecordId; nothing to verify');
|
|
263
656
|
}
|
|
264
|
-
const indexer = this.
|
|
657
|
+
const indexer = this.agent.indexer;
|
|
265
658
|
const session = await indexer.getParticipantSession(record.participantSessionId);
|
|
266
659
|
if (!session) {
|
|
267
660
|
throw new Error(`ParticipantSession ${record.participantSessionId} not found on indexer`);
|
|
@@ -282,13 +675,15 @@ class VtFlowOrchestrator {
|
|
|
282
675
|
if (record.schemaId != null && Number(issuer.schema_id) !== Number(record.schemaId)) {
|
|
283
676
|
throw new Error(`Issuer participant schema ${issuer.schema_id} does not match credential schema ${record.schemaId}`);
|
|
284
677
|
}
|
|
285
|
-
const credentialJson = await this.
|
|
286
|
-
const
|
|
287
|
-
const digest = (0, verre_1.computeCredentialDigestJCS)(credentialJson,
|
|
678
|
+
const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
|
|
679
|
+
const { digestAlgorithm, ecsKey } = await this.credentialSchema(issuer.schema_id);
|
|
680
|
+
const digest = (0, verre_1.computeCredentialDigestJCS)(credentialJson, digestAlgorithm);
|
|
288
681
|
const anchored = await indexer.getDigest(digest);
|
|
289
682
|
if (!anchored) {
|
|
290
683
|
throw new Error(`Credential digest ${digest} is not anchored on-chain`);
|
|
291
684
|
}
|
|
685
|
+
if (ecsKey)
|
|
686
|
+
await vtFlowApi.setEcsSchemaKey(record.id, ecsKey);
|
|
292
687
|
}
|
|
293
688
|
async onCredentialRevoked(vtFlowRecordId) {
|
|
294
689
|
const vtFlowApi = this.resolveVtFlowApi();
|
|
@@ -297,7 +692,7 @@ class VtFlowOrchestrator {
|
|
|
297
692
|
return;
|
|
298
693
|
if (!this.options.publicApiBaseUrl || !record.credentialExchangeRecordId)
|
|
299
694
|
return;
|
|
300
|
-
const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent,
|
|
695
|
+
const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent, record.credentialExchangeRecordId);
|
|
301
696
|
if (credentialId) {
|
|
302
697
|
this.agent.config.logger.info(`[vt-flow] removed revoked credential and its linked VP (${credentialId})`);
|
|
303
698
|
}
|
|
@@ -318,26 +713,29 @@ class VtFlowOrchestrator {
|
|
|
318
713
|
const chain = this.agent.veranaChain;
|
|
319
714
|
if (!chain || !chain.autoTriggerResolverEnabled)
|
|
320
715
|
return;
|
|
321
|
-
if (record.
|
|
716
|
+
if (record.applicantParticipantId == null)
|
|
322
717
|
return;
|
|
323
|
-
await chain.triggerResolver(Number(record.
|
|
718
|
+
await chain.triggerResolver(Number(record.applicantParticipantId));
|
|
324
719
|
}
|
|
325
|
-
|
|
326
|
-
|
|
720
|
+
/** The signed credential as the issuer attached it, which is the exact JSON its digest covers. */
|
|
721
|
+
async getReceivedCredentialJson(credentialExchangeRecordId) {
|
|
722
|
+
var _a, _b;
|
|
327
723
|
const formatData = await this.agent.didcomm.credentials.getFormatData(credentialExchangeRecordId);
|
|
328
|
-
const credentialJson = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.
|
|
724
|
+
const credentialJson = (_b = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.dataIntegrity) === null || _b === void 0 ? void 0 : _b.credential;
|
|
329
725
|
if (!credentialJson)
|
|
330
|
-
throw new Error('
|
|
726
|
+
throw new Error('Received credential has no data integrity credential body');
|
|
331
727
|
return credentialJson;
|
|
332
728
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
729
|
+
// VS-CONN-VS gate: consulted only after trust resolution rejected the peer.
|
|
730
|
+
async checkEcsIssuanceExemption(context) {
|
|
731
|
+
return (0, ecsIssuanceExemption_1.isEcsIssuanceExempt)({
|
|
732
|
+
indexer: this.agent.indexer,
|
|
733
|
+
agent: this.agent,
|
|
734
|
+
trustedEcosystemDids: this.agent.trustedEcosystemDids,
|
|
735
|
+
}, context);
|
|
338
736
|
}
|
|
339
737
|
async publishCredentialAsLinkedVp(vtFlowRecordId) {
|
|
340
|
-
var _a
|
|
738
|
+
var _a;
|
|
341
739
|
if (!this.options.publicApiBaseUrl) {
|
|
342
740
|
throw new Error('publicApiBaseUrl is required to publish a LinkedVerifiablePresentation');
|
|
343
741
|
}
|
|
@@ -350,18 +748,18 @@ class VtFlowOrchestrator {
|
|
|
350
748
|
if (!record.credentialExchangeRecordId) {
|
|
351
749
|
throw new Error(`vt-flow record ${vtFlowRecordId} has no credentialExchangeRecordId`);
|
|
352
750
|
}
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
if (!jsonld) {
|
|
356
|
-
throw new Error(`No jsonld credential in exchange ${record.credentialExchangeRecordId}`);
|
|
357
|
-
}
|
|
358
|
-
const schemaRef = (_b = jsonld.credentialSchema) === null || _b === void 0 ? void 0 : _b.id;
|
|
751
|
+
const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
|
|
752
|
+
const schemaRef = (_a = credentialJson.credentialSchema) === null || _a === void 0 ? void 0 : _a.id;
|
|
359
753
|
const schemaBaseId = schemaRef ? this.extractSchemaBaseId(schemaRef) : undefined;
|
|
360
754
|
if (!schemaBaseId) {
|
|
361
755
|
throw new Error(`Cannot publish Linked VP: credential has no extractable schema base id (credentialSchema.id=${schemaRef !== null && schemaRef !== void 0 ? schemaRef : 'undefined'})`);
|
|
362
756
|
}
|
|
363
|
-
|
|
364
|
-
|
|
757
|
+
// A data model 2.0 credential arrives secured with a DataIntegrityProof; a validator still on
|
|
758
|
+
// data model 1.1 sends a linked data proof, and both are published the same way
|
|
759
|
+
const credential = (0, utils_1.isVcdm2Credential)(credentialJson)
|
|
760
|
+
? core_1.W3cV2DataIntegrityVerifiableCredential.fromObject(credentialJson)
|
|
761
|
+
: core_1.JsonTransformer.fromJSON(credentialJson, core_1.W3cJsonLdVerifiableCredential);
|
|
762
|
+
await (0, utils_1.createVtc)(this.agent, this.options.publicApiBaseUrl, record.ecsSchemaKey ? (0, utils_1.linkedVpSchemaId)(record.ecsSchemaKey) : schemaBaseId, credential);
|
|
365
763
|
}
|
|
366
764
|
extractSchemaBaseId(jscUrl) {
|
|
367
765
|
var _a;
|