From 96801bbefb7e983e09c82001bfa7a4dc4e826fa7 Mon Sep 17 00:00:00 2001 From: David Nagy Date: Tue, 22 Oct 2024 10:56:40 +0200 Subject: [PATCH] feat: keep original headers after validation (#26) --- src/server/validation/validateRequest.test.ts | 56 ++++++++++++++++++- src/server/validation/validateRequest.ts | 25 ++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/server/validation/validateRequest.test.ts b/src/server/validation/validateRequest.test.ts index 948dfa8..ebf3614 100644 --- a/src/server/validation/validateRequest.test.ts +++ b/src/server/validation/validateRequest.test.ts @@ -97,9 +97,9 @@ describe('validateRequest.headers', () => { }) expect(response).toMatchObject({ ok: 1 }) - expect(response.headers).toEqual({ + expect(response.headers).toMatchObject({ shortstring: 'shortstring', - numeric: 123, + numeric: '123', bool: '1', sessionid: 'sessionid', }) @@ -154,4 +154,56 @@ describe('validateRequest.headers', () => { expect(err.cause.message).toContain('"REDACTED": "REDACTED"') expect(err.cause.message).not.toContain('sessionid') }) + + test('should not replace the headers with the validated value by default', async () => { + const response = await app.get('', { + headers: { + shortstring: 'shortstring', + numeric: '123', + bool: '1', + sessionid: 'sessionid', + foo: 'bar', + }, + }) + + expect(response.headers).toMatchObject({ + shortstring: 'shortstring', + numeric: '123', + bool: '1', + sessionid: 'sessionid', + foo: 'bar', + }) + }) + + test('should replace the headers with the validated value when configured so', async () => { + const resource = getDefaultRouter().get('/', async (req, res) => { + validateRequest.headers( + req, + objectSchema({ + shortstring: stringSchema.min(8).max(16), + numeric: numberSchema, + }), + { keepOriginal: false }, + ) + + res.json({ ok: 1, headers: req.headers }) + }) + const app = expressTestService.createAppFromResource(resource) + + const response = await app.get('', { + headers: { + shortstring: 'shortstring', + numeric: 123, + foo: 'bar', + }, + }) + + expect(response.headers).toEqual({ + shortstring: 'shortstring', + numeric: 123, // converted to number + // foo: 'bar' // fields not in the schema are removed + }) + + await app.close() + }) }) diff --git a/src/server/validation/validateRequest.ts b/src/server/validation/validateRequest.ts index 4f86fed..8b8b3fa 100644 --- a/src/server/validation/validateRequest.ts +++ b/src/server/validation/validateRequest.ts @@ -16,6 +16,11 @@ export interface ReqValidationOptions { * If true - `genericErrorHandler` will report it to errorReporter (aka Sentry). */ report?: boolean | ((err: ERR) => boolean) + + /** + * When set to true, the validated object will not be replaced with the Joi-converted value. + */ + keepOriginal?: boolean } /** @@ -55,12 +60,25 @@ class ValidateRequest { return this.validate(req, 'params', schema, opt) } + /** + * Validates `req.headers` against the provided Joi schema. + * + * Note: as opposed to other methods, this method does not mutate `req.headers` in case of success, + * i.e. schemas that cast values will not have any effect. + * + * If you wish to mutate `req.headers` with the validated value, use `keepOriginal: false` option. + * Keep in mind that this will also remove all values that are not in the schema. + */ headers( req: BackendRequest, schema: AnySchema, opt: ReqValidationOptions = {}, ): T { - return this.validate(req, 'headers', schema, opt) + const options: ReqValidationOptions = { + keepOriginal: true, + ...opt, + } + return this.validate(req, 'headers', schema, options) } private validate( @@ -92,7 +110,10 @@ class ValidateRequest { } // mutate req to replace the property with the value, converted by Joi - req[reqProperty] = value + if (!opt.keepOriginal) { + req[reqProperty] = value + } + return value } }