diff --git a/server/package-lock.json b/server/package-lock.json index 0e09631..b2f6f03 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dmgt-tech/the-usher-server", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dmgt-tech/the-usher-server", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "cors": "2.8.5", @@ -22,6 +22,7 @@ "js-yaml": "4.1.0", "jwks-rsa": "3.1.0", "moment": "2.30.1", + "node-cache": "5.1.2", "oas-tools": "2.2.2", "pem-jwk": "2.0.0", "serverless-http": "3.1.0", @@ -3116,6 +3117,17 @@ "node": ">= 0.6" } }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "dev": true, diff --git a/server/package.json b/server/package.json index a7dc8ab..e0b6911 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@dmgt-tech/the-usher-server", - "version": "2.0.0", + "version": "2.0.1", "description": "The Usher Authorization Server", "engines": { "node": ">=18" @@ -32,6 +32,7 @@ "js-yaml": "4.1.0", "jwks-rsa": "3.1.0", "moment": "2.30.1", + "node-cache": "5.1.2", "oas-tools": "2.2.2", "pem-jwk": "2.0.0", "serverless-http": "3.1.0", diff --git a/server/src/api_endpoints/endpoint_jwksjson.js b/server/src/api_endpoints/endpoint_jwksjson.js index 0a50233..89a8265 100644 --- a/server/src/api_endpoints/endpoint_jwksjson.js +++ b/server/src/api_endpoints/endpoint_jwksjson.js @@ -1,16 +1,28 @@ const keystore = require('database/layer/db-keys') const { pem2jwk } = require('pem-jwk') const createError = require('http-errors') +const NodeCache = require('node-cache') +const myCache = new NodeCache({ stdTTL: 60, checkperiod: 30 }) const getJwks = async (req, res) => { try { - const keyPairs = await keystore.selectAllKeys() - const publicKeys = keyPairs?.map(keyPair => { - const item = pem2jwk(keyPair.public_key) - item.kid = keyPair.kid - return item - }) - res.status(200).send({ keys: publicKeys }) + const cacheKeyName = 'usher-jwks' + let response = myCache.get(cacheKeyName) + + if (response) { + res.set('x-cache', 'Hit from Usher') + } else { + const keyPairs = await keystore.selectAllKeys() + const publicKeys = keyPairs?.map(keyPair => { + const item = pem2jwk(keyPair.public_key) + item.kid = keyPair.kid + return item + }) + response = { keys: publicKeys } + myCache.set(cacheKeyName, response) + } + + res.status(200).send(response) } catch ({ httpStatusCode = 500, message }) { return next(createError(httpStatusCode, { message })) } diff --git a/server/test/endpoint_jwks.test.js b/server/test/endpoint_jwks.test.js index 44b9fc5..f46fdc8 100644 --- a/server/test/endpoint_jwks.test.js +++ b/server/test/endpoint_jwks.test.js @@ -6,12 +6,19 @@ const { getServerUrl } = require('./lib/urls') describe('Get JWKS', () => { const url = `${getServerUrl()}/.well-known/jwks.json` + const fetchJWKS = () => fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); - it('should return a JWKS containing a list of keys', async function () { - const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }) - assert.strictEqual(response.status, 200) + it('should return a JWKS containing a list of keys', async () => { + const response = await fetchJWKS() + assert.equal(response.status, 200) const jwks = await response.json() assert('keys' in jwks, 'There should be a keys element in the object.') assert(jwks.keys.length > 0, 'There should be at least one key in the keys array') }) + + it('should return a JWKS from internal cache', async () => { + const response = await fetchJWKS() + assert.equal(response.status, 200) + assert.equal(response.headers.get('x-cache'), 'Hit from Usher') + }) })