@verana-labs/credo-ts-didcomm-vt-flow 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/build/VtFlowApi.d.ts +16 -3
- package/build/VtFlowApi.js +51 -11
- package/build/VtFlowApi.js.map +1 -1
- package/build/VtFlowModule.js +17 -13
- package/build/VtFlowModule.js.map +1 -1
- package/build/VtFlowModuleConfig.d.ts +27 -3
- package/build/VtFlowModuleConfig.js +10 -1
- package/build/VtFlowModuleConfig.js.map +1 -1
- package/build/errors/VtFlowErrorCode.d.ts +7 -3
- package/build/errors/VtFlowErrorCode.js +52 -17
- package/build/errors/VtFlowErrorCode.js.map +1 -1
- package/build/errors/buildVtFlowProblemReport.d.ts +4 -2
- package/build/errors/buildVtFlowProblemReport.js +7 -3
- package/build/errors/buildVtFlowProblemReport.js.map +1 -1
- package/build/errors/index.d.ts +2 -2
- package/build/errors/index.js +2 -1
- package/build/errors/index.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 +11 -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 +21 -7
- package/build/repository/VtFlowRecord.js +9 -3
- package/build/repository/VtFlowRecord.js.map +1 -1
- package/build/services/VtFlowService.d.ts +55 -14
- package/build/services/VtFlowService.js +280 -50
- package/build/services/VtFlowService.js.map +1 -1
- package/build/types.d.ts +94 -4
- package/build/types.js +55 -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
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { AgentContext, Logger, Query, QueryOptions } from '@credo-ts/core';
|
|
2
2
|
import type { DidCommConnectionRecord, DidCommCredentialExchangeRecord, DidCommInboundMessageContext } from '@credo-ts/didcomm';
|
|
3
3
|
import { EventEmitter } from '@credo-ts/core';
|
|
4
|
-
import { VtFlowModuleConfig } from '../VtFlowModuleConfig';
|
|
4
|
+
import { VtFlowModuleConfig, type VtFlowRequestPurpose } from '../VtFlowModuleConfig';
|
|
5
5
|
import { type BuildVtFlowProblemReportOptions, VtFlowErrorCode, buildVtFlowProblemReport } from '../errors';
|
|
6
|
-
import { CredentialStateChangeMessage, type CredentialStateChangeMessageOptions, IssuanceRequestMessage, OnboardingRequestMessage, OobLinkMessage, type OobLinkMessageOptions, ValidatingMessage, type ValidatingMessageOptions } from '../messages';
|
|
6
|
+
import { CredentialStateChangeMessage, type CredentialStateChangeMessageOptions, IssuanceRequestMessage, OnboardingRequestMessage, OobLinkMessage, type OobLinkMessageOptions, VtFlowProblemReportMessage, ValidatingMessage, type ValidatingMessageOptions } from '../messages';
|
|
7
7
|
import { VtFlowRecord, VtFlowRepository } from '../repository';
|
|
8
|
-
import { VtFlowState } from '../types';
|
|
8
|
+
import { type VtFlowMessage, type VtFlowValidation, VtFlowState } from '../types';
|
|
9
9
|
export interface CreateOnboardingRequestParams {
|
|
10
10
|
connectionId: string;
|
|
11
11
|
participantSessionId: string;
|
|
12
|
-
|
|
12
|
+
applicantParticipantId: string;
|
|
13
|
+
applicantParticipantRole?: number;
|
|
14
|
+
validatorParticipantId?: string;
|
|
15
|
+
schemaId?: string;
|
|
13
16
|
agentParticipantId: string;
|
|
14
17
|
walletAgentParticipantId: string;
|
|
15
18
|
claims?: Record<string, unknown>;
|
|
@@ -50,20 +53,34 @@ export declare class VtFlowService {
|
|
|
50
53
|
message: IssuanceRequestMessage;
|
|
51
54
|
record: VtFlowRecord;
|
|
52
55
|
}>;
|
|
56
|
+
reattachOnboardingProcessRecord(agentContext: AgentContext, params: {
|
|
57
|
+
vtFlowRecordId: string;
|
|
58
|
+
connectionId: string;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
message: OnboardingRequestMessage;
|
|
61
|
+
record: VtFlowRecord;
|
|
62
|
+
}>;
|
|
63
|
+
private releaseCredentialExchange;
|
|
53
64
|
/** Validator-side OnboardingProcess: create or re-attach (by `participant_session_id`) a record in `AWAITING_OR` from an inbound `onboarding-request`. */
|
|
54
65
|
processReceiveOnboardingRequest(messageContext: DidCommInboundMessageContext<OnboardingRequestMessage>): Promise<VtFlowRecord>;
|
|
55
66
|
/** Validator-side DirectIssuance counterpart of `processReceiveOnboardingRequest`, landing the record in `AWAITING_IR`. */
|
|
56
67
|
processReceiveIssuanceRequest(messageContext: DidCommInboundMessageContext<IssuanceRequestMessage>): Promise<VtFlowRecord>;
|
|
57
|
-
/** Applicant-side inbound `oob-link`;
|
|
68
|
+
/** Applicant-side inbound `oob-link`; `OR_SENT`, `IR_SENT`, `VALIDATING` or `OOB_PENDING` => `OOB_PENDING`. */
|
|
58
69
|
processReceiveOobLink(messageContext: DidCommInboundMessageContext<OobLinkMessage>): Promise<VtFlowRecord>;
|
|
59
|
-
/** Applicant-side inbound `validating`;
|
|
70
|
+
/** Applicant-side inbound `validating`; `OR_SENT`, `IR_SENT` or `OOB_PENDING` => `VALIDATING`. */
|
|
60
71
|
processReceiveValidating(messageContext: DidCommInboundMessageContext<ValidatingMessage>): Promise<VtFlowRecord>;
|
|
61
72
|
/** Applicant-side inbound `credential-state-change`; v1.0 handles `REVOKED` only. */
|
|
62
73
|
processReceiveCredentialStateChange(messageContext: DidCommInboundMessageContext<CredentialStateChangeMessage>): Promise<VtFlowRecord | null>;
|
|
63
74
|
/** `AWAITING_OR => VALIDATING`; caller is expected to have verified participant/agent/wallet IDs on-chain. */
|
|
64
|
-
acceptOnboardingRequest(agentContext: AgentContext, recordId: string): Promise<
|
|
75
|
+
acceptOnboardingRequest(agentContext: AgentContext, recordId: string): Promise<{
|
|
76
|
+
record: VtFlowRecord;
|
|
77
|
+
message: ValidatingMessage;
|
|
78
|
+
}>;
|
|
65
79
|
/** DirectIssuance: `AWAITING_IR => VALIDATING`; transient before `CRED_OFFERED`. */
|
|
66
|
-
acceptIssuanceRequest(agentContext: AgentContext, recordId: string): Promise<
|
|
80
|
+
acceptIssuanceRequest(agentContext: AgentContext, recordId: string): Promise<{
|
|
81
|
+
record: VtFlowRecord;
|
|
82
|
+
message: ValidatingMessage;
|
|
83
|
+
}>;
|
|
67
84
|
/** Reject with a problem-report; transitions to `TERMINATED_BY_{role}` and marks the connection as `TERMINATED`. */
|
|
68
85
|
rejectRequest(agentContext: AgentContext, recordId: string, params: RejectRequestParams): Promise<{
|
|
69
86
|
record: VtFlowRecord;
|
|
@@ -88,20 +105,33 @@ export declare class VtFlowService {
|
|
|
88
105
|
record: VtFlowRecord;
|
|
89
106
|
problemReport: ReturnType<typeof buildVtFlowProblemReport>;
|
|
90
107
|
}>;
|
|
91
|
-
/** Build an `oob-link`;
|
|
108
|
+
/** Build an `oob-link` from `VALIDATING` or `OOB_PENDING`; the flow moves to or stays in `OOB_PENDING`. */
|
|
92
109
|
sendOobLinkForSession(agentContext: AgentContext, recordId: string, params: SendOobLinkParams): Promise<{
|
|
93
110
|
record: VtFlowRecord;
|
|
94
111
|
message: OobLinkMessage;
|
|
95
112
|
}>;
|
|
96
|
-
/**
|
|
113
|
+
/** `OOB_PENDING` => `VALIDATING`, with the `validating` that tells the applicant its out-of-band step is done. */
|
|
97
114
|
sendValidatingForSession(agentContext: AgentContext, recordId: string, params?: SendValidatingParams): Promise<{
|
|
98
115
|
record: VtFlowRecord;
|
|
99
116
|
message: ValidatingMessage;
|
|
100
117
|
}>;
|
|
101
|
-
/**
|
|
118
|
+
/** Record the validation decision or its transaction outcome, moving the flow when `state` is given. */
|
|
119
|
+
recordValidation(agentContext: AgentContext, recordId: string, validation: VtFlowValidation, state?: VtFlowState): Promise<VtFlowRecord>;
|
|
120
|
+
/** Validator-side states preceding `VALIDATED` => `VALIDATED`; call after `SetParticipantOPtoValidated` lands on-chain. */
|
|
102
121
|
markValidated(agentContext: AgentContext, recordId: string): Promise<VtFlowRecord>;
|
|
122
|
+
/**
|
|
123
|
+
* Close an onboarding process that carries no credential exchange. Only a HOLDER receives a
|
|
124
|
+
* credential; for every other role the chain records the outcome with
|
|
125
|
+
* SetParticipantOPToValidated, and nothing else follows. Both sides use this, so it asserts no
|
|
126
|
+
* role: the validator calls it after it validates, and the applicant reaches it from the chain
|
|
127
|
+
* event.
|
|
128
|
+
*/
|
|
129
|
+
markCompleted(agentContext: AgentContext, recordId: string): Promise<VtFlowRecord>;
|
|
130
|
+
/** Validator states that an `offer-credential` moves to `CRED_OFFERED`; from `COMPLETED` it starts a new subprotocol run. */
|
|
131
|
+
assertCanOfferCredential(record: VtFlowRecord): void;
|
|
103
132
|
/** Link a Credo exchange record to the session and transition to `CRED_OFFERED`. */
|
|
104
133
|
attachCredentialExchangeRecord(agentContext: AgentContext, recordId: string, credentialExchangeRecord: DidCommCredentialExchangeRecord, credentialDigest?: string, issuerParticipantId?: number): Promise<VtFlowRecord>;
|
|
134
|
+
setEcsSchemaKey(agentContext: AgentContext, recordId: string, ecsSchemaKey: string): Promise<VtFlowRecord>;
|
|
105
135
|
setCredentialDigest(agentContext: AgentContext, recordId: string, credentialDigest: string): Promise<VtFlowRecord>;
|
|
106
136
|
/** Validator-side `credential-state-change`; `COMPLETED => CRED_REVOKED`, re-notify allowed from `CRED_REVOKED`. */
|
|
107
137
|
notifyCredentialStateChange(agentContext: AgentContext, recordId: string, params: NotifyCredentialStateChangeParams): Promise<{
|
|
@@ -119,6 +149,16 @@ export declare class VtFlowService {
|
|
|
119
149
|
findAllByQuery(agentContext: AgentContext, query: Query<VtFlowRecord>, queryOptions?: QueryOptions): Promise<VtFlowRecord[]>;
|
|
120
150
|
/** Transition the record, persist it, and emit `VtFlowStateChanged`. */
|
|
121
151
|
updateState(agentContext: AgentContext, record: VtFlowRecord, newState: VtFlowState): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Inbound adopted `problem-report`. The Error Codes table gives the state the receiving party
|
|
154
|
+
* moves to, so both sides run this same mapping. A retryable code records the message and leaves
|
|
155
|
+
* the flow where it is.
|
|
156
|
+
*/
|
|
157
|
+
processReceiveProblemReport(messageContext: DidCommInboundMessageContext<VtFlowProblemReportMessage>): Promise<VtFlowRecord | undefined>;
|
|
158
|
+
/** `terminated-by-sender` is the peer's termination, so the receiver lands in the state named after the sender. */
|
|
159
|
+
private resolveErrorFlowState;
|
|
160
|
+
/** Append to `messages[]`, which a validator fills with what it sent and an applicant with what it received. */
|
|
161
|
+
appendMessage(record: VtFlowRecord, message: VtFlowMessage): void;
|
|
122
162
|
/** Persist record changes without state-transition semantics (no event emitted). */
|
|
123
163
|
updateRecord(agentContext: AgentContext, record: VtFlowRecord): Promise<void>;
|
|
124
164
|
/** participant_session_ids are public on-chain, so only the original peer may re-attach a flow with one. */
|
|
@@ -126,7 +166,8 @@ export declare class VtFlowService {
|
|
|
126
166
|
private emitStateChanged;
|
|
127
167
|
getModuleConfig(): VtFlowModuleConfig;
|
|
128
168
|
getLogger(): Logger;
|
|
129
|
-
buildProblemReport(options: BuildVtFlowProblemReportOptions):
|
|
130
|
-
/** 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. */
|
|
131
|
-
checkIsVerifiableService(agentContext: AgentContext, connection: DidCommConnectionRecord): Promise<void>;
|
|
169
|
+
buildProblemReport(options: BuildVtFlowProblemReportOptions): VtFlowProblemReportMessage;
|
|
170
|
+
/** 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. */
|
|
171
|
+
checkIsVerifiableService(agentContext: AgentContext, connection: DidCommConnectionRecord, purpose?: VtFlowRequestPurpose): Promise<void>;
|
|
172
|
+
private isExemptFromVsConnVs;
|
|
132
173
|
}
|
|
@@ -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,
|
|
@@ -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,28 +96,79 @@ 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
|
+
}
|
|
95
141
|
/** Validator-side OnboardingProcess: create or re-attach (by `participant_session_id`) a record in `AWAITING_OR` from an inbound `onboarding-request`. */
|
|
96
142
|
async processReceiveOnboardingRequest(messageContext) {
|
|
97
143
|
const { message, agentContext } = messageContext;
|
|
98
144
|
const connection = messageContext.assertReadyConnection();
|
|
99
|
-
await this.checkIsVerifiableService(agentContext, connection);
|
|
145
|
+
await this.checkIsVerifiableService(agentContext, connection, { participantId: message.participantId });
|
|
100
146
|
const existing = await this.repository.findByParticipantSessionId(agentContext, message.participantSessionId, types_1.VtFlowRole.Validator);
|
|
101
147
|
if (existing) {
|
|
102
148
|
if ((0, types_1.isVtFlowTerminalState)(existing.state)) {
|
|
103
149
|
throw new core_1.CredoError(`vt-flow: participant_session_id '${message.participantSessionId}' collides with a terminated flow`);
|
|
104
150
|
}
|
|
151
|
+
if (existing.applicantParticipantId !== message.participantId) {
|
|
152
|
+
throw new core_1.CredoError(`vt-flow: participant_id '${message.participantId}' does not match the flow of participant_session_id '${message.participantSessionId}'`);
|
|
153
|
+
}
|
|
105
154
|
await this.assertSamePeer(agentContext, existing, connection);
|
|
106
155
|
existing.connectionId = connection.id;
|
|
107
|
-
existing.threadId
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
156
|
+
if (existing.threadId !== message.threadId) {
|
|
157
|
+
existing.threadId = message.threadId;
|
|
158
|
+
if (message.claims)
|
|
159
|
+
existing.claims = message.claims;
|
|
160
|
+
if (message.proofsAttach)
|
|
161
|
+
existing.proofsAttach = message.proofsAttach;
|
|
162
|
+
}
|
|
112
163
|
if (existing.state === types_1.VtFlowState.Completed || existing.state === types_1.VtFlowState.CredRevoked) {
|
|
113
164
|
// A finished flow re-entered with a new OR is a renewal (VSA-VTI-FLOW-OP-RENEW): re-run it.
|
|
114
|
-
existing.
|
|
165
|
+
existing.oobLink = undefined;
|
|
115
166
|
await this.updateState(agentContext, existing, types_1.VtFlowState.AwaitingOr);
|
|
116
167
|
}
|
|
168
|
+
else if (existing.state === types_1.VtFlowState.CredOffered) {
|
|
169
|
+
await this.releaseCredentialExchange(agentContext, existing);
|
|
170
|
+
await this.updateState(agentContext, existing, types_1.VtFlowState.Validated);
|
|
171
|
+
}
|
|
117
172
|
else {
|
|
118
173
|
await this.repository.update(agentContext, existing);
|
|
119
174
|
}
|
|
@@ -128,7 +183,7 @@ let VtFlowService = class VtFlowService {
|
|
|
128
183
|
variant: types_1.VtFlowVariant.OnboardingProcess,
|
|
129
184
|
agentParticipantId: message.agentParticipantId,
|
|
130
185
|
walletAgentParticipantId: message.walletAgentParticipantId,
|
|
131
|
-
|
|
186
|
+
applicantParticipantId: message.participantId,
|
|
132
187
|
claims: message.claims,
|
|
133
188
|
proofsAttach: message.proofsAttach,
|
|
134
189
|
});
|
|
@@ -140,16 +195,25 @@ let VtFlowService = class VtFlowService {
|
|
|
140
195
|
async processReceiveIssuanceRequest(messageContext) {
|
|
141
196
|
const { message, agentContext } = messageContext;
|
|
142
197
|
const connection = messageContext.assertReadyConnection();
|
|
143
|
-
await this.checkIsVerifiableService(agentContext, connection);
|
|
198
|
+
await this.checkIsVerifiableService(agentContext, connection, { schemaId: message.schemaId });
|
|
144
199
|
const existing = await this.repository.findByParticipantSessionId(agentContext, message.participantSessionId, types_1.VtFlowRole.Validator);
|
|
145
200
|
if (existing) {
|
|
146
201
|
if ((0, types_1.isVtFlowTerminalState)(existing.state)) {
|
|
147
202
|
throw new core_1.CredoError(`vt-flow: participant_session_id '${message.participantSessionId}' collides with a terminated flow`);
|
|
148
203
|
}
|
|
204
|
+
if (existing.variant !== types_1.VtFlowVariant.DirectIssuance || existing.schemaId !== message.schemaId) {
|
|
205
|
+
throw new core_1.CredoError(`vt-flow: schema_id '${message.schemaId}' does not match the flow of participant_session_id '${message.participantSessionId}'`);
|
|
206
|
+
}
|
|
149
207
|
await this.assertSamePeer(agentContext, existing, connection);
|
|
150
208
|
existing.connectionId = connection.id;
|
|
151
209
|
existing.threadId = message.threadId;
|
|
152
|
-
|
|
210
|
+
if (existing.state === types_1.VtFlowState.CredOffered) {
|
|
211
|
+
await this.releaseCredentialExchange(agentContext, existing);
|
|
212
|
+
await this.updateState(agentContext, existing, types_1.VtFlowState.Validating);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
await this.repository.update(agentContext, existing);
|
|
216
|
+
}
|
|
153
217
|
return existing;
|
|
154
218
|
}
|
|
155
219
|
const record = new repository_1.VtFlowRecord({
|
|
@@ -168,25 +232,51 @@ let VtFlowService = class VtFlowService {
|
|
|
168
232
|
this.emitStateChanged(agentContext, record, null);
|
|
169
233
|
return record;
|
|
170
234
|
}
|
|
171
|
-
/** Applicant-side inbound `oob-link`;
|
|
235
|
+
/** Applicant-side inbound `oob-link`; `OR_SENT`, `IR_SENT`, `VALIDATING` or `OOB_PENDING` => `OOB_PENDING`. */
|
|
172
236
|
async processReceiveOobLink(messageContext) {
|
|
237
|
+
var _a;
|
|
173
238
|
const { message, agentContext } = messageContext;
|
|
174
239
|
messageContext.assertReadyConnection();
|
|
175
240
|
const record = await this.getByThreadId(agentContext, message.threadId);
|
|
176
241
|
record.assertRole(types_1.VtFlowRole.Applicant);
|
|
242
|
+
record.assertState([
|
|
243
|
+
types_1.VtFlowState.OrSent,
|
|
244
|
+
types_1.VtFlowState.IrSent,
|
|
245
|
+
types_1.VtFlowState.Validating,
|
|
246
|
+
types_1.VtFlowState.OobPending,
|
|
247
|
+
]);
|
|
248
|
+
record.oobLink = {
|
|
249
|
+
url: message.url,
|
|
250
|
+
description: message.description,
|
|
251
|
+
expiresAt: (_a = message.expiresTime) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
252
|
+
at: new Date().toISOString(),
|
|
253
|
+
};
|
|
254
|
+
this.appendMessage(record, {
|
|
255
|
+
type: types_1.VtFlowMessageType.OobLink,
|
|
256
|
+
text: message.description,
|
|
257
|
+
at: record.oobLink.at,
|
|
258
|
+
url: message.url,
|
|
259
|
+
});
|
|
177
260
|
await this.updateState(agentContext, record, types_1.VtFlowState.OobPending);
|
|
178
261
|
return record;
|
|
179
262
|
}
|
|
180
|
-
/** Applicant-side inbound `validating`;
|
|
263
|
+
/** Applicant-side inbound `validating`; `OR_SENT`, `IR_SENT` or `OOB_PENDING` => `VALIDATING`. */
|
|
181
264
|
async processReceiveValidating(messageContext) {
|
|
182
265
|
var _a;
|
|
183
266
|
const { message, agentContext } = messageContext;
|
|
184
267
|
messageContext.assertReadyConnection();
|
|
185
268
|
const record = await this.getByThreadId(agentContext, message.threadId);
|
|
186
269
|
this.logger.debug(`[vt-flow] validating received for session ${record.threadId}: ${(_a = message.comment) !== null && _a !== void 0 ? _a : '(no comment)'}`);
|
|
270
|
+
if (record.role === types_1.VtFlowRole.Applicant && message.comment) {
|
|
271
|
+
this.appendMessage(record, {
|
|
272
|
+
type: types_1.VtFlowMessageType.Validating,
|
|
273
|
+
text: message.comment,
|
|
274
|
+
at: new Date().toISOString(),
|
|
275
|
+
});
|
|
276
|
+
await this.updateRecord(agentContext, record);
|
|
277
|
+
}
|
|
187
278
|
if (record.role === types_1.VtFlowRole.Applicant &&
|
|
188
|
-
|
|
189
|
-
record.state === types_1.VtFlowState.OrSent) {
|
|
279
|
+
[types_1.VtFlowState.OrSent, types_1.VtFlowState.IrSent, types_1.VtFlowState.OobPending].includes(record.state)) {
|
|
190
280
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
191
281
|
}
|
|
192
282
|
return record;
|
|
@@ -215,7 +305,7 @@ let VtFlowService = class VtFlowService {
|
|
|
215
305
|
record.assertState(types_1.VtFlowState.AwaitingOr);
|
|
216
306
|
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
217
307
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
218
|
-
return record;
|
|
308
|
+
return { record, message: new messages_1.ValidatingMessage({ threadId: record.threadId }) };
|
|
219
309
|
}
|
|
220
310
|
/** DirectIssuance: `AWAITING_IR => VALIDATING`; transient before `CRED_OFFERED`. */
|
|
221
311
|
async acceptIssuanceRequest(agentContext, recordId) {
|
|
@@ -224,7 +314,7 @@ let VtFlowService = class VtFlowService {
|
|
|
224
314
|
record.assertState(types_1.VtFlowState.AwaitingIr);
|
|
225
315
|
record.assertVariant(types_1.VtFlowVariant.DirectIssuance);
|
|
226
316
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
227
|
-
return record;
|
|
317
|
+
return { record, message: new messages_1.ValidatingMessage({ threadId: record.threadId }) };
|
|
228
318
|
}
|
|
229
319
|
/** Reject with a problem-report; transitions to `TERMINATED_BY_{role}` and marks the connection as `TERMINATED`. */
|
|
230
320
|
async rejectRequest(agentContext, recordId, params) {
|
|
@@ -264,6 +354,17 @@ let VtFlowService = class VtFlowService {
|
|
|
264
354
|
var _a, _b;
|
|
265
355
|
const record = await this.repository.getById(agentContext, recordId);
|
|
266
356
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
357
|
+
record.assertState([
|
|
358
|
+
types_1.VtFlowState.AwaitingOr,
|
|
359
|
+
types_1.VtFlowState.AwaitingIr,
|
|
360
|
+
types_1.VtFlowState.Validating,
|
|
361
|
+
types_1.VtFlowState.OobPending,
|
|
362
|
+
types_1.VtFlowState.AwaitingValidationTx,
|
|
363
|
+
types_1.VtFlowState.ValidationTxFailed,
|
|
364
|
+
// VtFlowModule ends the flow here when auto-issue fails, until verana-labs/vs-agent#738
|
|
365
|
+
types_1.VtFlowState.CredOffered,
|
|
366
|
+
types_1.VtFlowState.Completed,
|
|
367
|
+
]);
|
|
267
368
|
const code = (_a = params.code) !== null && _a !== void 0 ? _a : errors_1.VtFlowErrorCode.SessionTerminated;
|
|
268
369
|
const problemReport = (0, errors_1.buildVtFlowProblemReport)({
|
|
269
370
|
code,
|
|
@@ -288,61 +389,104 @@ let VtFlowService = class VtFlowService {
|
|
|
288
389
|
await this.updateState(agentContext, record, params.state);
|
|
289
390
|
return { record, problemReport };
|
|
290
391
|
}
|
|
291
|
-
/** Build an `oob-link`;
|
|
392
|
+
/** Build an `oob-link` from `VALIDATING` or `OOB_PENDING`; the flow moves to or stays in `OOB_PENDING`. */
|
|
292
393
|
async sendOobLinkForSession(agentContext, recordId, params) {
|
|
394
|
+
var _a;
|
|
293
395
|
const record = await this.repository.getById(agentContext, recordId);
|
|
294
396
|
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
|
-
]);
|
|
397
|
+
record.assertState([types_1.VtFlowState.Validating, types_1.VtFlowState.OobPending]);
|
|
302
398
|
const message = new messages_1.OobLinkMessage({
|
|
303
399
|
threadId: record.threadId,
|
|
304
400
|
url: params.url,
|
|
305
401
|
description: params.description,
|
|
306
402
|
expiresTime: params.expiresTime,
|
|
307
403
|
});
|
|
308
|
-
record.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
404
|
+
record.oobLink = {
|
|
405
|
+
url: params.url,
|
|
406
|
+
description: params.description,
|
|
407
|
+
expiresAt: (_a = params.expiresTime) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
408
|
+
at: new Date().toISOString(),
|
|
409
|
+
};
|
|
410
|
+
this.appendMessage(record, {
|
|
411
|
+
type: types_1.VtFlowMessageType.OobLink,
|
|
412
|
+
text: params.description,
|
|
413
|
+
at: record.oobLink.at,
|
|
414
|
+
url: params.url,
|
|
415
|
+
});
|
|
416
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.OobPending);
|
|
315
417
|
return { record, message };
|
|
316
418
|
}
|
|
317
|
-
/**
|
|
419
|
+
/** `OOB_PENDING` => `VALIDATING`, with the `validating` that tells the applicant its out-of-band step is done. */
|
|
318
420
|
async sendValidatingForSession(agentContext, recordId, params = {}) {
|
|
319
421
|
const record = await this.repository.getById(agentContext, recordId);
|
|
320
422
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
423
|
+
record.assertState(types_1.VtFlowState.OobPending);
|
|
321
424
|
const message = new messages_1.ValidatingMessage({
|
|
322
425
|
threadId: record.threadId,
|
|
323
426
|
comment: params.comment,
|
|
324
427
|
});
|
|
428
|
+
if (params.comment) {
|
|
429
|
+
this.appendMessage(record, {
|
|
430
|
+
type: types_1.VtFlowMessageType.Validating,
|
|
431
|
+
text: params.comment,
|
|
432
|
+
at: new Date().toISOString(),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Validating);
|
|
325
436
|
return { record, message };
|
|
326
437
|
}
|
|
327
|
-
/**
|
|
438
|
+
/** Record the validation decision or its transaction outcome, moving the flow when `state` is given. */
|
|
439
|
+
async recordValidation(agentContext, recordId, validation, state) {
|
|
440
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
441
|
+
record.assertRole(types_1.VtFlowRole.Validator);
|
|
442
|
+
record.validation = validation;
|
|
443
|
+
if (state)
|
|
444
|
+
await this.updateState(agentContext, record, state);
|
|
445
|
+
else
|
|
446
|
+
await this.updateRecord(agentContext, record);
|
|
447
|
+
return record;
|
|
448
|
+
}
|
|
449
|
+
/** Validator-side states preceding `VALIDATED` => `VALIDATED`; call after `SetParticipantOPtoValidated` lands on-chain. */
|
|
328
450
|
async markValidated(agentContext, recordId) {
|
|
329
451
|
const record = await this.repository.getById(agentContext, recordId);
|
|
330
452
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
331
|
-
record.assertState([types_1.
|
|
453
|
+
record.assertState([...types_1.VtFlowValidatedFromStates]);
|
|
332
454
|
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
333
455
|
await this.updateState(agentContext, record, types_1.VtFlowState.Validated);
|
|
334
456
|
return record;
|
|
335
457
|
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Close an onboarding process that carries no credential exchange. Only a HOLDER receives a
|
|
460
|
+
* credential; for every other role the chain records the outcome with
|
|
461
|
+
* SetParticipantOPToValidated, and nothing else follows. Both sides use this, so it asserts no
|
|
462
|
+
* role: the validator calls it after it validates, and the applicant reaches it from the chain
|
|
463
|
+
* event.
|
|
464
|
+
*/
|
|
465
|
+
async markCompleted(agentContext, recordId) {
|
|
338
466
|
const record = await this.repository.getById(agentContext, recordId);
|
|
467
|
+
record.assertVariant(types_1.VtFlowVariant.OnboardingProcess);
|
|
468
|
+
if (record.state === types_1.VtFlowState.Completed)
|
|
469
|
+
return record;
|
|
470
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state)) {
|
|
471
|
+
throw new core_1.CredoError(`VtFlow record is in terminal state '${record.state}'; cannot complete it.`);
|
|
472
|
+
}
|
|
473
|
+
await this.updateState(agentContext, record, types_1.VtFlowState.Completed);
|
|
474
|
+
return record;
|
|
475
|
+
}
|
|
476
|
+
/** Validator states that an `offer-credential` moves to `CRED_OFFERED`; from `COMPLETED` it starts a new subprotocol run. */
|
|
477
|
+
assertCanOfferCredential(record) {
|
|
339
478
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
340
479
|
record.assertState([
|
|
341
480
|
types_1.VtFlowState.Validated,
|
|
342
|
-
types_1.VtFlowState.
|
|
343
|
-
types_1.VtFlowState.OobPending,
|
|
481
|
+
types_1.VtFlowState.ValidatedPendingClaims,
|
|
344
482
|
types_1.VtFlowState.Completed,
|
|
483
|
+
...(record.variant === types_1.VtFlowVariant.DirectIssuance ? [types_1.VtFlowState.Validating] : []),
|
|
345
484
|
]);
|
|
485
|
+
}
|
|
486
|
+
/** Link a Credo exchange record to the session and transition to `CRED_OFFERED`. */
|
|
487
|
+
async attachCredentialExchangeRecord(agentContext, recordId, credentialExchangeRecord, credentialDigest, issuerParticipantId) {
|
|
488
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
489
|
+
this.assertCanOfferCredential(record);
|
|
346
490
|
record.credentialExchangeRecordId = credentialExchangeRecord.id;
|
|
347
491
|
if (credentialDigest)
|
|
348
492
|
record.credentialDigest = credentialDigest;
|
|
@@ -352,6 +496,13 @@ let VtFlowService = class VtFlowService {
|
|
|
352
496
|
await this.updateState(agentContext, record, types_1.VtFlowState.CredOffered);
|
|
353
497
|
return record;
|
|
354
498
|
}
|
|
499
|
+
async setEcsSchemaKey(agentContext, recordId, ecsSchemaKey) {
|
|
500
|
+
const record = await this.repository.getById(agentContext, recordId);
|
|
501
|
+
record.assertRole(types_1.VtFlowRole.Applicant);
|
|
502
|
+
record.ecsSchemaKey = ecsSchemaKey;
|
|
503
|
+
await this.repository.update(agentContext, record);
|
|
504
|
+
return record;
|
|
505
|
+
}
|
|
355
506
|
async setCredentialDigest(agentContext, recordId, credentialDigest) {
|
|
356
507
|
const record = await this.repository.getById(agentContext, recordId);
|
|
357
508
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
@@ -380,7 +531,7 @@ let VtFlowService = class VtFlowService {
|
|
|
380
531
|
async updateClaims(agentContext, recordId, claims) {
|
|
381
532
|
const record = await this.repository.getById(agentContext, recordId);
|
|
382
533
|
record.assertRole(types_1.VtFlowRole.Validator);
|
|
383
|
-
record.assertState([types_1.VtFlowState.Validating, types_1.VtFlowState.CredRevoked]);
|
|
534
|
+
record.assertState([types_1.VtFlowState.AwaitingOr, types_1.VtFlowState.Validating, types_1.VtFlowState.CredRevoked]);
|
|
384
535
|
record.claims = claims;
|
|
385
536
|
await this.updateRecord(agentContext, record);
|
|
386
537
|
return record;
|
|
@@ -448,12 +599,69 @@ let VtFlowService = class VtFlowService {
|
|
|
448
599
|
/** Transition the record, persist it, and emit `VtFlowStateChanged`. */
|
|
449
600
|
async updateState(agentContext, record, newState) {
|
|
450
601
|
const previousState = record.state;
|
|
451
|
-
if (previousState === newState)
|
|
602
|
+
if (previousState === newState) {
|
|
603
|
+
await this.repository.update(agentContext, record);
|
|
452
604
|
return;
|
|
605
|
+
}
|
|
606
|
+
if (previousState === types_1.VtFlowState.OobPending)
|
|
607
|
+
record.oobLink = undefined;
|
|
453
608
|
record.state = newState;
|
|
454
609
|
await this.repository.update(agentContext, record);
|
|
455
610
|
this.emitStateChanged(agentContext, record, previousState);
|
|
456
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Inbound adopted `problem-report`. The Error Codes table gives the state the receiving party
|
|
614
|
+
* moves to, so both sides run this same mapping. A retryable code records the message and leaves
|
|
615
|
+
* the flow where it is.
|
|
616
|
+
*/
|
|
617
|
+
async processReceiveProblemReport(messageContext) {
|
|
618
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
619
|
+
const { message, agentContext } = messageContext;
|
|
620
|
+
const record = await this.repository.findByThreadId(agentContext, message.threadId);
|
|
621
|
+
if (!record)
|
|
622
|
+
return undefined;
|
|
623
|
+
if ((0, types_1.isVtFlowTerminalState)(record.state))
|
|
624
|
+
return record;
|
|
625
|
+
const code = (_a = message.description) === null || _a === void 0 ? void 0 : _a.code;
|
|
626
|
+
const info = code && (0, errors_1.isVtFlowErrorCode)(code) ? errors_1.VT_FLOW_ERROR_INFO[code] : undefined;
|
|
627
|
+
if (record.role === types_1.VtFlowRole.Applicant) {
|
|
628
|
+
this.appendMessage(record, {
|
|
629
|
+
type: types_1.VtFlowMessageType.ProblemReport,
|
|
630
|
+
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',
|
|
631
|
+
at: new Date().toISOString(),
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
const target = info &&
|
|
635
|
+
this.resolveErrorFlowState(info.flowState, record.role, (_e = message.whoRetries) !== null && _e !== void 0 ? _e : errors_1.whoRetriesMap[info.whoRetries]);
|
|
636
|
+
if (!target) {
|
|
637
|
+
await this.updateRecord(agentContext, record);
|
|
638
|
+
return record;
|
|
639
|
+
}
|
|
640
|
+
record.errorMessage = (_g = (_f = message.description) === null || _f === void 0 ? void 0 : _f.en) !== null && _g !== void 0 ? _g : code;
|
|
641
|
+
await this.updateState(agentContext, record, target);
|
|
642
|
+
return record;
|
|
643
|
+
}
|
|
644
|
+
/** `terminated-by-sender` is the peer's termination, so the receiver lands in the state named after the sender. */
|
|
645
|
+
resolveErrorFlowState(flowState, receiverRole, whoRetries) {
|
|
646
|
+
if (flowState === 'unchanged')
|
|
647
|
+
return undefined;
|
|
648
|
+
if (flowState === 'unchanged-when-you') {
|
|
649
|
+
return whoRetries === didcomm_1.WhoRetriesStatus.You ? undefined : types_1.VtFlowState.Error;
|
|
650
|
+
}
|
|
651
|
+
if (flowState === 'error-when-fatal') {
|
|
652
|
+
return whoRetries === didcomm_1.WhoRetriesStatus.None ? types_1.VtFlowState.Error : undefined;
|
|
653
|
+
}
|
|
654
|
+
if (flowState !== 'terminated-by-sender')
|
|
655
|
+
return flowState;
|
|
656
|
+
return receiverRole === types_1.VtFlowRole.Applicant
|
|
657
|
+
? types_1.VtFlowState.TerminatedByValidator
|
|
658
|
+
: types_1.VtFlowState.TerminatedByApplicant;
|
|
659
|
+
}
|
|
660
|
+
/** Append to `messages[]`, which a validator fills with what it sent and an applicant with what it received. */
|
|
661
|
+
appendMessage(record, message) {
|
|
662
|
+
var _a;
|
|
663
|
+
record.messages = [...((_a = record.messages) !== null && _a !== void 0 ? _a : []), message];
|
|
664
|
+
}
|
|
457
665
|
/** Persist record changes without state-transition semantics (no event emitted). */
|
|
458
666
|
async updateRecord(agentContext, record) {
|
|
459
667
|
await this.repository.update(agentContext, record);
|
|
@@ -489,25 +697,47 @@ let VtFlowService = class VtFlowService {
|
|
|
489
697
|
buildProblemReport(options) {
|
|
490
698
|
return (0, errors_1.buildVtFlowProblemReport)(options);
|
|
491
699
|
}
|
|
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)
|
|
700
|
+
/** 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. */
|
|
701
|
+
async checkIsVerifiableService(agentContext, connection, purpose) {
|
|
702
|
+
const peerDid = (0, utils_1.peerAnchorDid)(connection);
|
|
703
|
+
if (!connection.theirDid || !peerDid) {
|
|
496
704
|
throw new core_1.CredoError(`vt-flow: ready connection '${connection.id}' has no theirDid`);
|
|
705
|
+
}
|
|
497
706
|
const hook = this.config.assertVerifiableService;
|
|
498
707
|
if (!hook) {
|
|
499
708
|
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
709
|
return;
|
|
501
710
|
}
|
|
502
711
|
let permitted = false;
|
|
712
|
+
let failure;
|
|
503
713
|
try {
|
|
504
|
-
permitted = await hook({
|
|
714
|
+
permitted = await hook({
|
|
715
|
+
agentContext,
|
|
716
|
+
peerDid,
|
|
717
|
+
connectionId: connection.id,
|
|
718
|
+
});
|
|
505
719
|
}
|
|
506
720
|
catch (error) {
|
|
507
|
-
|
|
721
|
+
failure = error.message;
|
|
508
722
|
}
|
|
509
|
-
if (
|
|
510
|
-
|
|
723
|
+
if (permitted)
|
|
724
|
+
return;
|
|
725
|
+
if (purpose && (await this.isExemptFromVsConnVs(agentContext, connection, peerDid, purpose))) {
|
|
726
|
+
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`);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
throw new core_1.CredoError(`vt-flow.not-a-verifiable-service: peer '${peerDid}' failed VS-CONN-VS check${failure ? ` (${failure})` : ''}`);
|
|
730
|
+
}
|
|
731
|
+
async isExemptFromVsConnVs(agentContext, connection, peerDid, purpose) {
|
|
732
|
+
const exemption = this.config.checkEcsIssuanceExemption;
|
|
733
|
+
if (!exemption)
|
|
734
|
+
return false;
|
|
735
|
+
try {
|
|
736
|
+
return await exemption({ agentContext, peerDid, connectionId: connection.id, purpose });
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
this.logger.warn(`[vt-flow] VS-CONN-VS exemption check failed for peer '${peerDid}': ${error.message}`);
|
|
740
|
+
return false;
|
|
511
741
|
}
|
|
512
742
|
}
|
|
513
743
|
};
|