Skip to content

Commit c16aeae

Browse files
committed
Merge branch 'main' of github.com:fastify/fastify-cors
Signed-off-by: Matteo Collina <[email protected]>
2 parents 9a4888f + dad4009 commit c16aeae

File tree

8 files changed

+135
-11
lines changed

8 files changed

+135
-11
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ on:
1414
- 'docs/**'
1515
- '*.md'
1616

17+
permissions:
18+
contents: read
19+
1720
jobs:
1821
test:
1922
permissions:

LICENSE

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
MIT License
22

3-
Copyright (c) 2018 Fastify
3+
Copyright (c) 2018-present The Fastify team
4+
5+
The Fastify team members are listed at https://github.com/fastify/fastify#team.
46

57
Permission is hereby granted, free of charge, to any person obtaining a copy
68
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ npm i @fastify/cors
1919
| `^10.x` | `^5.x` |
2020
| `^8.x` | `^4.x` |
2121
| `^7.x` | `^3.x` |
22-
| `^3.x` | `^2.x` |
23-
| `^1.x` | `^1.x` |
24-
22+
| `>=3.x <7.x` | `^2.x` |
23+
| `>=1.x <3.x` | `^1.x` |
2524

2625
Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
2726
in the table above.
@@ -76,6 +75,10 @@ You can use it as is without passing any option or you can configure it as expla
7675
* `preflight`: Disables preflight by passing `false`. Default: `true`.
7776
* `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`.
7877
* `hideOptionsRoute`: Hides the options route from documentation built using [@fastify/swagger](https://github.com/fastify/fastify-swagger). Default: `true`.
78+
* `logLevel`: Sets the Fastify log level **only** for the internal CORS pre-flight `OPTIONS *` route.
79+
Pass `'silent'` to suppress these requests in your logs, or any valid Fastify
80+
log level (`'trace'`, `'debug'`, `'info'`, `'warn'`, `'error'`, `'fatal'`).
81+
Default: inherits Fastify’s global log level.
7982

8083
#### :warning: DoS attacks
8184

index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ function fastifyCors (fastify, opts, next) {
4646
fastify.decorateRequest('corsPreflightEnabled', false)
4747

4848
let hideOptionsRoute = true
49+
let logLevel
50+
4951
if (typeof opts === 'function') {
5052
handleCorsOptionsDelegator(opts, fastify, { hook: defaultOptions.hook }, next)
5153
} else if (opts.delegator) {
5254
const { delegator, ...options } = opts
5355
handleCorsOptionsDelegator(delegator, fastify, options, next)
5456
} else {
55-
if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute
5657
const corsOptions = normalizeCorsOptions(opts)
5758
validateHook(corsOptions.hook, next)
5859
if (hookWithPayload.indexOf(corsOptions.hook) !== -1) {
@@ -65,14 +66,17 @@ function fastifyCors (fastify, opts, next) {
6566
})
6667
}
6768
}
69+
if (opts.logLevel !== undefined) logLevel = opts.logLevel
70+
if (opts.hideOptionsRoute !== undefined) hideOptionsRoute = opts.hideOptionsRoute
6871

6972
// The preflight reply must occur in the hook. This allows fastify-cors to reply to
7073
// preflight requests BEFORE possible authentication plugins. If the preflight reply
7174
// occurred in this handler, other plugins may deny the request since the browser will
7275
// remove most headers (such as the Authentication header).
7376
//
7477
// This route simply enables fastify to accept preflight requests.
75-
fastify.options('*', { schema: { hide: hideOptionsRoute } }, (req, reply) => {
78+
79+
fastify.options('*', { schema: { hide: hideOptionsRoute }, logLevel }, (req, reply) => {
7680
if (!req.corsPreflightEnabled) {
7781
// Do not handle preflight requests if the origin option disabled CORS
7882
reply.callNotFound()

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@
6060
],
6161
"devDependencies": {
6262
"@fastify/pre-commit": "^2.1.0",
63-
"@types/node": "^22.0.0",
63+
"@types/node": "^24.0.8",
6464
"c8": "^10.1.2",
6565
"cors": "^2.8.5",
6666
"eslint": "^9.17.0",
6767
"fastify": "^5.0.0",
6868
"neostandard": "^0.12.0",
69-
"tsd": "^0.31.1",
69+
"tsd": "^0.32.0",
7070
"typescript": "~5.8.2"
7171
},
7272
"dependencies": {

test/preflight.test.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,105 @@ test('Should support ongoing prefix ', async t => {
486486
'content-length': '0'
487487
})
488488
})
489+
490+
test('Silences preflight logs when logLevel is "silent"', async t => {
491+
const logs = []
492+
const fastify = Fastify({
493+
logger: {
494+
level: 'info',
495+
stream: {
496+
write (line) {
497+
try {
498+
logs.push(JSON.parse(line))
499+
} catch {
500+
}
501+
}
502+
}
503+
}
504+
})
505+
506+
await fastify.register(cors, { logLevel: 'silent' })
507+
508+
fastify.get('/', async () => ({ ok: true }))
509+
510+
await fastify.ready()
511+
t.assert.ok(fastify)
512+
513+
await fastify.inject({
514+
method: 'OPTIONS',
515+
url: '/',
516+
headers: {
517+
'access-control-request-method': 'GET',
518+
origin: 'https://example.com'
519+
}
520+
})
521+
522+
await fastify.inject({ method: 'GET', url: '/' })
523+
524+
const hasOptionsLog = logs.some(l => l.req && l.req.method === 'OPTIONS')
525+
const hasGetLog = logs.some(l => l.req && l.req.method === 'GET')
526+
527+
t.assert.strictEqual(hasOptionsLog, false)
528+
t.assert.strictEqual(hasGetLog, true)
529+
530+
await fastify.close()
531+
})
532+
test('delegator + logLevel:"silent" → OPTIONS logs are suppressed', async t => {
533+
t.plan(3)
534+
535+
const logs = []
536+
const app = Fastify({
537+
logger: {
538+
level: 'info',
539+
stream: { write: l => { try { logs.push(JSON.parse(l)) } catch {} } }
540+
}
541+
})
542+
543+
await app.register(cors, {
544+
delegator: () => ({ origin: '*' }),
545+
logLevel: 'silent'
546+
})
547+
548+
app.get('/', () => ({ ok: true }))
549+
await app.ready()
550+
t.assert.ok(app)
551+
552+
await app.inject({
553+
method: 'OPTIONS',
554+
url: '/',
555+
headers: {
556+
'access-control-request-method': 'GET',
557+
origin: 'https://example.com'
558+
}
559+
})
560+
561+
await app.inject({ method: 'GET', url: '/' })
562+
563+
const hasOptionsLog = logs.some(l => l.req?.method === 'OPTIONS')
564+
const hasGetLog = logs.some(l => l.req?.method === 'GET')
565+
566+
t.assert.strictEqual(hasOptionsLog, false)
567+
t.assert.strictEqual(hasGetLog, true)
568+
569+
await app.close()
570+
})
571+
test('delegator + hideOptionsRoute:false → OPTIONS route is visible', async t => {
572+
t.plan(2)
573+
574+
const app = Fastify()
575+
576+
app.addHook('onRoute', route => {
577+
if (route.method === 'OPTIONS' && route.url === '*') {
578+
t.assert.strictEqual(route.schema.hide, false)
579+
}
580+
})
581+
582+
await app.register(cors, {
583+
delegator: () => ({ origin: '*' }),
584+
hideOptionsRoute: false
585+
})
586+
587+
await app.ready()
588+
t.assert.ok(app)
589+
await app.close()
590+
})

types/index.d.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="node" />
22

3-
import { FastifyInstance, FastifyPluginCallback, FastifyRequest } from 'fastify'
3+
import { FastifyInstance, FastifyPluginCallback, FastifyRequest, LogLevel } from 'fastify'
44

55
type OriginCallback = (err: Error | null, origin: ValueOrArray<OriginType>) => void
66
type OriginType = string | boolean | RegExp
@@ -87,7 +87,7 @@ declare namespace fastifyCors {
8787
*/
8888
optionsSuccessStatus?: number;
8989
/**
90-
* Pass the CORS preflight response to the route handler (default: false).
90+
* Pass the CORS preflight response to the route handler (default: true).
9191
*/
9292
preflight?: boolean;
9393
/**
@@ -99,6 +99,15 @@ declare namespace fastifyCors {
9999
* Hide options route from the documentation built using fastify-swagger (default: true).
100100
*/
101101
hideOptionsRoute?: boolean;
102+
103+
/**
104+
* Sets the Fastify log level specifically for the internal OPTIONS route
105+
* used to handle CORS preflight requests. For example, setting this to `'silent'`
106+
* will prevent these requests from being logged.
107+
* Useful for reducing noise in application logs.
108+
* Default: inherits Fastify's global log level.
109+
*/
110+
logLevel?: LogLevel;
102111
}
103112

104113
export interface FastifyCorsOptionsDelegateCallback { (req: FastifyRequest, cb: (error: Error | null, corsOptions?: FastifyCorsOptions) => void): void }

types/index.test-d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ appHttp2.register(fastifyCors, {
165165
preflightContinue: false,
166166
optionsSuccessStatus: 200,
167167
preflight: false,
168-
strictPreflight: false
168+
strictPreflight: false,
169+
logLevel: 'silent'
169170
})
170171

171172
appHttp2.register(fastifyCors, {

0 commit comments

Comments
 (0)