From 8bc10615035366c7d02d40eb96f45d42ad627838 Mon Sep 17 00:00:00 2001 From: James Chartrand Date: Thu, 23 May 2024 17:46:17 -0400 Subject: [PATCH] add healthz endpoint --- package.json | 1 + src/TransactionException.js | 6 ++++ src/app.js | 65 +++++++++++++++++++++++++++-------- src/app.test.js | 16 +++++++-- src/didAuth.js | 27 +++++++++++++-- src/test-fixtures/testData.js | 4 +-- 6 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 src/TransactionException.js diff --git a/package.json b/package.json index 39fdc26..e2d752a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@digitalbazaar/ed25519-signature-2020": "^5.2.0", "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0", "@digitalbazaar/vc": "^6.0.1", + "axios": "^1.7.2", "cookie-parser": "~1.4.4", "cors": "^2.8.5", "credentials-context": "^2.0.0", diff --git a/src/TransactionException.js b/src/TransactionException.js new file mode 100644 index 0000000..3a1e014 --- /dev/null +++ b/src/TransactionException.js @@ -0,0 +1,6 @@ +export default function TransactionException(code, message, stack) { + this.code = code + this.message = message + this.stack = stack + } + \ No newline at end of file diff --git a/src/app.js b/src/app.js index 3684bd4..669a9e1 100644 --- a/src/app.js +++ b/src/app.js @@ -1,7 +1,11 @@ -import express from 'express'; +import express, { request } from 'express'; import logger from 'morgan'; import cors from 'cors'; +import axios from 'axios' import { initializeTransactionManager, setupExchange, retrieveStoredData, getVPR } from './transactionManager.js'; +import { getDataForExchangeSetupPost } from './test-fixtures/testData.js'; +import { getSignedDIDAuth } from './didAuth.js'; +import TransactionException from './TransactionException.js'; export async function build(opts = {}) { @@ -18,6 +22,37 @@ export async function build(opts = {}) { res.send({ message: 'transaction-service server status: ok.' }) }); + app.get('/healthz', async function (req, res) { + + const baseURL = `${req.protocol}://${req.headers.host}` + const testData = getDataForExchangeSetupPost('test', baseURL) + const exchangeURL = `${baseURL}/exchange` + try { + const response = await axios.post( + exchangeURL, + testData + ) + const { data: walletQuerys } = response + const walletQuery = walletQuerys.find(q => q.retrievalId === 'someId') + const parsedDeepLink = new URL(walletQuery.directDeepLink) + const requestURI = parsedDeepLink.searchParams.get('vc_request_url'); + const challenge = parsedDeepLink.searchParams.get('challenge'); + const didAuth = await getSignedDIDAuth('did:ex:223234', challenge) + const { data } = await axios.post(requestURI, didAuth) + const { tenantName, vc: unSignedVC } = data + if (!tenantName === 'test' || ! unSignedVC.name === "A Simply Wonderful Course") { + throw new TransactionException(503, 'transaction-service healthz failed') + } + } catch (e) { + console.log(`exception in healthz: ${JSON.stringify(e)}`) + return res.status(503).json({ + error: `transaction-service healthz check failed with error: ${e}`, + healthy: false + }) + } + res.send({ message: 'transaction-service server status: ok.', healthy: true }) + }) + /* This is step 1 in an exchange. Creates a new exchange and stores the provided data @@ -31,7 +66,7 @@ export async function build(opts = {}) { async (req, res) => { try { const data = req.body; - if (!data || !Object.keys(data).length) return res.status(400).send({code: 400, message: 'No data was provided in the body.'}) + if (!data || !Object.keys(data).length) return res.status(400).send({ code: 400, message: 'No data was provided in the body.' }) const walletQuerys = await setupExchange(data) return res.json(walletQuerys) } catch (error) { @@ -40,13 +75,13 @@ export async function build(opts = {}) { } }) - /* - This is step 2 in an exchange, where the wallet - has asked to initiate the exchange, and we reply - here with a Verifiable Presentation Request, asking - for a DIDAuth. Note that in some scenarios the wallet - may skip this step and directly present the DIDAuth. - */ + /* + This is step 2 in an exchange, where the wallet + has asked to initiate the exchange, and we reply + here with a Verifiable Presentation Request, asking + for a DIDAuth. Note that in some scenarios the wallet + may skip this step and directly present the DIDAuth. + */ app.post("/exchange/:exchangeId", async (req, res) => { try { @@ -58,12 +93,12 @@ export async function build(opts = {}) { } }) - /* - This is step 3 in an exchange, where we verify the - supplied DIDAuth, and if verified we return the previously - stored data for the exchange. - */ - app.post("/exchange/:exchangeId/:transactionId", + /* + This is step 3 in an exchange, where we verify the + supplied DIDAuth, and if verified we return the previously + stored data for the exchange. + */ + app.post("/exchange/:exchangeId/:transactionId", async (req, res) => { try { const didAuth = req.body diff --git a/src/app.test.js b/src/app.test.js index 65b8734..b56e2ea 100644 --- a/src/app.test.js +++ b/src/app.test.js @@ -50,12 +50,22 @@ describe('api', () => { expect(response.body.length).to.eql(testData.data.length) }) - - - }) + describe('GET /healthz', () => { + + it.only('returns 200 if running', async () => { + + const response = await request(app) + .get("/healthz") + + expect(response.header["content-type"]).to.have.string("json"); + expect(response.status).to.eql(200); + expect(response.body).to.eql({ message: 'transaction-service server status: ok.', healthy: true }) + + }) + }) }) diff --git a/src/didAuth.js b/src/didAuth.js index 7dfac4c..0f36069 100644 --- a/src/didAuth.js +++ b/src/didAuth.js @@ -1,12 +1,33 @@ -import {verify} from '@digitalbazaar/vc'; +import {signPresentation, createPresentation, verify} from '@digitalbazaar/vc'; +import {Ed25519VerificationKey2020} from '@digitalbazaar/ed25519-verification-key-2020'; import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020'; import { securityLoader } from './securityLoader.js'; const documentLoader = securityLoader().build() -const suite = new Ed25519Signature2020(); + +const signingKeyPairForTesting = await Ed25519VerificationKey2020.generate( + { + seed: new Uint8Array ([ + 217, 87, 166, 30, 75, 106, 132, 55, + 32, 120, 171, 23, 116, 73, 254, 74, + 230, 16, 127, 91, 2, 252, 224, 96, + 184, 172, 245, 157, 58, 217, 91, 240 + ]), + controller: "did:key:z6MkvL5yVCgPhYvQwSoSRQou6k6ZGfD5mNM57HKxufEXwfnP" + } +) +const suiteForSigning = new Ed25519Signature2020({key: signingKeyPairForTesting}); +const suiteForVerification = new Ed25519Signature2020(); +export const getSignedDIDAuth = async (holder = 'did:ex:12345', challenge) => { + const presentation = createPresentation({holder}); + return await signPresentation({ + presentation, suite: suiteForSigning, challenge, documentLoader + }); +} + export const verifyDIDAuth = async ({presentation, challenge}) => { - const result = await verify({presentation, challenge, suite, documentLoader}); + const result = await verify({presentation, challenge, suite: suiteForVerification, documentLoader}); return result.verified } diff --git a/src/test-fixtures/testData.js b/src/test-fixtures/testData.js index 7d4883e..5a40169 100644 --- a/src/test-fixtures/testData.js +++ b/src/test-fixtures/testData.js @@ -1,9 +1,9 @@ import testVC from './testVC.js'; -const getDataForExchangeSetupPost = (tenantName) => { +const getDataForExchangeSetupPost = (tenantName, exchangeHost = 'http://localhost:4005') => { const fakeData = { tenantName, - exchangeHost: 'http://localhost:4005', + exchangeHost, data: [ { vc: testVC, retrievalId: 'someId', }, { vc: testVC, retrievalId: 'blah' }