Skip to content

Commit 3dadfc7

Browse files
authored
fix: support mediation for connectionless exchange (openwallet-foundation#577)
Signed-off-by: Timo Glastra <[email protected]>
1 parent aa49f99 commit 3dadfc7

File tree

5 files changed

+220
-9
lines changed

5 files changed

+220
-9
lines changed

packages/core/src/modules/connections/ConnectionsModule.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ export class ConnectionsModule {
5454
invitation: ConnectionInvitationMessage
5555
connectionRecord: ConnectionRecord
5656
}> {
57-
const mediationRecord = await this.mediationRecipientService.discoverMediation(config?.mediatorId)
58-
const myRouting = await this.mediationRecipientService.getRouting(mediationRecord)
57+
const myRouting = await this.mediationRecipientService.getRouting({
58+
mediatorId: config?.mediatorId,
59+
useDefaultMediator: true,
60+
})
5961

6062
const { connectionRecord: connectionRecord, message: invitation } = await this.connectionService.createInvitation({
6163
autoAcceptConnection: config?.autoAcceptConnection,
@@ -86,8 +88,7 @@ export class ConnectionsModule {
8688
mediatorId?: string
8789
}
8890
): Promise<ConnectionRecord> {
89-
const mediationRecord = await this.mediationRecipientService.discoverMediation(config?.mediatorId)
90-
const myRouting = await this.mediationRecipientService.getRouting(mediationRecord)
91+
const myRouting = await this.mediationRecipientService.getRouting({ mediatorId: config?.mediatorId })
9192

9293
let connection = await this.connectionService.processInvitation(invitation, {
9394
autoAcceptConnection: config?.autoAcceptConnection,

packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ export class ConnectionRequestHandler implements Handler {
3838
// routing object is required for multi use invitation, because we're creating a
3939
// new keypair that possibly needs to be registered at a mediator
4040
if (connectionRecord.multiUseInvitation) {
41-
const mediationRecord = await this.mediationRecipientService.discoverMediation()
42-
routing = await this.mediationRecipientService.getRouting(mediationRecord)
41+
routing = await this.mediationRecipientService.getRouting()
4342
}
4443

4544
connectionRecord = await this.connectionService.processRequest(messageContext, routing)

packages/core/src/modules/routing/RecipientModule.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ export class RecipientModule {
280280
const connection = await this.connectionService.findByInvitationKey(invitation.recipientKeys[0])
281281
if (!connection) {
282282
this.logger.debug('Mediation Connection does not exist, creating connection')
283-
const routing = await this.mediationRecipientService.getRouting()
283+
// We don't want to use the current default mediator when connecting to another mediator
284+
const routing = await this.mediationRecipientService.getRouting({ useDefaultMediator: false })
284285

285286
const invitationConnectionRecord = await this.connectionService.processInvitation(invitation, {
286287
autoAcceptConnection: true,

packages/core/src/modules/routing/services/MediationRecipientService.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,17 @@ export class MediationRecipientService {
155155
return keylistUpdateMessage
156156
}
157157

158-
public async getRouting(mediationRecord?: MediationRecord): Promise<Routing> {
158+
public async getRouting({ mediatorId, useDefaultMediator = true }: GetRoutingOptions = {}): Promise<Routing> {
159+
let mediationRecord: MediationRecord | null = null
160+
161+
if (mediatorId) {
162+
mediationRecord = await this.getById(mediatorId)
163+
} else if (useDefaultMediator) {
164+
// If no mediatorId is provided, and useDefaultMediator is true (default)
165+
// We use the default mediator if available
166+
mediationRecord = await this.findDefaultMediator()
167+
}
168+
159169
let endpoints = this.config.endpoints
160170
let routingKeys: string[] = []
161171

@@ -288,3 +298,16 @@ export interface MediationProtocolMsgReturnType<MessageType extends AgentMessage
288298
message: MessageType
289299
mediationRecord: MediationRecord
290300
}
301+
302+
export interface GetRoutingOptions {
303+
/**
304+
* Identifier of the mediator to use when setting up routing
305+
*/
306+
mediatorId?: string
307+
308+
/**
309+
* Whether to use the default mediator if available and `mediatorId` has not been provided
310+
* @default true
311+
*/
312+
useDefaultMediator?: boolean
313+
}

packages/core/tests/connectionless-proofs.test.ts

+188-1
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,52 @@
1+
import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport'
2+
import type { ProofStateChangedEvent } from '../src/modules/proofs'
3+
4+
import { Subject, ReplaySubject } from 'rxjs'
5+
6+
import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport'
7+
import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport'
8+
import { Agent } from '../src/agent/Agent'
9+
import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment'
10+
import { CredentialPreview } from '../src/modules/credentials'
111
import {
212
PredicateType,
313
ProofState,
414
ProofAttributeInfo,
515
AttributeFilter,
616
ProofPredicateInfo,
717
AutoAcceptProof,
18+
ProofEventTypes,
819
} from '../src/modules/proofs'
20+
import { LinkedAttachment } from '../src/utils/LinkedAttachment'
21+
import { uuid } from '../src/utils/uuid'
922

10-
import { setupProofsTest, waitForProofRecordSubject } from './helpers'
23+
import {
24+
getBaseConfig,
25+
issueCredential,
26+
makeConnection,
27+
prepareForIssuance,
28+
setupProofsTest,
29+
waitForProofRecordSubject,
30+
} from './helpers'
1131
import testLogger from './logger'
1232

1333
describe('Present Proof', () => {
34+
let agents: Agent[]
35+
36+
afterEach(async () => {
37+
for (const agent of agents) {
38+
await agent.shutdown()
39+
await agent.wallet.delete()
40+
}
41+
})
42+
1443
test('Faber starts with connection-less proof requests to Alice', async () => {
1544
const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest(
1645
'Faber connection-less Proofs',
1746
'Alice connection-less Proofs',
1847
AutoAcceptProof.Never
1948
)
49+
agents = [aliceAgent, faberAgent]
2050
testLogger.test('Faber sends presentation request to Alice')
2151

2252
const attributes = {
@@ -93,6 +123,8 @@ describe('Present Proof', () => {
93123
AutoAcceptProof.Always
94124
)
95125

126+
agents = [aliceAgent, faberAgent]
127+
96128
const attributes = {
97129
name: new ProofAttributeInfo({
98130
name: 'name',
@@ -141,4 +173,159 @@ describe('Present Proof', () => {
141173
state: ProofState.Done,
142174
})
143175
})
176+
177+
test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => {
178+
testLogger.test('Faber sends presentation request to Alice')
179+
180+
const credentialPreview = CredentialPreview.fromRecord({
181+
name: 'John',
182+
age: '99',
183+
})
184+
185+
const unique = uuid().substring(0, 4)
186+
187+
const mediatorConfig = getBaseConfig(`Connectionless proofs with mediator Mediator-${unique}`, {
188+
autoAcceptMediationRequests: true,
189+
endpoints: ['rxjs:mediator'],
190+
})
191+
192+
const faberMessages = new Subject<SubjectMessage>()
193+
const aliceMessages = new Subject<SubjectMessage>()
194+
const mediatorMessages = new Subject<SubjectMessage>()
195+
196+
const subjectMap = {
197+
'rxjs:mediator': mediatorMessages,
198+
}
199+
200+
// Initialize mediator
201+
const mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies)
202+
mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(mediatorMessages, subjectMap))
203+
mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages))
204+
await mediatorAgent.initialize()
205+
206+
const faberMediationInvitation = await mediatorAgent.connections.createConnection()
207+
const aliceMediationInvitation = await mediatorAgent.connections.createConnection()
208+
209+
const faberConfig = getBaseConfig(`Connectionless proofs with mediator Faber-${unique}`, {
210+
autoAcceptProofs: AutoAcceptProof.Always,
211+
mediatorConnectionsInvite: faberMediationInvitation.invitation.toUrl({ domain: 'https://example.com' }),
212+
})
213+
214+
const aliceConfig = getBaseConfig(`Connectionless proofs with mediator Alice-${unique}`, {
215+
autoAcceptProofs: AutoAcceptProof.Always,
216+
// logger: new TestLogger(LogLevel.test),
217+
mediatorConnectionsInvite: aliceMediationInvitation.invitation.toUrl({ domain: 'https://example.com' }),
218+
})
219+
220+
const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies)
221+
faberAgent.registerOutboundTransport(new SubjectOutboundTransport(faberMessages, subjectMap))
222+
faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages))
223+
await faberAgent.initialize()
224+
225+
const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies)
226+
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(aliceMessages, subjectMap))
227+
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages))
228+
await aliceAgent.initialize()
229+
230+
agents = [aliceAgent, faberAgent, mediatorAgent]
231+
232+
const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1'])
233+
234+
const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent)
235+
expect(faberConnection.isReady).toBe(true)
236+
expect(aliceConnection.isReady).toBe(true)
237+
238+
await issueCredential({
239+
issuerAgent: faberAgent,
240+
issuerConnectionId: faberConnection.id,
241+
holderAgent: aliceAgent,
242+
credentialTemplate: {
243+
credentialDefinitionId: definition.id,
244+
comment: 'some comment about credential',
245+
preview: credentialPreview,
246+
linkedAttachments: [
247+
new LinkedAttachment({
248+
name: 'image_0',
249+
attachment: new Attachment({
250+
filename: 'picture-of-a-cat.png',
251+
data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }),
252+
}),
253+
}),
254+
new LinkedAttachment({
255+
name: 'image_1',
256+
attachment: new Attachment({
257+
filename: 'picture-of-a-dog.png',
258+
data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }),
259+
}),
260+
}),
261+
],
262+
},
263+
})
264+
const faberReplay = new ReplaySubject<ProofStateChangedEvent>()
265+
const aliceReplay = new ReplaySubject<ProofStateChangedEvent>()
266+
267+
faberAgent.events.observable<ProofStateChangedEvent>(ProofEventTypes.ProofStateChanged).subscribe(faberReplay)
268+
aliceAgent.events.observable<ProofStateChangedEvent>(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay)
269+
270+
const attributes = {
271+
name: new ProofAttributeInfo({
272+
name: 'name',
273+
restrictions: [
274+
new AttributeFilter({
275+
credentialDefinitionId: definition.id,
276+
}),
277+
],
278+
}),
279+
}
280+
281+
const predicates = {
282+
age: new ProofPredicateInfo({
283+
name: 'age',
284+
predicateType: PredicateType.GreaterThanOrEqualTo,
285+
predicateValue: 50,
286+
restrictions: [
287+
new AttributeFilter({
288+
credentialDefinitionId: definition.id,
289+
}),
290+
],
291+
}),
292+
}
293+
294+
// eslint-disable-next-line prefer-const
295+
let { proofRecord: faberProofRecord, requestMessage } = await faberAgent.proofs.createOutOfBandRequest(
296+
{
297+
name: 'test-proof-request',
298+
requestedAttributes: attributes,
299+
requestedPredicates: predicates,
300+
},
301+
{
302+
autoAcceptProof: AutoAcceptProof.ContentApproved,
303+
}
304+
)
305+
306+
const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator()
307+
if (!mediationRecord) {
308+
throw new Error('Faber agent has no default mediator')
309+
}
310+
311+
expect(requestMessage).toMatchObject({
312+
service: {
313+
recipientKeys: [expect.any(String)],
314+
routingKeys: mediationRecord.routingKeys,
315+
serviceEndpoint: mediationRecord.endpoint,
316+
},
317+
})
318+
319+
await aliceAgent.receiveMessage(requestMessage.toJSON())
320+
321+
await waitForProofRecordSubject(aliceReplay, {
322+
threadId: faberProofRecord.threadId,
323+
state: ProofState.Done,
324+
})
325+
326+
await waitForProofRecordSubject(faberReplay, {
327+
threadId: faberProofRecord.threadId,
328+
state: ProofState.Done,
329+
})
330+
})
144331
})

0 commit comments

Comments
 (0)