Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fastify-cors

Package Overview
Dependencies
Maintainers
8
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify-cors - npm Package Compare versions

Comparing version 5.0.0 to 5.1.0

4

index.d.ts

@@ -47,2 +47,6 @@ /// <reference types="node" />

/**
* Pass the CORS preflight response to the route handler (default: false).
*/
preflightContinue?: boolean;
/**
* Provides a status code to use for successful OPTIONS requests,

@@ -49,0 +53,0 @@ * since some legacy browsers (IE11, various SmartTVs) choke on 204.

91

index.js

@@ -31,3 +31,4 @@ 'use strict'

hideOptionsRoute,
strictPreflight
strictPreflight,
preflightContinue
} = Object.assign({}, defaultOptions, opts)

@@ -37,7 +38,21 @@

fastify.decorateRequest('corsPreflightEnabled', undefined)
fastify.decorateRequest('corsPreflightEnabled', false)
fastify.addHook('onRequest', onRequest)
if (preflight === true) {
fastify.options('*', { schema: { hide: hideOptionsRoute } }, preflightHandler)
// The preflight reply must occur in the hook. This allows fastify-cors to reply to
// preflight requests BEFORE possible authentication plugins. If the preflight reply
// occurred in this handler, other plugins may deny the request since the browser will
// remove most headers (such as the Authentication header).
//
// This route simply enables fastify to accept preflight requests.
fastify.options('*', { schema: { hide: hideOptionsRoute } }, (req, reply) => {
if (!req.corsPreflightEnabled) {
// Do not handle preflight requests if the origin option disabled CORS
reply.callNotFound()
return
}
reply.send()
})
}

@@ -57,4 +72,2 @@

req.corsPreflightEnabled = resolvedOriginOption
// Disable CORS and preflight if false

@@ -65,14 +78,30 @@ if (resolvedOriginOption === false) {

reply.header('Access-Control-Allow-Origin',
getAccessControlAllowOriginHeader(req.headers.origin, resolvedOriginOption))
if (credentials) {
reply.header('Access-Control-Allow-Credentials', 'true')
// Falsy values are invalid
if (!resolvedOriginOption) {
return next(new Error('Invalid CORS origin option'))
}
if (exposedHeaders !== null) {
reply.header(
'Access-Control-Expose-Headers',
Array.isArray(exposedHeaders) ? exposedHeaders.join(', ') : exposedHeaders
)
addCorsHeaders(req, reply, resolvedOriginOption)
if (req.raw.method === 'OPTIONS' && preflight === true) {
// Strict mode enforces the required headers for preflight
if (strictPreflight === true && (!req.headers.origin || !req.headers['access-control-request-method'])) {
reply.status(400).type('text/plain').send('Invalid Preflight Request')
return
}
req.corsPreflightEnabled = true
addPreflightHeaders(req, reply)
if (!preflightContinue) {
// Do not call the hook callback and terminate the request
// Safari (and potentially other browsers) need content-length 0,
// for 204 or they just hang waiting for a body
reply
.code(optionsSuccessStatus)
.header('Content-Length', '0')
.send()
return
}
}

@@ -84,16 +113,19 @@

function preflightHandler (req, reply) {
// Do not handle preflight requests if the origin was not allowed
if (!req.corsPreflightEnabled) {
reply.code(404).type('text/plain').send('Not Found')
return
function addCorsHeaders (req, reply, originOption) {
reply.header('Access-Control-Allow-Origin',
getAccessControlAllowOriginHeader(req.headers.origin, originOption))
if (credentials) {
reply.header('Access-Control-Allow-Credentials', 'true')
}
// Strict mode enforces the required headers for preflight
if (strictPreflight === true && (!req.headers.origin || !req.headers['access-control-request-method'])) {
reply.status(400).type('text/plain').send('Invalid Preflight Request')
return
if (exposedHeaders !== null) {
reply.header(
'Access-Control-Expose-Headers',
Array.isArray(exposedHeaders) ? exposedHeaders.join(', ') : exposedHeaders
)
}
}
// Handle preflight headers
function addPreflightHeaders (req, reply) {
reply.header(

@@ -120,9 +152,2 @@ 'Access-Control-Allow-Methods',

}
// Safari (and potentially other browsers) need content-length 0,
// for 204 or they just hang waiting for a body
reply
.code(optionsSuccessStatus)
.header('Content-Length', '0')
.send()
}

@@ -141,3 +166,3 @@

function getAccessControlAllowOriginHeader (reqOrigin, originOption) {
if (!originOption || originOption === '*') {
if (originOption === '*') {
// allow any origin

@@ -144,0 +169,0 @@ return '*'

{
"name": "fastify-cors",
"version": "5.0.0",
"version": "5.1.0",
"description": "Fastify CORS",

@@ -42,3 +42,3 @@ "main": "index.js",

"tap": "^14.11.0",
"tsd": "^0.13.1",
"tsd": "^0.14.0",
"typescript": "^4.0.2"

@@ -45,0 +45,0 @@ },

@@ -35,3 +35,3 @@ # fastify-cors

- `Boolean` - set `origin` to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), or set it to `false` to disable CORS.
- `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed.
- `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed. The special `*` value (default) allows any origin.
- `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com".

@@ -56,2 +56,3 @@ - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com".

* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. In seconds. Set to an integer to pass the header, otherwise it is omitted.
* `preflightContinue`: Pass the CORS preflight response to the route handler (default: `false`).
* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`.

@@ -62,20 +63,2 @@ * `preflight`: if needed you can entirely disable preflight by passing `false` here (default: `true`).

### Preflight Requests
When preflight is enabled (`preflight` option is `true`), a `*` wildcard `OPTIONS` route is added to the fastify instance. The response behavior can be overridden for individual routes by adding `OPTIONS` routes to the fastify instance (`*` wildcard routes are always lowest priority).
This is an important difference between fastify-cors and the [express cors](https://github.com/expressjs/cors#configuration-options) middleware `preflightContinue` option.
```js
const fastify = require('fastify')()
// Fastify-cors handles CORS preflight OPTIONS requests
fastify.register(require('fastify-cors'))
// Except for OPTIONS /not-preflight
fastify.options('/not-preflight', (req, reply) => {
reply.send({hello: 'world'})
})
```
## Acknowledgements

@@ -82,0 +65,0 @@

@@ -173,2 +173,22 @@ 'use strict'

test('Dynamic origin resolution (invalid result)', t => {
t.plan(3)
const fastify = Fastify()
const origin = (header, cb) => {
t.strictEqual(header, 'example.com')
cb(null, undefined)
}
fastify.register(cors, { origin })
fastify.inject({
method: 'GET',
url: '/',
headers: { origin: 'example.com' }
}, (err, res) => {
t.error(err)
t.strictEqual(res.statusCode, 500)
})
})
test('Dynamic origin resolution (valid origin - promises)', t => {

@@ -286,6 +306,6 @@ t.plan(5)

t.strictEqual(res.statusCode, 404)
t.strictEqual(res.payload, 'Not Found')
t.strictEqual(res.payload, '{"message":"Route OPTIONS:/ not found","error":"Not Found","statusCode":404}')
t.deepEqual(res.headers, {
'content-length': '9',
'content-type': 'text/plain',
'content-length': '76',
'content-type': 'application/json; charset=utf-8',
connection: 'keep-alive',

@@ -313,2 +333,26 @@ vary: 'Origin'

test('Server error if origin option is falsy but not false', t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { origin: '' })
fastify.inject({
method: 'GET',
url: '/',
headers: { origin: 'example.com' }
}, (err, res) => {
t.error(err)
delete res.headers.date
t.strictEqual(res.statusCode, 500)
t.deepEqual(res.json(), { statusCode: 500, error: 'Internal Server Error', message: 'Invalid CORS origin option' })
t.deepEqual(res.headers, {
'content-length': '89',
'content-type': 'application/json; charset=utf-8',
connection: 'keep-alive',
vary: 'Origin'
})
})
})
test('Allow only request from a specific origin', t => {

@@ -315,0 +359,0 @@ t.plan(4)

@@ -15,2 +15,3 @@ import fastify from 'fastify'

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -28,2 +29,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -41,2 +43,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -54,2 +57,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -67,2 +71,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -80,2 +85,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -117,2 +123,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -130,2 +137,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -143,2 +151,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -156,2 +165,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -169,2 +179,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -182,2 +193,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -201,2 +213,3 @@ preflight: false,

maxAge: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,

@@ -203,0 +216,0 @@ preflight: false,

@@ -103,3 +103,7 @@ 'use strict'

method: 'OPTIONS',
url: '/'
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {

@@ -287,1 +291,57 @@ t.error(err)

})
test('Default empty 200 response with preflightContinue on OPTIONS routes', t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { preflightContinue: true })
fastify.inject({
method: 'OPTIONS',
url: '/doesnotexist',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
delete res.headers.date
t.strictEqual(res.statusCode, 200)
t.strictEqual(res.payload, '')
t.match(res.headers, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Origin, Access-Control-Request-Headers'
})
})
})
test('Can override preflight response with preflightContinue', t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { preflightContinue: true })
fastify.options('/', (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.strictEqual(res.statusCode, 200)
t.strictEqual(res.payload, 'ok')
t.match(res.headers, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Origin, Access-Control-Request-Headers'
})
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc