homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge
174 lines (152 loc) • 4.63 kB
JavaScript
const fp = require('fastify-plugin')
const append = require('vary').append
function fastifyCors (fastify, opts, next) {
const {
origin,
credentials,
exposedHeaders,
allowedHeaders,
methods,
maxAge,
preflightContinue,
optionsSuccessStatus,
preflight,
hideOptionsRoute
} = Object.assign({
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: false,
exposedHeaders: null,
allowedHeaders: null,
maxAge: null,
preflight: true,
hideOptionsRoute: true
}, opts)
const isOriginFalsy = !origin
const isOriginString = typeof origin === 'string'
const isOriginFunction = typeof origin === 'function'
if (preflight === true) {
fastify.options('*', { schema: { hide: hideOptionsRoute } }, (req, reply) => reply.send())
}
fastify.addHook('onRequest', onRequest)
function onRequest (req, reply, next) {
if (isOriginFalsy) return next()
configureOrigin(req, reply, (err, origin) => {
if (err !== null) return next(err)
if (origin === false) return next()
if (credentials) {
reply.header('Access-Control-Allow-Credentials', 'true')
}
if (exposedHeaders !== null) {
reply.header(
'Access-Control-Expose-Headers',
Array.isArray(exposedHeaders) ? exposedHeaders.join(', ') : exposedHeaders
)
}
if (req.raw.method === 'OPTIONS' && preflight === true) {
// preflight
reply.header(
'Access-Control-Allow-Methods',
Array.isArray(methods) ? methods.join(', ') : methods
)
if (allowedHeaders === null) {
vary(reply, 'Access-Control-Request-Headers')
var reqAllowedHeaders = req.headers['access-control-request-headers']
if (reqAllowedHeaders !== undefined) {
reply.header('Access-Control-Allow-Headers', reqAllowedHeaders)
}
} else {
reply.header(
'Access-Control-Allow-Headers',
Array.isArray(allowedHeaders) ? allowedHeaders.join(', ') : allowedHeaders
)
}
if (maxAge !== null) {
reply.header('Access-Control-Max-Age', String(maxAge))
}
if (preflightContinue) {
next()
} else {
// Safari (and potentially other browsers) need content-length 0,
// for 204 or they just hang waiting for a body
reply
.code(optionsSuccessStatus)
.header('Content-Length', '0')
.send()
}
} else {
next()
}
})
}
function configureOrigin (req, reply, callback) {
var reqOrigin = req.headers.origin
if (isOriginFunction) {
var result = origin.call(fastify, reqOrigin, _onOrigin)
if (result && typeof result.then === 'function') {
result.then(res => _onOrigin(null, res), callback)
}
} else {
_configureOrigin(origin)
}
function _onOrigin (err, origin) {
if (err !== null || origin === false) {
return callback(err, origin)
}
_configureOrigin(origin)
}
function _configureOrigin (origin) {
if (!origin || origin === '*') {
// allow any origin
reply.header('Access-Control-Allow-Origin', '*')
} else if (isOriginString) {
// fixed origin
reply.header('Access-Control-Allow-Origin', origin)
vary(reply, 'Origin')
} else {
// reflect origin
reply.header(
'Access-Control-Allow-Origin',
isOriginAllowed(reqOrigin, origin) ? reqOrigin : false
)
vary(reply, 'Origin')
}
callback(null, origin)
}
}
function isOriginAllowed (reqOrigin, origin) {
if (Array.isArray(origin)) {
for (var i = 0; i < origin.length; ++i) {
if (isOriginAllowed(reqOrigin, origin[i])) {
return true
}
}
return false
} else if (typeof origin === 'string') {
return reqOrigin === origin
} else if (origin instanceof RegExp) {
return origin.test(reqOrigin)
} else {
return !!origin
}
}
next()
}
// https://github.com/fastify/fastify-sensible/blob/master/lib/vary.js
function vary (reply, field) {
var value = reply.getHeader('Vary') || ''
var header = Array.isArray(value)
? value.join(', ')
: String(value)
// set new header
if ((value = append(header, field))) {
reply.header('Vary', value)
}
}
module.exports = fp(fastifyCors, {
fastify: '>=2.x',
name: 'fastify-cors'
})