@verana-labs/credo-ts-didcomm-vt-flow 2.0.0-dev.8 → 2.0.0-dev.81
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/build/VtFlowApi.d.ts +20 -3
- package/build/VtFlowApi.js +123 -14
- package/build/VtFlowApi.js.map +1 -1
- package/build/VtFlowModule.js +27 -18
- package/build/VtFlowModule.js.map +1 -1
- package/build/VtFlowModuleConfig.d.ts +45 -3
- package/build/VtFlowModuleConfig.js +16 -1
- package/build/VtFlowModuleConfig.js.map +1 -1
- package/build/errors/VtFlowErrorCode.d.ts +13 -3
- package/build/errors/VtFlowErrorCode.js +62 -18
- package/build/errors/VtFlowErrorCode.js.map +1 -1
- package/build/errors/buildVtFlowProblemReport.d.ts +5 -2
- package/build/errors/buildVtFlowProblemReport.js +8 -3
- package/build/errors/buildVtFlowProblemReport.js.map +1 -1
- package/build/errors/index.d.ts +3 -3
- package/build/errors/index.js +4 -1
- package/build/errors/index.js.map +1 -1
- package/build/handlers/IssuanceRequestHandler.d.ts +2 -1
- package/build/handlers/IssuanceRequestHandler.js +11 -2
- package/build/handlers/IssuanceRequestHandler.js.map +1 -1
- package/build/handlers/OnboardingRequestHandler.d.ts +2 -1
- package/build/handlers/OnboardingRequestHandler.js +11 -2
- package/build/handlers/OnboardingRequestHandler.js.map +1 -1
- package/build/handlers/ProblemReportHandler.d.ts +10 -0
- package/build/handlers/ProblemReportHandler.js +17 -0
- package/build/handlers/ProblemReportHandler.js.map +1 -0
- package/build/handlers/index.d.ts +1 -0
- package/build/handlers/index.js +3 -1
- package/build/handlers/index.js.map +1 -1
- package/build/index.d.ts +5 -4
- package/build/index.js +12 -1
- package/build/index.js.map +1 -1
- package/build/messages/ValidatingMessage.d.ts +1 -1
- package/build/messages/ValidatingMessage.js +1 -1
- package/build/messages/ValidatingMessage.js.map +1 -1
- package/build/messages/VtFlowProblemReportMessage.d.ts +13 -0
- package/build/messages/VtFlowProblemReportMessage.js +53 -0
- package/build/messages/VtFlowProblemReportMessage.js.map +1 -0
- package/build/messages/VtFlowProtocol.d.ts +3 -1
- package/build/messages/VtFlowProtocol.js +4 -2
- package/build/messages/VtFlowProtocol.js.map +1 -1
- package/build/messages/index.d.ts +2 -1
- package/build/messages/index.js +4 -1
- package/build/messages/index.js.map +1 -1
- package/build/repository/VtFlowRecord.d.ts +25 -7
- package/build/repository/VtFlowRecord.js +10 -3
- package/build/repository/VtFlowRecord.js.map +1 -1
- package/build/services/VtFlowService.d.ts +65 -17
- package/build/services/VtFlowService.js +431 -87
- package/build/services/VtFlowService.js.map +1 -1
- package/build/types.d.ts +100 -5
- package/build/types.js +66 -2
- package/build/types.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +6 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/peerAnchorDid.d.ts +2 -0
- package/build/utils/peerAnchorDid.js +8 -0
- package/build/utils/peerAnchorDid.js.map +1 -0
- package/package.json +5 -5
|
@@ -19,6 +19,7 @@ const VtFlowModuleConfig_1 = require("../VtFlowModuleConfig");
|
|
|
19
19
|
const errors_1 = require("../errors");
|
|
20
20
|
const messages_1 = require("../messages");
|
|
21
21
|
const repository_1 = require("../repository");
|
|
22
|
+
const utils_1 = require("../utils");
|
|
22
23
|
const types_1 = require("../types");
|
|
23
24
|
/** Core state machine for vt-flow; each method performs one transition and returns the wire message without dispatching (dispatch lives in `VtFlowApi`). */
|
|
24
25
|
let VtFlowService = class VtFlowService {
|
|
@@ -31,7 +32,7 @@ let VtFlowService = class VtFlowService {
|
|
|
31
32
|
/** Applicant-side OnboardingProcess: build the outbound `onboarding-request` and persist a record in `OR_SENT`. */
|
|
32
33
|
async createOnboardingProcessRecord(agentContext, params) {
|
|
33
34
|
const message = new messages_1.OnboardingRequestMessage({
|
|
34
|
-
participantId: params.
|
|
35
|
+
participantId: params.applicantParticipantId,
|
|
35
36
|
participantSessionId: params.participantSessionId,
|
|
36
37
|
agentParticipantId: params.agentParticipantId,
|
|
37
38
|
walletAgentParticipantId: params.walletAgentParticipantId,
|
|
@@ -40,7 +41,7 @@ let VtFlowService = class VtFlowService {
|
|
|
40
41
|
message.setThread({ threadId: message.id });
|
|
41
42
|
const existing = await this.repository.findByParticipantSessionId(agentContext, params.participantSessionId, types_1.VtFlowRole.Applicant);
|
|
42
43
|
if (existing) {
|
|
43
|
-
if (
|
|
44
|
+
if (!(0, types_1.isVtFlowRenewable)(existing)) {
|
|
44
45
|
throw new core_1.CredoError(`vt-flow: participant_session_id '${params.participantSessionId}' already belongs to a flow in state ${existing.state}; use a new session id`);
|
|
45
46
|
}
|
|
46
47
|
existing.connectionId = params.connectionId;
|
|
@@ -59,7 +60,10 @@ let VtFlowService = class VtFlowService {
|
|
|
59
60
|
variant: types_1.VtFlowVariant.OnboardingProcess,
|
|
60
61
|
agentParticipantId: params.agentParticipantId,
|
|
61
62
|
walletAgentParticipantId: params.walletAgentParticipantId,
|
|
62
|
-
|
|
63
|
+
applicantParticipantId: params.applicantParticipantId,
|
|
64
|
+
applicantParticipantRole: params.applicantParticipantRole,
|
|
65
|
+
validatorParticipantId: params.validatorParticipantId,
|
|
66
|
+
schemaId: params.schemaId,
|
|
63
67
|
claims: params.claims,
|
|
64
68
|
});
|
|
65
69
|
await this.repository.save(agentContext, record);
|
|
@@ -92,46 +96,109 @@ let VtFlowService = class VtFlowService {
|
|
|
92
96
|
this.emitStateChanged(agentContext, record, null);
|
|
93
97
|
return { message, record };
|
|
94
98
|
}
|
|
99
|
+
async reattachOnboardingProcessRecord(agentContext, params) {
|
|
100
|
+
const record = await this.repository.getById(agentContext, params.vtFlowRecordId);
|
|
101
|
+
record.assertRole(types_1.VtFlowRole.Applicant);
|
|
102
|
+
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
103
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state) ||
|
|
104
|
+
record.state === types_1.VtFlowState.Completed ||
|
|
105
|
+
record.state === types_1.VtFlowState.CredRevoked) {
|
|
106
|
+
throw new core_1.CredoError(`vt-flow: flow '${record.id}' in state ${record.state} cannot be re-attached`);
|
|
107
|
+
}
|
|
108
|
+
if (!record.applicantParticipantId)
|
|
109
|
+
throw new core_1.CredoError(`vt-flow: flow '${record.id}' has no participant_id`);
|
|
110
|
+
const message = new messages_1.OnboardingRequestMessage({
|
|
111
|
+
participantId: record.applicantParticipantId,
|
|
112
|
+
participantSessionId: record.participantSessionId,
|
|
113
|
+
agentParticipantId: record.agentParticipantId,
|
|
114
|
+
walletAgentParticipantId: record.walletAgentParticipantId,
|
|
115
|
+
claims: record.claims,
|
|
116
|
+
proofsAttach: record.proofsAttach,
|
|
117
|
+
});
|
|
118
|
+
message.setThread({ threadId: record.threadId });
|
|
119
|
+
record.connectionId = params.connectionId;
|
|
120
|
+
if (record.state === types_1.VtFlowState.CredOffered) {
|
|
121
|
+
await this.releaseCredentialExchange(agentContext, record);
|
|
122
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await this.repository.update(agentContext, record);
|
|
126
|
+
}
|
|
127
|
+
return { message, record };
|
|
128
|
+
}
|
|
129
|
+
async releaseCredentialExchange(agentContext, record) {
|
|
130
|
+
if (record.credentialExchangeRecordId) {
|
|
131
|
+
const exchanges = agentContext.dependencyManager.resolve(didcomm_1.DidCommCredentialExchangeRepository);
|
|
132
|
+
const stale = await exchanges.findById(agentContext, record.credentialExchangeRecordId);
|
|
133
|
+
if (stale) {
|
|
134
|
+
stale.parentThreadId = undefined;
|
|
135
|
+
await exchanges.update(agentContext, stale);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
record.credentialExchangeRecordId = undefined;
|
|
139
|
+
record.subprotocolThid = undefined;
|
|
140
|
+
record.issuance = undefined;
|
|
141
|
+
}
|
|
95
142
|
/** Validator-side OnboardingProcess: create or re-attach (by `participant_session_id`) a record in `AWAITING_OR` from an inbound `onboarding-request`. */
|
|
96
143
|
async processReceiveOnboardingRequest(messageContext) {
|
|
97
144
|
const { message, agentContext } = messageContext;
|
|
98
145
|
const connection = messageContext.assertReadyConnection();
|
|
99
|
-
await this.checkIsVerifiableService(agentContext, connection);
|
|
100
146
|
const existing = await this.repository.findByParticipantSessionId(agentContext, message.participantSessionId, types_1.VtFlowRole.Validator);
|
|
147
|
+
const record = existing !== null && existing !== void 0 ? existing : new repository_1.VtFlowRecord({
|
|
148
|
+
threadId: message.threadId,
|
|
149
|
+
participantSessionId: message.participantSessionId,
|
|
150
|
+
connectionId: connection.id,
|
|
151
|
+
role: types_1.VtFlowRole.Validator,
|
|
152
|
+
state: types_1.VtFlowState.AwaitingOr,
|
|
153
|
+
variant: types_1.VtFlowVariant.OnboardingProcess,
|
|
154
|
+
agentParticipantId: message.agentParticipantId,
|
|
155
|
+
walletAgentParticipantId: message.walletAgentParticipantId,
|
|
156
|
+
applicantParticipantId: message.participantId,
|
|
157
|
+
claims: message.claims,
|
|
158
|
+
proofsAttach: message.proofsAttach,
|
|
159
|
+
});
|
|
160
|
+
await this.checkRequestFromVerifiableService(agentContext, connection, record, !existing, {
|
|
161
|
+
participantId: message.participantId,
|
|
162
|
+
});
|
|
101
163
|
if (existing) {
|
|
102
164
|
if ((0, types_1.isVtFlowTerminalState)(existing.state)) {
|
|
103
|
-
throw new
|
|
165
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantSessionId, `vt-flow: participant_session_id '${message.participantSessionId}' collides with a terminated flow`);
|
|
166
|
+
}
|
|
167
|
+
if (existing.applicantParticipantId !== message.participantId) {
|
|
168
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantSessionId, `vt-flow: participant_id '${message.participantId}' does not match the flow of participant_session_id '${message.participantSessionId}'`);
|
|
104
169
|
}
|
|
105
170
|
await this.assertSamePeer(agentContext, existing, connection);
|
|
171
|
+
const reconnected = existing.connectionTerminated;
|
|
106
172
|
existing.connectionId = connection.id;
|
|
107
|
-
existing.
|
|
108
|
-
if (message.
|
|
109
|
-
existing.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
173
|
+
existing.connectionTerminated = undefined;
|
|
174
|
+
if (existing.threadId !== message.threadId) {
|
|
175
|
+
existing.threadId = message.threadId;
|
|
176
|
+
if (message.claims)
|
|
177
|
+
existing.claims = message.claims;
|
|
178
|
+
if (message.proofsAttach)
|
|
179
|
+
existing.proofsAttach = message.proofsAttach;
|
|
180
|
+
}
|
|
181
|
+
if ((0, types_1.isVtFlowRenewable)(existing)) {
|
|
113
182
|
// A finished flow re-entered with a new OR is a renewal (VSA-VTI-FLOW-OP-RENEW): re-run it.
|
|
114
|
-
existing.
|
|
183
|
+
existing.oobLink = undefined;
|
|
184
|
+
existing.validation = undefined;
|
|
185
|
+
existing.issuance = undefined;
|
|
115
186
|
await this.updateState(agentContext, existing, types_1.VtFlowState.AwaitingOr);
|
|
116
187
|
}
|
|
188
|
+
else if (existing.state === types_1.VtFlowState.CredOffered) {
|
|
189
|
+
await this.releaseCredentialExchange(agentContext, existing);
|
|
190
|
+
await this.updateState(agentContext, existing, types_1.VtFlowState.Validated);
|
|
191
|
+
}
|
|
117
192
|
else {
|
|
118
193
|
await this.repository.update(agentContext, existing);
|
|
194
|
+
// [VSA-VTI-FLOW-OP-OR] checks every request, and [VSA-ADM-VT-FL-REJECT-2] issuance waits for this reconnection
|
|
195
|
+
if (existing.state === types_1.VtFlowState.AwaitingOr ||
|
|
196
|
+
(reconnected && existing.state === types_1.VtFlowState.Validated)) {
|
|
197
|
+
this.emitStateChanged(agentContext, existing, existing.state);
|
|
198
|
+
}
|
|
119
199
|
}
|
|
120
200
|
return existing;
|
|
121
201
|
}
|
|
122
|
-
const record = new repository_1.VtFlowRecord({
|
|
123
|
-
threadId: message.threadId,
|
|
124
|
-
participantSessionId: message.participantSessionId,
|
|
125
|
-
connectionId: connection.id,
|
|
126
|
-
role: types_1.VtFlowRole.Validator,
|
|
127
|
-
state: types_1.VtFlowState.AwaitingOr,
|
|
128
|
-
variant: types_1.VtFlowVariant.OnboardingProcess,
|
|
129
|
-
agentParticipantId: message.agentParticipantId,
|
|
130
|
-
walletAgentParticipantId: message.walletAgentParticipantId,
|
|
131
|
-
participantId: message.participantId,
|
|
132
|
-
claims: message.claims,
|
|
133
|
-
proofsAttach: message.proofsAttach,
|
|
134
|
-
});
|
|
135
202
|
await this.repository.save(agentContext, record);
|
|
136
203
|
this.emitStateChanged(agentContext, record, null);
|
|
137
204
|
return record;
|
|
@@ -140,19 +207,8 @@ let VtFlowService = class VtFlowService {
|
|
|
140
207
|
async processReceiveIssuanceRequest(messageContext) {
|
|
141
208
|
const { message, agentContext } = messageContext;
|
|
142
209
|
const connection = messageContext.assertReadyConnection();
|
|
143
|
-
await this.checkIsVerifiableService(agentContext, connection);
|
|
144
210
|
const existing = await this.repository.findByParticipantSessionId(agentContext, message.participantSessionId, types_1.VtFlowRole.Validator);
|
|
145
|
-
|
|
146
|
-
if ((0, types_1.isVtFlowTerminalState)(existing.state)) {
|
|
147
|
-
throw new core_1.CredoError(`vt-flow: participant_session_id '${message.participantSessionId}' collides with a terminated flow`);
|
|
148
|
-
}
|
|
149
|
-
await this.assertSamePeer(agentContext, existing, connection);
|
|
150
|
-
existing.connectionId = connection.id;
|
|
151
|
-
existing.threadId = message.threadId;
|
|
152
|
-
await this.repository.update(agentContext, existing);
|
|
153
|
-
return existing;
|
|
154
|
-
}
|
|
155
|
-
const record = new repository_1.VtFlowRecord({
|
|
211
|
+
const record = existing !== null && existing !== void 0 ? existing : new repository_1.VtFlowRecord({
|
|
156
212
|
threadId: message.threadId,
|
|
157
213
|
participantSessionId: message.participantSessionId,
|
|
158
214
|
connectionId: connection.id,
|
|
@@ -164,20 +220,61 @@ let VtFlowService = class VtFlowService {
|
|
|
164
220
|
schemaId: message.schemaId,
|
|
165
221
|
claims: message.claims,
|
|
166
222
|
});
|
|
223
|
+
await this.checkRequestFromVerifiableService(agentContext, connection, record, !existing, {
|
|
224
|
+
schemaId: message.schemaId,
|
|
225
|
+
});
|
|
226
|
+
if (existing) {
|
|
227
|
+
if ((0, types_1.isVtFlowTerminalState)(existing.state)) {
|
|
228
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantSessionId, `vt-flow: participant_session_id '${message.participantSessionId}' collides with a terminated flow`);
|
|
229
|
+
}
|
|
230
|
+
if (existing.variant !== types_1.VtFlowVariant.DirectIssuance || existing.schemaId !== message.schemaId) {
|
|
231
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantSessionId, `vt-flow: schema_id '${message.schemaId}' does not match the flow of participant_session_id '${message.participantSessionId}'`);
|
|
232
|
+
}
|
|
233
|
+
await this.assertSamePeer(agentContext, existing, connection);
|
|
234
|
+
existing.connectionId = connection.id;
|
|
235
|
+
existing.threadId = message.threadId;
|
|
236
|
+
if (existing.state === types_1.VtFlowState.CredOffered) {
|
|
237
|
+
await this.releaseCredentialExchange(agentContext, existing);
|
|
238
|
+
await this.updateState(agentContext, existing, types_1.VtFlowState.Validating);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
await this.repository.update(agentContext, existing);
|
|
242
|
+
}
|
|
243
|
+
return existing;
|
|
244
|
+
}
|
|
167
245
|
await this.repository.save(agentContext, record);
|
|
168
246
|
this.emitStateChanged(agentContext, record, null);
|
|
169
247
|
return record;
|
|
170
248
|
}
|
|
171
|
-
/** Applicant-side inbound `oob-link`;
|
|
249
|
+
/** Applicant-side inbound `oob-link`; `OR_SENT`, `IR_SENT`, `VALIDATING` or `OOB_PENDING` => `OOB_PENDING`. */
|
|
172
250
|
async processReceiveOobLink(messageContext) {
|
|
251
|
+
var _a;
|
|
173
252
|
const { message, agentContext } = messageContext;
|
|
174
253
|
messageContext.assertReadyConnection();
|
|
175
254
|
const record = await this.getByThreadId(agentContext, message.threadId);
|
|
176
255
|
record.assertRole(types_1.VtFlowRole.Applicant);
|
|
256
|
+
record.assertState([
|
|
257
|
+
types_1.VtFlowState.OrSent,
|
|
258
|
+
types_1.VtFlowState.IrSent,
|
|
259
|
+
types_1.VtFlowState.Validating,
|
|
260
|
+
types_1.VtFlowState.OobPending,
|
|
261
|
+
]);
|
|
262
|
+
record.oobLink = {
|
|
263
|
+
url: message.url,
|
|
264
|
+
description: message.description,
|
|
265
|
+
expiresAt: (_a = message.expiresTime) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
266
|
+
at: new Date().toISOString(),
|
|
267
|
+
};
|
|
268
|
+
this.appendMessage(record, {
|
|
269
|
+
type: types_1.VtFlowMessageType.OobLink,
|
|
270
|
+
text: message.description,
|
|
271
|
+
at: record.oobLink.at,
|
|
272
|
+
url: message.url,
|
|
273
|
+
});
|
|
177
274
|
await this.updateState(agentContext, record, types_1.VtFlowState.OobPending);
|
|
178
275
|
return record;
|
|
179
276
|
}
|
|
180
|
-
/** Applicant-side inbound `validating`;
|
|
277
|
+
/** Applicant-side inbound `validating`; `OR_SENT`, `IR_SENT` or `OOB_PENDING` => `VALIDATING`. */
|
|
181
278
|
async processReceiveValidating(messageContext) {
|
|
182
279
|
var _a;
|
|
183
280
|
const { message, agentContext } = messageContext;
|
|
@@ -185,8 +282,16 @@ let VtFlowService = class VtFlowService {
|
|
|
185
282
|
const record = await this.getByThreadId(agentContext, message.threadId);
|
|
186
283
|
this.logger.debug(`[vt-flow] validating received for session ${record.threadId}: ${(_a = message.comment) !== null && _a !== void 0 ? _a : '(no comment)'}`);
|
|
187
284
|
if (record.role === types_1.VtFlowRole.Applicant &&
|
|
188
|
-
record.
|
|
189
|
-
record
|
|
285
|
+
(message.comment || record.state === types_1.VtFlowState.OobPending)) {
|
|
286
|
+
this.appendMessage(record, {
|
|
287
|
+
type: types_1.VtFlowMessageType.Validating,
|
|
288
|
+
text: message.comment,
|
|
289
|
+
at: new Date().toISOString(),
|
|
290
|
+
});
|
|
291
|
+
await this.updateRecord(agentContext, record);
|
|
292
|
+
}
|
|
293
|
+
if (record.role === types_1.VtFlowRole.Applicant &&
|
|
294
|
+
[types_1.VtFlowState.OrSent, types_1.VtFlowState.IrSent, types_1.VtFlowState.OobPending].includes(record.state)) {
|
|
190
295
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
191
296
|
}
|
|
192
297
|
return record;
|
|
@@ -208,14 +313,26 @@ let VtFlowService = class VtFlowService {
|
|
|
208
313
|
}
|
|
209
314
|
return record;
|
|
210
315
|
}
|
|
211
|
-
/** `AWAITING_OR => VALIDATING
|
|
316
|
+
/** `AWAITING_OR => VALIDATING` once the `checkParticipantId` hook accepts the `participant_id`, with no transition and no message once it reports the request settled by a `VALIDATED` entry; caller is expected to have verified agent/wallet IDs on-chain. */
|
|
212
317
|
async acceptOnboardingRequest(agentContext, recordId) {
|
|
213
|
-
|
|
318
|
+
let record = await this.repository.getById(agentContext, recordId);
|
|
214
319
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
215
320
|
record.assertState(types_1.VtFlowState.AwaitingOr);
|
|
216
321
|
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
322
|
+
const { checkParticipantId } = this.config;
|
|
323
|
+
if (checkParticipantId) {
|
|
324
|
+
const checked = await checkParticipantId({ agentContext, record });
|
|
325
|
+
if (!checked) {
|
|
326
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantId, `vt-flow: participant_id '${record.applicantParticipantId}' is not a PENDING entry that names this agent as its validator`);
|
|
327
|
+
}
|
|
328
|
+
// the check can wait for the indexer while the flow moves on
|
|
329
|
+
record = await this.repository.getById(agentContext, recordId);
|
|
330
|
+
if (checked === 'validated')
|
|
331
|
+
return { record };
|
|
332
|
+
record.assertState(types_1.VtFlowState.AwaitingOr);
|
|
333
|
+
}
|
|
217
334
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
218
|
-
return record;
|
|
335
|
+
return { record, message: new messages_1.ValidatingMessage({ threadId: record.threadId }) };
|
|
219
336
|
}
|
|
220
337
|
/** DirectIssuance: `AWAITING_IR => VALIDATING`; transient before `CRED_OFFERED`. */
|
|
221
338
|
async acceptIssuanceRequest(agentContext, recordId) {
|
|
@@ -224,23 +341,44 @@ let VtFlowService = class VtFlowService {
|
|
|
224
341
|
record.assertState(types_1.VtFlowState.AwaitingIr);
|
|
225
342
|
record.assertVariant(types_1.VtFlowVariant.DirectIssuance);
|
|
226
343
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
227
|
-
return record;
|
|
344
|
+
return { record, message: new messages_1.ValidatingMessage({ threadId: record.threadId }) };
|
|
228
345
|
}
|
|
229
|
-
/** Reject with a problem-report;
|
|
346
|
+
/** Reject with a problem-report; a fatal code moves the sender to the Error Codes state, a retryable one returns a validator to `AWAITING_OR` or `AWAITING_IR`. */
|
|
230
347
|
async rejectRequest(agentContext, recordId, params) {
|
|
231
348
|
var _a;
|
|
232
349
|
const record = await this.repository.getById(agentContext, recordId);
|
|
350
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state)) {
|
|
351
|
+
throw new core_1.CredoError(`vt-flow: flow '${record.id}' in state ${record.state} cannot be rejected`);
|
|
352
|
+
}
|
|
233
353
|
const problemReport = (0, errors_1.buildVtFlowProblemReport)({
|
|
234
354
|
code: params.code,
|
|
235
355
|
threadId: record.threadId,
|
|
236
356
|
enDescription: params.enDescription,
|
|
237
357
|
fixHintEn: params.fixHintEn,
|
|
238
358
|
});
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
359
|
+
const info = errors_1.VT_FLOW_ERROR_INFO[params.code];
|
|
360
|
+
const receiverRole = record.role === types_1.VtFlowRole.Validator ? types_1.VtFlowRole.Applicant : types_1.VtFlowRole.Validator;
|
|
361
|
+
// a fatal code lands both parties in the state the table gives the receiver
|
|
362
|
+
const fatalState = this.resolveErrorFlowState(info.flowState, receiverRole, errors_1.whoRetriesMap[info.whoRetries]);
|
|
363
|
+
const retryState = record.variant === types_1.VtFlowVariant.OnboardingProcess ? types_1.VtFlowState.AwaitingOr : types_1.VtFlowState.AwaitingIr;
|
|
364
|
+
if (!fatalState && record.role === types_1.VtFlowRole.Validator) {
|
|
365
|
+
record.assertState([
|
|
366
|
+
types_1.VtFlowState.AwaitingOr,
|
|
367
|
+
types_1.VtFlowState.AwaitingIr,
|
|
368
|
+
types_1.VtFlowState.Validating,
|
|
369
|
+
types_1.VtFlowState.OobPending,
|
|
370
|
+
]);
|
|
371
|
+
}
|
|
372
|
+
if (record.role === types_1.VtFlowRole.Validator) {
|
|
373
|
+
this.appendMessage(record, {
|
|
374
|
+
type: types_1.VtFlowMessageType.ProblemReport,
|
|
375
|
+
text: problemReport.description.en,
|
|
376
|
+
at: new Date().toISOString(),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if (fatalState)
|
|
380
|
+
record.errorMessage = (_a = params.enDescription) !== null && _a !== void 0 ? _a : params.code;
|
|
381
|
+
await this.updateState(agentContext, record, fatalState !== null && fatalState !== void 0 ? fatalState : (record.role === types_1.VtFlowRole.Validator ? retryState : record.state));
|
|
244
382
|
return { record, problemReport };
|
|
245
383
|
}
|
|
246
384
|
/** Applicant-side termination producing a problem-report and landing in `TERMINATED_BY_APPLICANT`. */
|
|
@@ -264,6 +402,17 @@ let VtFlowService = class VtFlowService {
|
|
|
264
402
|
var _a, _b;
|
|
265
403
|
const record = await this.repository.getById(agentContext, recordId);
|
|
266
404
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
405
|
+
record.assertState([
|
|
406
|
+
types_1.VtFlowState.AwaitingOr,
|
|
407
|
+
types_1.VtFlowState.AwaitingIr,
|
|
408
|
+
types_1.VtFlowState.Validating,
|
|
409
|
+
types_1.VtFlowState.OobPending,
|
|
410
|
+
types_1.VtFlowState.AwaitingValidationTx,
|
|
411
|
+
types_1.VtFlowState.ValidationTxFailed,
|
|
412
|
+
// VtFlowModule ends the flow here when auto-issue throws. A failed anchoring stays in CRED_OFFERED instead
|
|
413
|
+
types_1.VtFlowState.CredOffered,
|
|
414
|
+
types_1.VtFlowState.Completed,
|
|
415
|
+
]);
|
|
267
416
|
const code = (_a = params.code) !== null && _a !== void 0 ? _a : errors_1.VtFlowErrorCode.SessionTerminated;
|
|
268
417
|
const problemReport = (0, errors_1.buildVtFlowProblemReport)({
|
|
269
418
|
code,
|
|
@@ -272,6 +421,12 @@ let VtFlowService = class VtFlowService {
|
|
|
272
421
|
fixHintEn: params.fixHintEn,
|
|
273
422
|
});
|
|
274
423
|
record.errorMessage = (_b = params.enDescription) !== null && _b !== void 0 ? _b : code;
|
|
424
|
+
this.appendMessage(record, {
|
|
425
|
+
type: types_1.VtFlowMessageType.ProblemReport,
|
|
426
|
+
text: problemReport.description.en,
|
|
427
|
+
at: new Date().toISOString(),
|
|
428
|
+
});
|
|
429
|
+
record.connectionTerminated = true;
|
|
275
430
|
await this.updateState(agentContext, record, types_1.VtFlowState.TerminatedByValidator);
|
|
276
431
|
return { record, problemReport };
|
|
277
432
|
}
|
|
@@ -288,62 +443,120 @@ let VtFlowService = class VtFlowService {
|
|
|
288
443
|
await this.updateState(agentContext, record, params.state);
|
|
289
444
|
return { record, problemReport };
|
|
290
445
|
}
|
|
291
|
-
/** Build an `oob-link`;
|
|
446
|
+
/** Build an `oob-link` from `VALIDATING` or `OOB_PENDING`; the flow moves to or stays in `OOB_PENDING`. */
|
|
292
447
|
async sendOobLinkForSession(agentContext, recordId, params) {
|
|
448
|
+
var _a;
|
|
293
449
|
const record = await this.repository.getById(agentContext, recordId);
|
|
294
450
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
295
|
-
record.assertState([
|
|
296
|
-
types_1.VtFlowState.AwaitingOr,
|
|
297
|
-
types_1.VtFlowState.AwaitingIr,
|
|
298
|
-
types_1.VtFlowState.Validating,
|
|
299
|
-
types_1.VtFlowState.OobPending,
|
|
300
|
-
types_1.VtFlowState.Completed,
|
|
301
|
-
]);
|
|
451
|
+
record.assertState([types_1.VtFlowState.Validating, types_1.VtFlowState.OobPending]);
|
|
302
452
|
const message = new messages_1.OobLinkMessage({
|
|
303
453
|
threadId: record.threadId,
|
|
304
454
|
url: params.url,
|
|
305
455
|
description: params.description,
|
|
306
456
|
expiresTime: params.expiresTime,
|
|
307
457
|
});
|
|
308
|
-
record.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
458
|
+
record.oobLink = {
|
|
459
|
+
url: params.url,
|
|
460
|
+
description: params.description,
|
|
461
|
+
expiresAt: (_a = params.expiresTime) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
462
|
+
at: new Date().toISOString(),
|
|
463
|
+
};
|
|
464
|
+
this.appendMessage(record, {
|
|
465
|
+
type: types_1.VtFlowMessageType.OobLink,
|
|
466
|
+
text: params.description,
|
|
467
|
+
at: record.oobLink.at,
|
|
468
|
+
url: params.url,
|
|
469
|
+
});
|
|
470
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.OobPending);
|
|
315
471
|
return { record, message };
|
|
316
472
|
}
|
|
317
|
-
/**
|
|
473
|
+
/** `OOB_PENDING` => `VALIDATING`, with the `validating` that tells the applicant its out-of-band step is done. */
|
|
318
474
|
async sendValidatingForSession(agentContext, recordId, params = {}) {
|
|
319
475
|
const record = await this.repository.getById(agentContext, recordId);
|
|
320
476
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
477
|
+
record.assertState(types_1.VtFlowState.OobPending);
|
|
321
478
|
const message = new messages_1.ValidatingMessage({
|
|
322
479
|
threadId: record.threadId,
|
|
323
480
|
comment: params.comment,
|
|
324
481
|
});
|
|
482
|
+
this.appendMessage(record, {
|
|
483
|
+
type: types_1.VtFlowMessageType.Validating,
|
|
484
|
+
text: params.comment,
|
|
485
|
+
at: new Date().toISOString(),
|
|
486
|
+
});
|
|
487
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
325
488
|
return { record, message };
|
|
326
489
|
}
|
|
327
|
-
/**
|
|
490
|
+
/** Record the validation decision or its transaction outcome, moving the flow when `state` is given. */
|
|
491
|
+
async recordValidation(agentContext, recordId, validation, state) {
|
|
492
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
493
|
+
record.assertRole(types_1.VtFlowRole.Validator);
|
|
494
|
+
record.validation = validation;
|
|
495
|
+
if (state)
|
|
496
|
+
await this.updateState(agentContext, record, state);
|
|
497
|
+
else
|
|
498
|
+
await this.updateRecord(agentContext, record);
|
|
499
|
+
return record;
|
|
500
|
+
}
|
|
501
|
+
/** Record the outcome of the `CreateOrUpdateParticipantSession` transaction that anchors the issued credential. */
|
|
502
|
+
async recordIssuance(agentContext, recordId, issuance) {
|
|
503
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
504
|
+
record.assertRole(types_1.VtFlowRole.Validator);
|
|
505
|
+
record.issuance = issuance;
|
|
506
|
+
await this.updateRecord(agentContext, record);
|
|
507
|
+
return record;
|
|
508
|
+
}
|
|
509
|
+
/** `VALIDATED` => `VALIDATED_PENDING_CLAIMS`, when the claims fail the schema at offer time ([VSA-VTI-FLOW-OP-ISSUE-4]). */
|
|
510
|
+
async markPendingClaims(agentContext, recordId) {
|
|
511
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
512
|
+
record.assertRole(types_1.VtFlowRole.Validator);
|
|
513
|
+
record.assertState(types_1.VtFlowState.Validated);
|
|
514
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.ValidatedPendingClaims);
|
|
515
|
+
return record;
|
|
516
|
+
}
|
|
517
|
+
/** Validator-side states preceding `VALIDATED` => `VALIDATED`; call after `SetParticipantOPtoValidated` lands on-chain. */
|
|
328
518
|
async markValidated(agentContext, recordId) {
|
|
329
519
|
const record = await this.repository.getById(agentContext, recordId);
|
|
330
520
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
331
|
-
record.assertState([types_1.
|
|
521
|
+
record.assertState([...types_1.VtFlowValidatedFromStates]);
|
|
332
522
|
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
333
523
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validated);
|
|
334
524
|
return record;
|
|
335
525
|
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Close an onboarding process that carries no credential exchange. Only a HOLDER receives a
|
|
528
|
+
* credential; for every other role the chain records the outcome with
|
|
529
|
+
* SetParticipantOPToValidated, and nothing else follows. Both sides use this, so it asserts no
|
|
530
|
+
* role: the validator calls it after it validates, and the applicant reaches it from the chain
|
|
531
|
+
* event.
|
|
532
|
+
*/
|
|
533
|
+
async markCompleted(agentContext, recordId) {
|
|
338
534
|
const record = await this.repository.getById(agentContext, recordId);
|
|
535
|
+
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
536
|
+
if (record.state === types_1.VtFlowState.Completed)
|
|
537
|
+
return record;
|
|
538
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state)) {
|
|
539
|
+
throw new core_1.CredoError(`VtFlow record is in terminal state '${record.state}'; cannot complete it.`);
|
|
540
|
+
}
|
|
541
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Completed);
|
|
542
|
+
return record;
|
|
543
|
+
}
|
|
544
|
+
/** Validator states that an `offer-credential` moves to `CRED_OFFERED`; from `COMPLETED` it starts a new subprotocol run. */
|
|
545
|
+
assertCanOfferCredential(record) {
|
|
339
546
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
340
547
|
record.assertState([
|
|
341
548
|
types_1.VtFlowState.Validated,
|
|
342
|
-
types_1.VtFlowState.
|
|
343
|
-
types_1.VtFlowState.OobPending,
|
|
549
|
+
types_1.VtFlowState.ValidatedPendingClaims,
|
|
344
550
|
types_1.VtFlowState.Completed,
|
|
551
|
+
...(record.variant === types_1.VtFlowVariant.DirectIssuance ? [types_1.VtFlowState.Validating] : []),
|
|
345
552
|
]);
|
|
553
|
+
}
|
|
554
|
+
/** Link a Credo exchange record to the session and transition to `CRED_OFFERED`. */
|
|
555
|
+
async attachCredentialExchangeRecord(agentContext, recordId, credentialExchangeRecord, credentialDigest, issuerParticipantId, credentialId) {
|
|
556
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
557
|
+
this.assertCanOfferCredential(record);
|
|
346
558
|
record.credentialExchangeRecordId = credentialExchangeRecord.id;
|
|
559
|
+
record.credentialId = credentialId;
|
|
347
560
|
if (credentialDigest)
|
|
348
561
|
record.credentialDigest = credentialDigest;
|
|
349
562
|
if (issuerParticipantId !== undefined)
|
|
@@ -352,6 +565,13 @@ let VtFlowService = class VtFlowService {
|
|
|
352
565
|
await this.updateState(agentContext, record, types_1.VtFlowState.CredOffered);
|
|
353
566
|
return record;
|
|
354
567
|
}
|
|
568
|
+
async setEcsSchemaKey(agentContext, recordId, ecsSchemaKey) {
|
|
569
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
570
|
+
record.assertRole(types_1.VtFlowRole.Applicant);
|
|
571
|
+
record.ecsSchemaKey = ecsSchemaKey;
|
|
572
|
+
await this.repository.update(agentContext, record);
|
|
573
|
+
return record;
|
|
574
|
+
}
|
|
355
575
|
async setCredentialDigest(agentContext, recordId, credentialDigest) {
|
|
356
576
|
const record = await this.repository.getById(agentContext, recordId);
|
|
357
577
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
@@ -380,7 +600,16 @@ let VtFlowService = class VtFlowService {
|
|
|
380
600
|
async updateClaims(agentContext, recordId, claims) {
|
|
381
601
|
const record = await this.repository.getById(agentContext, recordId);
|
|
382
602
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
383
|
-
record.assertState([
|
|
603
|
+
record.assertState([
|
|
604
|
+
types_1.VtFlowState.AwaitingOr,
|
|
605
|
+
types_1.VtFlowState.OobPending,
|
|
606
|
+
types_1.VtFlowState.Validating,
|
|
607
|
+
types_1.VtFlowState.AwaitingValidationTx,
|
|
608
|
+
types_1.VtFlowState.ValidationTxSubmitted,
|
|
609
|
+
types_1.VtFlowState.ValidationTxFailed,
|
|
610
|
+
types_1.VtFlowState.ValidatedPendingClaims,
|
|
611
|
+
types_1.VtFlowState.CredRevoked,
|
|
612
|
+
]);
|
|
384
613
|
record.claims = claims;
|
|
385
614
|
await this.updateRecord(agentContext, record);
|
|
386
615
|
return record;
|
|
@@ -401,6 +630,11 @@ let VtFlowService = class VtFlowService {
|
|
|
401
630
|
}
|
|
402
631
|
break;
|
|
403
632
|
case didcomm_1.DidCommCredentialState.OfferReceived:
|
|
633
|
+
// the offer can beat the chain notification, and an Onboarding Process offers only from VALIDATED
|
|
634
|
+
if (record.variant === types_1.VtFlowVariant.OnboardingProcess &&
|
|
635
|
+
[types_1.VtFlowState.OrSent, types_1.VtFlowState.Validating, types_1.VtFlowState.OobPending].includes(record.state)) {
|
|
636
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Validated);
|
|
637
|
+
}
|
|
404
638
|
if (record.state !== types_1.VtFlowState.CredOffered) {
|
|
405
639
|
await this.updateState(agentContext, record, types_1.VtFlowState.CredOffered);
|
|
406
640
|
}
|
|
@@ -414,6 +648,7 @@ let VtFlowService = class VtFlowService {
|
|
|
414
648
|
}
|
|
415
649
|
break;
|
|
416
650
|
case didcomm_1.DidCommCredentialState.Abandoned:
|
|
651
|
+
case didcomm_1.DidCommCredentialState.Declined:
|
|
417
652
|
if (record.state !== types_1.VtFlowState.Error) {
|
|
418
653
|
record.errorMessage = (_a = record.errorMessage) !== null && _a !== void 0 ? _a : 'Subprotocol abandoned';
|
|
419
654
|
await this.updateState(agentContext, record, types_1.VtFlowState.Error);
|
|
@@ -448,24 +683,111 @@ let VtFlowService = class VtFlowService {
|
|
|
448
683
|
/** Transition the record, persist it, and emit `VtFlowStateChanged`. */
|
|
449
684
|
async updateState(agentContext, record, newState) {
|
|
450
685
|
const previousState = record.state;
|
|
451
|
-
if (previousState === newState)
|
|
686
|
+
if (previousState === newState) {
|
|
687
|
+
await this.repository.update(agentContext, record);
|
|
452
688
|
return;
|
|
689
|
+
}
|
|
690
|
+
if (previousState === types_1.VtFlowState.OobPending)
|
|
691
|
+
record.oobLink = undefined;
|
|
453
692
|
record.state = newState;
|
|
454
693
|
await this.repository.update(agentContext, record);
|
|
455
694
|
this.emitStateChanged(agentContext, record, previousState);
|
|
456
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Inbound adopted `problem-report`. The Error Codes table gives the state the receiving party
|
|
698
|
+
* moves to, so both sides run this same mapping. A retryable code records the message and leaves
|
|
699
|
+
* the flow where it is.
|
|
700
|
+
*/
|
|
701
|
+
async processReceiveProblemReport(messageContext) {
|
|
702
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
703
|
+
const { message, agentContext } = messageContext;
|
|
704
|
+
const record = await this.repository.findByThreadId(agentContext, message.threadId);
|
|
705
|
+
if (!record)
|
|
706
|
+
return undefined;
|
|
707
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state))
|
|
708
|
+
return record;
|
|
709
|
+
const code = (_a = message.description) === null || _a === void 0 ? void 0 : _a.code;
|
|
710
|
+
const info = code && (0, errors_1.isVtFlowErrorCode)(code) ? errors_1.VT_FLOW_ERROR_INFO[code] : undefined;
|
|
711
|
+
if (record.role === types_1.VtFlowRole.Applicant) {
|
|
712
|
+
this.appendMessage(record, {
|
|
713
|
+
type: types_1.VtFlowMessageType.ProblemReport,
|
|
714
|
+
text: (_d = (_c = (_b = message.description) === null || _b === void 0 ? void 0 : _b.en) !== null && _c !== void 0 ? _c : code) !== null && _d !== void 0 ? _d : 'problem-report',
|
|
715
|
+
at: new Date().toISOString(),
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
const target = info &&
|
|
719
|
+
this.resolveErrorFlowState(info.flowState, record.role, (_e = message.whoRetries) !== null && _e !== void 0 ? _e : errors_1.whoRetriesMap[info.whoRetries]);
|
|
720
|
+
if (!target) {
|
|
721
|
+
await this.updateRecord(agentContext, record);
|
|
722
|
+
return record;
|
|
723
|
+
}
|
|
724
|
+
record.errorMessage = (_g = (_f = message.description) === null || _f === void 0 ? void 0 : _f.en) !== null && _g !== void 0 ? _g : code;
|
|
725
|
+
await this.updateState(agentContext, record, target);
|
|
726
|
+
return record;
|
|
727
|
+
}
|
|
728
|
+
/** `terminated-by-sender` is the peer's termination, so the receiver lands in the state named after the sender. */
|
|
729
|
+
resolveErrorFlowState(flowState, receiverRole, whoRetries) {
|
|
730
|
+
if (flowState === 'unchanged')
|
|
731
|
+
return undefined;
|
|
732
|
+
if (flowState === 'unchanged-when-you') {
|
|
733
|
+
return whoRetries === didcomm_1.WhoRetriesStatus.You ? undefined : types_1.VtFlowState.Error;
|
|
734
|
+
}
|
|
735
|
+
if (flowState === 'error-when-fatal') {
|
|
736
|
+
return whoRetries === didcomm_1.WhoRetriesStatus.None ? types_1.VtFlowState.Error : undefined;
|
|
737
|
+
}
|
|
738
|
+
if (flowState !== 'terminated-by-sender')
|
|
739
|
+
return flowState;
|
|
740
|
+
return receiverRole === types_1.VtFlowRole.Applicant
|
|
741
|
+
? types_1.VtFlowState.TerminatedByValidator
|
|
742
|
+
: types_1.VtFlowState.TerminatedByApplicant;
|
|
743
|
+
}
|
|
744
|
+
/** Append to `messages[]`, which a validator fills with what it sent and an applicant with what it received. */
|
|
745
|
+
appendMessage(record, message) {
|
|
746
|
+
var _a;
|
|
747
|
+
record.messages = [...((_a = record.messages) !== null && _a !== void 0 ? _a : []), message];
|
|
748
|
+
}
|
|
457
749
|
/** Persist record changes without state-transition semantics (no event emitted). */
|
|
458
750
|
async updateRecord(agentContext, record) {
|
|
459
751
|
await this.repository.update(agentContext, record);
|
|
460
752
|
}
|
|
461
753
|
/** participant_session_ids are public on-chain, so only the original peer may re-attach a flow with one. */
|
|
462
|
-
async
|
|
754
|
+
async isSamePeer(agentContext, record, connection) {
|
|
463
755
|
if (record.connectionId === connection.id)
|
|
464
|
-
return;
|
|
756
|
+
return true;
|
|
465
757
|
const connectionRepository = agentContext.dependencyManager.resolve(didcomm_1.DidCommConnectionRepository);
|
|
466
758
|
const previous = await connectionRepository.findById(agentContext, record.connectionId);
|
|
467
|
-
|
|
468
|
-
|
|
759
|
+
return !((previous === null || previous === void 0 ? void 0 : previous.theirDid) && connection.theirDid && previous.theirDid !== connection.theirDid);
|
|
760
|
+
}
|
|
761
|
+
async assertSamePeer(agentContext, record, connection) {
|
|
762
|
+
if (!(await this.isSamePeer(agentContext, record, connection))) {
|
|
763
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.InvalidParticipantSessionId, `vt-flow: connection '${connection.id}' peer does not match the original connection of participant_session_id '${record.participantSessionId}'`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/** VS-CONN-VS failure is fatal, so it ends the flow of the request in ERROR unless that flow has ended or belongs to another peer. */
|
|
767
|
+
async checkRequestFromVerifiableService(agentContext, connection, record, isNew, purpose) {
|
|
768
|
+
try {
|
|
769
|
+
await this.checkIsVerifiableService(agentContext, connection, purpose);
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
if (error instanceof errors_1.VtFlowError &&
|
|
773
|
+
(isNew ||
|
|
774
|
+
(!(0, types_1.isVtFlowTerminalState)(record.state) && (await this.isSamePeer(agentContext, record, connection))))) {
|
|
775
|
+
record.errorMessage = error.message;
|
|
776
|
+
this.appendMessage(record, {
|
|
777
|
+
type: types_1.VtFlowMessageType.ProblemReport,
|
|
778
|
+
text: (0, errors_1.defaultEnglishDescription)(error.code),
|
|
779
|
+
at: new Date().toISOString(),
|
|
780
|
+
});
|
|
781
|
+
if (isNew) {
|
|
782
|
+
record.state = types_1.VtFlowState.Error;
|
|
783
|
+
await this.repository.save(agentContext, record);
|
|
784
|
+
this.emitStateChanged(agentContext, record, null);
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Error);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
throw error;
|
|
469
791
|
}
|
|
470
792
|
}
|
|
471
793
|
emitStateChanged(agentContext, record, previousState) {
|
|
@@ -489,25 +811,47 @@ let VtFlowService = class VtFlowService {
|
|
|
489
811
|
buildProblemReport(options) {
|
|
490
812
|
return (0, errors_1.buildVtFlowProblemReport)(options);
|
|
491
813
|
}
|
|
492
|
-
/** Spec Verifiable Service Identity Check: invokes the caller-provided VS-CONN-VS hook. Throws `vt-flow.not-a-verifiable-service` when the peer fails the check. When no hook is configured, logs a warning and permits. */
|
|
493
|
-
async checkIsVerifiableService(agentContext, connection) {
|
|
494
|
-
const peerDid = connection
|
|
495
|
-
if (!peerDid)
|
|
814
|
+
/** Spec Verifiable Service Identity Check: invokes the caller-provided VS-CONN-VS hook. Throws `vt-flow.not-a-verifiable-service` when the peer fails the check. When no hook is configured, logs a warning and permits. A `purpose` is passed on the Validator side only, and lets a rejected peer through the ECS issuance exemption. */
|
|
815
|
+
async checkIsVerifiableService(agentContext, connection, purpose) {
|
|
816
|
+
const peerDid = (0, utils_1.peerAnchorDid)(connection);
|
|
817
|
+
if (!connection.theirDid || !peerDid) {
|
|
496
818
|
throw new core_1.CredoError(`vt-flow: ready connection '${connection.id}' has no theirDid`);
|
|
819
|
+
}
|
|
497
820
|
const hook = this.config.assertVerifiableService;
|
|
498
821
|
if (!hook) {
|
|
499
822
|
this.logger.warn(`[vt-flow] assertVerifiableService hook not configured; skipping VS-CONN-VS check for peer '${peerDid}'. Configure VtFlowModuleConfig.assertVerifiableService for spec-conformant trust resolution.`);
|
|
500
823
|
return;
|
|
501
824
|
}
|
|
502
825
|
let permitted = false;
|
|
826
|
+
let failure;
|
|
503
827
|
try {
|
|
504
|
-
permitted = await hook({
|
|
828
|
+
permitted = await hook({
|
|
829
|
+
agentContext,
|
|
830
|
+
peerDid,
|
|
831
|
+
connectionId: connection.id,
|
|
832
|
+
});
|
|
505
833
|
}
|
|
506
834
|
catch (error) {
|
|
507
|
-
|
|
835
|
+
failure = error.message;
|
|
836
|
+
}
|
|
837
|
+
if (permitted)
|
|
838
|
+
return;
|
|
839
|
+
if (purpose && (await this.isExemptFromVsConnVs(agentContext, connection, peerDid, purpose))) {
|
|
840
|
+
this.logger.info(`[vt-flow] peer '${peerDid}' is not (yet) a Verifiable Service, but the request qualifies for the VS-CONN-VS ECS issuance exemption`);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
throw new errors_1.VtFlowError(errors_1.VtFlowErrorCode.NotAVerifiableService, `vt-flow.not-a-verifiable-service: peer '${peerDid}' failed VS-CONN-VS check${failure ? ` (${failure})` : ''}`);
|
|
844
|
+
}
|
|
845
|
+
async isExemptFromVsConnVs(agentContext, connection, peerDid, purpose) {
|
|
846
|
+
const exemption = this.config.checkEcsIssuanceExemption;
|
|
847
|
+
if (!exemption)
|
|
848
|
+
return false;
|
|
849
|
+
try {
|
|
850
|
+
return await exemption({ agentContext, peerDid, connectionId: connection.id, purpose });
|
|
508
851
|
}
|
|
509
|
-
|
|
510
|
-
|
|
852
|
+
catch (error) {
|
|
853
|
+
this.logger.warn(`[vt-flow] VS-CONN-VS exemption check failed for peer '${peerDid}': ${error.message}`);
|
|
854
|
+
return false;
|
|
511
855
|
}
|
|
512
856
|
}
|
|
513
857
|
};
|