Skip to content

Commit

Permalink
feat(did-comm) Routing protocol to handle forward messages
Browse files Browse the repository at this point in the history
  • Loading branch information
codynhat committed Jan 10, 2023
1 parent e241fc2 commit c752ea1
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 5 deletions.
259 changes: 259 additions & 0 deletions packages/did-comm/src/__tests__/routing-message-handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { DIDComm } from '../didcomm'
import {
createAgent,
IDIDManager,
IEventListener,
IIdentifier,
IKeyManager,
IMessageHandler,
IResolver,
TAgent,
} from '../../../core/src'
import { DIDManager, MemoryDIDStore } from '../../../did-manager/src'
import { KeyManager, MemoryKeyStore, MemoryPrivateKeyStore } from '../../../key-manager/src'
import { KeyManagementSystem } from '../../../kms-local/src'
import { DIDResolverPlugin } from '../../../did-resolver/src'
import { Resolver } from 'did-resolver'
import { DIDCommHttpTransport } from '../transports/transports'
import { IDIDComm } from '../types/IDIDComm'
import { MessageHandler } from '../../../message-handler/src'
import {
CoordinateMediationMediatorMessageHandler,
CoordinateMediationRecipientMessageHandler,
createMediateRequestMessage,
createMediateGrantMessage,
} from '../protocols/coordinate-mediation-message-handler'
import { DIDCommMessageMediaType } from '../types/message-types'
import {
RoutingMessageHandler,
FORWARD_MESSAGE_TYPE,
QUEUE_MESSAGE_TYPE,
} from '../protocols/routing-message-handler'
import { FakeDidProvider, FakeDidResolver } from '../../../test-utils/src'
import { MessagingRouter, RequestWithAgentRouter } from '../../../remote-server/src'
import { Entities, IDataStore, migrations } from '../../../data-store/src'
import express from 'express'
import { Server } from 'http'
import { DIDCommMessageHandler } from '../message-handler'
import { DataStore, DataStoreORM } from '../../../data-store/src'
import { DataSource } from 'typeorm'
import { v4 } from 'uuid'

const DIDCommEventSniffer: IEventListener = {
eventTypes: ['DIDCommV2Message-sent', 'DIDCommV2Message-received', 'DIDCommV2Message-forwardMessageSaved'],
onEvent: jest.fn(),
}

const databaseFile = `./tmp/local-database2-${Math.random().toPrecision(5)}.sqlite`

