Skip to content

Instantly share code, notes, and snippets.

@Merott
Created July 29, 2022 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Merott/8558261f6e16360c67f0b8dbe6bec92c to your computer and use it in GitHub Desktop.
Save Merott/8558261f6e16360c67f0b8dbe6bec92c to your computer and use it in GitHub Desktop.
A partial port of fastify/fastify-cors to vercel/micro
// 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
}
@Merott
Copy link
Author

Merott commented Jul 29, 2022

Basic usage:

import { send } from 'micro'

export default cors(
  {
    origin: ['https://example.com', 'https://example.org'],
    allowedHeaders: ['Content-Type'],
    credentials: true,
  },
  async (req, res) => {
    await send(res, 200, 'Hello World')
  },
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment