-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(did-comm) Routing protocol to handle forward messages
- Loading branch information
Showing
4 changed files
with
351 additions
and
5 deletions.
There are no files selected for viewing
259 changes: 259 additions & 0 deletions
259
packages/did-comm/src/__tests__/routing-message-handler.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
82
packages/did-comm/src/protocols/routing-message-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |