Skip to content

Commit

Permalink
Partial implementation for #2840
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Oct 16, 2015
1 parent 8358da1 commit c93454c
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 152 deletions.
6 changes: 1 addition & 5 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -2157,10 +2157,6 @@ following options:
Defaults to `['Authorization', 'Content-Type', 'If-None-Match']`.
- `additionalHeaders` - a strings array of additional headers to `headers`. Use this to
keep the default headers in place.
- `methods` - a strings array of allowed HTTP methods ('Access-Control-Allow-Methods').
Defaults to `['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS']`.
- `additionalMethods` - a strings array of additional methods to `methods`. Use this to
keep the default methods in place.
- `exposedHeaders` - a strings array of exposed headers
('Access-Control-Expose-Headers'). Defaults to
`['WWW-Authenticate', 'Server-Authorization']`.
Expand All @@ -2170,7 +2166,7 @@ following options:
('Access-Control-Allow-Credentials'). Defaults to `false`.
- `override` - if `false`, preserves existing CORS headers set manually before the
response is sent. If set to `'merge'`, appends the configured values to the manually set
headers. Defaults to `true`.
headers (applies only to Access-Control-Expose-Headers). Defaults to `true`.

- `ext` - defined a route-level [request extension points](#request-lifecycle) by setting
the option to an object with a key for each of the desired extension points (`'onRequest'`
Expand Down
20 changes: 3 additions & 17 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ exports = module.exports = internals.Connection = function (server, options) {
this.states = new Statehood.Definitions(this.settings.state);
this.auth = new Auth(this);
this._router = new Call.Router(this.settings.router);
this._corsPaths = {};
this._defaultRoutes();

this.plugins = {}; // Registered plugin APIs by plugin name
Expand Down Expand Up @@ -388,7 +387,7 @@ internals.Connection.prototype._defaultRoutes = function () {
method: 'notFound',
path: '/{p*}',
config: {
auth: false, // Override any defaults
auth: false, // Override any defaults
handler: function (request, reply) {

return reply(Boom.notFound());
Expand All @@ -402,7 +401,7 @@ internals.Connection.prototype._defaultRoutes = function () {
method: 'badRequest',
path: '/{p*}',
config: {
auth: false, // Override any defaults
auth: false, // Override any defaults
handler: function (request, reply) {

return reply(Boom.badRequest());
Expand All @@ -413,19 +412,6 @@ internals.Connection.prototype._defaultRoutes = function () {
this._router.special('badRequest', badRequest);

if (this.settings.routes.cors) {
var optionsRoute = new Route({
path: '/{p*}',
method: 'options',
config: {
auth: false, // Override any defaults
cors: this.settings.routes.cors,
handler: function (request, reply) {

return reply();
}
}
}, this, this.server);

this._router.special('options', optionsRoute);
Cors.handler(this);
}
};
102 changes: 68 additions & 34 deletions lib/cors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Load modules

var Boom = require('boom');
var Hoek = require('hoek');
var Defaults = require('./defaults');
var Route = null; // Delayed load due to circular dependency


// Declare internals
Expand All @@ -16,8 +18,8 @@ exports.route = function (options) {
return false;
}

settings._headers = settings.headers.concat(settings.additionalHeaders).join(',');
settings._methods = settings.methods.concat(settings.additionalMethods).join(',');
settings._headers = settings.headers.concat(settings.additionalHeaders);
settings._headersString = settings._headers.join(',');
settings._exposedHeaders = settings.exposedHeaders.concat(settings.additionalExposedHeaders).join(',');

if (settings.origin.length) {
Expand Down Expand Up @@ -52,10 +54,10 @@ exports.route = function (options) {
};


exports.headers = function (response) {
exports.headers = function (response, options) {

var request = response.request;
var settings = request.route.settings.cors;
var settings = options || request.route.settings.cors;
if (!settings) {
return;
}
Expand All @@ -81,7 +83,6 @@ exports.headers = function (response) {
}

var config = { override: !!settings.override }; // Value can be 'merge'
response._header('access-control-max-age', settings.maxAge, { override: settings.override });

if (settings.credentials) {
response._header('access-control-allow-credentials', 'true', { override: settings.override });
Expand All @@ -93,9 +94,6 @@ exports.headers = function (response) {
config.append = true;
}

response._header('access-control-allow-methods', settings._methods, config);
response._header('access-control-allow-headers', settings._headers, config);

if (settings._exposedHeaders.length !== 0) {
response._header('access-control-expose-headers', settings._exposedHeaders, config);
}
Expand Down Expand Up @@ -126,45 +124,81 @@ internals.matchOrigin = function (origin, settings) {
};


exports.options = function (route, connection, plugin) {
exports.options = function (route, connection, server) {

var settings = Hoek.clone(route.settings.cors);
if (route.method === 'options' ||
!route.settings.cors) {

if (settings) {
delete settings._origin;
delete settings._exposedHeaders;
delete settings._headers;
delete settings._methods;
return;
}

if (route.method === 'options' ||
Hoek.deepEqual(connection.settings.routes.cors, settings)) {
exports.handler(connection);
};


exports.handler = function (connection) {

Route = Route || require('./route');

if (connection._router.specials.options) {
return;
}

if (!settings) {
return;
var optionsRoute = new Route({
path: '/{p*}',
method: 'options',
config: {
auth: false, // Override any defaults
cors: false, // CORS headers are set in handler()
handler: internals.handler
}
}, connection, connection.server);

connection._router.special('options', optionsRoute);
};


internals.handler = function (request, reply) {

// Validate CORS preflight request

var origin = request.headers.origin;
if (!origin) {
return reply(Boom.notFound());
}

var path = route.path;
if (connection._corsPaths[path]) {
Hoek.assert(Hoek.deepEqual(connection._corsPaths[path], settings), 'Cannot add multiple routes with different CORS options on different methods:', route.method.toUpperCase(), path);
return;
var method = request.headers['access-control-request-method'];
if (!method) {
return reply(Boom.notFound());
}

connection._corsPaths[path] = settings;
// Lookup route

connection._route({
path: path,
method: 'options',
config: {
auth: false, // Override any defaults
cors: settings,
handler: function (request, reply) {
var route = request.connection.match(method, request.path, request.headers.host);
if (!route) {
return reply(Boom.notFound());
}

return reply();
}
var settings = route.settings.cors;
if (!settings) {
return reply(Boom.notFound());
}

// Validate allowed headers

var headers = request.headers['access-control-request-headers'];
if (headers) {
headers = headers.split(/\s*,\s*/);
if (Hoek.intersect(headers, settings._headers).length !== headers.length) {
return reply(Boom.notFound());
}
}, plugin);
}

// Reply with the route CORS headers

var response = reply();
exports.headers(response, settings);
response._header('access-control-allow-methods', method);
response._header('access-control-allow-headers', settings._headersString);
response._header('access-control-max-age', settings.maxAge);
};
10 changes: 0 additions & 10 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,6 @@ exports.cors = {
'If-None-Match'
],
additionalHeaders: [],
methods: [
'GET',
'HEAD',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS'
],
additionalMethods: [],
exposedHeaders: [
'WWW-Authenticate',
'Server-Authorization'
Expand Down
23 changes: 12 additions & 11 deletions lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ exports = module.exports = internals.Route = function (options, connection, plug
// Apply settings in order: {connection} <- {handler} <- {realm} <- {route}

var handlerDefaults = Handler.defaults(method, handler, connection.server);
var base = Hoek.applyToDefaultsWithShallow(connection.settings.routes, handlerDefaults, ['bind']);
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind']);
this.settings = Hoek.applyToDefaultsWithShallow(base, options.config || {}, ['bind']);
var base = Hoek.applyToDefaultsWithShallow(connection.settings.routes, handlerDefaults, ['bind', 'cors']);
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind', 'cors']);
this.settings = Hoek.applyToDefaultsWithShallow(base, options.config || {}, ['bind', 'cors']);
this.settings.handler = handler;
this.settings = Schema.apply('routeConfig', this.settings, options.path);

Expand All @@ -65,14 +65,6 @@ exports = module.exports = internals.Route = function (options, connection, plug
this.method = method;
this.plugin = plugin;

this.public = {
method: this.method,
path: this.path,
vhost: this.vhost,
realm: this.plugin.realm,
settings: this.settings
};

this.settings.vhost = options.vhost;
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin name
this.settings.app = this.settings.app || {}; // Route-specific application settings
Expand All @@ -83,6 +75,15 @@ exports = module.exports = internals.Route = function (options, connection, plug
this.params = this._analysis.params;
this.fingerprint = this._analysis.fingerprint;

this.public = {
method: this.method,
path: this.path,
vhost: this.vhost,
realm: this.plugin.realm,
settings: this.settings,
fingerprint: this.fingerprint
};

// Validation

var validation = this.settings.validate;
Expand Down
2 changes: 0 additions & 2 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ internals.routeBase = Joi.object({
maxAge: Joi.number(),
headers: Joi.array(),
additionalHeaders: Joi.array(),
methods: Joi.array(),
additionalMethods: Joi.array(),
exposedHeaders: Joi.array(),
additionalExposedHeaders: Joi.array(),
credentials: Joi.boolean(),
Expand Down
Loading

0 comments on commit c93454c

Please sign in to comment.