UNPKG

headgear

Version:

Sets various security related headers

271 lines (218 loc) 6.49 kB
'use strict'; var dashify = require('dashify'); // Taken from // https://www.owasp.org/index.php/List_of_useful_HTTP_headers // https://en.wikipedia.org/wiki/List_of_HTTP_header_fields // http://www.html5rocks.com/en/tutorials/security/content-security-policy/ // http://content-security-policy.com/ // https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives // https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning var Headgear = function() {}; /** * Removes the powered by header. */ Headgear.prototype.removePoweredBy = function() { return function(request, response, next) { response.removeHeader('X-Powered-By'); next(); }; }; /** * Sets the content type option to nosniff. */ Headgear.prototype.noSniff = function() { return function(request, response, next) { response.setHeader('X-Content-Type-Options', 'nosniff'); next(); }; }; /** * Sets the frame option header. * @param {String} option The option string. * @param {String} allowed The allowed from url. */ Headgear.prototype.frameOption = function(option, allowed) { var available = ['deny', 'sameorigin', 'allow-from', 'allowall']; option = option || 'sameorigin'; if(!~available.indexOf(option)) { throw new Error('option can only be deny, sameorigin, allow-from or allowall'); } if(option === 'allow-from') { if(typeof allowed !== 'string') { throw new Error('allowed must be a string'); } else { option = option + ' ' + allowed; return function(request, response, next) { response.setHeader('X-Frame-Options', option); response.setHeader('Frame-Options', option); next(); }; } } else { return function(request, response, next) { response.setHeader('X-Frame-Options', option); response.setHeader('Frame-Options', option); next(); }; } }; /** * Sets the download option to noopen. */ Headgear.prototype.downloadOption = function() { return function(request, response, next) { response.setHeader('X-Download-Options', 'noopen'); next(); }; }; /** * Sets the transport security header. * @param {Number} seconds The number of seconds to continue using HTTPS. * @param {Boolean} withSubdomains Whether or not to include subdomains. */ Headgear.prototype.transportSecurity = function(seconds, withSubdomains) { seconds = seconds || 31536000; withSubdomains = withSubdomains == null ? true : withSubdomains; return function(request, response, next) { var val = 'max-age=' + seconds; if(withSubdomains) { response.setHeader('Strict-Transport-Security', val + '; includeSubDomains;'); } else { response.setHeader('Strict-Transport-Security', val); } next(); }; }; /** * Sets the xss protection header. */ Headgear.prototype.xssProtect = function() { return function(request, response, next) { response.setHeader('X-XSS-Protection', '1; mode=block;'); next(); }; }; /** * Sets the cache control header to no-cache. */ Headgear.prototype.noCache = function() { return function(request, response, next) { response.setHeader('Cache-Control', 'no-cache'); next(); }; }; /** * Sets the content security policy header. * @param {Object} options The object containing the header options. */ Headgear.prototype.contentSecurity = function(options) { if(options === null) { throw new Error('options can not be null'); } if(typeof options !== 'object') { throw new Error('options must be an object'); } var needWrapped = ['none', 'self', 'unsafe-inline', 'unsafe-eval']; var header = 'Content-Security-Policy'; if(options.report) { header += '-Report-Only'; } delete options.report; var applied = {}; var result = ''; Object.keys(options).forEach(function(key) { if(!applied[key]) { applied[key] = true; var val = dashify(key) + ' '; if(key === 'upgradeInsecureRequests') { if(options[key]) { val += '1; '; } else { val += '0; '; } } else { if(Array.isArray(options[key])) { for(var i=0, len=options[key].length; i < len; i++) { if(~needWrapped.indexOf(options[key][i])) { options[key][i] = '\'' + options[key][i] + '\''; } } val += options[key].join(' ') + '; '; } else { val += options[key] + '; '; } } result += val; } }); return function(request, response, next) { response.setHeader(header, result); next(); }; }; /** * Sets the public key pinning header. * @param {Array} keys An array of base64 encoded SHA256 keys. * @param {Number} maxAge The max age, in seconds, remember the site. * @param {Boolean} subdomains Whether or not to include subdomains. * @param {String} reportUrl The failure reporting URL. */ Headgear.prototype.keyPinning = function(keys, maxAge, subdomains, reportUrl) { if(keys === null) { throw new Error('keys can not be null'); } if(!Array.isArray(keys)) { throw new Error('keys must be an array'); } if(keys.length < 1) { throw new Error('keys must have at least one value'); } if(!maxAge) { throw new Error('maxAge can not be null'); } var val =''; var header = 'Public-Key-Pins'; if(reportUrl) { header += '-Report-Only'; } for(var i=0,len=keys.length; i < len; i++) { val += 'pin-sha256="' + keys[i] + '"; '; } val += 'max-age=' + (maxAge * 1) + '; '; if(subdomains) { val += 'includeSubdomains; '; } if(reportUrl) { val += 'report-uri="' + reportUrl + '"; '; } return function(request, response, next) { response.setHeader(header, val); next(); }; }; /** * Sets several of the above headers. * @param {Object} options The config object. */ Headgear.prototype.all = function(options) { if(options === null) { throw new Error('options can not be null'); } var _this = this; var connect = require('connect'); var con = connect(); con.use(_this.removePoweredBy()); con.use(_this.noSniff()); con.use(_this.downloadOption()); con.use(_this.xssProtect()); con.use(_this.frameOption(options.frameOption)); con.use(_this.transportSecurity(options.transportSecurity.seconds, options.transportSecurity.hasSubdomains)); return con; }; module.exports = new Headgear();