Skip to content

Commit

Permalink
fix: support mediation for connectionless exchange (#577)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra committed Dec 15, 2021
1 parent aa49f99 commit 3dadfc7
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 9 deletions.
9 changes: 5 additions & 4 deletions packages/core/src/modules/connections/ConnectionsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ export class ConnectionsModule {
invitation: ConnectionInvitationMessage
connectionRecord: ConnectionRecord
}> {
const mediationRecord = await this.mediationRecipientService.discoverMediation(config?.mediatorId)
const myRouting = await this.mediationRecipientService.getRouting(mediationRecord)
const myRouting = await this.mediationRecipientService.getRouting({
mediatorId: config?.mediatorId,
useDefaultMediator: true,
})

const { connectionRecord: connectionRecord, message: invitation } = await this.connectionService.createInvitation({
autoAcceptConnection: config?.autoAcceptConnection,
Expand Down Expand Up @@ -86,8 +88,7 @@ export class ConnectionsModule {
mediatorId?: string
}
): Promise<ConnectionRecord> {
const mediationRecord = await this.mediationRecipientService.discoverMediation(config?.mediatorId)
const myRouting = await this.mediationRecipientService.getRouting(mediationRecord)
const myRouting = await this.mediationRecipientService.getRouting({ mediatorId: config?.mediatorId })

let connection = await this.connectionService.processInvitation(invitation, {
autoAcceptConnection: config?.autoAcceptConnection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export class ConnectionRequestHandler implements Handler {
// routing object is required for multi use invitation, because we're creating a
// new keypair that possibly needs to be registered at a mediator
if (connectionRecord.multiUseInvitation) {
const mediationRecord = await this.mediationRecipientService.discoverMediation()
routing = await this.mediationRecipientService.getRouting(mediationRecord)
routing = await this.mediationRecipientService.getRouting()
}

connectionRecord = await this.connectionService.processRequest(messageContext, routing)
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/modules/routing/RecipientModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ export class RecipientModule {
const connection = await this.connectionService.findByInvitationKey(invitation.recipientKeys[0])
if (!connection) {
this.logger.debug('Mediation Connection does not exist, creating connection')
const routing = await this.mediationRecipientService.getRouting()
// We don't want to use the current default mediator when connecting to another mediator
const routing = await this.mediationRecipientService.getRouting({ useDefaultMediator: false })

const invitationConnectionRecord = await this.connectionService.processInvitation(invitation, {
autoAcceptConnection: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ export class MediationRecipientService {
return keylistUpdateMessage
}

public async getRouting(mediationRecord?: MediationRecord): Promise<Routing> {
public async getRouting({ mediatorId, useDefaultMediator = true }: GetRoutingOptions = {}): Promise<Routing> {
let mediationRecord: MediationRecord | null = null

if (mediatorId) {
mediationRecord = await this.getById(mediatorId)
} else if (useDefaultMediator) {
// If no mediatorId is provided, and useDefaultMediator is true (default)
// We use the default mediator if available
mediationRecord = await this.findDefaultMediator()
}

let endpoints = this.config.endpoints
let routingKeys: string[] = []

Expand Down Expand Up @@ -288,3 +298,16 @@ export interface MediationProtocolMsgReturnType<MessageType extends AgentMessage
message: MessageType
mediationRecord: MediationRecord
}

export interface GetRoutingOptions {
/**
* Identifier of the mediator to use when setting up routing
*/
mediatorId?: string

/**
* Whether to use the default mediator if available and `mediatorId` has not been provided
* @default true
*/
useDefaultMediator?: boolean
}
189 changes: 188 additions & 1 deletion packages/core/tests/connectionless-proofs.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@
import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport'
import type { ProofStateChangedEvent } from '../src/modules/proofs'

import { Subject, ReplaySubject } from 'rxjs'

import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport'
import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport'
import { Agent } from '../src/agent/Agent'
import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment'
import { CredentialPreview } from '../src/modules/credentials'
import {
PredicateType,
ProofState,
ProofAttributeInfo,
AttributeFilter,
ProofPredicateInfo,
AutoAcceptProof,
ProofEventTypes,
} from '../src/modules/proofs'
import { LinkedAttachment } from '../src/utils/LinkedAttachment'
import { uuid } from '../src/utils/uuid'

import { setupProofsTest, waitForProofRecordSubject } from './helpers'
import {
getBaseConfig,
issueCredential,
makeConnection,
prepareForIssuance,
setupProofsTest,
waitForProofRecordSubject,
} from './helpers'
import testLogger from './logger'

describe('Present Proof', () => {
let agents: Agent[]

afterEach(async () => {
for (const agent of agents) {
await agent.shutdown()
await agent.wallet.delete()
}
})

test('Faber starts with connection-less proof requests to Alice', async () => {
const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest(
'Faber connection-less Proofs',
'Alice connection-less Proofs',
AutoAcceptProof.Never
)
agents = [aliceAgent, faberAgent]
testLogger.test('Faber sends presentation request to Alice')

const attributes = {
Expand Down Expand Up @@ -93,6 +123,8 @@ describe('Present Proof', () => {
AutoAcceptProof.Always
)

agents = [aliceAgent, faberAgent]

const attributes = {
name: new ProofAttributeInfo({
name: 'name',
Expand Down Expand Up @@ -141,4 +173,159 @@ describe('Present Proof', () => {
state: ProofState.Done,
})
})

test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => {
testLogger.test('Faber sends presentation request to Alice')

const credentialPreview = CredentialPreview.fromRecord({
name: 'John',
age: '99',
})

const unique = uuid().substring(0, 4)

const mediatorConfig = getBaseConfig(`Connectionless proofs with mediator Mediator-${unique}`, {
autoAcceptMediationRequests: true,
endpoints: ['rxjs:mediator'],
})

const faberMessages = new Subject<SubjectMessage>()
const aliceMessages = new Subject<SubjectMessage>()
const mediatorMessages = new Subject<SubjectMessage>()

const subjectMap = {
'rxjs:mediator': mediatorMessages,
}

// Initialize mediator
const mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies)
mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(mediatorMessages, subjectMap))
mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages))
await mediatorAgent.initialize()

const faberMediationInvitation = await mediatorAgent.connections.createConnection()
const aliceMediationInvitation = await mediatorAgent.connections.createConnection()

const faberConfig = getBaseConfig(`Connectionless proofs with mediator Faber-${unique}`, {
autoAcceptProofs: AutoAcceptProof.Always,
mediatorConnectionsInvite: faberMediationInvitation.invitation.toUrl({ domain: 'https://example.com' }),
})

const aliceConfig = getBaseConfig(`Connectionless proofs with mediator Alice-${unique}`, {
autoAcceptProofs: AutoAcceptProof.Always,
// logger: new TestLogger(LogLevel.test),
mediatorConnectionsInvite: aliceMediationInvitation.invitation.toUrl({ domain: 'https://example.com' }),
})

const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies)
faberAgent.registerOutboundTransport(new SubjectOutboundTransport(faberMessages, subjectMap))
faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages))
await faberAgent.initialize()

const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies)
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(aliceMessages, subjectMap))
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages))
await aliceAgent.initialize()

agents = [aliceAgent, faberAgent, mediatorAgent]

const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1'])

const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent)
expect(faberConnection.isReady).toBe(true)
expect(aliceConnection.isReady).toBe(true)

await issueCredential({
issuerAgent: faberAgent,
issuerConnectionId: faberConnection.id,
holderAgent: aliceAgent,
credentialTemplate: {
credentialDefinitionId: definition.id,
comment: 'some comment about credential',
preview: credentialPreview,
linkedAttachments: [
new LinkedAttachment({
name: 'image_0',
attachment: new Attachment({
filename: 'picture-of-a-cat.png',
data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }),
}),
}),
new LinkedAttachment({
name: 'image_1',
attachment: new Attachment({
filename: 'picture-of-a-dog.png',
data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }),
}),
}),
],
},
})
const faberReplay = new ReplaySubject<ProofStateChangedEvent>()
const aliceReplay = new ReplaySubject<ProofStateChangedEvent>()

faberAgent.events.observable<ProofStateChangedEvent>(ProofEventTypes.ProofStateChanged).subscribe(faberReplay)
aliceAgent.events.observable<ProofStateChangedEvent>(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay)

const attributes = {
name: new ProofAttributeInfo({
name: 'name',
restrictions: [
new AttributeFilter({
credentialDefinitionId: definition.id,
}),
],
}),
}

const predicates = {
age: new ProofPredicateInfo({
name: 'age',
predicateType: PredicateType.GreaterThanOrEqualTo,
predicateValue: 50,
restrictions: [
new AttributeFilter({
credentialDefinitionId: definition.id,
}),
],
}),
}

// eslint-disable-next-line prefer-const
let { proofRecord: faberProofRecord, requestMessage } = await faberAgent.proofs.createOutOfBandRequest(
{
name: 'test-proof-request',
requestedAttributes: attributes,
requestedPredicates: predicates,
},
{
autoAcceptProof: AutoAcceptProof.ContentApproved,
}
)

const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator()
if (!mediationRecord) {
throw new Error('Faber agent has no default mediator')
}

expect(requestMessage).toMatchObject({
service: {
recipientKeys: [expect.any(String)],
routingKeys: mediationRecord.routingKeys,
serviceEndpoint: mediationRecord.endpoint,
},
})

await aliceAgent.receiveMessage(requestMessage.toJSON())

await waitForProofRecordSubject(aliceReplay, {
threadId: faberProofRecord.threadId,
state: ProofState.Done,
})

await waitForProofRecordSubject(faberReplay, {
threadId: faberProofRecord.threadId,
state: ProofState.Done,
})
})
})

0 comments on commit 3dadfc7

Please sign in to comment.