Skip to content

Commit

Permalink
Adjust CORS origin handling. Closes #1174
Browse files Browse the repository at this point in the history
  • Loading branch information
Eran Hammer committed Nov 24, 2013
1 parent 68aa36c commit fde5841
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 22 deletions.
6 changes: 4 additions & 2 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ When creating a server instance, the following options configure the server's be
default. To enable, set `cors` to `true`, or to an object with the following options:
- `origin` - a strings array of allowed origin servers ('Access-Control-Allow-Origin'). The array can contain any combination of fully qualified origins
along with origin strings containing a wilcard '*' character, or a single `'*'` origin string. Defaults to any origin `['*']`.
- `isOriginExposed` - optional boolean indicating if the server should return the allowed origin values if the incoming origin header does not match
any of the values. Defaults to `true`.
- `isOriginExposed` - if `false`, prevents the server from returning the full list of non-wildcard `origin` values if the incoming origin header
does not match any of the values. Has no impact if `matchOrigin` is set to `false`. Defaults to `true`.
- `matchOrigin` - if `false`, returns the list of `origin` values without attempting to match the incoming origin value. Cannot be used with
wildcard `origin` values. Defaults to `true`.
- `maxAge` - number of seconds the browser should cache the CORS response ('Access-Control-Max-Age'). The greater the value, the longer it
will take before the browser checks for changes in policy. Defaults to `86400` (one day).
- `headers` - a strings array of allowed headers ('Access-Control-Allow-Headers'). Defaults to `['Authorization', 'Content-Type', 'If-None-Match']`.
Expand Down
1 change: 1 addition & 0 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ exports.server = {
exports.cors = {
origin: ['*'],
isOriginExposed: true, // Return the list of supported origins if incoming origin does not match
matchOrigin: true, // Attempt to match incoming origin against allowed values and return narrow response
maxAge: 86400, // One day
headers: [
'Authorization',
Expand Down
17 changes: 8 additions & 9 deletions lib/response/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,18 @@ exports.cors = function (response, request) {
if (cors._origin.any) {
response.header('access-control-allow-origin', '*');
}
else {
if (cors._origin.qualifiedString && cors.isOriginExposed) {
response.header('access-control-allow-origin', cors._origin.qualifiedString);
}
else if (internals.matchOrigin(request.headers.origin, cors)) {
else if (cors.matchOrigin) {
response.header('vary', 'origin', true);
if (internals.matchOrigin(request.headers.origin, cors)) {
response.header('access-control-allow-origin', request.headers.origin);
response.header('vary', 'origin', true);
}
else {
// Need to provide vary for misses to prevent improper caching of misses
response.header('vary', 'origin', true);
else if (cors._origin.qualifiedString && cors.isOriginExposed) {
response.header('access-control-allow-origin', cors._origin.qualifiedString);
}
}
else {
response.header('access-control-allow-origin', cors._origin.qualifiedString);
}
}

response.header('access-control-max-age', cors.maxAge);
Expand Down
1 change: 1 addition & 0 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ internals.serverSchema = {
cors: Joi.object({
origin: Joi.array(),
isOriginExposed: Joi.boolean(),
matchOrigin: Joi.boolean(),
maxAge: Joi.number(),
headers: Joi.array(),
additionalHeaders: Joi.array(),
Expand Down
6 changes: 2 additions & 4 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,8 @@ module.exports = internals.Server = function (/* host, port, options */) {
}
}

// If there are any wildcards then we have to work in vary origin mode
if (!this.settings.cors._origin.wildcards.length) {
this.settings.cors._origin.qualifiedString = this.settings.cors._origin.qualified.join(' ');
}
Utils.assert(this.settings.cors.matchOrigin || !this.settings.cors._origin.wildcards.length, 'Cannot include wildcard origin values with matchOrigin disabled');
this.settings.cors._origin.qualifiedString = this.settings.cors._origin.qualified.join(' ');
}
}
}
Expand Down
35 changes: 28 additions & 7 deletions test/integration/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('Response', function () {
});
});

it('return CORS origin', function (done) {
it('returns CORS origin', function (done) {

var handler = function () {

Expand All @@ -87,7 +87,7 @@ describe('Response', function () {
});
});

it('not return CORS for no origin without isOriginExposed', function (done) {
it('does not return CORS for no origin without isOriginExposed', function (done) {

var handler = function () {

Expand All @@ -107,7 +107,7 @@ describe('Response', function () {
});
});

it('hide CORS origin if no match found', function (done) {
it('hides CORS origin if no match found', function (done) {

var handler = function () {

Expand All @@ -127,7 +127,7 @@ describe('Response', function () {
});
});

it('return matching CORS origin', function (done) {
it('returns matching CORS origin', function (done) {

var handler = function () {

Expand All @@ -148,15 +148,15 @@ describe('Response', function () {
});
});

it('return matching hide CORS origin', function (done) {
it('returns matching CORS origin without exposing full list', function (done) {

var handler = function () {

this.reply('Tada')
.header('vary', 'x-test', true);
};

var server = new Hapi.Server({ cors: {isOriginExposed: false, origin: ['http://test.example.com', 'http://www.example.com'] } });
var server = new Hapi.Server({ cors: { isOriginExposed: false, origin: ['http://test.example.com', 'http://www.example.com'] } });
server.route({ method: 'GET', path: '/', handler: handler });

server.inject({ url: '/', headers: { origin: 'http://www.example.com' } }, function (res) {
Expand All @@ -169,7 +169,7 @@ describe('Response', function () {
});
});

it('return matching CORS origin wildcard', function (done) {
it('returns matching CORS origin wildcard', function (done) {

var handler = function () {

Expand All @@ -190,6 +190,27 @@ describe('Response', function () {
});
});

it('returns all CORS origins when match is disabled', function (done) {

var handler = function () {

this.reply('Tada')
.header('vary', 'x-test', true);
};

var server = new Hapi.Server({ cors: { origin: ['http://test.example.com', 'http://www.example.com'], matchOrigin: false } });
server.route({ method: 'GET', path: '/', handler: handler });

server.inject({ url: '/', headers: { origin: 'http://www.a.com' } }, function (res) {

expect(res.result).to.exist;
expect(res.result).to.equal('Tada');
expect(res.headers['access-control-allow-origin']).to.equal('http://test.example.com http://www.example.com');
expect(res.headers.vary).to.equal('x-test');
done();
});
});

it('returns error on created with GET', function (done) {

var handler = function () {
Expand Down

0 comments on commit fde5841

Please sign in to comment.