diff --git a/.env.example b/.env.example index ef25a8d..bd0861e 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +DID_WEB_SEED=qxerQNrTbyrrNQeAhwhJuOLfYIvTSRyG +WALLET_KEY=EAyhwhZJmdzHenyDnDCFBfFxPyc9F4zXsTwzWueHHjgH POSTGRES_USER= POSTGRES_PASSWORD= POSTGRES_HOST= diff --git a/.github/workflows/deploy-stack.yml b/.github/workflows/deploy-stack.yml index 83d1b10..c645c4f 100644 --- a/.github/workflows/deploy-stack.yml +++ b/.github/workflows/deploy-stack.yml @@ -21,6 +21,7 @@ jobs: uses: ./.github/actions/deploy env: WALLET_KEY: ${{ secrets.WALLET_KEY }} + DID_WEB_SEED: ${{ secrets.DID_WEB_SEED}} POSTGRES_USER: ${{ secrets.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} diff --git a/README.md b/README.md index c6f0f0e..3cae31d 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,8 @@ The `POSTGRES_` variables won't be used in development mode (`NODE_ENV=developme | `AGENT_NAME` | The name of the agent. This will be used in invitations and will be publicly advertised. | | `AGENT_PORT` | The port that is exposed for incoming traffic. Both the HTTP and WS inbound transport handlers are exposes on this port, and HTTP traffic will be upgraded to the WebSocket server when applicable. | | `WALLET_NAME` | The name of the wallet to use. | -| `WALLET_KEY` | The key to unlock the wallet. | +| `WALLET_KEY` | The raw wallet key to unlock the wallet. | +| `DID_WEB_SEED` | The seed to deterministically generate the did web. | | `INVITATION_URL` | Optional URL that can be used as the base for the invitation url. This would allow you to render a certain web page that can extract the invitation form the `oob` parameter, and show the QR code, or show useful information to the end-user. Less applicable to mediator URLs. | | `POSTGRES_HOST` | Host of the database to use. Should include both host and port. | | `POSTGRES_USER` | The postgres user. | diff --git a/docker-compose-animo-mediator.yml b/docker-compose-animo-mediator.yml index 9e3fefe..e67f532 100644 --- a/docker-compose-animo-mediator.yml +++ b/docker-compose-animo-mediator.yml @@ -21,6 +21,7 @@ services: AGENT_NAME: Animo Mediator WALLET_NAME: animo-mediator WALLET_KEY: ${WALLET_KEY} + DID_WEB_SEED: ${DID_WEB_SEED} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_HOST: ${POSTGRES_HOST} diff --git a/docker-compose.yml b/docker-compose.yml index 23fbfde..722ffe8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: AGENT_NAME: Mediator WALLET_NAME: mediator WALLET_KEY: ${WALLET_KEY} + DID_WEB_SEED: ${DID_WEB_SEED} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_HOST: ${POSTGRES_HOST} diff --git a/package.json b/package.json index 3e79a8a..217295d 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@types/node-fetch": "^2.6.4", "dotenv": "^16.0.1", "jest": "^29.2.2", - "ngrok": "^4.3.1", + "ngrok": "^5.0.0-beta.2", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", "typescript": "^4.8.4" diff --git a/src/agent.ts b/src/agent.ts index f601ed2..8776b38 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -2,15 +2,25 @@ import { AskarModule, AskarMultiWalletDatabaseScheme } from '@aries-framework/as import { Agent, CacheModule, + ConnectionEventTypes, + ConnectionState, + ConnectionStateChangedEvent, ConnectionsModule, DidCommMimeType, + DidCommV1Service, + DidDocument, + DidExchangeState, HttpOutboundTransport, InMemoryLruCache, + KeyDerivationMethod, + KeyType, MediatorModule, OutOfBandRole, OutOfBandState, + TypedArrayEncoder, WalletConfig, WsOutboundTransport, + getEd25519VerificationKey2018, } from '@aries-framework/core' import { HttpInboundTransport, WsInboundTransport, agentDependencies } from '@aries-framework/node' import { ariesAskar } from '@hyperledger/aries-askar-nodejs' @@ -19,7 +29,16 @@ import type { Socket } from 'net' import express from 'express' import { Server } from 'ws' -import { AGENT_ENDPOINTS, AGENT_NAME, AGENT_PORT, LOG_LEVEL, POSTGRES_HOST, WALLET_KEY, WALLET_NAME } from './constants' +import { + AGENT_ENDPOINTS, + AGENT_NAME, + AGENT_PORT, + DID_WEB_SEED, + LOG_LEVEL, + POSTGRES_HOST, + WALLET_KEY, + WALLET_NAME, +} from './constants' import { askarPostgresConfig } from './database' import { Logger } from './logger' import { StorageMessageQueueModule } from './storage/StorageMessageQueueModule' @@ -45,6 +64,57 @@ function createModules() { return modules } +async function createAndImportDid(agent: MediatorAgent) { + const httpEndpoint = agent.config.endpoints.find((e) => e.startsWith('http')) as string + const wsEndpoint = agent.config.endpoints.find((e) => e.startsWith('ws')) as string + const domain = httpEndpoint.replace(/^https?:\/\//, '') + const did = `did:web:${domain}` + + const createdDids = await agent.dids.getCreatedDids({ did }) + if (createdDids.length > 0) { + const { did, didDocument } = createdDids[0] + return { did, didDocument } + } + + // TODO: if there is a did:web in the wallet and the did was not found previously + // the url on which the did:web is hosted has probably changed. + const didWebs = await agent.dids.getCreatedDids({ method: 'web' }) + if (didWebs.length > 0) { + // TODO: remake + await agent.wallet.delete() + await agent.shutdown() + await agent.initialize() + } + + const privateKey = TypedArrayEncoder.fromString(DID_WEB_SEED) + const key = await agent.wallet.createKey({ keyType: KeyType.Ed25519, privateKey }) + + const verificationMethod = getEd25519VerificationKey2018({ id: `${did}#${key.fingerprint}`, key, controller: did }) + + const didDocument = new DidDocument({ + id: did, + context: ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + verificationMethod: [verificationMethod], + keyAgreement: [verificationMethod.id], + service: [ + new DidCommV1Service({ + id: `did:web:${domain}#animo-mediator`, + serviceEndpoint: wsEndpoint, + recipientKeys: [verificationMethod.id], + }), + ], + }) + + await agent.dids.import({ + did, + didDocument, + overwrite: true, + privateKeys: [{ keyType: KeyType.Ed25519, privateKey }], + }) + + return { did, didDocument } +} + export async function createAgent() { // We create our own instance of express here. This is not required // but allows use to use the same server (and port) for both WebSockets and HTTP @@ -60,6 +130,7 @@ export async function createAgent() { id: WALLET_NAME, key: WALLET_KEY, storage: storageConfig, + keyDerivationMethod: KeyDerivationMethod.Raw, } if (storageConfig) { @@ -86,9 +157,7 @@ export async function createAgent() { didCommMimeType: DidCommMimeType.V0, }, dependencies: agentDependencies, - modules: { - ...createModules(), - }, + modules: createModules(), }) // Create all transports @@ -103,6 +172,20 @@ export async function createAgent() { agent.registerInboundTransport(wsInboundTransport) agent.registerOutboundTransport(wsOutboundTransport) + await agent.initialize() + + agent.events.on(ConnectionEventTypes.ConnectionStateChanged, async (event) => { + const { connectionRecord } = event.payload + if (connectionRecord.state !== DidExchangeState.RequestReceived) return + + agent.config.logger.info( + `Accepting connection request for connection '${connectionRecord.id}' for '${connectionRecord.theirLabel}'` + ) + await agent.connections.acceptRequest(connectionRecord.id) + }) + + const { didDocument } = await createAndImportDid(agent) + // eslint-disable-next-line @typescript-eslint/no-misused-promises httpInboundTransport.app.get('/invite', async (req, res) => { if (!req.query._oobid || typeof req.query._oobid !== 'string') { @@ -121,7 +204,10 @@ export async function createAgent() { return res.send(outOfBandRecord.outOfBandInvitation.toJSON()) }) - await agent.initialize() + // eslint-disable-next-line @typescript-eslint/no-misused-promises + httpInboundTransport.app.get('/.well-known/did.json', async (_req, res) => { + return res.send(didDocument) + }) // When an 'upgrade' to WS is made on our http server, we forward the // request to the WS server @@ -134,4 +220,4 @@ export async function createAgent() { return agent } -export type MediatorAgent = ReturnType +export type MediatorAgent = Awaited> diff --git a/src/constants.ts b/src/constants.ts index a8d0bd7..72f430c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,11 +3,12 @@ import { LogLevel } from '@aries-framework/core' export const AGENT_PORT = process.env.AGENT_PORT ? Number(process.env.AGENT_PORT) : 3000 export const AGENT_NAME = process.env.AGENT_NAME || 'Animo Mediator' export const WALLET_NAME = process.env.WALLET_NAME || 'animo-mediator-dev' -export const WALLET_KEY = process.env.WALLET_KEY || 'animo-mediator-dev' +export const WALLET_KEY = process.env.WALLET_KEY || 'EAyhwhZJmdzHenyDnDCFBfFxPyc9F4zXsTwzWueHHjgH' export const AGENT_ENDPOINTS = process.env.AGENT_ENDPOINTS?.split(',') ?? [ `http://localhost:${AGENT_PORT}`, `ws://localhost:${AGENT_PORT}`, ] +export const DID_WEB_SEED = process.env.DID_WEB_SEED || 'qxerQNrTbyrrNQeAhwhJuOLfYIvTSRyG' export const POSTGRES_HOST = process.env.POSTGRES_HOST export const POSTGRES_USER = process.env.POSTGRES_USER diff --git a/src/index.ts b/src/index.ts index e742504..bb8c793 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,4 +29,5 @@ void createAgent().then(async (agent) => { }) agent.config.logger.info(`Out of band invitation url: \n\n\t${mediatorInvitationUrlLong}`) + agent.config.logger.info(`Did Web Url '${httpEndpoint}/.well-known/did.json'`) }) diff --git a/yarn.lock b/yarn.lock index 159fd84..393e359 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1029,11 +1029,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.46.tgz#9f2102d0ba74a318fcbe170cbff5463f119eab59" integrity sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg== -"@types/node@^8.10.50": - version "8.10.66" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" - integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== - "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -3202,17 +3197,16 @@ next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== -ngrok@^4.3.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/ngrok/-/ngrok-4.3.3.tgz#c51a1c4af2271ac3c9092ede3b0975caf7833217" - integrity sha512-a2KApnkiG5urRxBPdDf76nNBQTnNNWXU0nXw0SsqsPI+Kmt2lGf9TdVYpYrHMnC+T9KhcNSWjCpWqBgC6QcFvw== +ngrok@^5.0.0-beta.2: + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/ngrok/-/ngrok-5.0.0-beta.2.tgz#7976690a592b27de9a91ff93892cfabda24360ad" + integrity sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ== dependencies: - "@types/node" "^8.10.50" extract-zip "^2.0.1" got "^11.8.5" lodash.clonedeep "^4.5.0" uuid "^7.0.0 || ^8.0.0" - yaml "^1.10.0" + yaml "^2.2.2" optionalDependencies: hpagent "^0.1.2" @@ -4184,10 +4178,10 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1"