UNPKG

sails

Version:

API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)

382 lines (321 loc) 22.6 kB
module.exports = function(sails) { /** * Module dependencies. */ var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var checkOriginUrl = require('../../util/check-origin-url'); var detectVerb = require('../../util/detect-verb'); var initializeCors = require('./cors')(sails); var initializeCsrf = require('./csrf')(sails); var grantCsrfToken = require('./csrf/grant-csrf-token'); /** * Expose hook definition */ return { defaults: { security: { cors: { allowOrigins: '*', allRoutes: false, allowCredentials: false, allowRequestMethods: 'GET,HEAD,PUT,PATCH,POST,DELETE', allowRequestHeaders: 'content-type', allowResponseHeaders: '', allowAnyOriginWithCredentialsUnsafe: false }, csrf: false } }, configure: function() { // ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗██████╗ ███████╗ // ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝ ██║ ██║██╔══██╗██╔════╝ // ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗██║ ██║██████╔╝█████╗ // ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║██║ ██║██╔══██╗██╔══╝ // ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝╚██████╔╝██║ ██║███████╗ // ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ // // ██████╗███████╗██████╗ ███████╗ // ██╔════╝██╔════╝██╔══██╗██╔════╝ // ██║ ███████╗██████╔╝█████╗ // ██║ ╚════██║██╔══██╗██╔══╝ // ╚██████╗███████║██║ ██║██║ // ╚═════╝╚══════╝╚═╝ ╚═╝╚═╝ if (sails.config.csrf) { sails.log.debug('The `sails.config.csrf` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.csrf` instead.'); sails.log.debug('(we\'ll use your `sails.config.csrf` settings for now).\n'); sails.config.security.csrf = sails.config.csrf; } if (sails.config.security.csrf === true && !sails.hooks.session) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error( '\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n'+ 'Detected `sails.config.security.csrf set to `true` while session hook is disabled.\n'+ 'Sails CSRF support requires the session hook to be enabled.\n'+ 'See http://sailsjs.com/docs/reference/config/sails-config-session#?disabling-sessions.\n'+ '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n' )); } if (!_.isUndefined(sails.config.security.csrf.routesDisabled)) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error( '\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n'+ 'Invalid global CSRF settings: `routesDisabled` is no longer supported as of Sails v1.0.\n'+ 'Instead, set `csrf: false` in `config/routes.js` for any route that you want exempted\n'+ 'from CSRF protection.\n'+ 'For more info see: http://sailsjs.com/docs/concepts/security/csrf#?enabling-csrf-protection.\n'+ '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n' )); } if (!_.isUndefined(sails.config.security.csrf.origin)) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error( '\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n'+ 'Invalid global CSRF settings: `origin` is no longer supported as of Sails v1.0.\n'+ 'Instead, apply CORS settings directly to the CSRF-token-dispensing route in `config/routes.js`.\n'+ 'For more info see: \n'+ 'http://next.sailsjs.com/docs/concepts/security/csrf#?using-ajax-websockets\n'+ 'http://next.sailsjs.com/documentation/concepts/security/cors\n'+ '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n' )); } if (!_.isUndefined(sails.config.security.csrf.grantTokenViaAjax)) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error( '\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n'+ 'Invalid global CSRF settings: `grantTokenViaAjax` is no longer supported as of Sails v1.0.\n'+ 'Instead, add a route to your `config/routes.js` file using the `security/grant-csrf-token` action.\n'+ 'For more info see: http://next.sailsjs.com/docs/concepts/security/csrf#?using-ajax-websockets\n'+ '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n' )); } // ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗██████╗ ███████╗ // ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝ ██║ ██║██╔══██╗██╔════╝ // ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗██║ ██║██████╔╝█████╗ // ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║██║ ██║██╔══██╗██╔══╝ // ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝╚██████╔╝██║ ██║███████╗ // ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ // // ██████╗ ██████╗ ██████╗ ███████╗ // ██╔════╝██╔═══██╗██╔══██╗██╔════╝ // ██║ ██║ ██║██████╔╝███████╗ // ██║ ██║ ██║██╔══██╗╚════██║ // ╚██████╗╚██████╔╝██║ ██║███████║ // ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ // ┌─┐┬ ┌─┐┌┐ ┌─┐┬ ┌─┐┌─┐┌┐┌┌─┐┬┌─┐ // │ ┬│ │ │├┴┐├─┤│ │ │ ││││├┤ ││ ┬ // └─┘┴─┘└─┘└─┘┴ ┴┴─┘ └─┘└─┘┘└┘└ ┴└─┘ if (!_.isUndefined(sails.config.cors)) { sails.log.debug('The `sails.config.cors` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors` instead.'); sails.log.debug('(we\'ll use your `sails.config.cors` settings for now).\n'); sails.config.security.cors = _.extend(sails.config.security.cors, sails.config.cors); } // Fail to lift if `securityLevel` is used if (!_.isUndefined(sails.config.security.cors.securityLevel)) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error( '\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n'+ 'Invalid global CORS settings: `securityLevel` is no longer supported as of Sails v1.0.\n'+ 'Instead, to secure your socket requests use `sails.config.sockets.onlyAllowOrigins`.\n'+ 'For more info see: http://sailsjs.com/config/sockets.\n'+ '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n' )); } // Deprecate `origin` in favor of `allowOrigins` if (!_.isUndefined(sails.config.security.cors.origin)) { sails.log.debug('The `sails.config.security.cors.origin` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors.allowOrigins` instead.'); sails.log.debug('(See http://sailsjs.com/config/security for more info.)'+'\n'); sails.config.security.cors.allowOrigins = sails.config.security.cors.origin; delete sails.config.security.cors.origin; } // Deprecate declaring `allowOrigins` as a string (except for '*'). if (_.isString(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*') { sails.log.debug('When specifying multiple origins, the `sails.config.security.cors.allowOrigins`'); sails.log.debug('setting should be an array of strings. We\'ll split it up for you this time...\n'); sails.config.security.cors.allowOrigins = _.map(sails.config.security.cors.allowOrigins.split(','), function(origin){ return origin.trim(); }); } // Bail out if `allowOrigins` is not an array or `*`. else if (!_.isUndefined(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*' && !_.isArray(sails.config.security.cors.allowOrigins)) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS settings: if `allowOrigins` is specified, it must be either \'*\' or an array of strings. See http://sailsjs.com/config/security for more info.')); } // Deprecate `credentials` in favor of `allowCredentials` if (!_.isUndefined(sails.config.security.cors.credentials)) { sails.log.debug('The `sails.config.security.cors.credentials` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors.allowCredentials` instead.\n'); sails.config.security.cors.allowCredentials = sails.config.security.cors.credentials; delete sails.config.security.cors.credentials; } // Deprecate `headers` in favor of `allowRequestHeaders` if (!_.isUndefined(sails.config.security.cors.headers)) { sails.log.debug('The `sails.config.security.cors.headers` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors.allowRequestHeaders` instead.\n'); sails.config.security.cors.allowRequestHeaders = sails.config.security.cors.headers; delete sails.config.security.cors.headers; } // Deprecate `methods` in favor of `allowRequestMethods` if (!_.isUndefined(sails.config.security.cors.methods)) { sails.log.debug('The `sails.config.security.cors.methods` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors.allowRequestMethods` instead.\n'); sails.config.security.cors.allowRequestMethods = sails.config.security.cors.methods; delete sails.config.security.cors.methods; } // Deprecate `sails.config.cors.exposeHeaders` in favor of `sails.config.cors.allowResponseHeaders` if (!_.isUndefined(sails.config.security.cors.exposeHeaders)) { sails.log.debug('The `sails.config.security.cors.exposeHeaders` config has been deprecated.'); sails.log.debug('Please use `sails.config.security.cors.allowResponseHeaders` instead.\n'); if (!sails.config.security.cors.allowResponseHeaders) { sails.config.security.cors.allowResponseHeaders = sails.config.security.cors.exposeHeaders; } delete sails.config.security.cors.exposeHeaders; } // Split up non-* strings into an array. // We'll complain about this later when we actually act on the route's CORS config // rather than just validating it. if (_.isString(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*') { sails.log.debug('When specifying multiple allowable CORS origins, the sails.config.security.cors.allowOrigins setting'); sails.log.debug('should be an array of strings. We\'ll split it up for you this time...\n'); sails.config.security.cors.allowOrigins = _.map(sails.config.security.cors.allowOrigins.split(','), function(origin){ return origin.trim(); }); } // If `allowOrigins` is not `*` and not an array at this point, bail. else if (sails.config.security.cors.allowOrigins && sails.config.security.cors.allowOrigins !== '*' && !_.isArray(sails.config.security.cors.allowOrigins)) { throw flaverr({ name: 'userError', code: 'E_BAD_ORIGIN_CONFIG' }, new Error('Invalid global CORS settings: if `sails.config.security.cors.allowOrigins` is specified, it must be \'*\' or an array of strings.')); } // Validate the passed-in origins. // `checkOriginUrl` will throw if any origins are poorly-formed. if (_.isArray(sails.config.security.cors.allowOrigins)) { try { _.each(sails.config.security.cors.allowOrigins, function(origin) { checkOriginUrl(origin); }); } catch (e) { // If we got a poorly-formed origin, throw a more descriptive error. if (e.code === 'E_INVALID') { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS `allowOrigins` setting: ' + e.message+' (See http://sailsjs.com/config/security for help.)')); } // Otherwise just throw whatever error we got. throw e; } } // If the app attempts to set `allowOrigins: '*'` and `allowCredentials: true`, bail out if (sails.config.security.cors.allowOrigins === '*' && sails.config.security.cors.allowCredentials === true) { if (sails.config.security.cors.allowAnyOriginWithCredentialsUnsafe !== true) { throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS settings: if `allowOrigins` is \'*\', `allowCredentials` cannot also be `true` (unless you enable the `allowAnyOriginWithCredentialsUnsafe` flag). For more info, see http://sailsjs.com/config/security.')); } sails.config.security.cors.allowOrigins = true; } // If we're operating in unsafe mode, and origin is '*' and credentials is `true`, // set the default origin to `true` as well which means "reflect origin header". if (sails.config.security.cors.allowAnyOriginWithCredentialsUnsafe && sails.config.security.cors.credentials === true && sails.config.security.cors.allowOrigins === '*') { sails.config.security.cors.allowOrigins = true; } // ┬─┐┌─┐┬ ┬┌┬┐┌─┐ ┌─┐┌─┐┌┐┌┌─┐┬┌─┐ // ├┬┘│ ││ │ │ ├┤ │ │ ││││├┤ ││ ┬ // ┴└─└─┘└─┘ ┴ └─┘ └─┘└─┘┘└┘└ ┴└─┘ // Loop through all of the explicitly-configured routes and look for // deprecated config and/or fatal config issues. _.each(sails.config.routes, function(routeConfig, address) { // Get some info about the route, like its path and verb. // This is used in console messages. var routeInfo = detectVerb(address); var path = routeInfo.original.toLowerCase(); var verb = routeInfo.verb.toLowerCase(); // If this route doesn't have a CORS config, continue. if (!_.isPlainObject(routeConfig.cors)) { return; } // Get a reference to the route CORS config, so that we don't // accidentally mess with routeConfig instead. var routeCorsConfig = routeConfig.cors; // Handle deprecated config. // Deprecate `origin` in favor of `allowOrigins` if (!_.isUndefined(routeCorsConfig.origin)) { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('The `cors.origin` config has been deprecated.'); sails.log.debug('Please use `cors.allowOrigins` instead.'); sails.log.debug('(See http://sailsjs.com/config/security for more info.)'+'\n'); routeCorsConfig.allowOrigins = routeCorsConfig.origin; delete routeCorsConfig.origin; } // Deprecate `credentials` in favor of `allowCredentials` if (!_.isUndefined(routeCorsConfig.credentials)) { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('The `cors.credentials` config has been deprecated.'); sails.log.debug('Please use `cors.allowCredentials` instead.\n'); routeCorsConfig.allowCredentials = routeCorsConfig.credentials; delete routeCorsConfig.credentials; } // Deprecate `headers` in favor of `allowRequestHeaders` if (!_.isUndefined(routeCorsConfig.headers)) { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('The `cors.headers` config has been deprecated.'); sails.log.debug('Please use `cors.allowRequestHeaders` instead.\n'); routeCorsConfig.allowRequestHeaders = routeCorsConfig.headers; delete routeCorsConfig.headers; } // Deprecate `methods` in favor of `allowRequestMethods` if (!_.isUndefined(routeCorsConfig.methods)) { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('The `cors.methods` config has been deprecated.'); sails.log.debug('Please use `cors.allowRequestMethods` instead.\n'); routeCorsConfig.allowRequestMethods = routeCorsConfig.methods; delete routeCorsConfig.methods; } // Deprecate `sails.config.cors.exposeHeaders` in favor of `sails.config.cors.allowResponseHeaders` if (!_.isUndefined(routeCorsConfig.exposeHeaders)) { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('The `cors.exposeHeaders` config has been deprecated.'); sails.log.debug('Please use `cors.allowResponseHeaders` instead.\n'); if (!routeCorsConfig.allowResponseHeaders) { routeCorsConfig.allowResponseHeaders = routeCorsConfig.exposeHeaders; } delete routeCorsConfig.exposeHeaders; } // Apply the global CORS settings as defaults for the route CORS config. routeCorsConfig = _.defaults(routeCorsConfig, sails.config.security.cors); // Bail if `allowOrigins` is `*`, `allowCredentials` is `true` and `allowAnyOriginWithCredentialsUnsafe` is not true. if (routeCorsConfig.allowOrigins === '*' && routeCorsConfig.allowCredentials === true && routeCorsConfig.allowAnyOriginWithCredentialsUnsafe !== true) { throw flaverr({ name: 'userError', code: 'E_UNSAFE'}, new Error('Route `' + address + '` has invalid CORS settings: if `allowOrigins` is \'*\', `credentials` cannot be `true` unless `allowAnyOriginWithCredentialsUnsafe` is also true.')); } // Split up non-* strings into an array. if (_.isString(routeCorsConfig.allowOrigins) && routeCorsConfig.allowOrigins !== '*') { sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: '); sails.log.debug('When specifying multiple allowable CORS origins, the allowOrigins setting'); sails.log.debug('should be an array of strings. We\'ll split it up for you this time...\n'); routeCorsConfig.allowOrigins = _.map(routeCorsConfig.allowOrigins.split(','), function(origin){ return origin.trim(); }); } // If `allowOrigins` is not `*` and not an array at this point, bail. else if (routeCorsConfig.allowOrigins && routeCorsConfig.allowOrigins !== '*' && !_.isArray(routeCorsConfig.allowOrigins)) { throw flaverr({ name: 'userError', code: 'E_BAD_ORIGIN_CONFIG'}, new Error('Route `' + address + '` has invalid CORS settings: if `allowOrigins` is specified, it must be \'*\' or an array of strings.')); } // If `allowOrigins` is an array, loop through and validate each origin. if (_.isArray(routeCorsConfig.allowOrigins)) { try { _.each(routeCorsConfig.allowOrigins, function(origin) { checkOriginUrl(origin); }); } // If an error occurred validating an origin, forward it up the chain. catch (e) { // If it's an actual origin validation error, gussy it up first. if (e.code === 'E_INVALID') { throw flaverr({ name: 'userError', code: 'E_INVALID_ORIGIN'}, new Error('Route `' + address + '` has invalid CORS `allowOrigins` setting: ' + e.message)); } // Otherwise just throw whatever error we got. throw e; } } }); }, initialize: function(cb) { try { initializeCors(); initializeCsrf(); return sails.hooks.security.registerActions(cb); } catch (err) { return cb(err); } }, registerActions: function(cb) { // Add the csrf-token-granting action (see below for the function definition). sails.registerAction(grantCsrfToken, 'security/grant-csrf-token'); return cb(); } }; };