First commit, supports 12/15 items of the spec
This commit is contained in:
commit
635d779252
7 changed files with 323 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
24
package.json
Normal file
24
package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "restify-cors-middleware",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"author": "Tabcorp Digital Technology Team",
|
||||||
|
"license": "MIT",
|
||||||
|
"description": "Common middlewares for all Node.js web services",
|
||||||
|
"keywords": ["restify", "cors", "cross origin", "headers"],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/TabDigital/restify-cors-middleware.git"
|
||||||
|
},
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "./node_modules/.bin/mocha"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"restify": "~2.7.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "~1.18.2",
|
||||||
|
"should": "~3.3.1",
|
||||||
|
"supertest": "~0.12.0"
|
||||||
|
}
|
||||||
|
}
|
76
src/index.js
Normal file
76
src/index.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
var restify = require('restify');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
DEFAULT_ALLOW_HEADERS = restify.CORS.ALLOW_HEADERS;
|
||||||
|
|
||||||
|
var HTTP_NO_CONTENT = 204;
|
||||||
|
|
||||||
|
function matchOrigin(req, origins) {
|
||||||
|
var origin = req.headers["origin"];
|
||||||
|
function belongs(o) {
|
||||||
|
if (origin === o || o === "*") {
|
||||||
|
origin = o;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (origin && origins.some(belongs)) {
|
||||||
|
return origin;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function preflightHandler(options) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
if (req.method !== 'OPTIONS') return next();
|
||||||
|
|
||||||
|
// 6.2.1 and 6.2.2
|
||||||
|
if (matchOrigin(req, options.origins) === false) return next();
|
||||||
|
|
||||||
|
// 6.2.3
|
||||||
|
requestedMethod = req.headers['access-control-request-method'];
|
||||||
|
if (!requestedMethod) return next();
|
||||||
|
|
||||||
|
// 6.2.4
|
||||||
|
requestedHeaders = req.headers['access-control-request-headers'];
|
||||||
|
requestedHeaders = requestedHeaders ? requestedHeaders.split(', ') : [];
|
||||||
|
|
||||||
|
allowedMethods = [requestedMethod, 'OPTIONS'];
|
||||||
|
allowedHeaders = DEFAULT_ALLOW_HEADERS.concat(['x-requested-with'])
|
||||||
|
.concat(options.allowHeaders);
|
||||||
|
|
||||||
|
// 6.2.7
|
||||||
|
res.header('Access-Control-Allow-Origin', req.headers['origin']);
|
||||||
|
res.header('Access-Control-Allow-Credentials', true);
|
||||||
|
|
||||||
|
// 6.2.9
|
||||||
|
res.header('Access-Control-Allow-Methods', allowedMethods.join(', '));
|
||||||
|
|
||||||
|
// 6.2.10
|
||||||
|
res.header('Access-Control-Allow-Headers', allowedHeaders.join(', '));
|
||||||
|
res.send(HTTP_NO_CONTENT);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
|
||||||
|
if (! util.isArray(options.origins)) options.origins = ['*'];
|
||||||
|
if (! util.isArray(options.allowHeaders)) options.allowHeaders = [];
|
||||||
|
if (! util.isArray(options.exposeHeaders)) options.exposeHeaders = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
actual: restify.CORS({
|
||||||
|
origins: options.origins,
|
||||||
|
headers: options.exposeHeaders
|
||||||
|
}),
|
||||||
|
|
||||||
|
preflight: preflightHandler({
|
||||||
|
origins: options.origins,
|
||||||
|
allowHeaders: options.allowHeaders
|
||||||
|
})
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
75
test/cors.actual.spec.js
Normal file
75
test/cors.actual.spec.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// Based on the spec at http://www.w3.org/TR/cors/
|
||||||
|
// The test numbers correspond do steps in the specification
|
||||||
|
//
|
||||||
|
|
||||||
|
var request = require('supertest');
|
||||||
|
var should = require('should');
|
||||||
|
var test = require('./test');
|
||||||
|
|
||||||
|
describe('CORS: simple / actual requests', function() {
|
||||||
|
|
||||||
|
it('6.1.1 Does not set headers if Origin is missing', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.get('/test')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.1.2 Does not set headers if Origin does not match', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.get('/test')
|
||||||
|
.set('Origin', 'http://random-website.com')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.1.3 Sets Allow-Origin headers if the Origin matches', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.get('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.expect('access-control-allow-origin', 'http://api.myapp.com')
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.1.4 Does not set exposed headers if empty', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.get('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.expect('access-control-allow-origin', 'http://api.myapp.com')
|
||||||
|
.expect('access-control-expose-headers', /api-version/) // defaults
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.1.4 Sets exposed headers if configured', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com'],
|
||||||
|
exposeHeaders: ['HeaderA', 'HeaderB']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.get('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.expect('access-control-allow-origin', 'http://api.myapp.com')
|
||||||
|
.expect('access-control-expose-headers', /HeaderA, HeaderB/) // custom
|
||||||
|
.expect('access-control-expose-headers', /api-version/) // defaults
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
120
test/cors.preflight.spec.js
Normal file
120
test/cors.preflight.spec.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
//
|
||||||
|
// Based on the spec at http://www.w3.org/TR/cors/
|
||||||
|
// The test numbers correspond do steps in the specification
|
||||||
|
//
|
||||||
|
|
||||||
|
var request = require('supertest');
|
||||||
|
var should = require('should');
|
||||||
|
var test = require('./test');
|
||||||
|
|
||||||
|
var METHOD_NOT_ALLOWED = 405;
|
||||||
|
|
||||||
|
describe('CORS: preflight requests', function() {
|
||||||
|
|
||||||
|
it('6.2.1 Does not set headers if Origin is missing', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(METHOD_NOT_ALLOWED)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.2 Does not set headers if Origin does not match', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://random-website.com')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(METHOD_NOT_ALLOWED)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.3 Does not set headers if Access-Control-Request-Method is missing', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(test.noHeader('access-control-allow-methods'))
|
||||||
|
.expect(METHOD_NOT_ALLOWED)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('6.2.4 Does not terminate if parsing of Access-Control-Request-Headers fails', function(done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('6.2.5 Always matches Access-Control-Request-Method (spec says it is acceptable)', function(done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.6 Does not set headers if Access-Control-Request-Headers does not match', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com'],
|
||||||
|
acceptHeaders: ['API-Token']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.set('Access-Control-Request-Headers', 'Weird-Header')
|
||||||
|
.expect(test.noHeader('access-control-allow-origin'))
|
||||||
|
.expect(test.noHeader('access-control-allow-methods'))
|
||||||
|
.expect(test.noHeader('access-control-allow-headers'))
|
||||||
|
.expect(METHOD_NOT_ALLOWED)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.7 Set the Allow-Origin header if it matches', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.set('Access-Control-Request-Method', 'GET')
|
||||||
|
.expect('Access-Control-Allow-Origin', 'http://api.myapp.com')
|
||||||
|
.expect(204)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('6.2.8 Access-Control-Max-Age not supported', function(done) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.9 Set the Allow-Method header', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.set('Access-Control-Request-Method', 'GET')
|
||||||
|
.expect('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||||
|
.expect(204)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('6.2.10 Set the Allow-Headers to all configured custom headers', function(done) {
|
||||||
|
var server = test.corsServer({
|
||||||
|
origins: ['http://api.myapp.com', 'http://www.myapp.com'],
|
||||||
|
allowHeaders: ['HeaderA']
|
||||||
|
});
|
||||||
|
request(server)
|
||||||
|
.options('/test')
|
||||||
|
.set('Origin', 'http://api.myapp.com')
|
||||||
|
.set('Access-Control-Request-Method', 'GET')
|
||||||
|
.expect('Access-Control-Allow-Headers', /accept-version/) // restify defaults
|
||||||
|
.expect('Access-Control-Allow-Headers', /x-api-version/) // restify defaults
|
||||||
|
.expect('Access-Control-Allow-Headers', /HeaderA/) // custom header
|
||||||
|
.expect(204)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
3
test/mocha.opts
Normal file
3
test/mocha.opts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
--reporter spec
|
||||||
|
--slow 500
|
||||||
|
--timeout 1000
|
24
test/test.js
Normal file
24
test/test.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
var restify = require('restify');
|
||||||
|
var cors = require('../src/index');
|
||||||
|
|
||||||
|
exports.corsServer = function(corsConfig) {
|
||||||
|
var middleware = cors(corsConfig);
|
||||||
|
var server = restify.createServer();
|
||||||
|
server.pre(middleware.preflight);
|
||||||
|
server.use(middleware.actual);
|
||||||
|
server.get('/test', function(req, res, next) {
|
||||||
|
res.header('Custom-Response-Header', '123456');
|
||||||
|
res.header('Max-Age', 5*60*1000);
|
||||||
|
res.send(200, 'ok');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
return server;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.noHeader = function(name) {
|
||||||
|
return function(res) {
|
||||||
|
if (res.headers.hasOwnProperty(name)) {
|
||||||
|
return 'Should not have header ' + name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
Reference in a new issue