From 10910d94b6cac76d5a042155fb366b8bdf89023c Mon Sep 17 00:00:00 2001 From: Giuliano Kranevitter Date: Fri, 4 Nov 2022 11:17:05 -0400 Subject: [PATCH 01/12] feature: allow custom hook name --- README.md | 54 +++++++++++++++++++++++++- index.js | 20 ++++++---- test/cors.test.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8bd8a2f..054bc8d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ npm i @fastify/cors ``` ## Usage -Require `@fastify/cors` and register it as any other plugin, it will add a `preHandler` hook and a [wildcard options route](https://github.com/fastify/fastify/issues/326#issuecomment-411360862). +Require `@fastify/cors` and register it as any other plugin, it will add a `onRequest` hook and a [wildcard options route](https://github.com/fastify/fastify/issues/326#issuecomment-411360862). ```js import Fastify from 'fastify' import cors from '@fastify/cors' @@ -97,6 +97,58 @@ fastify.register(async function (fastify) { fastify.listen({ port: 3000 }) ``` +### Custom Fastify hook name + +By default, `@fastify/cors` adds a `onRequest` hook where the validation and header injection are executed. This can be customized by passing `hook` in the options. + +```js +import Fastify from 'fastify' +import cors from '@fastify/cors' + +const fastify = Fastify() +await fastify.register(cors, { + hook: 'preHandler', +}) + +fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) +}) + +await fastify.listen({ port: 3000 }) +``` + +When configuring CORS asynchronously, an object with `delegator` key is expected: + +```js +const fastify = require('fastify')() + +fastify.register(require('@fastify/cors'), { + hook: 'preHandler', + delegator: (req, callback) => { + const corsOptions = { + // This is NOT recommended for production as it enables reflection exploits + origin: true + }; + + // do not include CORS headers for requests from localhost + if (/^localhost$/m.test(req.headers.origin)) { + corsOptions.origin = false + } + + // callback expects two parameters: error and options + callback(null, corsOptions) + }, +}) + +fastify.register(async function (fastify) { + fastify.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) +}) + +fastify.listen({ port: 3000 }) +``` + ## Acknowledgements The code is a port for Fastify of [`expressjs/cors`](https://github.com/expressjs/cors). diff --git a/index.js b/index.js index b923caa..97f69a4 100644 --- a/index.js +++ b/index.js @@ -19,17 +19,22 @@ const defaultOptions = { strictPreflight: true } +const defaultHook = 'onRequest' + function fastifyCors (fastify, opts, next) { fastify.decorateRequest('corsPreflightEnabled', false) let hideOptionsRoute = true if (typeof opts === 'function') { handleCorsOptionsDelegator(opts, fastify) + } else if (opts.delegator) { + const { delegator, ...options } = opts + handleCorsOptionsDelegator(delegator, fastify, options) } else { if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute const corsOptions = Object.assign({}, defaultOptions, opts) - fastify.addHook('onRequest', function onRequestCors (req, reply, next) { - onRequest(fastify, corsOptions, req, reply, next) + fastify.addHook(opts.hook || defaultHook, function handleCors (req, reply, next) { + addCorsHeadersHandler(fastify, corsOptions, req, reply, next) }) } @@ -52,8 +57,8 @@ function fastifyCors (fastify, opts, next) { next() } -function handleCorsOptionsDelegator (optionsResolver, fastify) { - fastify.addHook('onRequest', function onRequestCors (req, reply, next) { +function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultHook }) { + fastify.addHook(hook, function handleCors (req, reply, next) { if (optionsResolver.length === 2) { handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) return @@ -62,7 +67,7 @@ function handleCorsOptionsDelegator (optionsResolver, fastify) { const ret = optionsResolver(req) if (ret && typeof ret.then === 'function') { ret.then(options => Object.assign({}, defaultOptions, options)) - .then(corsOptions => onRequest(fastify, corsOptions, req, reply, next)).catch(next) + .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) return } } @@ -76,15 +81,16 @@ function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, repl next(err) } else { const corsOptions = Object.assign({}, defaultOptions, options) - onRequest(fastify, corsOptions, req, reply, next) + addCorsHeadersHandler(fastify, corsOptions, req, reply, next) } }) } -function onRequest (fastify, options, req, reply, next) { +function addCorsHeadersHandler (fastify, options, req, reply, next) { // Always set Vary header // https://github.com/rs/cors/issues/10 addOriginToVaryHeader(reply) + const resolveOriginOption = typeof options.origin === 'function' ? resolveOriginWrapper(fastify, options.origin) : (_, cb) => cb(null, options.origin) resolveOriginOption(req, (error, resolvedOriginOption) => { diff --git a/test/cors.test.js b/test/cors.test.js index 0c52eab..31473cb 100644 --- a/test/cors.test.js +++ b/test/cors.test.js @@ -275,6 +275,103 @@ test('Should support dynamic config (Promise)', t => { }) }) +test('Should support custom hook with dynamic config', t => { + t.plan(16) + + const configs = [{ + origin: 'example.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['foo', 'bar'], + allowedHeaders: ['baz', 'woo'], + maxAge: 123 + }, { + origin: 'sample.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['zoo', 'bar'], + allowedHeaders: ['baz', 'foo'], + maxAge: 321 + }] + + const fastify = Fastify() + let requestId = 0 + const configDelegation = function (req, cb) { + // request should have id + t.ok(req.id) + // request should not have send + t.notOk(req.send) + const config = configs[requestId] + requestId++ + if (config) { + cb(null, config) + } else { + cb(new Error('ouch')) + } + } + fastify.register(cors, { + hook: 'preHandler', + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': 'example.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'foo, bar', + 'content-length': '2' + }) + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.match(res.headers, { + 'access-control-allow-origin': 'sample.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'zoo, bar', + 'access-control-allow-methods': 'GET', + 'access-control-allow-headers': 'baz, foo', + 'access-control-max-age': '321', + 'content-length': '0' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) + test('Should support dynamic config. (Invalid function)', t => { t.plan(2) From 9dddf67cf7d4dccbe5cc0a4fbf5d8a8f50f60f00 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 13:59:51 +0100 Subject: [PATCH 02/12] add typings --- index.d.ts | 19 +++++++++++++++++ test/index.test-d.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/index.d.ts b/index.d.ts index e0abaa0..b51edc8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -13,10 +13,29 @@ type FastifyCorsPlugin = FastifyPluginCallback< NonNullable | fastifyCors.FastifyCorsOptionsDelegate >; +type FastifyCorsHook = + | 'onRequest' + | 'preParsing' + | 'preValidation' + | 'preHandler' + | 'preSerialization' + | 'onSend' + | 'onError' + declare namespace fastifyCors { export type OriginFunction = (origin: string, callback: OriginCallback) => void; export interface FastifyCorsOptions { + /** + * Configures the Lifecycle Hook. + */ + hook?: FastifyCorsHook; + + /** + * Configures the delegate function. + */ + delegator?: FastifyCorsOptionsDelegate; + /** * Configures the Access-Control-Allow-Origin CORS header. */ diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 051cab9..7cff5ff 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -253,4 +253,54 @@ const delegate: FastifyPluginOptionsDelegate strictPreflight: false } } + +appHttp2.register(fastifyCors, { + hook: 'preHandler' +}) +appHttp2.register(fastifyCors, { + hook: 'onRequest' +}) +appHttp2.register(fastifyCors, { + hook: 'preValidation' +}) +appHttp2.register(fastifyCors, { + hook: 'preParsing' +}) + +appHttp2.register(fastifyCors, { + hook: 'preParsing', + delegator: () => { + return { + origin: [/\*/, /something/], + allowedHeaders: ['authorization', 'content-type'], + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + credentials: true, + exposedHeaders: ['authorization'], + maxAge: 13000, + preflightContinue: false, + optionsSuccessStatus: 200, + preflight: false, + strictPreflight: false + } + } +}) + +appHttp2.register(fastifyCors, { + hook: 'preParsing', + delegator: () => { + return { + origin: [/\*/, /something/], + allowedHeaders: ['authorization', 'content-type'], + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + credentials: true, + exposedHeaders: ['authorization'], + maxAge: 13000, + preflightContinue: false, + optionsSuccessStatus: 200, + preflight: false, + strictPreflight: false + } + } +}) + appHttp2.register(fastifyCors, delegate) From 54962691f035a70667613fbed2c4d4152989343d Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:02:37 +0100 Subject: [PATCH 03/12] fix lint issue --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 97f69a4..f77d843 100644 --- a/index.js +++ b/index.js @@ -90,7 +90,7 @@ function addCorsHeadersHandler (fastify, options, req, reply, next) { // Always set Vary header // https://github.com/rs/cors/issues/10 addOriginToVaryHeader(reply) - + const resolveOriginOption = typeof options.origin === 'function' ? resolveOriginWrapper(fastify, options.origin) : (_, cb) => cb(null, options.origin) resolveOriginOption(req, (error, resolvedOriginOption) => { From 28df88c2becbf6540c1916cd1eb1215c42110ba1 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:09:19 +0100 Subject: [PATCH 04/12] simplify handleCorsOptionsDelegator --- index.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index f77d843..a8ac0f2 100644 --- a/index.js +++ b/index.js @@ -58,21 +58,22 @@ function fastifyCors (fastify, opts, next) { } function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultHook }) { - fastify.addHook(hook, function handleCors (req, reply, next) { - if (optionsResolver.length === 2) { + if (optionsResolver.length === 2) { + fastify.addHook(hook, function handleCors (req, reply, next) { handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) - return - } else { - // handle delegator based on Promise + }) + } else { + fastify.addHook(hook, function handleCors (req, reply, next) { const ret = optionsResolver(req) + // handle delegator based on Promise if (ret && typeof ret.then === 'function') { ret.then(options => Object.assign({}, defaultOptions, options)) .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) return } - } - next(new Error('Invalid CORS origin option')) - }) + next(new Error('Invalid CORS origin option')) + }) + } } function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, reply, next) { From 26499ed55215549c5b05c25cb2f9e79991d15b67 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:17:39 +0100 Subject: [PATCH 05/12] validate hook in simple case --- index.js | 40 ++++++++++++++++++++++++++++------------ test/hooks.test.js | 12 ++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 test/hooks.test.js diff --git a/index.js b/index.js index a8ac0f2..28083bd 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ const { const defaultOptions = { origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + hook: 'onRequest', preflightContinue: false, optionsSuccessStatus: 204, credentials: false, @@ -19,7 +20,22 @@ const defaultOptions = { strictPreflight: true } -const defaultHook = 'onRequest' +const validHooks = [ + 'onRequest', + 'preParsing', + 'preValidation', + 'preHandler', + 'preSerialization', + 'onSend', + 'onError' +] + +function validateHook (value, next) { + if (validHooks.indexOf(value) !== -1) { + return + } + next(new TypeError('@fastify/cors: Invalid hook option provided.')) +} function fastifyCors (fastify, opts, next) { fastify.decorateRequest('corsPreflightEnabled', false) @@ -33,7 +49,8 @@ function fastifyCors (fastify, opts, next) { } else { if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute const corsOptions = Object.assign({}, defaultOptions, opts) - fastify.addHook(opts.hook || defaultHook, function handleCors (req, reply, next) { + validateHook(corsOptions.hook, next) + fastify.addHook(corsOptions.hook, function handleCors (req, reply, next) { addCorsHeadersHandler(fastify, corsOptions, req, reply, next) }) } @@ -57,23 +74,22 @@ function fastifyCors (fastify, opts, next) { next() } -function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultHook }) { - if (optionsResolver.length === 2) { - fastify.addHook(hook, function handleCors (req, reply, next) { +function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultOptions.hook }) { + fastify.addHook(hook, function handleCors (req, reply, next) { + if (optionsResolver.length === 2) { handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) - }) - } else { - fastify.addHook(hook, function handleCors (req, reply, next) { - const ret = optionsResolver(req) + return + } else { // handle delegator based on Promise + const ret = optionsResolver(req) if (ret && typeof ret.then === 'function') { ret.then(options => Object.assign({}, defaultOptions, options)) .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) return } - next(new Error('Invalid CORS origin option')) - }) - } + } + next(new Error('Invalid CORS origin option')) + }) } function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, reply, next) { diff --git a/test/hooks.test.js b/test/hooks.test.js new file mode 100644 index 0000000..b334885 --- /dev/null +++ b/test/hooks.test.js @@ -0,0 +1,12 @@ +'use strict' + +const { test } = require('tap') +const Fastify = require('fastify') +const cors = require('..') + +test('Should error on invalid hook option', async (t) => { + t.plan(1) + + const fastify = Fastify() + t.rejects(fastify.register(cors, { hook: 'invalid' }), new TypeError('@fastify/cors: Invalid hook option provided.')) +}) From 104761482d93f948c53c5bf3af81d8784951e5fb Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:42:46 +0100 Subject: [PATCH 06/12] add more checks for hooks --- test/hooks.test.js | 287 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/test/hooks.test.js b/test/hooks.test.js index b334885..55c8fc2 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -2,6 +2,7 @@ const { test } = require('tap') const Fastify = require('fastify') +const kFastifyContext = require('fastify/lib/symbols').kRouteContext const cors = require('..') test('Should error on invalid hook option', async (t) => { @@ -10,3 +11,289 @@ test('Should error on invalid hook option', async (t) => { const fastify = Fastify() t.rejects(fastify.register(cors, { hook: 'invalid' }), new TypeError('@fastify/cors: Invalid hook option provided.')) }) + +test('Should set hook onRequest if hook option is not set', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 2) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook onRequest if hook option is set to onRequest', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'onRequest' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 2) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook preParsing if hook option is set to preParsing', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preParsing' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing.length, 1) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook preValidation if hook option is set to preValidation', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preValidation' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation.length, 1) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook preParsing if hook option is set to preParsing', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preParsing' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing.length, 1) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook preHandler if hook option is set to preHandler', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preHandler' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler.length, 1) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook onSend if hook option is set to onSend', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'onSend' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend.length, 1) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook onError if hook option is set to onError', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'onError' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError.length, 1) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) + +test('Should set hook preSerialization if hook option is set to preSerialization', async (t) => { + t.plan(7) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preSerialization' + }) + + fastify.addHook('onRequest', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onSend, null) + t.equal(request[kFastifyContext].preHandler, null) + t.equal(request[kFastifyContext].preParsing, null) + t.equal(request[kFastifyContext].preSerialization.length, 1) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + await fastify.inject({ + method: 'GET', + url: '/' + }) +}) From 74786676d7d10184401cfedb0a25c49a20c128ff Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:49:40 +0100 Subject: [PATCH 07/12] use onRespone hook for hook tests --- test/hooks.test.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/hooks.test.js b/test/hooks.test.js index 55c8fc2..aaa349d 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -19,9 +19,9 @@ test('Should set hook onRequest if hook option is not set', async (t) => { fastify.register(cors) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 2) + t.equal(request[kFastifyContext].onRequest.length, 1) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) @@ -51,9 +51,9 @@ test('Should set hook onRequest if hook option is set to onRequest', async (t) = hook: 'onRequest' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 2) + t.equal(request[kFastifyContext].onRequest.length, 1) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) @@ -83,9 +83,9 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t) hook: 'preParsing' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing.length, 1) @@ -115,9 +115,9 @@ test('Should set hook preValidation if hook option is set to preValidation', asy hook: 'preValidation' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) @@ -147,9 +147,9 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t) hook: 'preParsing' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing.length, 1) @@ -179,9 +179,9 @@ test('Should set hook preHandler if hook option is set to preHandler', async (t) hook: 'preHandler' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler.length, 1) t.equal(request[kFastifyContext].preParsing, null) @@ -211,9 +211,9 @@ test('Should set hook onSend if hook option is set to onSend', async (t) => { hook: 'onSend' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend.length, 1) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) @@ -243,9 +243,9 @@ test('Should set hook onError if hook option is set to onError', async (t) => { hook: 'onError' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError.length, 1) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) @@ -275,9 +275,9 @@ test('Should set hook preSerialization if hook option is set to preSerialization hook: 'preSerialization' }) - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onResponse', (request, reply, done) => { t.equal(request[kFastifyContext].onError, null) - t.equal(request[kFastifyContext].onRequest.length, 1) + t.equal(request[kFastifyContext].onRequest, null) t.equal(request[kFastifyContext].onSend, null) t.equal(request[kFastifyContext].preHandler, null) t.equal(request[kFastifyContext].preParsing, null) From 24aae3c74bf57da29b162ad0d51cb1010789b0ed Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:51:23 +0100 Subject: [PATCH 08/12] move hook related test to hooks.test.js --- test/cors.test.js | 97 ---------------------------------------------- test/hooks.test.js | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/test/cors.test.js b/test/cors.test.js index 31473cb..0c52eab 100644 --- a/test/cors.test.js +++ b/test/cors.test.js @@ -275,103 +275,6 @@ test('Should support dynamic config (Promise)', t => { }) }) -test('Should support custom hook with dynamic config', t => { - t.plan(16) - - const configs = [{ - origin: 'example.com', - methods: 'GET', - credentials: true, - exposedHeaders: ['foo', 'bar'], - allowedHeaders: ['baz', 'woo'], - maxAge: 123 - }, { - origin: 'sample.com', - methods: 'GET', - credentials: true, - exposedHeaders: ['zoo', 'bar'], - allowedHeaders: ['baz', 'foo'], - maxAge: 321 - }] - - const fastify = Fastify() - let requestId = 0 - const configDelegation = function (req, cb) { - // request should have id - t.ok(req.id) - // request should not have send - t.notOk(req.send) - const config = configs[requestId] - requestId++ - if (config) { - cb(null, config) - } else { - cb(new Error('ouch')) - } - } - fastify.register(cors, { - hook: 'preHandler', - delegator: configDelegation - }) - - fastify.get('/', (req, reply) => { - reply.send('ok') - }) - - fastify.inject({ - method: 'GET', - url: '/' - }, (err, res) => { - t.error(err) - delete res.headers.date - t.equal(res.statusCode, 200) - t.equal(res.payload, 'ok') - t.match(res.headers, { - 'access-control-allow-origin': 'example.com', - vary: 'Origin', - 'access-control-allow-credentials': 'true', - 'access-control-expose-headers': 'foo, bar', - 'content-length': '2' - }) - }) - - fastify.inject({ - method: 'OPTIONS', - url: '/', - headers: { - 'access-control-request-method': 'GET', - origin: 'example.com' - } - }, (err, res) => { - t.error(err) - delete res.headers.date - t.equal(res.statusCode, 204) - t.equal(res.payload, '') - t.match(res.headers, { - 'access-control-allow-origin': 'sample.com', - vary: 'Origin', - 'access-control-allow-credentials': 'true', - 'access-control-expose-headers': 'zoo, bar', - 'access-control-allow-methods': 'GET', - 'access-control-allow-headers': 'baz, foo', - 'access-control-max-age': '321', - 'content-length': '0' - }) - }) - - fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'access-control-request-method': 'GET', - origin: 'example.com' - } - }, (err, res) => { - t.error(err) - t.equal(res.statusCode, 500) - }) -}) - test('Should support dynamic config. (Invalid function)', t => { t.plan(2) diff --git a/test/hooks.test.js b/test/hooks.test.js index aaa349d..91ea7fe 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -297,3 +297,100 @@ test('Should set hook preSerialization if hook option is set to preSerialization url: '/' }) }) + +test('Should support custom hook with dynamic config', t => { + t.plan(16) + + const configs = [{ + origin: 'example.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['foo', 'bar'], + allowedHeaders: ['baz', 'woo'], + maxAge: 123 + }, { + origin: 'sample.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['zoo', 'bar'], + allowedHeaders: ['baz', 'foo'], + maxAge: 321 + }] + + const fastify = Fastify() + let requestId = 0 + const configDelegation = function (req, cb) { + // request should have id + t.ok(req.id) + // request should not have send + t.notOk(req.send) + const config = configs[requestId] + requestId++ + if (config) { + cb(null, config) + } else { + cb(new Error('ouch')) + } + } + fastify.register(cors, { + hook: 'preHandler', + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': 'example.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'foo, bar', + 'content-length': '2' + }) + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.match(res.headers, { + 'access-control-allow-origin': 'sample.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'zoo, bar', + 'access-control-allow-methods': 'GET', + 'access-control-allow-headers': 'baz, foo', + 'access-control-max-age': '321', + 'content-length': '0' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) From 8bd4146002c282a3c88b1781f593a3f6cfceb6f5 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 14:55:53 +0100 Subject: [PATCH 09/12] simplify handleCorsOptionsDelegator --- index.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 28083bd..f4c0a9a 100644 --- a/index.js +++ b/index.js @@ -75,21 +75,22 @@ function fastifyCors (fastify, opts, next) { } function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultOptions.hook }) { - fastify.addHook(hook, function handleCors (req, reply, next) { - if (optionsResolver.length === 2) { + if (optionsResolver.length === 2) { + fastify.addHook(hook, function handleCors (req, reply, next) { handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) - return - } else { - // handle delegator based on Promise + }) + } else { + // handle delegator based on Promise + fastify.addHook(hook, function handleCors (req, reply, next) { const ret = optionsResolver(req) if (ret && typeof ret.then === 'function') { ret.then(options => Object.assign({}, defaultOptions, options)) .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) return } - } - next(new Error('Invalid CORS origin option')) - }) + next(new Error('Invalid CORS origin option')) + }) + } } function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, reply, next) { From db39d53df8a1e700b4ca240d735d32707bbfbe7c Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Mon, 7 Nov 2022 16:12:48 +0100 Subject: [PATCH 10/12] handle hooks with payload parameter properly --- index.js | 71 ++++++--- test/hooks.test.js | 389 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 421 insertions(+), 39 deletions(-) diff --git a/index.js b/index.js index f4c0a9a..79950d9 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,12 @@ const validHooks = [ 'onError' ] +const hookWithPayload = [ + 'preSerialization', + 'preParsing', + 'onSend' +] + function validateHook (value, next) { if (validHooks.indexOf(value) !== -1) { return @@ -42,17 +48,23 @@ function fastifyCors (fastify, opts, next) { let hideOptionsRoute = true if (typeof opts === 'function') { - handleCorsOptionsDelegator(opts, fastify) + handleCorsOptionsDelegator(opts, fastify, { hook: defaultOptions.hook }, next) } else if (opts.delegator) { const { delegator, ...options } = opts - handleCorsOptionsDelegator(delegator, fastify, options) + handleCorsOptionsDelegator(delegator, fastify, options, next) } else { if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute const corsOptions = Object.assign({}, defaultOptions, opts) validateHook(corsOptions.hook, next) - fastify.addHook(corsOptions.hook, function handleCors (req, reply, next) { - addCorsHeadersHandler(fastify, corsOptions, req, reply, next) - }) + if (hookWithPayload.indexOf(corsOptions.hook) !== -1) { + fastify.addHook(corsOptions.hook, function handleCors (req, reply, payload, next) { + addCorsHeadersHandler(fastify, corsOptions, req, reply, next) + }) + } else { + fastify.addHook(corsOptions.hook, function handleCors (req, reply, next) { + addCorsHeadersHandler(fastify, corsOptions, req, reply, next) + }) + } } // The preflight reply must occur in the hook. This allows fastify-cors to reply to @@ -74,22 +86,43 @@ function fastifyCors (fastify, opts, next) { next() } -function handleCorsOptionsDelegator (optionsResolver, fastify, { hook } = { hook: defaultOptions.hook }) { +function handleCorsOptionsDelegator (optionsResolver, fastify, opts, next) { + const hook = (opts && opts.hook) || defaultOptions.hook + validateHook(hook, next) if (optionsResolver.length === 2) { - fastify.addHook(hook, function handleCors (req, reply, next) { - handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) - }) + if (hookWithPayload.indexOf(hook) !== -1) { + fastify.addHook(hook, function handleCors (req, reply, payload, next) { + handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) + }) + } else { + fastify.addHook(hook, function handleCors (req, reply, next) { + handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) + }) + } } else { - // handle delegator based on Promise - fastify.addHook(hook, function handleCors (req, reply, next) { - const ret = optionsResolver(req) - if (ret && typeof ret.then === 'function') { - ret.then(options => Object.assign({}, defaultOptions, options)) - .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) - return - } - next(new Error('Invalid CORS origin option')) - }) + if (hookWithPayload.indexOf(hook) !== -1) { + // handle delegator based on Promise + fastify.addHook(hook, function handleCors (req, reply, payload, next) { + const ret = optionsResolver(req) + if (ret && typeof ret.then === 'function') { + ret.then(options => Object.assign({}, defaultOptions, options)) + .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) + return + } + next(new Error('Invalid CORS origin option')) + }) + } else { + // handle delegator based on Promise + fastify.addHook(hook, function handleCors (req, reply, next) { + const ret = optionsResolver(req) + if (ret && typeof ret.then === 'function') { + ret.then(options => Object.assign({}, defaultOptions, options)) + .then(corsOptions => addCorsHeadersHandler(fastify, corsOptions, req, reply, next)).catch(next) + return + } + next(new Error('Invalid CORS origin option')) + }) + } } } diff --git a/test/hooks.test.js b/test/hooks.test.js index 91ea7fe..8de7ab2 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -13,7 +13,7 @@ test('Should error on invalid hook option', async (t) => { }) test('Should set hook onRequest if hook option is not set', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -36,14 +36,20 @@ test('Should set hook onRequest if hook option is not set', async (t) => { await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*' + }) }) test('Should set hook onRequest if hook option is set to onRequest', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -68,14 +74,20 @@ test('Should set hook onRequest if hook option is set to onRequest', async (t) = await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*' + }) }) test('Should set hook preParsing if hook option is set to preParsing', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -100,14 +112,21 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t) await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook preValidation if hook option is set to preValidation', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -132,14 +151,21 @@ test('Should set hook preValidation if hook option is set to preValidation', asy await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook preParsing if hook option is set to preParsing', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -164,14 +190,21 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t) await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook preHandler if hook option is set to preHandler', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -196,14 +229,21 @@ test('Should set hook preHandler if hook option is set to preHandler', async (t) await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook onSend if hook option is set to onSend', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -228,14 +268,21 @@ test('Should set hook onSend if hook option is set to onSend', async (t) => { await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook onError if hook option is set to onError', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -255,19 +302,26 @@ test('Should set hook onError if hook option is set to onError', async (t) => { }) fastify.get('/', (req, reply) => { - reply.send('ok') + throw new Error('Failed') }) await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 500) + t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Failed"}') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should set hook preSerialization if hook option is set to preSerialization', async (t) => { - t.plan(7) + t.plan(10) const fastify = Fastify() @@ -287,15 +341,22 @@ test('Should set hook preSerialization if hook option is set to preSerialization }) fastify.get('/', (req, reply) => { - reply.send('ok') + reply.send({ nonString: true }) }) await fastify.ready() - await fastify.inject({ + const res = await fastify.inject({ method: 'GET', url: '/' }) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, '{"nonString":true}') + t.match(res.headers, { + 'access-control-allow-origin': '*', + vary: 'Origin' + }) }) test('Should support custom hook with dynamic config', t => { @@ -394,3 +455,291 @@ test('Should support custom hook with dynamic config', t => { t.equal(res.statusCode, 500) }) }) + +test('Should support custom hook with dynamic config (callback)', t => { + t.plan(16) + + const configs = [{ + origin: 'example.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['foo', 'bar'], + allowedHeaders: ['baz', 'woo'], + maxAge: 123 + }, { + origin: 'sample.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['zoo', 'bar'], + allowedHeaders: ['baz', 'foo'], + maxAge: 321 + }] + + const fastify = Fastify() + let requestId = 0 + const configDelegation = function (req, cb) { + // request should have id + t.ok(req.id) + // request should not have send + t.notOk(req.send) + const config = configs[requestId] + requestId++ + if (config) { + cb(null, config) + } else { + cb(new Error('ouch')) + } + } + fastify.register(cors, { + hook: 'preParsing', + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': 'example.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'foo, bar', + 'content-length': '2' + }) + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.match(res.headers, { + 'access-control-allow-origin': 'sample.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'zoo, bar', + 'access-control-allow-methods': 'GET', + 'access-control-allow-headers': 'baz, foo', + 'access-control-max-age': '321', + 'content-length': '0' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) + +test('Should support custom hook with dynamic config (Promise)', t => { + t.plan(16) + + const configs = [{ + origin: 'example.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['foo', 'bar'], + allowedHeaders: ['baz', 'woo'], + maxAge: 123 + }, { + origin: 'sample.com', + methods: 'GET', + credentials: true, + exposedHeaders: ['zoo', 'bar'], + allowedHeaders: ['baz', 'foo'], + maxAge: 321 + }] + + const fastify = Fastify() + let requestId = 0 + const configDelegation = function (req) { + // request should have id + t.ok(req.id) + // request should not have send + t.notOk(req.send) + const config = configs[requestId] + requestId++ + if (config) { + return Promise.resolve(config) + } else { + return Promise.reject(new Error('ouch')) + } + } + + fastify.register(cors, { + hook: 'preParsing', + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'GET', + url: '/' + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 200) + t.equal(res.payload, 'ok') + t.match(res.headers, { + 'access-control-allow-origin': 'example.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'foo, bar', + 'content-length': '2' + }) + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 204) + t.equal(res.payload, '') + t.match(res.headers, { + 'access-control-allow-origin': 'sample.com', + vary: 'Origin', + 'access-control-allow-credentials': 'true', + 'access-control-expose-headers': 'zoo, bar', + 'access-control-allow-methods': 'GET', + 'access-control-allow-headers': 'baz, foo', + 'access-control-max-age': '321', + 'content-length': '0' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) + +test('Should support custom hook with dynamic config (Promise), but should error /1', t => { + t.plan(6) + + const fastify = Fastify() + const configDelegation = function () { + return false + } + + fastify.register(cors, { + hook: 'preParsing', + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 500) + t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Invalid CORS origin option"}') + t.match(res.headers, { + 'content-length': '89' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) + +test('Should support custom hook with dynamic config (Promise), but should error /2', t => { + t.plan(6) + + const fastify = Fastify() + const configDelegation = function () { + return false + } + + fastify.register(cors, { + delegator: configDelegation + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + fastify.inject({ + method: 'OPTIONS', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + delete res.headers.date + t.equal(res.statusCode, 500) + t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Invalid CORS origin option"}') + t.match(res.headers, { + 'content-length': '89' + }) + }) + + fastify.inject({ + method: 'GET', + url: '/', + headers: { + 'access-control-request-method': 'GET', + origin: 'example.com' + } + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 500) + }) +}) From 226380ffce6dfcda8b38e4b24a5c6796108763bb Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 8 Nov 2022 10:11:14 +0100 Subject: [PATCH 11/12] remove onError --- index.d.ts | 1 - index.js | 3 +-- test/hooks.test.js | 39 --------------------------------------- test/index.test-d.ts | 12 +++++++++--- 4 files changed, 10 insertions(+), 45 deletions(-) diff --git a/index.d.ts b/index.d.ts index b51edc8..03cceb1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -20,7 +20,6 @@ type FastifyCorsHook = | 'preHandler' | 'preSerialization' | 'onSend' - | 'onError' declare namespace fastifyCors { export type OriginFunction = (origin: string, callback: OriginCallback) => void; diff --git a/index.js b/index.js index 79950d9..132a6f5 100644 --- a/index.js +++ b/index.js @@ -26,8 +26,7 @@ const validHooks = [ 'preValidation', 'preHandler', 'preSerialization', - 'onSend', - 'onError' + 'onSend' ] const hookWithPayload = [ diff --git a/test/hooks.test.js b/test/hooks.test.js index 8de7ab2..6495d73 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -281,45 +281,6 @@ test('Should set hook onSend if hook option is set to onSend', async (t) => { }) }) -test('Should set hook onError if hook option is set to onError', async (t) => { - t.plan(10) - - const fastify = Fastify() - - fastify.register(cors, { - hook: 'onError' - }) - - fastify.addHook('onResponse', (request, reply, done) => { - t.equal(request[kFastifyContext].onError.length, 1) - t.equal(request[kFastifyContext].onRequest, null) - t.equal(request[kFastifyContext].onSend, null) - t.equal(request[kFastifyContext].preHandler, null) - t.equal(request[kFastifyContext].preParsing, null) - t.equal(request[kFastifyContext].preSerialization, null) - t.equal(request[kFastifyContext].preValidation, null) - done() - }) - - fastify.get('/', (req, reply) => { - throw new Error('Failed') - }) - - await fastify.ready() - - const res = await fastify.inject({ - method: 'GET', - url: '/' - }) - delete res.headers.date - t.equal(res.statusCode, 500) - t.equal(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Failed"}') - t.match(res.headers, { - 'access-control-allow-origin': '*', - vary: 'Origin' - }) -}) - test('Should set hook preSerialization if hook option is set to preSerialization', async (t) => { t.plan(10) diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 7cff5ff..38e69cb 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -255,16 +255,22 @@ const delegate: FastifyPluginOptionsDelegate } appHttp2.register(fastifyCors, { - hook: 'preHandler' + hook: 'onRequest' }) appHttp2.register(fastifyCors, { - hook: 'onRequest' + hook: 'preParsing' }) appHttp2.register(fastifyCors, { hook: 'preValidation' }) appHttp2.register(fastifyCors, { - hook: 'preParsing' + hook: 'preHandler' +}) +appHttp2.register(fastifyCors, { + hook: 'preSerialization' +}) +appHttp2.register(fastifyCors, { + hook: 'onSend' }) appHttp2.register(fastifyCors, { From 4832978095ea8039bc790909ef60f81220eb1b23 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 8 Nov 2022 10:18:29 +0100 Subject: [PATCH 12/12] improve Readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 054bc8d..26e46e6 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ You can use it as is without passing any option or you can configure it as expla } ``` * `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`). +* `hook`: See the section `Custom Fastify hook name` (default: `onResponse`) * `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: `'Content-Type,Authorization'`) or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. * `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: `'Content-Range,X-Content-Range'`) or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. * `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. @@ -99,7 +100,7 @@ fastify.listen({ port: 3000 }) ### Custom Fastify hook name -By default, `@fastify/cors` adds a `onRequest` hook where the validation and header injection are executed. This can be customized by passing `hook` in the options. +By default, `@fastify/cors` adds a `onRequest` hook where the validation and header injection are executed. This can be customized by passing `hook` in the options. Valid values are `onRequest`, `preParsing`, `preValidation`, `preHandler`, `preSerialization`, and `onSend`. ```js import Fastify from 'fastify'