diff --git a/README.md b/README.md index 8bd8a2f..26e46e6 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' @@ -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. @@ -97,6 +98,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. Valid values are `onRequest`, `preParsing`, `preValidation`, `preHandler`, `preSerialization`, and `onSend`. + +```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.d.ts b/index.d.ts index e0abaa0..03cceb1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -13,10 +13,28 @@ type FastifyCorsPlugin = FastifyPluginCallback< NonNullable | fastifyCors.FastifyCorsOptionsDelegate >; +type FastifyCorsHook = + | 'onRequest' + | 'preParsing' + | 'preValidation' + | 'preHandler' + | 'preSerialization' + | 'onSend' + 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/index.js b/index.js index b923caa..132a6f5 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,18 +20,50 @@ const defaultOptions = { strictPreflight: true } +const validHooks = [ + 'onRequest', + 'preParsing', + 'preValidation', + 'preHandler', + 'preSerialization', + 'onSend' +] + +const hookWithPayload = [ + 'preSerialization', + 'preParsing', + 'onSend' +] + +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) 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, next) } 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) - }) + validateHook(corsOptions.hook, 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 @@ -52,22 +85,44 @@ function fastifyCors (fastify, opts, next) { next() } -function handleCorsOptionsDelegator (optionsResolver, fastify) { - fastify.addHook('onRequest', function onRequestCors (req, reply, next) { - if (optionsResolver.length === 2) { - handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next) - return +function handleCorsOptionsDelegator (optionsResolver, fastify, opts, next) { + const hook = (opts && opts.hook) || defaultOptions.hook + validateHook(hook, next) + if (optionsResolver.length === 2) { + 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 { + if (hookWithPayload.indexOf(hook) !== -1) { // handle delegator based on Promise - 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) - return - } + 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')) + }) } - next(new Error('Invalid CORS origin option')) - }) + } } function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, reply, next) { @@ -76,15 +131,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/hooks.test.js b/test/hooks.test.js new file mode 100644 index 0000000..6495d73 --- /dev/null +++ b/test/hooks.test.js @@ -0,0 +1,706 @@ +'use strict' + +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) => { + t.plan(1) + + 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(10) + + const fastify = Fastify() + + fastify.register(cors) + + fastify.addHook('onResponse', (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, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'onRequest' + }) + + fastify.addHook('onResponse', (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, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preParsing' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preValidation' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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.length, 1) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preParsing' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preHandler' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'onSend' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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) + t.equal(request[kFastifyContext].preSerialization, null) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send('ok') + }) + + await fastify.ready() + + 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 preSerialization if hook option is set to preSerialization', async (t) => { + t.plan(10) + + const fastify = Fastify() + + fastify.register(cors, { + hook: 'preSerialization' + }) + + fastify.addHook('onResponse', (request, reply, done) => { + t.equal(request[kFastifyContext].onError, null) + 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.length, 1) + t.equal(request[kFastifyContext].preValidation, null) + done() + }) + + fastify.get('/', (req, reply) => { + reply.send({ nonString: true }) + }) + + await fastify.ready() + + 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 => { + 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 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) + }) +}) diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 051cab9..38e69cb 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -253,4 +253,60 @@ const delegate: FastifyPluginOptionsDelegate strictPreflight: false } } + +appHttp2.register(fastifyCors, { + hook: 'onRequest' +}) +appHttp2.register(fastifyCors, { + hook: 'preParsing' +}) +appHttp2.register(fastifyCors, { + hook: 'preValidation' +}) +appHttp2.register(fastifyCors, { + hook: 'preHandler' +}) +appHttp2.register(fastifyCors, { + hook: 'preSerialization' +}) +appHttp2.register(fastifyCors, { + hook: 'onSend' +}) + +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)