Created
July 29, 2022 16:49
-
-
Save Merott/8558261f6e16360c67f0b8dbe6bec92c to your computer and use it in GitHub Desktop.
A partial port of fastify/fastify-cors to vercel/micro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A partial port of fastify-cors: | |
// https://github.com/fastify/fastify-cors/blob/master/index.js | |
import type { RequestHandler } from 'micro' | |
import { send } from 'micro' | |
export interface MicroCorsOptions { | |
/** | |
* Configures the Access-Control-Allow-Origin CORS header. | |
*/ | |
origin: string | string[] | ((origin: string) => boolean | Promise<boolean>) | |
/** | |
* Configures the Access-Control-Allow-Credentials CORS header. | |
* Set to true to pass the header, otherwise it is omitted. | |
*/ | |
credentials?: boolean | |
/** | |
* 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. | |
*/ | |
exposedHeaders?: string | string[] | |
/** | |
* Configures the Access-Control-Allow-Headers CORS header. | |
* Expects a comma-delimited string (ex: 'Content-Type,Authorization') | |
* or an array (ex: ['Content-Type', 'Authorization']). | |
*/ | |
allowedHeaders?: string | string[] | |
/** | |
* Configures the Access-Control-Allow-Methods CORS header. | |
* Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: ['GET', 'PUT', 'POST']). | |
*/ | |
methods?: string | string[] | |
/** | |
* Configures the Access-Control-Max-Age CORS header. | |
* Set to an integer to pass the header, otherwise it is omitted. | |
*/ | |
maxAge?: number | |
/** | |
* Pass the CORS preflight response to the route handler (default: false). | |
*/ | |
preflightContinue?: boolean | |
/** | |
* Provides a status code to use for successful OPTIONS requests, | |
* since some legacy browsers (IE11, various SmartTVs) choke on 204. | |
*/ | |
optionsSuccessStatus?: number | |
/** | |
* Enforces strict requirement of the CORS preflight request headers (Access-Control-Request-Method and Origin). | |
* Preflight requests without the required headers will result in 400 errors when set to `true` (default: `true`). | |
*/ | |
strictPreflight?: boolean | |
} | |
const defaultOptions: Required<MicroCorsOptions> = { | |
origin: '*', | |
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', | |
preflightContinue: false, | |
optionsSuccessStatus: 204, | |
credentials: false, | |
exposedHeaders: [], | |
allowedHeaders: [], | |
maxAge: 60 * 60 * 24, // 24 hours, | |
strictPreflight: true, | |
} | |
export const cors = ( | |
options: MicroCorsOptions, | |
nextHandler: RequestHandler, | |
) => { | |
const opts = { ...defaultOptions, ...options } | |
const corsHandler: RequestHandler = async (req, res) => { | |
if (res.writableEnded) { | |
return nextHandler(req, res) as unknown | |
} | |
if (typeof opts.origin === 'string') { | |
res.setHeader('Access-Control-Allow-Origin', opts.origin) | |
} else { | |
const reqOrigin = req.headers['origin'] | |
if (reqOrigin) { | |
if (Array.isArray(opts.origin) && opts.origin.includes(reqOrigin)) { | |
res.setHeader('Access-Control-Allow-Origin', reqOrigin) | |
} else if ( | |
typeof opts.origin === 'function' && | |
(await opts.origin(reqOrigin)) | |
) { | |
res.setHeader('Access-Control-Allow-Origin', reqOrigin) | |
} | |
} | |
} | |
if (opts.credentials) { | |
res.setHeader('Access-Control-Allow-Credentials', 'true') | |
} | |
if (opts.exposedHeaders?.length) { | |
res.setHeader( | |
'Access-Control-Expose-Headers', | |
Array.isArray(opts.exposedHeaders) | |
? opts.exposedHeaders.join(', ') | |
: opts.exposedHeaders, | |
) | |
} | |
if (req.method === 'OPTIONS') { | |
if ( | |
opts.strictPreflight && | |
(!req.headers.origin || !req.headers['access-control-request-method']) | |
) { | |
await send(res, 400, 'Invalid Preflight Request') | |
return | |
} | |
if (opts.exposedHeaders?.length) { | |
res.setHeader( | |
'Access-Control-Allow-Methods', | |
Array.isArray(opts.methods) ? opts.methods.join(', ') : opts.methods, | |
) | |
} | |
if (opts.allowedHeaders?.length) { | |
res.setHeader( | |
'Access-Control-Allow-Headers', | |
Array.isArray(opts.allowedHeaders) | |
? opts.allowedHeaders.join(', ') | |
: opts.allowedHeaders, | |
) | |
} | |
res.setHeader('Access-Control-Max-Age', opts.maxAge) | |
if (!opts.preflightContinue) { | |
// Safari (and potentially other browsers) need content-length 0, | |
// for 204 or they just hang waiting for a body | |
res.setHeader('Content-Length', 0) | |
await send(res, opts.optionsSuccessStatus) | |
return | |
} | |
} | |
return nextHandler(req, res) as unknown | |
} | |
return corsHandler | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Basic usage: