express-standard
Version:
Easy method to define standard express headers and assist with Content-Security-Policy including social media widgets
248 lines (199 loc) • 6.59 kB
JavaScript
// express-standard
// Easy method to define standard express headers
var fs = require('fs')
, _headers = {}
, _header_list = []
, _csp = {
}
, _csp_report_only = {
}
, csp_field = 'Content-Security-Policy'
, csp_report_field = 'Content-Security-Policy-Report-Only'
, src_areas = ['default','object','script','style','img','media','frame','font']
, handle = function handle(req, res, next) {
for(var i=0; i<_header_list.length; i++) {
var item = _header_list[i];
res.setHeader(item.name, item.value);
}
next();
}
function add(headers) {
headers = headers || {};
_header_list = [];
var headerNames = Object.getOwnPropertyNames(headers);
headerNames.forEach(function(name) {
var value = headers[name];
_headers[name] = value;
});
headerNames = Object.getOwnPropertyNames(_headers);
headerNames.forEach(function(name) {
var value = _headers[name];
_header_list.push({
name: name,
value: value
});
});
return handle;
}
function get() {
return _headers;
}
function set(headers) {
_headers = headers;
_csp = {};
_csp_report_only = {};
return add(headers);
}
function powered_by(value) {
_headers["x-powered-by"] = value;
return add(_headers);
}
function app_powered_by(includeVersion) {
if (fs.existsSync(__dirname + '/package.json')) {
var appPackage = require(__dirname + '/package.json');
var name = (includeVersion) ? appPackage.name + '/' + appPackage.version : appPackage.name;
return powered_by(name);
} else {
console.log(__dirname + '/package.json missing');
}
}
function add_csp_headers(reportOnly) {
var csp = (reportOnly) ? _csp_report_only : _csp
, header_field = (reportOnly) ? csp_report_field : csp_field;
var header = [];
var headerNames = Object.getOwnPropertyNames(csp);
headerNames.forEach(function(name) {
var value = csp[name];
header.push(name + ' ' + value);
});
_headers[header_field] = header.join('; ');
return add({});
}
function add_csp(area, value, reportOnly) {
if (typeof value === 'undefined') {
value = area;
area = 'default-src';
}
reportOnly = reportOnly || false;
if (area == '*') {
area = src_areas;
}
if (area.indexOf(',')>0) {
var areas = area.split(',');
for (var i=0; i<areas.length; i++) {
area = areas[i];
add_csp(area, value, reportOnly);
}
return handle;
}
var areas = ',' + src_areas.join(',');
if (areas.indexOf(area)>0) {
area = area + '-src';
}
if (reportOnly) {
_csp_report_only[area] = (_csp_report_only[area]) ? _csp_report_only[area] + ' ' + value : value ;
} else {
_csp[area] = (_csp[area]) ? _csp[area] + ' ' + value : value ;
}
return add_csp_headers(reportOnly);
}
function add_csp_report(area, value) {
return add_csp(area, value, true);
}
function add_csp_self(area) {
if (area) {
return add_csp(area, "'self'");
} else {
return add_csp("'self'");
}
}
function add_csp_domain(area, domain, protocols, subdomains) {
if (typeof domain == 'undefined') {
domain = area;
area = 'default-src';
}
subdomains = subdomains || false;
if (typeof protocols == 'undefined') {
protocols = ['http://', 'https://']; // 'ws://', 'wss://'
} else if (typeof protocols == 'string') {
protocols = protocols.split(',');
}
if (typeof domain == 'string' && domain.indexOf(',')>-1) {
domain = domain.split(',');
}
if (typeof domain == 'array' || (typeof domain == 'object' && domain.length>0)) {
for(var i=0; i<domain.length; i++) {
var dom = domain[i];
add_csp_domain(area, dom, protocols, subdomains);
}
return handle;
}
if (subdomains) {
domain = '*.' + domain;
}
if (domain.indexOf('//')>-1) { // specific domain and protocol
return add_csp(area, domain);
}
for(var i=0; i<protocols.length; i++) {
var protocol = protocols[i];
if (protocol.indexOf('://')<0) {
protocol = protocol + '://';
}
add_csp(area, protocols[i] + domain);
}
return handle;
}
// this is best avoided as per: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#directives
function add_csp_allow_unsafe(area, notEval) {
area = area || 'script-src,style-src,img-src';
if (!notEval) {
add_csp('script-src', "'unsafe-eval'");
}
add_csp('img', 'data:'); // allow embedded base64 images
return add_csp(area, "'unsafe-inline'");
}
// attribution: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#use-case-1-social-media-widgets
function add_csp_social_widgets(widgets) {
widgets = widgets || 'facebook,twitter,google+';
if (typeof widgets == 'array' || (typeof widgets == 'object' && widgets.length>0)) {
widgets = widgets.join(',');
}
if (widgets.indexOf('facebook')>-1) {
add_csp('script-src', 'http://graph.facebook.com');
add_csp('script-src', 'https://graph.facebook.com');
add_csp('frame-src', 'https://facebook.com');
}
if (widgets.indexOf('twitter')>-1) {
add_csp('script-src,frame-src', 'https://platform.twitter.com');
add_csp('script-src', 'http://urls.api.twitter.com');
}
if (widgets.indexOf('google+')>-1) {
add_csp('script-src', 'https://apis.google.com');
add_csp('frame-src', 'https://plusone.google.com');
}
return handle;
}
function add_csp_youtube() {
add_csp('script-src', 'https://youtube.com');
add_csp_domain('img-src', '*.ytimg.com', ['http://','https://']);
add_csp_domain('frame-src', 'www.youtube.com', ['http://','https://']);
}
function add_csp_firebase() {
add_csp('script-src,frame-src', 'https://*.firebaseio.com');
add_csp('script-src', 'https://cdn.firebase.com');
add_csp('connect-src', 'wss://*.firebaseio.com');
}
module.exports.add = add;
module.exports.add_csp = add_csp;
module.exports.add_csp_self = add_csp_self;
module.exports.add_csp_report = add_csp_report;
module.exports.add_csp_domain = add_csp_domain;
module.exports.add_csp_youtube = add_csp_youtube;
module.exports.add_csp_firebase = add_csp_firebase;
module.exports.add_csp_allow_unsafe = add_csp_allow_unsafe;
module.exports.add_csp_social_widgets = add_csp_social_widgets;
module.exports.set = set;
module.exports.get = get;
module.exports.handle = handle;
module.exports.powered_by = powered_by;
module.exports.app_powered_by = app_powered_by;