From c0569b88c27ee7785cf150ee14a5f9ebcc99898b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 20 Dec 2022 10:26:24 +0800 Subject: [PATCH] fix(connections): use new did for each connection from reusable invitation (#1174) Signed-off-by: Timo Glastra --- .../src/modules/connections/ConnectionsApi.ts | 10 +- .../__tests__/connection-manual.e2e.test.ts | 142 ++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index 9b12210091..f767cd1041 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -144,12 +144,17 @@ export class ConnectionsApi { throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) } + // If the outOfBandRecord is reusable we need to use new routing keys for the connection, otherwise + // all connections will use the same routing keys + const routing = outOfBandRecord.reusable ? await this.routingService.getRouting(this.agentContext) : undefined + let outboundMessageContext if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { const message = await this.didExchangeProtocol.createResponse( this.agentContext, connectionRecord, - outOfBandRecord + outOfBandRecord, + routing ) outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, @@ -159,7 +164,8 @@ export class ConnectionsApi { const { message } = await this.connectionService.createResponse( this.agentContext, connectionRecord, - outOfBandRecord + outOfBandRecord, + routing ) outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, diff --git a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts new file mode 100644 index 0000000000..dc07e9639f --- /dev/null +++ b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts @@ -0,0 +1,142 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { ConnectionStateChangedEvent } from '../ConnectionEvents' + +import { firstValueFrom } from 'rxjs' +import { filter, first, map, timeout } from 'rxjs/operators' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { ConnectionEventTypes } from '../ConnectionEvents' +import { DidExchangeState } from '../models' + +function waitForRequest(agent: Agent, theirLabel: string) { + return firstValueFrom( + agent.events.observable(ConnectionEventTypes.ConnectionStateChanged).pipe( + map((event) => event.payload.connectionRecord), + // Wait for request received + filter( + (connectionRecord) => + connectionRecord.state === DidExchangeState.RequestReceived && connectionRecord.theirLabel === theirLabel + ), + first(), + timeout(5000) + ) + ) +} + +function waitForResponse(agent: Agent, connectionId: string) { + return firstValueFrom( + agent.events.observable(ConnectionEventTypes.ConnectionStateChanged).pipe( + // Wait for response received + map((event) => event.payload.connectionRecord), + filter( + (connectionRecord) => + connectionRecord.state === DidExchangeState.ResponseReceived && connectionRecord.id === connectionId + ), + first(), + timeout(5000) + ) + ) +} + +describe('Manual Connection Flow', () => { + // This test was added to reproduce a bug where all connections based on a reusable invitation would use the same keys + // This was only present in the manual flow, which is almost never used. + it('can connect multiple times using the same reusable invitation without manually using the connections api', async () => { + const aliceInboundTransport = new SubjectInboundTransport() + const bobInboundTransport = new SubjectInboundTransport() + const faberInboundTransport = new SubjectInboundTransport() + + const subjectMap = { + 'rxjs:faber': faberInboundTransport.ourSubject, + 'rxjs:alice': aliceInboundTransport.ourSubject, + 'rxjs:bob': bobInboundTransport.ourSubject, + } + const aliceAgentOptions = getAgentOptions('Manual Connection Flow Alice', { + label: 'alice', + autoAcceptConnections: false, + endpoints: ['rxjs:alice'], + }) + const bobAgentOptions = getAgentOptions('Manual Connection Flow Bob', { + label: 'bob', + autoAcceptConnections: false, + endpoints: ['rxjs:bob'], + }) + const faberAgentOptions = getAgentOptions('Manual Connection Flow Faber', { + autoAcceptConnections: false, + endpoints: ['rxjs:faber'], + }) + + const aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(aliceInboundTransport) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + const bobAgent = new Agent(bobAgentOptions) + bobAgent.registerInboundTransport(bobInboundTransport) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + const faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(faberInboundTransport) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + await aliceAgent.initialize() + await bobAgent.initialize() + await faberAgent.initialize() + + const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ + autoAcceptConnection: false, + multiUseInvitation: true, + }) + + const waitForAliceRequest = waitForRequest(faberAgent, 'alice') + const waitForBobRequest = waitForRequest(faberAgent, 'bob') + + let { connectionRecord: aliceConnectionRecord } = await aliceAgent.oob.receiveInvitation( + faberOutOfBandRecord.outOfBandInvitation, + { + autoAcceptInvitation: true, + autoAcceptConnection: false, + } + ) + + let { connectionRecord: bobConnectionRecord } = await bobAgent.oob.receiveInvitation( + faberOutOfBandRecord.outOfBandInvitation, + { + autoAcceptInvitation: true, + autoAcceptConnection: false, + } + ) + + let faberAliceConnectionRecord = await waitForAliceRequest + let faberBobConnectionRecord = await waitForBobRequest + + const waitForAliceResponse = waitForResponse(aliceAgent, aliceConnectionRecord!.id) + const waitForBobResponse = waitForResponse(bobAgent, bobConnectionRecord!.id) + + await faberAgent.connections.acceptRequest(faberAliceConnectionRecord.id) + await faberAgent.connections.acceptRequest(faberBobConnectionRecord.id) + + aliceConnectionRecord = await waitForAliceResponse + await aliceAgent.connections.acceptResponse(aliceConnectionRecord!.id) + + bobConnectionRecord = await waitForBobResponse + await bobAgent.connections.acceptResponse(bobConnectionRecord!.id) + + aliceConnectionRecord = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionRecord!.id) + bobConnectionRecord = await bobAgent.connections.returnWhenIsConnected(bobConnectionRecord!.id) + faberAliceConnectionRecord = await faberAgent.connections.returnWhenIsConnected(faberAliceConnectionRecord!.id) + faberBobConnectionRecord = await faberAgent.connections.returnWhenIsConnected(faberBobConnectionRecord!.id) + + expect(aliceConnectionRecord).toBeConnectedWith(faberAliceConnectionRecord) + expect(bobConnectionRecord).toBeConnectedWith(faberBobConnectionRecord) + + await aliceAgent.wallet.delete() + await aliceAgent.shutdown() + await bobAgent.wallet.delete() + await bobAgent.shutdown() + await faberAgent.wallet.delete() + await faberAgent.shutdown() + }) +})