describe('routing-message-handler', () => {
let recipient: IIdentifier
let mediator: IIdentifier
let agent: TAgent<IResolver & IKeyManager & IDIDManager & IDIDComm & IMessageHandler & IDataStore>
let didCommEndpointServer: Server
let listeningPort = Math.round(Math.random() * 32000 + 2048)
let dbConnection: DataSource

beforeAll(async () => {
dbConnection = new DataSource({
name: 'test',
type: 'sqlite',
database: databaseFile,
synchronize: false,
migrations: migrations,
migrationsRun: true,
logging: false,
entities: Entities,
})
agent = createAgent({
plugins: [
new KeyManager({
store: new MemoryKeyStore(),
kms: {
// @ts-ignore
local: new KeyManagementSystem(new MemoryPrivateKeyStore()),
},
}),
new DIDManager({
providers: {
'did:fake': new FakeDidProvider(),
// 'did:web': new WebDIDProvider({ defaultKms: 'local' })
},
store: new MemoryDIDStore(),
defaultProvider: 'did:fake',
}),
new DIDResolverPlugin({
resolver: new Resolver({
...new FakeDidResolver(() => agent).getDidFakeResolver(),
}),
}),
// @ts-ignore
new DIDComm([new DIDCommHttpTransport()]),
new MessageHandler({
messageHandlers: [
// @ts-ignore
new DIDCommMessageHandler(),
new CoordinateMediationMediatorMessageHandler(),
new CoordinateMediationRecipientMessageHandler(),
new RoutingMessageHandler(),
],
}),
new DataStore(dbConnection),
new DataStoreORM(dbConnection),
DIDCommEventSniffer,
],
})

recipient = await agent.didManagerImport({
did: 'did:fake:z6MkgbqNU4uF9NKSz5BqJQ4XKVHuQZYcUZP8pXGsJC8nTHwo',
keys: [
{
type: 'Ed25519',
kid: 'didcomm-senderKey-1',
publicKeyHex: '1fe9b397c196ab33549041b29cf93be29b9f2bdd27322f05844112fad97ff92a',
privateKeyHex:
'b57103882f7c66512dc96777cbafbeb2d48eca1e7a867f5a17a84e9a6740f7dc1fe9b397c196ab33549041b29cf93be29b9f2bdd27322f05844112fad97ff92a',
kms: 'local',
},
],
services: [
{
id: 'msg1',
type: 'DIDCommMessaging',
serviceEndpoint: `http://localhost:${listeningPort}/messaging`,
},
],
provider: 'did:fake',
alias: 'sender',
})

mediator = await agent.didManagerImport({
did: 'did:fake:z6MkrPhffVLBZpxH7xvKNyD4sRVZeZsNTWJkLdHdgWbfgNu3',
keys: [
{
type: 'Ed25519',
kid: 'didcomm-receiverKey-1',
publicKeyHex: 'b162e405b6485eff8a57932429b192ec4de13c06813e9028a7cdadf0e2703636',
privateKeyHex:
'19ed9b6949cfd0f9a57e30f0927839a985fa699491886ebcdda6a954d869732ab162e405b6485eff8a57932429b192ec4de13c06813e9028a7cdadf0e2703636',
kms: 'local',
},
],
services: [
{
id: 'msg2',
type: 'DIDCommMessaging',
serviceEndpoint: `http://localhost:${listeningPort}/messaging`,
},
],
provider: 'did:fake',
alias: 'receiver',
})
// console.log('sender: ', sender)
// console.log('recipient: ', recipient)

const requestWithAgent = RequestWithAgentRouter({ agent })

await new Promise((resolve) => {
//setup a server to receive HTTP messages and forward them to this agent to be processed as DIDComm messages
const app = express()
// app.use(requestWithAgent)
app.use(
'/messaging',
requestWithAgent,
MessagingRouter({
metaData: { type: 'DIDComm', value: 'integration test' },
}),
)
didCommEndpointServer = app.listen(listeningPort, () => {
resolve(true)
})
})
})

afterAll(async () => {
try {
await new Promise((resolve, reject) => didCommEndpointServer?.close(resolve))
} catch (e) {
//nop
}
})

it('should save forward message in queue for recipient', async () => {
expect.assertions(2)

// 1. Coordinate mediation
const mediateRequestMessage = createMediateRequestMessage(recipient.did, mediator.did)
const packedMessage = await agent.packDIDCommMessage({
packing: 'authcrypt',
message: mediateRequestMessage,
})
await agent.sendDIDCommMessage({
messageId: mediateRequestMessage.id,
packedMessage,
recipientDidUrl: mediator.did,
})

// 2. Forward message
const innerMessage = await agent.packDIDCommMessage({
packing: 'authcrypt',
message: {
type: 'test',
to: recipient.did,
from: mediator.did,
id: 'test',
body: { hello: 'world' },
},
})
const msgId = v4()
const packedForwardMessage = await agent.packDIDCommMessage({
packing: 'anoncrypt',
message: {
type: FORWARD_MESSAGE_TYPE,
to: mediator.did,
id: msgId,
body: {
next: recipient.did,
},
attachments: [{ media_type: DIDCommMessageMediaType.ENCRYPTED, data: { json: innerMessage } }],
},
})
await agent.sendDIDCommMessage({
messageId: msgId,
packedMessage: packedForwardMessage,
recipientDidUrl: mediator.did,
})

expect(DIDCommEventSniffer.onEvent).toHaveBeenCalledWith(
{
data: {
message: {
body: { next: recipient.did },
id: msgId,
to: mediator.did,
type: FORWARD_MESSAGE_TYPE,
attachments: [{ media_type: DIDCommMessageMediaType.ENCRYPTED, data: { json: innerMessage } }],
},
metaData: { packing: 'anoncrypt' },
},
type: 'DIDCommV2Message-received',
},
expect.anything(),
)

expect(DIDCommEventSniffer.onEvent).toHaveBeenCalledWith(
{
data: {
id: expect.anything(),
to: recipient.did,
type: QUEUE_MESSAGE_TYPE,
raw: innerMessage,
createdAt: expect.anything(),
metaData: [{ type: 'didCommForwardMsgId', value: msgId }],
},
type: 'DIDCommV2Message-forwardMessageSaved',
},
expect.anything(),
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { v4 } from 'uuid'
import { IDIDComm } from '../types/IDIDComm'
import { IDIDCommMessage, DIDCommMessageMediaType } from '../types/message-types'

const debug = Debug('veramo:did-comm:trust-ping-message-handler')
const debug = Debug('veramo:did-comm:coordinate-mediation-message-handler')

type IContext = IAgentContext<IDIDManager & IKeyManager & IDIDComm & IDataStore>

const MEDIATE_REQUEST_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-request'
const MEDIATE_GRANT_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-grant'
const MEDIATE_DENY_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-deny'
export const MEDIATE_REQUEST_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-request'
export const MEDIATE_GRANT_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-grant'
export const MEDIATE_DENY_MESSAGE_TYPE = 'https://didcomm.org/coordinate-mediation/2.0/mediate-deny'

export function createMediateRequestMessage(
recipientDidUrl: string,
Expand Down Expand Up @@ -82,6 +82,10 @@ export class CoordinateMediationMediatorMessageHandler extends AbstractMessageHa
contentType: DIDCommMessageMediaType.ENCRYPTED,
}
message.addMetaData({ type: 'ReturnRouteResponse', value: JSON.stringify(returnResponse) })

// Save message to track recipients
// FIXME: Save IMessage
await context.agent.dataStoreSaveMessage({ message: response })
}
} catch (ex) {
debug(ex)
Expand Down
3 changes: 2 additions & 1 deletion packages/did-comm/src/protocols/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { TrustPingMessageHandler } from "./trust-ping-message-handler"
export { CoordinateMediationMediatorMessageHandler } from "./coordinate-mediation-message-handler"
export { CoordinateMediationMediatorMessageHandler, CoordinateMediationRecipientMessageHandler } from "./coordinate-mediation-message-handler"
export { RoutingMessageHandler } from "./routing-message-handler"
82 changes: 82 additions & 0 deletions packages/did-comm/src/protocols/routing-message-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { IAgentContext, IDIDManager, IKeyManager, IDataStore, IDataStoreORM } from '@veramo/core'
import { AbstractMessageHandler, Message } from '@veramo/message-handler'
import Debug from 'debug'
import { v4 } from 'uuid'
import { IDIDComm } from '../types/IDIDComm'
import { MEDIATE_GRANT_MESSAGE_TYPE, MEDIATE_DENY_MESSAGE_TYPE } from './coordinate-mediation-message-handler'

const debug = Debug('veramo:did-comm:routing-message-handler')

type IContext = IAgentContext<IDIDManager & IKeyManager & IDIDComm & IDataStore & IDataStoreORM>

export const FORWARD_MESSAGE_TYPE = 'https://didcomm.org/routing/2.0/forward'
export const QUEUE_MESSAGE_TYPE = 'https://didcomm.org/routing/2.0/forward/queue-message'

/**
* A plugin for the {@link @veramo/message-handler#MessageHandler} that handles forward messages for the Routing protocol.
* @beta This API may change without a BREAKING CHANGE notice.
*/
export class RoutingMessageHandler extends AbstractMessageHandler {
constructor() {
super()
}

/**
* Handles forward messages for Routing protocol
* https://didcomm.org/routing/2.0/
*/
public async handle(message: Message, context: IContext): Promise<Message> {
if (message.type === FORWARD_MESSAGE_TYPE) {
debug('Forward Message Received')
try {
const { attachments, data } = message
if (!attachments) {
throw new Error('invalid_argument: Forward received without `attachments` set')
}
if (!data.next) {
throw new Error('invalid_argument: Forward received without `body.next` set')
}

if (attachments.length > 0) {
// Check if receiver has been granted mediation
const mediationResponses = await context.agent.dataStoreORMGetMessages({
where: [
{
column: 'type',
value: [MEDIATE_GRANT_MESSAGE_TYPE, MEDIATE_DENY_MESSAGE_TYPE],
op: 'In',
},
{
column: 'to',
value: [data.next],
op: 'In',
},
],
order: [{ column: 'createdAt', direction: 'DESC' }],
})

// If last mediation response was a grant (not deny)
if (mediationResponses.length > 0 && mediationResponses[0].type === MEDIATE_GRANT_MESSAGE_TYPE) {
// Save message for queue
const messageToQueue = new Message({ raw: attachments[0].data.json })
messageToQueue.id = v4()
messageToQueue.type = QUEUE_MESSAGE_TYPE
messageToQueue.to = data.next
messageToQueue.createdAt = new Date().toISOString()
messageToQueue.addMetaData({ type: 'didCommForwardMsgId', value: message.id })

await context.agent.dataStoreSaveMessage({ message: messageToQueue })
context.agent.emit('DIDCommV2Message-forwardMessageSaved', messageToQueue)
} else {
debug('Forward received for DID without granting mediation')
}
}
} catch (ex) {
debug(ex)
}
return message
}

return super.handle(message, context)
}
}

0 comments on commit c752ea1

Please sign in to comment.