Skip to content

Instantly share code, notes, and snippets.

@thedude42
Last active August 29, 2015 14:18
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 thedude42/958edb95230e00e9617c to your computer and use it in GitHub Desktop.
Save thedude42/958edb95230e00e9617c to your computer and use it in GitHub Desktop.
cors_server.js
"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