diff --git a/.gitignore b/.gitignore index f387123..d13d9f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ coverage/ -node_modules/ \ No newline at end of file +node_modules/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 525833c..8f9154d 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,9 @@ app.get('/:id', (req, res) => { ... }) }) -``` \ No newline at end of file +``` + +### URL rewriting +In order to match up with downstream middleware or handlers, this middleware also rewrites the url variables on the Express.js request object. + +This is done by rewriting `req.url`, which is parsed to produce `req.path`. This does not modify `req.baseUrl` or `req.originalUrl`. \ No newline at end of file diff --git a/index.js b/index.js index 40ef4d0..b7e9664 100644 --- a/index.js +++ b/index.js @@ -49,6 +49,11 @@ const required = (name) => { throw new Error(`Required parameter: ${name}`) } +const rewriteUrl = ({ req, alias, value }) => { + const re = new RegExp(`(?<=/)${alias}(?=/)?`) + return req.url.replace(re, value) +} + /** * Allows using route parameter aliases in a request. Aliases are mapped to values * in the payload of a JWT provided elsewhere in the request @@ -93,7 +98,11 @@ module.exports = ({ const middleware = (req, _, next) => { if (req.params[paramName] === alias) { - req.params[paramName] = getParamValue({ req, tokenLocation, tokenName, payloadKey }) + const value = getParamValue({ req, tokenLocation, tokenName, payloadKey }) + + req.params[paramName] = value + + req.url = rewriteUrl({ req, alias, value }) } next() diff --git a/test/test.test.js b/test/test.test.js index cff93a0..69a0a3f 100644 --- a/test/test.test.js +++ b/test/test.test.js @@ -5,7 +5,13 @@ const jwt = require('jsonwebtoken') const any = () => Math.random().toString(36).substring(2, 15) -const echoParams = (req, res) => res.json(req.params) +const echoRequestObject = (req, res) => res.json({ + params: req.params, + baseUrl: req.baseUrl, + path: req.path, + originalUrl: req.originalUrl, + url: req.url +}) const createApp = ({ middlewares = [], paramNames = [] }) => { const app = express() @@ -13,7 +19,7 @@ const createApp = ({ middlewares = [], paramNames = [] }) => { // ex: '/:param1/:param2' const route = paramNames.map(paramName => `/:${paramName}`).join() - app.post(route, ...middlewares, echoParams) + app.post(route, ...middlewares, echoRequestObject) return app } @@ -92,15 +98,35 @@ describe('routeParamAlias middleware', () => { const token = jwt.sign({ [paramName]: headerParam }, 'super_secret') + const route = `/${routeParam}` + const res = await request(app) - .post(`/${routeParam}`) + .post(route) .set('x-param', token) .expect(200) .expect('Content-Type', /json/) - expect(res.body).toMatchObject({ + const { + params, + originalUrl, + baseUrl, + url, + path + } = res.body + + expect(params).toMatchObject({ [paramName]: isAlias ? headerParam : routeParam }) + + expect(originalUrl).toEqual(route) + + expect(originalUrl).toEqual(route) + expect(baseUrl).toEqual('') + + const expectedRoute = isAlias ? route.replace(alias, headerParam) : route + + expect(url).toEqual(expectedRoute) + expect(path).toEqual(expectedRoute) }) it('should return a 4xx error if the token does not contain the parameter', async () => { @@ -111,9 +137,7 @@ describe('routeParamAlias middleware', () => { .set('x-param', token) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) it('should return a 4xx error if the token does not exist', async () => { @@ -121,9 +145,7 @@ describe('routeParamAlias middleware', () => { .post(`/${alias}`) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) it('should return a 4xx error if the query parameter is not a JWT', async () => { @@ -134,9 +156,7 @@ describe('routeParamAlias middleware', () => { .set('x-param', token) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) }) @@ -166,14 +186,32 @@ describe('routeParamAlias middleware', () => { const token = jwt.sign({ [paramName]: queryParam }, 'super_secret') + const route = `/${routeParam}` + + const routeWithQuery = `${route}?${tokenName}=${token}` + const res = await request(app) - .post(`/${routeParam}?${tokenName}=${token}`) + .post(routeWithQuery) .expect(200) .expect('Content-Type', /json/) - expect(res.body).toMatchObject({ + const { + params, + originalUrl, + baseUrl, + url, + path + } = res.body + + expect(params).toMatchObject({ [paramName]: isAlias ? queryParam : routeParam }) + + expect(originalUrl).toEqual(routeWithQuery) + expect(baseUrl).toEqual('') + + expect(url).toEqual(isAlias ? routeWithQuery.replace(alias, queryParam) : routeWithQuery) + expect(path).toEqual(isAlias ? route.replace(alias, queryParam) : route) }) it('should return a 4xx error if the token does not contain the parameter', async () => { @@ -183,9 +221,7 @@ describe('routeParamAlias middleware', () => { .post(`/${alias}?${tokenName}=${token}`) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) it('should return a 4xx error if the token does not exist', async () => { @@ -193,9 +229,7 @@ describe('routeParamAlias middleware', () => { .post(`/${alias}`) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) it('should return a 4xx error if the query parameter is not a JWT', async () => { @@ -205,9 +239,7 @@ describe('routeParamAlias middleware', () => { .post(`/${alias}?${tokenName}=${token}`) .expect(400) - expect(res.body).not.toMatchObject({ - [paramName]: expect.any(String) - }) + expect(res.body.params).toBeFalsy() }) }) })