Skip to content
This repository has been archived by the owner on Dec 20, 2019. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 10, 2016
0 parents commit 7591c4c
Show file tree
Hide file tree
Showing 7 changed files with 575 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
node_modules
coverage
.git
npm-debug.log
6 changes: 6 additions & 0 deletions .npmignore
@@ -0,0 +1,6 @@
node_modules
coverage
test
examples
npm-debug.log
.editorconfig
29 changes: 29 additions & 0 deletions package.json
@@ -0,0 +1,29 @@
{
"name": "node-csp",
"version": "1.0.0",
"description": "I/O module to add csp headers to http response",
"main": "index.js",
"directories": {},
"scripts": {
"test": "_mocha test/ --bail",
"coverage": "istanbul cover _mocha test/"
},
"keywords": [
"csp"
],
"author": "amanvirk",
"license": "MIT",
"devDependencies": {
"chai": "^3.4.1",
"co-mocha": "^1.1.2",
"coveralls": "^2.11.6",
"istanbul": "^0.4.1",
"mocha": "^2.3.4",
"mocha-lcov-reporter": "^1.0.0",
"standard": "^5.4.1",
"supertest": "^1.1.0"
},
"dependencies": {
"platform": "^1.3.1"
}
}
146 changes: 146 additions & 0 deletions src/Csp/headers.js
@@ -0,0 +1,146 @@
'use strict'

/**
* node-scp
* Copyright(c) 2016-2016 Harminder Virk
* MIT Licensed
*/

let headers = exports = module.exports = {}

/**
* @description list of csp headers used by different browsers
* @type {Array}
*/
const cspHeaders = [
'Content-Security-Policy',
'X-Content-Security-Policy',
'X-WebKit-CSP'
]

/**
* @description returns headers required by IE
* @method IE
* @param {Object} browser
* @return {Array}
* @public
*/
headers.IE = function (browser) {
const version = parseFloat(browser.version)
if(version < 12 & version > 9) {
return ['X-Content-Security-Policy']
} else if(version > 12) {
return ['Content-Security-Policy']
} else {
return []
}
}

/**
* @description returns headers required by FireFox
* @method FireFox
* @param {Object} browser
* @return {Array}
* @public
*/
headers.Firefox = function (browser) {
const version = parseFloat(browser.version)
if (version >= 23) {
return ['Content-Security-Policy']
} else if (version >=4 && version < 23) {
return ['X-Content-Security-Policy']
} else {
return []
}
}

/**
* @description returns headers required by FireFox
* @method Chrome
* @param {Object} browser
* @return {Array}
* @public
*/
headers.Chrome = function (browser) {
const version = parseFloat(browser.version)
if (version >= 14 && version < 25) {
return ['X-WebKit-CSP']
} else if (version >= 25) {
return ['Content-Security-Policy']
} else {
return []
}
}

/**
* @description returns headers required by FireFox
* @method Safari
* @param {Object} browser
* @return {Array}
* @public
*/
headers.Safari = function (browser) {
const version = parseFloat(browser.version)
if (version >= 7) {
return ['Content-Security-Policy']
} else if (version >= 6) {
return ['X-WebKit-CSP']
} else {
return []
}
}

/**
* @description returns headers required by FireFox
* @method Opera
* @param {Object} browser
* @return {Array}
* @public
*/
headers.Opera = function (browser) {
const version = parseFloat(browser.version)
return version >= 15 ? ['Content-Security-Policy'] : []
}

/**
* @descrption returns headers required by Android Browser
* @method Android Browser
* @param {Object} browser
* @param {Object} options
* @return {Array}
* @public
*/
headers['Android Browser'] = function (browser, options) {
const version = parseFloat(browser.os.version)
return (version < 4.4 || options.disableAndroid) ? [] : ['Content-Security-Policy']
}

/**
* @description returns headers required by Chrome Mobile
* @method Chrome Mobile
* @param {Object} browser
* @return {Array}
* @public
*/
headers['Chrome Mobile'] = function (browser) {
return browser.os.family === 'iOS' ? ['Content-Security-Policy'] : headers['Android Browser'].apply(this, arguments)
}

/**
* @description returns headers required by IE Mobile
* @method IE Mobile
* @param {Object} browser
* @return {Array}
* @public
*/
headers['IE Mobile'] = headers.IE

/**
* @description returns all csp headers
* @method getAllHeaders
* @return {Array}
* @public
*/
headers.getAllHeaders = function () {
return cspHeaders
}
135 changes: 135 additions & 0 deletions src/Csp/index.js
@@ -0,0 +1,135 @@
'use strict'

/**
* node-csp
* Copyright(c) 2016-2016 Harminder Virk
* http://www.kayako.com/license
*/

const platform = require('platform')
const headers = require('./headers')

let Csp = exports = module.exports = {}

/**
* Add quotes to below keywords
* @example
* self becomes 'self'
* @type {RegExp}
*/
const keywords = /(none|self|unsafe-inline|unsafe-eval)/g

/**
* @description list of allowed directives by all browsers
* @type {Array}
*/
const directivesList = [
'base-uri',
'child-src',
'connect-src',
'default-src',
'font-src',
'form-action',
'frame-ancestors',
'frame-src',
'img-src',
'media-src',
'object-src',
'plugin-types',
'report-uri',
'style-src',
'script-src',
'upgrade-insecure-requests'
]

/**
* @description adds csp policy to http response
* @method add
* @param {Object} request
* @param {Object} response
* @param {Ojbect} options
* @public
*/
Csp.add = function (request, response, options) {
const cspHeaders = Csp.build(request, options)
const headerKeys = Object.keys(cspHeaders)
if(headerKeys.length) {
headerKeys.forEach(function (key) {
response.setHeader(key, cspHeaders[key])
})
}
}

/**
* @description builds the final object to be used
* by response for adding csp header
* @method build
* @param {Object} request
* @param {Object} options
* @return {Object}
* @public
*/
Csp.build = function (request, options) {
const userAgent = request.headers['user-agent']
let cspHeaders = headers.getAllHeaders()

if(userAgent && !options.setAllHeaders) {
const browser = platform.parse(userAgent) || {}
cspHeaders = typeof(headers[browser.name]) === 'function' ? headers[browser.name](browser, options) : headers.getAllHeaders()
}

if(!cspHeaders.length) {
return {}
}

const cspString = Csp._quoteKeywords(Csp._formatDirectives(options.directives))

if(cspString.trim().length <= 0) {
return {}
}

let cspBuildHeaders = {}
cspHeaders.forEach(function (headerKey) {
if(options.reportOnly) {
headerKey += '-Report-Only'
}
cspBuildHeaders[headerKey] = cspString
})

return cspBuildHeaders
}

/**
* @description format directives objects to a string
* to be consumed by web browsers.
* @method _formatDirectives
* @param {Object} directives
* @return {String}
* @public
*/
Csp._formatDirectives = function (directives) {
const directiveNames = Object.keys(directives)
let cspString = ''
directiveNames.forEach(function (name) {
const directive = directives[name]
if(directivesList.indexOf(name) <= -1) {
throw new Error(`invalid directive: ${name}`)
}
cspString += `${name} ${directive.join(' ')}; `
})
return cspString
}

/**
* @description add quotes to special keywords using
* regex.
* @method _quoteKeywords
* @param {String} cspString
* @return {String}
* @public
*/
Csp._quoteKeywords = function (cspString) {
return cspString.replace(keywords, function (index, group) {
return `'${group}'`
})
}

0 comments on commit 7591c4c

Please sign in to comment.