Last active
August 29, 2015 14:18
-
-
Save thedude42/958edb95230e00e9617c to your computer and use it in GitHub Desktop.
cors_server.js
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
"use strict"; | |
var vsm = require("lrs/virtualServerModule"), | |
async = require("async"), | |
CORS_PREFLIGHT_HEADERS = "Access-Control-Request-Headers"; | |
function CorsService(vs, defaultOrigin, uriAccessList) { | |
// Constructor, takes the name of a virtual server, default origin value and a configuration object | |
var self = this; | |
this.origin = defaultOrigin; | |
this.uriConfiguration = uriAccessList; | |
vsm.on('exist', vsName, function(vs) { | |
vs.on("request", function(servReq, servResp, cliReq) { | |
self.handleRequest(servReq, servResp, cliReq); | |
}); | |
}); | |
console.log("Loaded CORS enforcement on virtual-server "+vsName+" for configuration: "+JSON.stringify(this.uriConfiguration)); | |
} | |
CorsService.prototype.handleRequest = function(servReq, servResp, cliReq) { | |
var self = this, | |
uriAccessCfg = {}, | |
maxage = self.uriConfiguration.maxAge; | |
async.waterfall([ | |
function(callback) { | |
// Decide whether to enforce CORS, and if so reject any violation | |
self.checkEnforcedOrAllowed(self.uriConfiguration, servReq, callback); | |
}, | |
function(uriCfg, callback) { | |
uriAccessCfg = uriCfg; | |
// Verify if this is a CORS "preflight" or "simple" request | |
self.checkPreflight(uriAccessCfg, servReq, callback); | |
}, | |
function(isPreflight, callback) { | |
if (isPreflight) { | |
//console.log("doing preflight response"); | |
self.doPreflightResponse(uriAccessCfg, servReq, servResp, maxage, callback); | |
} | |
else { | |
//console.log("doing simple response"); | |
self.doNormalResponse(uriAccessCfg, servReq, servResp, cliReq, callback); | |
} | |
} | |
], function(err, result) { | |
//console.log("default final function called, result: "+result+" | err: "+err); | |
// Only do anything if an error condition was found | |
// error condition 1: fastpipe request because we don't enforce CORS for this request | |
if (err === "NO_ENFORCE") { | |
servReq.bindHeaders(cliReq); | |
servReq.fastPipe(cliReq, {"response": servResp}); | |
servReq.on("data", function() { | |
}); | |
} | |
//error condition 2: reject this request because it violates our CORS policy | |
else if (err === "REJECT") { | |
servReq.on("end", function() { | |
servResp.writeHead(200, { | |
"Content-Type": "text/html; charset=utf-8", | |
"Content-Length": 0 | |
}); | |
servResp.end(); | |
}); | |
servReq.resume(); | |
} | |
}); | |
}; | |
CorsService.prototype.checkEnforcedOrAllowed = function(uriConfiguration, servReq, cb) { | |
var self = this, | |
corsEnforced = false, | |
bareUri = servReq.url.split("?")[0]; | |
if (bareUri in uriConfiguration) { | |
corsEnforced = true; | |
} | |
if (corsEnforced) { | |
if (!servReq.headers["Origin"]) { | |
cb("REJECT", "noOriginHeader"); | |
} | |
else if(uriConfiguration[bareUri].methods.indexOf(servReq.method) === -1) { | |
cb("REJECT", "badMethod"); | |
} | |
else if (!uriConfiguration[bareUri].allowCreds && servReq.headers["Cookie"]) { | |
cb("REJECT", "violateCookiePolicy"); | |
} | |
// if the origin header is the not default origin, -AND- we have no wildcard origin in our configuration, | |
// -AND- the Origin header of the request is in the configured origin list | |
else if (servReq.headers["Origin"] !== self.origin && | |
uriConfiguration[bareUri].origins.indexOf("*") === -1 && uriConfiguration[bareUri].origins.indexOf(servReq.headers["Origin"]) === -1) { | |
console.log("rejecting request for "+ servReq.url +" from "+servReq.connection.remoteAddress); | |
cb("REJECT", "!invalidOrigin!"); | |
} | |
else { | |
cb(null, uriConfiguration[bareUri]); | |
} | |
} | |
else { | |
cb("NO_ENFORCE"); | |
} | |
}; | |
CorsService.prototype.checkPreflight = function(uriAccessCfg, servReq, cb) { | |
// If the HTTP method is OPTIONS and contains the header "Access-Control-Request-Method", we are preflight | |
var CORS_PREFLIGHT_METHOD = "Access-Control-Request-Method"; | |
if (servReq.method === "OPTIONS" && servReq.headers[CORS_PREFLIGHT_METHOD]) { | |
//console.log("uriAccessConfig: "+JSON.stringify(uriAccessCfg)); | |
if (uriAccessCfg.methods.indexOf(servReq.headers[CORS_PREFLIGHT_METHOD]) === -1) { | |
cb("REJECT", "checkPreflightMethod"); | |
return; | |
} | |
if (servReq.headers[CORS_PREFLIGHT_HEADERS]) { | |
var headersRequested = servReq.headers[CORS_PREFLIGHT_HEADERS].split(","); | |
for (var i = 0; i < headersRequested.length; i++) { | |
if (uriAccessCfg.allowHeaders.indexOf(headersRequested[i]) === -1) { | |
cb("REJECT", "checkPreflightHeaders"); | |
return; | |
} | |
} | |
} | |
cb(null, true); | |
} | |
else { | |
cb(null, false); | |
} | |
}; | |
CorsService.prototype.doPreflightResponse = function(uriAccessCfg, servReq, servResp, maxage, cb) { | |
// Build appropriate "preflight" response. The request does not need to be forwarded | |
var self = this; | |
servReq.on("end", function() { | |
var headers = { | |
"Content-Type": "text/html; charset=utf-8", | |
"Content-Length": 0, | |
"Access-Control-Allow-Origin": uriAccessCfg.origins.join(", "), | |
"Access-Control-Allow-Methods": uriAccessCfg.methods.join(", ") | |
}; | |
if (servReq.headers[CORS_PREFLIGHT_HEADERS]) { | |
headers["Access-Control-Allow-Headers"] = uriAccessCfg.allowHeaders.join(", "); | |
} | |
if (uriAccessCfg.allowCreds) { | |
headers["Access-Control-Allow-Credentials"] = true; | |
} | |
if (maxage) { | |
headers["Access-Control-Max-Age"] = maxage; | |
} | |
console.log(JSON.stringify(headers)); | |
servResp.writeHead(200, headers); | |
servResp.end(); | |
cb(null, "preflight"); | |
}); | |
// un-pause the servReq to allow the 'end' event to fire | |
servReq.resume(); | |
}; | |
CorsService.prototype.doNormalResponse = function(uriAccessCfg, servReq, servResp, cliReq, cb) { | |
// For a routine CORS request, forward the request and add appropriate headers to the response | |
cliReq.on("response", function(cliResp) { | |
// clean up headers before sending response | |
cliResp.bindHeaders(servResp); | |
var vary = cliResp.headers["Vary"]; | |
if (vary) { | |
servResp.setHeader("Vary", vary+", Origin"); | |
} | |
else { | |
servResp.setHeader("Vary", "Origin"); | |
} | |
if (uriAccessCfg.allowHeaders) { | |
servResp.setHeader("Access-Control-Expose-Headers", uriAccessCfg.allowHeaders.join(", ")); | |
} | |
cliResp.fastPipe(servResp); | |
cliResp.resume(); | |
}); | |
// proxy the request on the fast-path | |
servReq.bindHeaders(cliReq); | |
servReq.fastPipe(cliReq); | |
servReq.resume(); | |
cb(null, "simple"); | |
}; | |
exports.CorsService = CorsService; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment