diff --git a/lib/index.js b/lib/index.js index ad899ca..331ebd7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -39,11 +39,23 @@ isAllowed; if (!options.origin || options.origin === '*') { - // allow any origin - headers.push([{ - key: 'Access-Control-Allow-Origin', - value: '*' - }]); + if (options.credentials === true) { + // reflect origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: requestOrigin + }]); + headers.push([{ + key: 'Vary', + value: 'Origin' + }]); + } else { + // allow any origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: '*' + }]); + } } else if (isString(options.origin)) { // fixed origin headers.push([{ @@ -75,6 +87,9 @@ if (methods.join) { methods = options.methods.join(','); // .methods is an array, so turn it into a string } + if (methods === '*' && options.credentials === true) { + return null; + } return { key: 'Access-Control-Allow-Methods', value: methods @@ -105,6 +120,9 @@ allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string } if (allowedHeaders && allowedHeaders.length) { + if (allowedHeaders === '*' && options.credentials === true) { + return headers; // don't set the header + } headers.push([{ key: 'Access-Control-Allow-Headers', value: allowedHeaders @@ -122,6 +140,9 @@ headers = headers.join(','); // .headers is an array, so turn it into a string } if (headers && headers.length) { + if (headers === '*' && options.credentials === true) { + return null; + } return { key: 'Access-Control-Expose-Headers', value: headers diff --git a/test/issue-333.js b/test/issue-333.js new file mode 100644 index 0000000..7385c8a --- /dev/null +++ b/test/issue-333.js @@ -0,0 +1,116 @@ +'use strict'; + +var assert = require('assert'); +var cors = require('..'); + +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + +var FakeRequest = function (method, headers) { + this.headers = headers || { + 'origin': 'http://example.com', + 'access-control-request-headers': 'x-header-1, x-header-2' + }; + this.method = method || 'GET'; +}; + +var FakeResponse = function () { + this._headers = {}; + this.statusCode = 200; +}; + +util.inherits(FakeResponse, EventEmitter); + +FakeResponse.prototype.end = function end() { + var response = this; + + process.nextTick(function () { + response.emit('finish'); + }); +}; + +FakeResponse.prototype.getHeader = function getHeader(name) { + var key = name.toLowerCase(); + return this._headers[key]; +}; + +FakeResponse.prototype.setHeader = function setHeader(name, value) { + var key = name.toLowerCase(); + this._headers[key] = value; +}; + +describe('issue #333 - CORS requests with credentials should forbid *', function () { + it('forbids * for Access-Control-Allow-Origin when credentials is true', function (done) { + var req = new FakeRequest('GET'); + var res = new FakeResponse(); + var options = { + origin: '*', + credentials: true + }; + + cors(options)(req, res, function (err) { + assert.ifError(err); + // Currently, this fails (it returns '*') + assert.notEqual(res.getHeader('Access-Control-Allow-Origin'), '*'); + done(); + }); + }); + + it('forbids * for Access-Control-Allow-Methods when credentials is true', function (done) { + var req = new FakeRequest('OPTIONS', { + 'origin': 'http://example.com', + 'access-control-request-method': 'GET' + }); + var res = new FakeResponse(); + var options = { + methods: '*', // if user explicitly sets * + credentials: true + }; + + res.on('finish', function () { + assert.notEqual(res.getHeader('Access-Control-Allow-Methods'), '*'); + done(); + }); + + cors(options)(req, res, function (err) { + done(err || new Error('should not be called')); + }); + }); + + it('forbids * for Access-Control-Allow-Headers when credentials is true', function (done) { + var req = new FakeRequest('OPTIONS', { + 'origin': 'http://example.com', + 'access-control-request-method': 'GET', + 'access-control-request-headers': 'X-Custom' + }); + var res = new FakeResponse(); + var options = { + allowedHeaders: '*', // if user explicitly sets * + credentials: true + }; + + res.on('finish', function () { + assert.notEqual(res.getHeader('Access-Control-Allow-Headers'), '*'); + done(); + }); + + cors(options)(req, res, function (err) { + done(err || new Error('should not be called')); + }); + }); + + it('forbids * for Access-Control-Expose-Headers when credentials is true', function (done) { + var req = new FakeRequest('GET'); + var res = new FakeResponse(); + var options = { + exposedHeaders: '*', // if user explicitly sets * + credentials: true + }; + + cors(options)(req, res, function (err) { + assert.ifError(err); + assert.notEqual(res.getHeader('Access-Control-Expose-Headers'), '*'); + done(); + }); + }); +});