Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

View File

@@ -9,8 +9,5 @@ updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
open-pull-requests-limit: 10
ignore:
- dependency-name: tap
update-types: ["version-update:semver-major"]

View File

@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- master
- next
- 'v*'
paths-ignore:
@@ -15,9 +14,20 @@ on:
- 'docs/**'
- '*.md'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
permissions:
contents: write
pull-requests: write
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5
with:
lint: true
license-check: true
lint: true

View File

@@ -1,4 +0,0 @@
ts: false
jsx: false
coverage: true
flow: true

View File

@@ -1,6 +1,8 @@
MIT License
Copyright (c) 2018 Fastify
Copyright (c) 2018-present The Fastify team
The Fastify team members are listed at https://github.com/fastify/fastify#team.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,9 +1,8 @@
# @fastify/cors
![CI](https://github.com/fastify/fastify-cors/workflows/CI/badge.svg)
[![CI](https://github.com/fastify/fastify-cors/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-cors/actions/workflows/ci.yml)
[![NPM version](https://img.shields.io/npm/v/@fastify/cors.svg?style=flat)](https://www.npmjs.com/package/@fastify/cors)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)
`@fastify/cors` enables the use of [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) in a Fastify application.
@@ -15,25 +14,26 @@ npm i @fastify/cors
### Compatibility
| Plugin version | Fastify version |
| -------------- |---------------- |
| `^8.0.0` | `^4.0.0` |
| `^7.0.0` | `^3.0.0` |
| `^3.0.0` | `^2.0.0` |
| `^1.0.0` | `^1.0.0` |
| ---------------|-----------------|
| `^11.x` | `^5.x` |
| `^10.x` | `^5.x` |
| `^8.x` | `^4.x` |
| `^7.x` | `^3.x` |
| `>=3.x <7.x` | `^2.x` |
| `>=1.x <3.x` | `^1.x` |
Please note that if a Fastify version is out of support, then so are the corresponding version(s) of this plugin
Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
in the table above.
See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details.
## Usage
Require `@fastify/cors` and register it as any other plugin, it will add an `onRequest` 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 adds an `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'
const fastify = Fastify()
await fastify.register(cors, {
await fastify.register(cors, {
// put your options here
})
@@ -45,12 +45,12 @@ await fastify.listen({ port: 3000 })
```
You can use it as is without passing any option or you can configure it as explained below.
### Options
* `origin`: Configures the **Access-Control-Allow-Origin** CORS header. The value of origin could be of different types:
- `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. The special `*` value (default) allows any origin.
- `RegExp` - set `origin` to a regular expression pattern that will be used to test the request origin. If it is 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".
- `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".
- `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback as a second (which expects the signature `err [Error | null], origin`), where `origin` is a non-function value of the origin option. *Async-await* and promises are supported as well. The Fastify instance is bound to function call and you may access via `this`. For example:
* `origin`: Configures the **Access-Control-Allow-Origin** CORS header. The value of origin can be:
- `Boolean`: Set to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), or `false` to disable CORS.
- `String`: Set to a specific origin (e.g., `"http://example.com"`). The special `*` value (default) allows any origin.
- `RegExp`: Set to a regular expression pattern to test the request origin. If it matches, the request origin is reflected (e.g., `/example\.com$/` returns the origin only if it ends with `example.com`).
- `Array`: Set to an array of valid origins, each being a `String` or `RegExp` (e.g., `["http://example1.com", /\.example2\.com$/]`).
- `Function`: Set to a function with custom logic. The function takes the request origin as the first parameter and a callback as the second (signature `err [Error | null], origin`). *Async-await* and promises are supported. The Fastify instance is bound to the function call and can be accessed via `this`. For example:
```js
origin: (origin, cb) => {
const hostname = new URL(origin).hostname
@@ -63,18 +63,27 @@ You can use it as is without passing any option or you can configure it as expla
cb(new Error("Not allowed"), false)
}
```
* `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: `onRequest`)
* `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.
* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. In seconds. Set to an integer to pass the header, otherwise it is omitted.
* `cacheControl`: Configures the **Cache-Control** header for CORS preflight responses. Set to an integer to pass the header as `Cache-Control: max-age=${cacheControl}`, or set to a string to pass the header as `Cache-Control: ${cacheControl}` (fully define the header value), otherwise the header 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`.
* `preflight`: if needed you can entirely disable preflight by passing `false` here (default: `true`).
* `strictPreflight`: Enforces strict requirement of the CORS preflight request headers (**Access-Control-Request-Method** and **Origin**) as defined by the [W3C CORS specification](https://www.w3.org/TR/2020/SPSD-cors-20200602/#resource-preflight-requests) (the current [fetch living specification](https://fetch.spec.whatwg.org/) does not define server behavior for missing headers). Preflight requests without the required headers will result in 400 errors when set to `true` (default: `true`).
* `hideOptionsRoute`: hide options route from the documentation built using [@fastify/swagger](https://github.com/fastify/fastify-swagger) (default: `true`).
* `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (e.g., 'GET,HEAD,POST') or an array (e.g., `['GET', 'HEAD', 'POST']`). Default: [CORS-safelisted methods](https://fetch.spec.whatwg.org/#methods) `GET,HEAD,POST`.
* `hook`: See [Custom Fastify hook name](#custom-fastify-hook-name). Default: `onRequest`.
* `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (e.g., `'Content-Type,Authorization'`) or an array (e.g., `['Content-Type', 'Authorization']`). Defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header if not specified.
* `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (e.g., `'Content-Range,X-Content-Range'`) or an array (e.g., `['Content-Range', 'X-Content-Range']`). No custom headers are exposed if not specified.
* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header; otherwise, it is omitted.
* `maxAge`: Configures the **Access-Control-Max-Age** CORS header in seconds. Set to an integer to pass the header; otherwise, it is omitted.
* `cacheControl`: Configures the **Cache-Control** header for CORS preflight responses. Set to an integer to pass the header as `Cache-Control: max-age=${cacheControl}`, or set to a string to pass the header as `Cache-Control: ${cacheControl}`. Otherwise, the header is omitted.
* `preflightContinue`: Passes the CORS preflight response to the route handler. Default: `false`.
* `optionsSuccessStatus`: Provides a status code for successful `OPTIONS` requests, as some legacy browsers (IE11, various SmartTVs) choke on `204`.
* `preflight`: Disables preflight by passing `false`. Default: `true`.
* `strictPreflight`: Enforces strict requirements for the CORS preflight request headers (**Access-Control-Request-Method** and **Origin**) as defined by the [W3C CORS specification](https://www.w3.org/TR/2020/SPSD-cors-20200602/#resource-preflight-requests). Preflight requests without the required headers result in 400 errors when set to `true`. Default: `true`.
* `hideOptionsRoute`: Hides the options route from documentation built using [@fastify/swagger](https://github.com/fastify/fastify-swagger). Default: `true`.
* `logLevel`: Sets the Fastify log level **only** for the internal CORS pre-flight `OPTIONS *` route.
Pass `'silent'` to suppress these requests in your logs, or any valid Fastify
log level (`'trace'`, `'debug'`, `'info'`, `'warn'`, `'error'`, `'fatal'`).
Default: inherits Fastifys global log level.
#### :warning: DoS attacks
Using `RegExp` or a `function` for the `origin` parameter may enable Denial of Service attacks.
Craft with extreme care.
### Configuring CORS Asynchronously
@@ -107,16 +116,50 @@ fastify.register(async function (fastify) {
fastify.listen({ port: 3000 })
```
### Route-Level CORS Overrides
It is possible to override the CORS plugin options provided during registration on a per-route basis using the `config.cors` option.
```js
const fastify = require('fastify')()
fastify.register(require('@fastify/cors'), { origin: 'https://example.com' })
fastify.get('/cors-enabled', (_req, reply) => {
reply.send('CORS headers applied')
})
fastify.get('/cors-allow-all', {
config: {
cors: {
origin: '*', // Allow all origins for this route
},
},
}, (_req, reply) => {
reply.send('Custom CORS headers applied')
})
fastify.get('/cors-disabled', {
config: {
cors: false, // Disable CORS for this route
},
}, (_req, reply) => {
reply.send('No CORS headers')
})
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`.
By default, `@fastify/cors` adds an `onRequest` hook for validation and header injection. 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, {
await fastify.register(cors, {
hook: 'preHandler',
})
@@ -127,7 +170,7 @@ fastify.get('/', (req, reply) => {
await fastify.listen({ port: 3000 })
```
When configuring CORS asynchronously, an object with `delegator` key is expected:
To configure CORS asynchronously, provide an object with the `delegator` key:
```js
const fastify = require('fastify')()
@@ -159,7 +202,7 @@ fastify.register(async function (fastify) {
fastify.listen({ port: 3000 })
```
## Acknowledgements
## Acknowledgments
The code is a port for Fastify of [`expressjs/cors`](https://github.com/expressjs/cors).

View File

@@ -2,15 +2,15 @@
const fastify = require('fastify')()
fastify.register((instance, opts, next) => {
fastify.register((instance, _opts, next) => {
instance.register(require('./index'))
instance.get('/fastify', (req, reply) => reply.send('ok'))
instance.get('/fastify', (_req, reply) => reply.send('ok'))
next()
})
fastify.register((instance, opts, next) => {
fastify.register((instance, _opts, next) => {
instance.use(require('cors')())
instance.get('/express', (req, reply) => reply.send('ok'))
instance.get('/express', (_req, reply) => reply.send('ok'))
next()
})

6
backend/node_modules/@fastify/cors/eslint.config.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
'use strict'
module.exports = require('neostandard')({
ignores: require('neostandard').resolveIgnoresFromGitignore(),
ts: true
})

View File

@@ -8,7 +8,7 @@ const {
const defaultOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
methods: 'GET,HEAD,POST',
hook: 'onRequest',
preflightContinue: false,
optionsSuccessStatus: 204,
@@ -46,17 +46,18 @@ function fastifyCors (fastify, opts, next) {
fastify.decorateRequest('corsPreflightEnabled', false)
let hideOptionsRoute = true
let logLevel
if (typeof opts === 'function') {
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 = normalizeCorsOptions(opts)
validateHook(corsOptions.hook, next)
if (hookWithPayload.indexOf(corsOptions.hook) !== -1) {
fastify.addHook(corsOptions.hook, function handleCors (req, reply, payload, next) {
fastify.addHook(corsOptions.hook, function handleCors (req, reply, _payload, next) {
addCorsHeadersHandler(fastify, corsOptions, req, reply, next)
})
} else {
@@ -65,6 +66,8 @@ function fastifyCors (fastify, opts, next) {
})
}
}
if (opts.logLevel !== undefined) logLevel = opts.logLevel
if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute
// 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
@@ -72,7 +75,8 @@ function fastifyCors (fastify, opts, next) {
// remove most headers (such as the Authentication header).
//
// This route simply enables fastify to accept preflight requests.
fastify.options('*', { schema: { hide: hideOptionsRoute } }, (req, reply) => {
fastify.options('*', { schema: { hide: hideOptionsRoute }, logLevel }, (req, reply) => {
if (!req.corsPreflightEnabled) {
// Do not handle preflight requests if the origin option disabled CORS
reply.callNotFound()
@@ -86,11 +90,11 @@ function fastifyCors (fastify, opts, next) {
}
function handleCorsOptionsDelegator (optionsResolver, fastify, opts, next) {
const hook = (opts && opts.hook) || defaultOptions.hook
const hook = 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) {
fastify.addHook(hook, function handleCors (req, reply, _payload, next) {
handleCorsOptionsCallbackDelegator(optionsResolver, fastify, req, reply, next)
})
} else {
@@ -101,7 +105,7 @@ function handleCorsOptionsDelegator (optionsResolver, fastify, opts, next) {
} else {
if (hookWithPayload.indexOf(hook) !== -1) {
// handle delegator based on Promise
fastify.addHook(hook, function handleCors (req, reply, payload, next) {
fastify.addHook(hook, function handleCors (req, reply, _payload, next) {
const ret = optionsResolver(req)
if (ret && typeof ret.then === 'function') {
ret.then(options => addCorsHeadersHandler(fastify, normalizeCorsOptions(options, true), req, reply, next)).catch(next)
@@ -152,7 +156,9 @@ function normalizeCorsOptions (opts, dynamic) {
return corsOptions
}
function addCorsHeadersHandler (fastify, options, req, reply, next) {
function addCorsHeadersHandler (fastify, globalOptions, req, reply, next) {
const options = { ...globalOptions, ...req.routeOptions.config?.cors }
if ((typeof options.origin !== 'string' && options.origin !== false) || options.dynamic) {
// Always set Vary header for non-static origin option
// https://fetch.spec.whatwg.org/#cors-protocol-and-http-caches
@@ -171,6 +177,11 @@ function addCorsHeadersHandler (fastify, options, req, reply, next) {
return next()
}
// Allow routes to disable CORS individually
if (req.routeOptions.config?.cors === false) {
return next()
}
// Falsy values are invalid
if (!resolvedOriginOption) {
return next(new Error('Invalid CORS origin option'))
@@ -294,7 +305,7 @@ function isRequestOriginAllowed (reqOrigin, allowedOrigin) {
}
const _fastifyCors = fp(fastifyCors, {
fastify: '4.x',
fastify: '5.x',
name: '@fastify/cors'
})

View File

@@ -1,17 +1,16 @@
{
"name": "@fastify/cors",
"version": "9.0.1",
"version": "11.2.0",
"description": "Fastify CORS",
"main": "index.js",
"type": "commonjs",
"types": "types/index.d.ts",
"scripts": {
"coverage": "tap --cov --coverage-report=html test",
"lint": "standard",
"lint:fix": "standard --fix",
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "npm run test:unit && npm run test:typescript",
"test:typescript": "tsd",
"test:unit": "tap test/*.test.js"
"test:unit": "c8 --100 node --test"
},
"keywords": [
"fastify",
@@ -21,6 +20,25 @@
"control"
],
"author": "Tomas Della Vedova - @delvedor (http://delved.org)",
"contributors": [
{
"name": "Matteo Collina",
"email": "hello@matteocollina.com"
},
{
"name": "Manuel Spigolon",
"email": "behemoth89@gmail.com"
},
{
"name": "Cemre Mengu",
"email": "cemremengu@gmail.com"
},
{
"name": "Frazer Smith",
"email": "frazer.dev@icloud.com",
"url": "https://github.com/fdawgs"
}
],
"license": "MIT",
"repository": {
"type": "git",
@@ -30,30 +48,34 @@
"url": "https://github.com/fastify/fastify-cors/issues"
},
"homepage": "https://github.com/fastify/fastify-cors#readme",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"@types/node": "^20.1.0",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.0",
"@types/node": "^24.0.8",
"c8": "^10.1.2",
"cors": "^2.8.5",
"fastify": "^4.0.0-rc.2",
"standard": "^17.0.0",
"tap": "16.3.9",
"tsd": "^0.30.0",
"typescript": "^5.0.2"
"eslint": "^9.17.0",
"fastify": "^5.0.0",
"neostandard": "^0.12.0",
"tsd": "^0.33.0",
"typescript": "~5.9.2"
},
"dependencies": {
"fastify-plugin": "^4.0.0",
"mnemonist": "0.39.6"
"fastify-plugin": "^5.0.0",
"toad-cache": "^3.7.0"
},
"tsd": {
"directory": "test"
},
"publishConfig": {
"access": "public"
},
"pre-commit": [
"lint",
"test"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,23 @@
'use strict'
const { test } = require('tap')
const { test } = require('node:test')
const Fastify = require('fastify')
const kFastifyContext = require('fastify/lib/symbols').kRouteContext
const cors = require('..')
const { setTimeout: sleep } = require('node:timers/promises')
test('Should error on invalid hook option', async (t) => {
t.plan(1)
t.plan(3)
const fastify = Fastify()
t.rejects(fastify.register(cors, { hook: 'invalid' }), new TypeError('@fastify/cors: Invalid hook option provided.'))
await t.assert.rejects(
async () => fastify.register(cors, { hook: 'invalid' }),
(err) => {
t.assert.strictEqual(err.name, 'TypeError')
t.assert.strictEqual(err.message, '@fastify/cors: Invalid hook option provided.')
return true
}
)
})
test('Should set hook onRequest if hook option is not set', async (t) => {
@@ -19,18 +27,18 @@ test('Should set hook onRequest if hook option is not set', async (t) => {
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest.length, 1)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -41,9 +49,12 @@ test('Should set hook onRequest if hook option is not set', async (t) => {
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
})
@@ -57,18 +68,18 @@ test('Should set hook onRequest if hook option is set to onRequest', async (t) =
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest.length, 1)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -79,9 +90,12 @@ test('Should set hook onRequest if hook option is set to onRequest', async (t) =
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
})
@@ -95,18 +109,18 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t)
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing.length, 1)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -117,12 +131,15 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t)
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should set hook preValidation if hook option is set to preValidation', async (t) => {
@@ -134,18 +151,18 @@ test('Should set hook preValidation if hook option is set to preValidation', asy
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation.length, 1)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -156,12 +173,15 @@ test('Should set hook preValidation if hook option is set to preValidation', asy
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should set hook preParsing if hook option is set to preParsing', async (t) => {
@@ -173,18 +193,18 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t)
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing.length, 1)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -195,12 +215,15 @@ test('Should set hook preParsing if hook option is set to preParsing', async (t)
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should set hook preHandler if hook option is set to preHandler', async (t) => {
@@ -212,18 +235,18 @@ test('Should set hook preHandler if hook option is set to preHandler', async (t)
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler.length, 1)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -234,12 +257,15 @@ test('Should set hook preHandler if hook option is set to preHandler', async (t)
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should set hook onSend if hook option is set to onSend', async (t) => {
@@ -251,18 +277,18 @@ test('Should set hook onSend if hook option is set to onSend', async (t) => {
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend.length, 1)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization, null)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -273,12 +299,15 @@ test('Should set hook onSend if hook option is set to onSend', async (t) => {
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should set hook preSerialization if hook option is set to preSerialization', async (t) => {
@@ -290,18 +319,18 @@ test('Should set hook preSerialization if hook option is set to preSerialization
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)
fastify.addHook('onResponse', (request, _reply, done) => {
t.assert.strictEqual(request[kFastifyContext].onError, null)
t.assert.strictEqual(request[kFastifyContext].onRequest, null)
t.assert.strictEqual(request[kFastifyContext].onSend, null)
t.assert.strictEqual(request[kFastifyContext].preHandler, null)
t.assert.strictEqual(request[kFastifyContext].preParsing, null)
t.assert.strictEqual(request[kFastifyContext].preSerialization.length, 1)
t.assert.strictEqual(request[kFastifyContext].preValidation, null)
done()
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send({ nonString: true })
})
@@ -312,15 +341,18 @@ test('Should set hook preSerialization if hook option is set to preSerialization
url: '/'
})
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, '{"nonString":true}')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, '{"nonString":true}')
const actualHeader = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeader, {
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should support custom hook with dynamic config', t => {
test('Should support custom hook with dynamic config', async t => {
t.plan(16)
const configs = [{
@@ -341,83 +373,97 @@ test('Should support custom hook with dynamic config', t => {
const fastify = Fastify()
let requestId = 0
const configDelegation = function (req, cb) {
const configDelegation = async function (req) {
// request should have id
t.ok(req.id)
t.assert.ok(req.id)
// request should not have send
t.notOk(req.send)
t.assert.ifError(req.send)
const config = configs[requestId]
requestId++
if (config) {
cb(null, config)
return Promise.resolve(config)
} else {
cb(new Error('ouch'))
return Promise.reject(new Error('ouch'))
}
}
fastify.register(cors, {
await fastify.register(cors, {
hook: 'preHandler',
delegator: configDelegation
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
let res = await 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',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'foo, bar',
'content-length': '2',
vary: 'Origin'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
let actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'example.com',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'foo, bar',
'content-length': '2',
vary: 'Origin'
})
fastify.inject({
res = await 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',
'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',
vary: 'Origin'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
'access-control-allow-headers': res.headers['access-control-allow-headers'],
'access-control-max-age': res.headers['access-control-max-age'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'sample.com',
'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',
vary: 'Origin'
})
fastify.inject({
res = await fastify.inject({
method: 'GET',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 500)
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 500)
})
test('Should support custom hook with dynamic config (callback)', t => {
test('Should support custom hook with dynamic config (callback)', async t => {
t.plan(16)
const configs = [{
@@ -440,9 +486,9 @@ test('Should support custom hook with dynamic config (callback)', t => {
let requestId = 0
const configDelegation = function (req, cb) {
// request should have id
t.ok(req.id)
t.assert.ok(req.id)
// request should not have send
t.notOk(req.send)
t.assert.ifError(req.send)
const config = configs[requestId]
requestId++
if (config) {
@@ -456,7 +502,7 @@ test('Should support custom hook with dynamic config (callback)', t => {
delegator: configDelegation
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
@@ -464,11 +510,18 @@ test('Should support custom hook with dynamic config (callback)', t => {
method: 'GET',
url: '/'
}, (err, res) => {
t.error(err)
t.assert.ifError(err)
delete res.headers.date
t.equal(res.statusCode, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'example.com',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'foo, bar',
@@ -485,11 +538,21 @@ test('Should support custom hook with dynamic config (callback)', t => {
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.assert.ifError(err)
delete res.headers.date
t.equal(res.statusCode, 204)
t.equal(res.payload, '')
t.match(res.headers, {
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
'access-control-allow-headers': res.headers['access-control-allow-headers'],
'access-control-max-age': res.headers['access-control-max-age'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'sample.com',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'zoo, bar',
@@ -509,12 +572,13 @@ test('Should support custom hook with dynamic config (callback)', t => {
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 500)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 500)
})
await sleep()
})
test('Should support custom hook with dynamic config (Promise)', t => {
test('Should support custom hook with dynamic config (Promise)', async t => {
t.plan(16)
const configs = [{
@@ -535,11 +599,11 @@ test('Should support custom hook with dynamic config (Promise)', t => {
const fastify = Fastify()
let requestId = 0
const configDelegation = function (req) {
const configDelegation = async function (req) {
// request should have id
t.ok(req.id)
t.assert.ok(req.id)
// request should not have send
t.notOk(req.send)
t.assert.ifError(req.send)
const config = configs[requestId]
requestId++
if (config) {
@@ -549,70 +613,85 @@ test('Should support custom hook with dynamic config (Promise)', t => {
}
}
fastify.register(cors, {
await fastify.register(cors, {
hook: 'preParsing',
delegator: configDelegation
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
let res = await 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',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'foo, bar',
'content-length': '2',
vary: 'Origin'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
let actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'example.com',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'foo, bar',
'content-length': '2',
vary: 'Origin'
})
fastify.inject({
res = await 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',
'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',
vary: 'Origin'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-credentials': res.headers['access-control-allow-credentials'],
'access-control-expose-headers': res.headers['access-control-expose-headers'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
'access-control-allow-headers': res.headers['access-control-allow-headers'],
'access-control-max-age': res.headers['access-control-max-age'],
'content-length': res.headers['content-length'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': 'sample.com',
'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',
vary: 'Origin'
})
fastify.inject({
res = await fastify.inject({
method: 'GET',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 500)
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 500)
})
test('Should support custom hook with dynamic config (Promise), but should error /1', t => {
test('Should support custom hook with dynamic config (Promise), but should error /1', async t => {
t.plan(6)
const fastify = Fastify()
@@ -620,46 +699,47 @@ test('Should support custom hook with dynamic config (Promise), but should error
return false
}
fastify.register(cors, {
await fastify.register(cors, {
hook: 'preParsing',
delegator: configDelegation
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
let res = await 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'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 500)
t.assert.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Invalid CORS origin option"}')
const actualHeaders = {
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'content-length': '89'
})
fastify.inject({
res = await fastify.inject({
method: 'GET',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 500)
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 500)
})
test('Should support custom hook with dynamic config (Promise), but should error /2', t => {
test('Should support custom hook with dynamic config (Promise), but should error /2', async t => {
t.plan(6)
const fastify = Fastify()
@@ -667,40 +747,41 @@ test('Should support custom hook with dynamic config (Promise), but should error
return false
}
fastify.register(cors, {
await fastify.register(cors, {
delegator: configDelegation
})
fastify.get('/', (req, reply) => {
fastify.get('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
let res = await 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'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 500)
t.assert.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"Invalid CORS origin option"}')
const actualHeaders = {
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'content-length': '89'
})
fastify.inject({
res = await fastify.inject({
method: 'GET',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 500)
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 500)
})

View File

@@ -1,43 +1,48 @@
'use strict'
const { test } = require('tap')
const { test } = require('node:test')
const Fastify = require('fastify')
const cors = require('../')
test('Should reply to preflight requests', t => {
test('Should reply to preflight requests', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors)
await fastify.register(cors)
fastify.inject({
const res = await 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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Should add access-control-allow-headers to response if preflight req has access-control-request-headers', t => {
test('Should add access-control-allow-headers to response if preflight req has access-control-request-headers', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors)
await fastify.register(cors)
fastify.inject({
const res = await fastify.inject({
method: 'OPTIONS',
url: '/',
headers: {
@@ -46,390 +51,540 @@ test('Should add access-control-allow-headers to response if preflight req has a
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'access-control-allow-headers': 'x-requested-with',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
'access-control-allow-headers': res.headers['access-control-allow-headers'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
'access-control-allow-headers': 'x-requested-with',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Should reply to preflight requests with custom status code', t => {
test('Should reply to preflight requests with custom status code', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { optionsSuccessStatus: 200 })
await fastify.register(cors, { optionsSuccessStatus: 200 })
fastify.inject({
const res = await 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, 200)
t.equal(res.payload, '')
t.match(res.headers, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Should be able to override preflight response with a route', t => {
test('Should be able to override preflight response with a route', async t => {
t.plan(5)
const fastify = Fastify()
fastify.register(cors, { preflightContinue: true })
await fastify.register(cors, { preflightContinue: true })
fastify.options('/', (req, reply) => {
fastify.options('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
const res = await 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, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
// Only the base cors headers and no preflight headers
'access-control-allow-origin': '*'
})
t.notMatch(res.headers, { vary: 'Origin' })
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin']
}
t.assert.deepStrictEqual(actualHeaders, {
// Only the base cors headers and no preflight headers
'access-control-allow-origin': '*'
})
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should reply to all options requests', t => {
test('Should reply to all options requests', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors)
await fastify.register(cors)
fastify.inject({
const res = await fastify.inject({
method: 'OPTIONS',
url: '/hello',
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Should support a prefix for preflight requests', t => {
test('Should support a prefix for preflight requests', async t => {
t.plan(6)
const fastify = Fastify()
fastify.register((instance, opts, next) => {
await fastify.register((instance, _opts, next) => {
instance.register(cors)
next()
}, { prefix: '/subsystem' })
fastify.inject({
let res = await fastify.inject({
method: 'OPTIONS',
url: '/hello'
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 404)
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 404)
fastify.inject({
res = await fastify.inject({
method: 'OPTIONS',
url: '/subsystem/hello',
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('hide options route by default', t => {
test('hide options route by default', async t => {
t.plan(2)
const fastify = Fastify()
fastify.addHook('onRoute', (route) => {
if (route.method === 'OPTIONS' && route.url === '*') {
t.equal(route.schema.hide, true)
t.assert.strictEqual(route.schema.hide, true)
}
})
fastify.register(cors)
await fastify.register(cors)
fastify.ready(err => {
t.error(err)
})
const ready = await fastify.ready()
t.assert.ok(ready)
})
test('show options route', t => {
test('show options route', async t => {
t.plan(2)
const fastify = Fastify()
fastify.addHook('onRoute', (route) => {
if (route.method === 'OPTIONS' && route.url === '*') {
t.equal(route.schema.hide, false)
t.assert.strictEqual(route.schema.hide, false)
}
})
fastify.register(cors, { hideOptionsRoute: false })
await fastify.register(cors, { hideOptionsRoute: false })
fastify.ready(err => {
t.error(err)
})
const ready = await fastify.ready()
t.assert.ok(ready)
})
test('Allow only request from with specific methods', t => {
test('Allow only request from with specific methods', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { methods: ['GET', 'POST'] })
await fastify.register(cors, { methods: ['GET', 'POST'] })
fastify.inject({
const res = await 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.match(res.headers, {
'access-control-allow-methods': 'GET, POST'
})
t.notMatch(res.headers, { vary: 'Origin' })
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
const actualHeaders = {
'access-control-allow-methods': res.headers['access-control-allow-methods']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-methods': 'GET, POST'
})
t.assert.notStrictEqual(res.headers.vary, 'Origin')
})
test('Should reply with 400 error to OPTIONS requests missing origin header when default strictPreflight', t => {
test('Should reply with 400 error to OPTIONS requests missing origin header when default strictPreflight', async t => {
t.plan(3)
const fastify = Fastify()
fastify.register(cors)
await fastify.register(cors)
fastify.inject({
const res = await fastify.inject({
method: 'OPTIONS',
url: '/',
headers: {
'access-control-request-method': 'GET'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.equal(res.payload, 'Invalid Preflight Request')
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 400)
t.assert.strictEqual(res.payload, 'Invalid Preflight Request')
})
test('Should reply with 400 to OPTIONS requests when missing Access-Control-Request-Method header when default strictPreflight', t => {
test('Should reply with 400 to OPTIONS requests when missing Access-Control-Request-Method header when default strictPreflight', async t => {
t.plan(3)
const fastify = Fastify()
fastify.register(cors, {
await fastify.register(cors, {
strictPreflight: true
})
fastify.inject({
const res = await fastify.inject({
method: 'OPTIONS',
url: '/',
headers: {
origin: 'example.com'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.equal(res.payload, 'Invalid Preflight Request')
})
t.assert.ok(res)
t.assert.strictEqual(res.statusCode, 400)
t.assert.strictEqual(res.payload, 'Invalid Preflight Request')
})
test('Should reply to all preflight requests when strictPreflight is disabled', t => {
test('Should reply to all preflight requests when strictPreflight is disabled', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { strictPreflight: false })
await fastify.register(cors, { strictPreflight: false })
fastify.inject({
const res = await fastify.inject({
method: 'OPTIONS',
url: '/'
// No access-control-request-method or origin headers
}, (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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Default empty 200 response with preflightContinue on OPTIONS routes', t => {
test('Default empty 200 response with preflightContinue on OPTIONS routes', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { preflightContinue: true })
await fastify.register(cors, { preflightContinue: true })
fastify.inject({
const res = await 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.equal(res.statusCode, 200)
t.equal(res.payload, '')
t.match(res.headers, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, '')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers'
})
})
test('Can override preflight response with preflightContinue', t => {
test('Can override preflight response with preflightContinue', async t => {
t.plan(4)
const fastify = Fastify()
fastify.register(cors, { preflightContinue: true })
await fastify.register(cors, { preflightContinue: true })
fastify.options('/', (req, reply) => {
fastify.options('/', (_req, reply) => {
reply.send('ok')
})
fastify.inject({
const res = await 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, 200)
t.equal(res.payload, 'ok')
t.match(res.headers, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'ok')
const actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers'
})
})
test('Should support ongoing prefix ', t => {
test('Should support ongoing prefix ', async t => {
t.plan(12)
const fastify = Fastify()
fastify.register(async (instance) => {
await fastify.register(async (instance) => {
instance.register(cors)
}, { prefix: '/prefix' })
// support prefixed route
fastify.inject({
let res = await fastify.inject({
method: 'OPTIONS',
url: '/prefix',
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
let actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
// support prefixed route without / continue
fastify.inject({
res = await fastify.inject({
method: 'OPTIONS',
url: '/prefixfoo',
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
// support prefixed route with / continue
fastify.inject({
res = await fastify.inject({
method: 'OPTIONS',
url: '/prefix/foo',
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': '*',
'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
t.assert.ok(res)
delete res.headers.date
t.assert.strictEqual(res.statusCode, 204)
t.assert.strictEqual(res.payload, '')
actualHeaders = {
'access-control-allow-origin': res.headers['access-control-allow-origin'],
'access-control-allow-methods': res.headers['access-control-allow-methods'],
vary: res.headers.vary,
'content-length': res.headers['content-length']
}
t.assert.deepStrictEqual(actualHeaders, {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,HEAD,POST',
vary: 'Access-Control-Request-Headers',
'content-length': '0'
})
})
test('Silences preflight logs when logLevel is "silent"', async t => {
const logs = []
const fastify = Fastify({
logger: {
level: 'info',
stream: {
write (line) {
try {
logs.push(JSON.parse(line))
} catch {
}
}
}
}
})
await fastify.register(cors, { logLevel: 'silent' })
fastify.get('/', async () => ({ ok: true }))
await fastify.ready()
t.assert.ok(fastify)
await fastify.inject({
method: 'OPTIONS',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'https://example.com'
}
})
await fastify.inject({ method: 'GET', url: '/' })
const hasOptionsLog = logs.some(l => l.req && l.req.method === 'OPTIONS')
const hasGetLog = logs.some(l => l.req && l.req.method === 'GET')
t.assert.strictEqual(hasOptionsLog, false)
t.assert.strictEqual(hasGetLog, true)
await fastify.close()
})
test('delegator + logLevel:"silent" → OPTIONS logs are suppressed', async t => {
t.plan(3)
const logs = []
const app = Fastify({
logger: {
level: 'info',
stream: { write: l => { try { logs.push(JSON.parse(l)) } catch {} } }
}
})
await app.register(cors, {
delegator: () => ({ origin: '*' }),
logLevel: 'silent'
})
app.get('/', () => ({ ok: true }))
await app.ready()
t.assert.ok(app)
await app.inject({
method: 'OPTIONS',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'https://example.com'
}
})
await app.inject({ method: 'GET', url: '/' })
const hasOptionsLog = logs.some(l => l.req?.method === 'OPTIONS')
const hasGetLog = logs.some(l => l.req?.method === 'GET')
t.assert.strictEqual(hasOptionsLog, false)
t.assert.strictEqual(hasGetLog, true)
await app.close()
})
test('delegator + hideOptionsRoute:false → OPTIONS route is visible', async t => {
t.plan(2)
const app = Fastify()
app.addHook('onRoute', route => {
if (route.method === 'OPTIONS' && route.url === '*') {
t.assert.strictEqual(route.schema.hide, false)
}
})
await app.register(cors, {
delegator: () => ({ origin: '*' }),
hideOptionsRoute: false
})
await app.ready()
t.assert.ok(app)
await app.close()
})

View File

@@ -1,24 +1,24 @@
'use strict'
const test = require('tap').test
const createAddFieldnameToVary = require('../vary').createAddFieldnameToVary
const parse = require('../vary').parse
const { test } = require('node:test')
const { createAddFieldnameToVary } = require('../vary')
const { parse } = require('../vary')
test('Should set * even if we set a specific field', t => {
test('Should set * even if we set a specific field', async t => {
t.plan(1)
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return '*'
},
header (name, value) {
header () {
t.fail('Should not be here')
}
}
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('Should set * even if we set a specific field', t => {
@@ -26,12 +26,12 @@ test('Should set * even if we set a specific field', t => {
const addWildcardToVary = createAddFieldnameToVary('*')
const replyMock = {
getHeader (name) {
getHeader () {
return 'Origin'
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, '*')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, '*')
}
}
@@ -43,17 +43,17 @@ test('Should set * when field contains a *', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return ['Origin', '*', 'Access-Control-Request-Headers']
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, '*')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, '*')
}
}
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('Should concat vary values', t => {
@@ -61,17 +61,17 @@ test('Should concat vary values', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return 'Access-Control-Request-Headers'
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, 'Access-Control-Request-Headers, Origin')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, 'Access-Control-Request-Headers, Origin')
}
}
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('Should concat vary values ignoring consecutive commas', t => {
@@ -79,17 +79,17 @@ test('Should concat vary values ignoring consecutive commas', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return ' Access-Control-Request-Headers,Access-Control-Request-Method'
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, ' Access-Control-Request-Headers,Access-Control-Request-Method, Origin')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, ' Access-Control-Request-Headers,Access-Control-Request-Method, Origin')
}
}
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('Should concat vary values ignoring whitespace', t => {
@@ -97,17 +97,17 @@ test('Should concat vary values ignoring whitespace', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return ' Access-Control-Request-Headers ,Access-Control-Request-Method'
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, ' Access-Control-Request-Headers ,Access-Control-Request-Method, Origin')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, ' Access-Control-Request-Headers ,Access-Control-Request-Method, Origin')
}
}
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('Should set the field as value for vary if no vary is defined', t => {
@@ -115,12 +115,12 @@ test('Should set the field as value for vary if no vary is defined', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return undefined
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, 'Origin')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, 'Origin')
}
}
@@ -132,12 +132,12 @@ test('Should set * as value for vary if vary contains *', t => {
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return 'Accept,*'
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, '*')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, '*')
}
}
@@ -149,12 +149,12 @@ test('Should set Accept-Encoding as value for vary if vary is empty string', t =
const addAcceptEncodingToVary = createAddFieldnameToVary('Accept-Encoding')
const replyMock = {
getHeader (name) {
getHeader () {
return ''
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, 'Accept-Encoding')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, 'Accept-Encoding')
}
}
@@ -167,12 +167,12 @@ test('Should have no issues with values containing dashes', t => {
const addXFooToVary = createAddFieldnameToVary('X-Foo')
const replyMock = {
value: 'Accept-Encoding',
getHeader (name) {
getHeader () {
return this.value
},
header (name, value) {
t.same(name, 'Vary')
t.same(value, 'Accept-Encoding, X-Foo')
t.assert.deepStrictEqual(name, 'Vary')
t.assert.deepStrictEqual(value, 'Accept-Encoding, X-Foo')
this.value = value
}
}
@@ -186,44 +186,52 @@ test('Should ignore the header as value for vary if it is already in vary', t =>
const addOriginToVary = createAddFieldnameToVary('Origin')
const replyMock = {
getHeader (name) {
getHeader () {
return 'Origin'
},
header (name, value) {
header () {
t.fail('Should not be here')
}
}
addOriginToVary(replyMock)
addOriginToVary(replyMock)
t.pass()
t.assert.ok(true) // equalivant to tap t.pass()
})
test('parse', t => {
t.plan(18)
t.same(parse(''), [])
t.same(parse('a'), ['a'])
t.same(parse('a,b'), ['a', 'b'])
t.same(parse(' a,b'), ['a', 'b'])
t.same(parse('a,b '), ['a', 'b'])
t.same(parse('a,b,c'), ['a', 'b', 'c'])
t.same(parse('A,b,c'), ['a', 'b', 'c'])
t.same(parse('a,b,c,'), ['a', 'b', 'c'])
t.same(parse('a,b,c, '), ['a', 'b', 'c'])
t.same(parse(',a,b,c'), ['a', 'b', 'c'])
t.same(parse(' ,a,b,c'), ['a', 'b', 'c'])
t.same(parse('a,,b,c'), ['a', 'b', 'c'])
t.same(parse('a,,,b,,c'), ['a', 'b', 'c'])
t.same(parse('a, b,c'), ['a', 'b', 'c'])
t.same(parse('a, b,c'), ['a', 'b', 'c'])
t.same(parse('a, , b,c'), ['a', 'b', 'c'])
t.same(parse('a, , b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse(''), [])
t.assert.deepStrictEqual(parse('a'), ['a'])
t.assert.deepStrictEqual(parse('a,b'), ['a', 'b'])
t.assert.deepStrictEqual(parse(' a,b'), ['a', 'b'])
t.assert.deepStrictEqual(parse('a,b '), ['a', 'b'])
t.assert.deepStrictEqual(parse('a,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('A,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a,b,c,'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a,b,c, '), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse(',a,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse(' ,a,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a,,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a,,,b,,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a, b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a, b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a, , b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('a, , b,c'), ['a', 'b', 'c'])
// one for the cache
t.same(parse('A,b,c'), ['a', 'b', 'c'])
t.assert.deepStrictEqual(parse('A,b,c'), ['a', 'b', 'c'])
})
test('createAddFieldnameToVary', t => {
t.plan(2)
t.same(typeof createAddFieldnameToVary('valid-header'), 'function')
t.throws(() => createAddFieldnameToVary('invalid:header'), TypeError, 'Field contains invalid characters.')
test('createAddFieldnameToVary', async t => {
t.plan(4)
t.assert.strictEqual(typeof createAddFieldnameToVary('valid-header'), 'function')
await t.assert.rejects(
async () => createAddFieldnameToVary('invalid:header'),
(err) => {
t.assert.strictEqual(err.name, 'TypeError')
t.assert.strictEqual(err.message, 'Fieldname contains invalid characters.')
return true
}
)
})

View File

@@ -1,17 +1,17 @@
/// <reference types="node" />
import { FastifyInstance, FastifyPluginCallback, FastifyRequest } from 'fastify';
import { FastifyInstance, FastifyPluginCallback, FastifyRequest, LogLevel } from 'fastify'
type OriginCallback = (err: Error | null, origin: ValueOrArray<OriginType>) => void;
type OriginType = string | boolean | RegExp;
type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;
type OriginCallback = (err: Error | null, origin: ValueOrArray<OriginType>) => void
type OriginType = string | boolean | RegExp
type ValueOrArray<T> = T | ArrayOfValueOrArray<T>
interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {
}
type FastifyCorsPlugin = FastifyPluginCallback<
NonNullable<fastifyCors.FastifyCorsOptions> | fastifyCors.FastifyCorsOptionsDelegate
>;
>
type FastifyCorsHook =
| 'onRequest'
@@ -22,7 +22,8 @@ type FastifyCorsHook =
| 'onSend'
declare namespace fastifyCors {
export type OriginFunction = (origin: string | undefined, callback: OriginCallback) => void;
export type OriginFunction = (origin: string | undefined, callback: OriginCallback) => void
export type AsyncOriginFunction = (origin: string | undefined) => Promise<ValueOrArray<OriginType>>
export interface FastifyCorsOptions {
/**
@@ -38,7 +39,7 @@ declare namespace fastifyCors {
/**
* Configures the Access-Control-Allow-Origin CORS header.
*/
origin?: ValueOrArray<OriginType> | fastifyCors.OriginFunction;
origin?: ValueOrArray<OriginType> | fastifyCors.AsyncOriginFunction | fastifyCors.OriginFunction;
/**
* Configures the Access-Control-Allow-Credentials CORS header.
* Set to true to pass the header, otherwise it is omitted.
@@ -86,7 +87,7 @@ declare namespace fastifyCors {
*/
optionsSuccessStatus?: number;
/**
* Pass the CORS preflight response to the route handler (default: false).
* Pass the CORS preflight response to the route handler (default: true).
*/
preflight?: boolean;
/**
@@ -98,19 +99,28 @@ declare namespace fastifyCors {
* Hide options route from the documentation built using fastify-swagger (default: true).
*/
hideOptionsRoute?: boolean;
/**
* Sets the Fastify log level specifically for the internal OPTIONS route
* used to handle CORS preflight requests. For example, setting this to `'silent'`
* will prevent these requests from being logged.
* Useful for reducing noise in application logs.
* Default: inherits Fastify's global log level.
*/
logLevel?: LogLevel;
}
export interface FastifyCorsOptionsDelegateCallback { (req: FastifyRequest, cb: (error: Error | null, corsOptions?: FastifyCorsOptions) => void): void }
export interface FastifyCorsOptionsDelegatePromise { (req: FastifyRequest): Promise<FastifyCorsOptions> }
export interface FastifyCorsOptionsDelegatePromise { (req: FastifyRequest): Promise<FastifyCorsOptions> }
export type FastifyCorsOptionsDelegate = FastifyCorsOptionsDelegateCallback | FastifyCorsOptionsDelegatePromise
export type FastifyPluginOptionsDelegate<T = FastifyCorsOptionsDelegate> = (instance: FastifyInstance) => T;
export type FastifyPluginOptionsDelegate<T = FastifyCorsOptionsDelegate> = (instance: FastifyInstance) => T
export const fastifyCors: FastifyCorsPlugin
export { fastifyCors as default };
export { fastifyCors as default }
}
declare function fastifyCors(
declare function fastifyCors (
...params: Parameters<FastifyCorsPlugin>
): ReturnType<FastifyCorsPlugin>;
): ReturnType<FastifyCorsPlugin>
export = fastifyCors;
export = fastifyCors

View File

@@ -1,6 +1,7 @@
import fastify, { FastifyRequest } from 'fastify'
import { expectType } from 'tsd'
import fastifyCors, {
AsyncOriginFunction,
FastifyCorsOptions,
FastifyCorsOptionsDelegate,
FastifyCorsOptionsDelegatePromise,
@@ -117,16 +118,36 @@ app.register(fastifyCors, {
strictPreflight: false
})
const asyncCorsDelegate: OriginFunction = async (origin) => {
if (origin === undefined || /localhost/.test(origin)) {
return true
}
return false
}
app.register(fastifyCors, {
origin: (origin, cb) => cb(null, true)
origin: asyncCorsDelegate,
allowedHeaders: ['authorization', 'content-type'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
optionsSuccessStatus: 200,
preflight: false,
strictPreflight: false
})
app.register(fastifyCors, {
origin: (origin, cb) => cb(null, '*')
origin: (_origin, cb) => cb(null, true)
})
app.register(fastifyCors, {
origin: (origin, cb) => cb(null, /\*/)
origin: (_origin, cb) => cb(null, '*')
})
app.register(fastifyCors, {
origin: (_origin, cb) => cb(null, /\*/)
})
const appHttp2 = fastify({ http2: true })
@@ -144,7 +165,8 @@ appHttp2.register(fastifyCors, {
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
strictPreflight: false
strictPreflight: false,
logLevel: 'silent'
})
appHttp2.register(fastifyCors, {
@@ -237,7 +259,7 @@ appHttp2.register(fastifyCors, {
strictPreflight: false
})
appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegate => (req, cb) => {
appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegate => (_req, cb) => {
cb(null, {
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
@@ -253,7 +275,7 @@ appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegate => (req, cb) => {
})
})
appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegatePromise => (req) => {
appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegatePromise => () => {
return Promise.resolve({
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
@@ -269,7 +291,7 @@ appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegatePromise => (req) =>
})
})
const delegate: FastifyPluginOptionsDelegate<FastifyCorsOptionsDelegatePromise> = () => async (req) => {
const delegate: FastifyPluginOptionsDelegate<FastifyCorsOptionsDelegatePromise> = () => async () => {
return {
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
@@ -328,7 +350,7 @@ appHttp2.register(fastifyCors, {
appHttp2.register(fastifyCors, {
hook: 'preParsing',
delegator: async (req: FastifyRequest): Promise<FastifyCorsOptions> => {
delegator: async (_req: FastifyRequest): Promise<FastifyCorsOptions> => {
return {
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
@@ -349,7 +371,18 @@ appHttp2.register(fastifyCors, delegate)
appHttp2.register(fastifyCors, {
hook: 'preParsing',
origin: function (origin) {
expectType<string|undefined>(origin)
origin: function (origin, cb) {
expectType<string | undefined>(origin)
cb(null, false)
},
})
const asyncOriginFn: AsyncOriginFunction = async function (origin): Promise<boolean> {
expectType<string | undefined>(origin)
return false
}
appHttp2.register(fastifyCors, {
hook: 'preParsing',
origin: asyncOriginFn,
})

View File

@@ -1,6 +1,6 @@
'use strict'
const LRUCache = require('mnemonist/lru-cache')
const { FifoMap: FifoCache } = require('toad-cache')
/**
* Field Value Components
@@ -42,7 +42,7 @@ function parse (header) {
let char
// tokenize the header
for (i = 0; i < il; ++i) {
for (i; i < il; ++i) {
char = header[i]
// when we have whitespace set the pos to the next position
if (char === ' ') {
@@ -67,7 +67,7 @@ function parse (header) {
}
function createAddFieldnameToVary (fieldname) {
const headerCache = new LRUCache(1000)
const headerCache = new FifoCache(1000)
validateFieldname(fieldname)
@@ -92,7 +92,7 @@ function createAddFieldnameToVary (fieldname) {
header = header.join(', ')
}
if (!headerCache.has(header)) {
if (headerCache.get(header) === undefined) {
const vals = parse(header)
if (vals.indexOf('*') !== -1) {