Skip to content

Commit

Permalink
fix: CORS is not working as it should
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasdao committed Jan 18, 2018
1 parent 2bc2cca commit 68f1437
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 794 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"http-errors": "^1.6.1",
"path-to-regexp": "^2.1.0",
"raw-body": "^2.3.2",
"shortid": "^2.2.8"
"shortid": "^2.2.8",
"vary": "^1.1.2"
},
"devDependencies": {
"chai": "^4.1.0",
Expand Down
123 changes: 49 additions & 74 deletions src/cors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,89 +5,64 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
const url = require('url')

const getRequiredResponseHeaders = (config={}) => {
const headers = config.headers || {}
const headersCollection = []
for (let key in headers)
headersCollection.push({ key, value: headers[key] })

return headersCollection
}

const getAllowedOrigins = (config={}) =>
((config.headers || {})['Access-Control-Allow-Origin'] || '')
.split(',')
.reduce((a, s) => {
if (s) a[s.trim().toLowerCase().replace(/\/$/,'')] = true
return a
}, {})

const getAllowedMethods = (config={}) =>
((config.headers || {})['Access-Control-Allow-Methods'] || '')
.split(',')
.reduce((a, s) => {
if (s) a[s.trim().toLowerCase()] = true
return a
}, {})

const setResponseHeaders = (res, responseHeaders=[]) => responseHeaders.forEach(header => res.set(header.key, header.value))

const getRequestOrigin = req => {
const origin = (req.headers.origin || '').toLowerCase()
const referer = (req.headers.referer || req.headers.referrer || '').toLowerCase()
const refUrl = url.parse(referer)

if (origin)
return origin
else if (referer && refUrl.host)
return `${refUrl.protocol}//${refUrl.host}`
else
return null
}

const validateCORS = (req, res, allowedOrigins={}, allowedMethods={}) => {
const origin = getRequestOrigin(req)
const method = (req.method || '').toLowerCase()

if (!allowedOrigins['*'] && Object.keys(allowedOrigins).length > 0 && !(origin in allowedOrigins)) {
res.status(403).send(`Forbidden - CORS issue. Origin '${origin || 'undefined'}' is not allowed.`)
return false
}

if (Object.keys(allowedMethods).length > 0 && method != 'get' && method != 'head' && !(method in allowedMethods)) {
res.status(403).send(`Forbidden - CORS issue. Method '${method.toUpperCase()}' is not allowed.`)
return false
}

return true
}
const vary = require('vary')

/**
* Create the Express-like middleware that will add headers to the response as well as check for CORS
* compliant request.
* @param {Object} options.headers Headers that should be added to all responses regardless of what happens
*/
const cors = (options={}) => {
const requiredHeaders = getRequiredResponseHeaders(options) || []
const allowedOrigins = getAllowedOrigins(options)
const allowedMethods = getAllowedMethods(options)
const setHeaders = requiredHeaders.length > 0 ? res => setResponseHeaders(res, requiredHeaders) : () => null
const cors = ({ allowedHeaders, origins, methods, credentials, maxAge }) => {
const originList = (origins || ['*']).map(x => x.toLowerCase().trim())
const allOriginsAllowed = originList.some(x => x == '*')
const addOriginToVary = !allOriginsAllowed && originList.length > 0
let headers = {
'Access-Control-Allow-Methods' : (methods || ['GET','HEAD','PUT','PATCH','POST','DELETE']).map(x => x.toUpperCase()).join(', '),
'Access-Control-Allow-Origin': allOriginsAllowed ? '*' : originList.join(', ')
//'Access-Control-Request-Headers': '',
//'Access-Control-Expose-Headers': '',
}
if (credentials !== undefined)
headers['Access-Control-Allow-Credentials'] = credentials ? true : false
if (maxAge)
headers['Access-Control-Max-Age'] = maxAge
if (allowedHeaders && allowedHeaders.length > 0)
headers['Access-Control-Allow-Headers'] = (allowedHeaders || []).join(', ')

return (req, res, next) => {
setHeaders(res)
validateCORS(req, res, allowedOrigins, allowedMethods)
const requestOrigin = ((req.headers || {}).origin || '').trim()
const requestAllowed = allOriginsAllowed || originList.some(x => x == requestOrigin)
const creds = headers['Access-Control-Allow-Credentials']
const _allowedHeaders = headers['Access-Control-Allow-Headers']
const _maxAge = headers['Access-Control-Max-Age']

// 1. For all requests, set Origin
res.set('Access-Control-Allow-Origin', requestAllowed ? requestOrigin : null)
// 2. For all requests, set Credential boolean if it was defined.
if (creds)
res.set('Access-Control-Allow-Credentials', creds)
// 3. For all requests, set Expose-Headers if it was defined.
if (_allowedHeaders)
res.set('Access-Control-Expose-Headers', _allowedHeaders)

// 4. For OPTIONS requests, add more headers
const method = req.method && req.method.toUpperCase && req.method.toUpperCase()
if (method == 'OPTIONS') {
// 4.1. For OPTIONS requests, set Methods
res.set('Access-Control-Allow-Methods', headers['Access-Control-Allow-Methods'])
// 4.2. For OPTIONS requests, set Allow-Headers if it was defined.
if (_allowedHeaders)
res.set('Access-Control-Allow-Headers', _allowedHeaders)
// 4.3. For OPTIONS requests, set Max-Age if it was defined.
if (_maxAge)
res.set('Access-Control-Max-Age', _maxAge)
}

if (addOriginToVary && res.headers)
vary(res, 'Origin')

next()
}
}

module.exports = {
getRequiredResponseHeaders,
getAllowedOrigins,
getAllowedMethods,
setResponseHeaders,
validateCORS,
getRequestOrigin,
cors
}
module.exports = { cors }
48 changes: 12 additions & 36 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ const shortid = require('shortid')
const path = require('path')
const { getRouteDetails, matchRoute } = require('./routing')
const { reqUtil } = require('./utils')
const {
getRequiredResponseHeaders,
getAllowedOrigins,
getAllowedMethods,
setResponseHeaders,
validateCORS,
cors
} = require('./cors')
const { cors } = require('./cors')
require('colors')

/*eslint-disable */
Expand All @@ -35,9 +28,6 @@ const PARAMSMODE = { 'all': true, 'body': true, 'route': true, 'none': true }
const getAppConfig = () => fs.existsSync(CONFIGPATH) ? require(CONFIGPATH) : {}

let _config = getAppConfig() // Object
let _requiredResponseHeaders = getRequiredResponseHeaders(_config) // Array
let _allowedOrigins = getAllowedOrigins(_config) // Object
let _allowedMethods = getAllowedMethods(_config) // Object
let _preEvent = () => Promise.resolve(null)
let _postEvent = () => Promise.resolve(null)

Expand Down Expand Up @@ -78,9 +68,6 @@ const executeHandlers = (req, res, handlers=[]) =>

const resetConfig = (config={}) => {
_config = config
_requiredResponseHeaders = getRequiredResponseHeaders(config)
_allowedOrigins = getAllowedOrigins(config)
_allowedMethods = getAllowedMethods(config)
}

let _handlers = []
Expand Down Expand Up @@ -135,7 +122,7 @@ const app = {
_postEvent = (req, res) => Promise.resolve(null).then(() => fn(req, res))
}
},
handleEvent: () => (req, res) => processEvent(req, res, _config, _endpoints, _handlers, _requiredResponseHeaders, _allowedOrigins, _allowedMethods, _preEvent, _postEvent),
handleEvent: () => (req, res) => processEvent(req, res, _config, _endpoints, _handlers, _preEvent, _postEvent),
listen: (appName, port) => {
const input = createListenArity(appName, port, 3000)
const hostingType = getHostingType()
Expand All @@ -153,7 +140,7 @@ const app = {
if (!input.appName) {
const __express__ = require('express')
const __server__ = __express__()
__server__.all('*', (req, res) => processEvent(req, res, _config, _endpoints, _handlers, _requiredResponseHeaders, _allowedOrigins, _allowedMethods, _preEvent, _postEvent))
__server__.all('*', (req, res) => processEvent(req, res, _config, _endpoints, _handlers, _preEvent, _postEvent))
__server__.listen(input.port, () => {
console.log(startMessage)
if (secondMsg)
Expand Down Expand Up @@ -321,7 +308,7 @@ const matchEndpoint = (pathname, httpMethod, endpoints=[]) => (
.sort((a, b) => b.winningRoute.match.length - a.winningRoute.match.length)[0] || {}
).endpoint

const processEvent = (req, res, config={}, endpoints=[], handlers=[], requiredHeaders=[], allowedOrigins={}, allowedMethods={}, preEvent, postEvent) => {
const processEvent = (req, res, config={}, endpoints=[], handlers=[], preEvent, postEvent) => {
// 0. Create a request identity for tracing purpose
req.__receivedTime = Date.now()
req.__transactionId = shortid.generate().replace(/-/g, 'r').replace(/_/g, '9')
Expand All @@ -345,7 +332,6 @@ const processEvent = (req, res, config={}, endpoints=[], handlers=[], requiredHe

// 3. Prepare response with required headers and APIs
extendResponse(res)
setResponseHeaders(res, requiredHeaders)

// 4. Run pre-event processing
return preEvent(req, res)
Expand All @@ -358,16 +344,12 @@ const processEvent = (req, res, config={}, endpoints=[], handlers=[], requiredHe
// 5. Run the main request/response processing
.then(() => {
if (!res.headersSent && !_preEventErr) {
// 5.1. Validate CORS
if (!validateCORS(req, res, allowedOrigins, allowedMethods))
return

// 5.2. Stop if this is a HEAD or OPTIONS request
// 5.1. Stop if this is a HEAD or OPTIONS request
const method = new String(req.method).toLowerCase()
if (method == 'head' || method == 'options')
if (method == 'head')
return res.status(200).send()

// 5.3. Validate the request and make sure that there is an endpoint for it.
// 5.2. Validate the request and make sure that there is an endpoint for it.
const pathname = ((req._parsedUrl || {}).pathname || '/').toLowerCase()
const httpMethod = (req.method || '').toUpperCase()

Expand All @@ -376,17 +358,17 @@ const processEvent = (req, res, config={}, endpoints=[], handlers=[], requiredHe
if (!endpoint)
return res.status(404).send(`Endpoint '${pathname}' for method ${httpMethod} not found.`)

// 5.4. Extract all params from that request, including both the url route params and the payload params.
// 5.3. Extract all params from that request, including both the url route params and the payload params.
const validParamsMode = PARAMSMODE[paramsMode] ? paramsMode : 'all'
const paramts = validParamsMode == 'all' || validParamsMode == 'route' ? Object.assign({}, endpoint.winningRoute.parameters) : {}
const getParams = validParamsMode == 'all' || validParamsMode == 'body' ? reqUtil.getParams(req) : Promise.resolve({})
return getParams.then(parameters => Object.assign(parameters, paramts))
.then(parameters => {
// 5.5. Add all paramaters to the request object
// 5.4. Add all paramaters to the request object
Object.assign(req[paramsPropName], parameters || {})
// 5.6. Process all global handlers
// 5.5. Process all global handlers
return executeHandlers(req, res, handlers)
// 5.8. Process the endpoint
// 5.6. Process the endpoint
.then(() => !res.headersSent && endpoint.execute(req, res))
})
}
Expand Down Expand Up @@ -429,12 +411,6 @@ const extendResponse = res => {
module.exports = {
app,
cors,
get appConfig() { return getEnv() },
utils: {
headers: {
setResponseHeaders,
getRequiredResponseHeaders
}
}
get appConfig() { return getEnv() }
}

Loading

0 comments on commit 68f1437

Please sign in to comment.