Skip to content

Commit

Permalink
feature: allow custom hook name
Browse files Browse the repository at this point in the history
  • Loading branch information
giulianok committed Nov 4, 2022
1 parent 06e16fc commit 10910d9
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 8 deletions.
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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).
Expand Down
20 changes: 13 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}

Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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) => {
Expand Down
97 changes: 97 additions & 0 deletions test/cors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 10910d9

Please sign in to comment.