spartan-shield
Version:
nodejs project to package and configure common security middleware.
239 lines (233 loc) • 9.94 kB
JavaScript
var secJson = require('../security.json')
let validate = require('validate.js')
let sanitizeHtml = require('sanitize-html')
var whitelists = require('./.whitelists.json')
const tls = require('tls')
/* Module obligations
1. Browser checks?
2. validate connection source (https?)
3. header check => origin, host, referrer
4. javascript, html, css sanitization
5. form inputs
6. sessions (id, cookies)
7. authentication
8. authorization
9. pristine data prior to storage in database
*/
let validationMessages = {}
function validated (whatToValidate, callback) {
if (whatToValidate.includes('header')) {
return function headerCheck (requestHeaders, callback) {
let whitelistRequired = secJson.contentValidationPolicy.semanticValidation.whitelistRequired
// for each header in requestHeaders
for (let header in requestHeaders) {
// check to see if the header requires a whitelist
// then check to see if THAT header's whitelist actually exists
if (whitelistRequired.includes(header)) {
if (whitelists[header]) {
// check to see if the VALUE of THAT header is on the whitelist
if (whitelists[header].includes(requestHeaders[header])) {
// send 'valid' to the callback
callback(null, 'valid')
} else {
// if it isn't, send an error to the callback function
let error = `${ requestHeaders[header] } is invalid in header ${ header } `
error.status = 401
callback(error)
}
} else {
// otherwise, report an error to the call back function that a whitelist for THAT header could not be found
let error = `Unable to check ${ header } 's whitelist to validate ${requestHeaders[header]}`
error.status = 500
callback(error, requestHeaders[header])
}
} else {
// if the header does not require a whitelist, report a MESSAGE in the callback that the header does not require a whitelist and its value was not validated
let message = { [header]: `Header ${header} does not require validation` }
validationMessages = message
callback(null, message)
}
}
}
} else if (whatToValidate.includes('form')) {
return function formValidator(form, rules) {
// build the constraints object for each element in the rules
let constraints = {}
function passwordPolicy() {
return {
presence: true,
length: {
minimum: secJson.accessControlsPolicy.authenticationPolicy.passwords.minLen,
maximum: secJson.accessControlsPolicy.authenticationPolicy.passwords.maxLen
},
format: secJson.accessControlsPolicy.authenticationPolicy.passwords.regex
}
}
for (let element in rules.fields) {
let valid = Object.keys(rules.fields[element].validation)
let values = rules.fields[element].validation
if (rules.fields.password) {
constraints.password = passwordPolicy()
}
if (valid.includes('required')) {
constraints[element] = { presence: true }
}
if (valid.includes('length')) {
constraints[element] = { length: { is: values.length } }
}
if (rules.fields.includes('email') || valid.includes('email')) {
constraints[element] = { email: true }
}
if (valid.includes('matches')) {
constraints[element] = { equality: values.matches }
}
if (valid.includes('excludes')) {
constraints[element] = { exclusion: { within: values.excludes } } // either a list (array) or an object
}
if (valid.includes('format')) {
constraints[element] = { format: values.format } // matches regular expression pattern
}
if (valid.includes('includes')) {
constraints[element] = { inclusion: { within: values.includes } } // either a list (array) or object
}
if (valid.includes('url')) {
constraints[element] = { url: true }
}
let check = validate({ element: form.body[element] }, constraints[element]) // validate that the value for that element matches the constraints defined in the rules
// convert elements to their correct type if needed
}
}
} else if (whatToValidate.includes('authentication')) {
return function authenticationCheck(user, callback) {
}
} else if (whatToValidate.includes('authorization')) {
return function authorizationCheck(role, callback) {
}
} else if (whatToValidate.includes('session')) {
return function sessionCheck(sessionId, callback) {
}
} else if (whatToValidate.includes('connection')) {
return function connectionCheck(requestOrigin, callback) {
// returns an error to the callback if the server is configured in a way that would contribute to a successful downgrade attack.
// Downgrade attacks happen like this:
// 1. Client uses weak or insecure SSL ciphers and the server DOESN'T reject it
let message = `These are the currently supported ciphers: ${tls.getCiphers()}`
validationMessages = { [message]: message }
// 2. Sever offers both HTTP and HTTPS applications and DOES NOT redirect from HTTP
// 3. Server DOES not have HSTS enabled (thus not forcing a redirect from HTTP)
// HOWEVER if the application provides an HTTPS service AND has the HSTS header enabled, this check should pass
}
} else if (whatToValidate.includes('view')) {
return function sanitizeView(html, callback) {
let clean = sanitizeHtml(html)
return clean
}
} else {
let message = 'Validation failed'
return message
}
}
/* -----------------------------------------Content Validation-------------------------------------- */
function contentValidation(obj, rules) {
let constraints = {}
validate.validators.unique = function (value, options, key, attributes) {
console.log(value)
}
function authSchema() {
for (let attrs in rules) {
if (rules[attrs] === 'password' && secJson.accessControlsPolicy.enabled && secJson.accessControlsPolicy.authenticationRequired) {
constraints.password.presence = true
constraints.password.format = {
pattern: secJson.accessControlsPolicy.authenticationPolicy.password.matches,
flags: 'i',
message: 'That is not a valid password'
}
constraints.password.length = {
is: secJson.accessControlsPolicy.authenticationPolicy.password.length,
wrongLength: 'Needs to be %{count} characters'
}
}
if (rules[attrs] === 'email' && secJson.accessControlsPolicy.enabled && secJson.accessControlsPolicy.authenticationRequired) {
constraints.email.presence = true
constraints.email.email = true
}
}
return constraints
}
function syntaxCheck() {
for (let rule in rules) {
for (let object in obj) {
if (rules[rule].type === 'string') {
validate.isString(obj[object])
} else if (rules[rule].type === 'date' || rules[rule].type === 'Date') {
validate.isDate(obj[object])
} else if (rules[rule].type === 'array' || rules[rule].type === 'Array') {
validate.isArray(obj[object])
} else if (rules[rule].type === 'object' || rules[rule].type === 'Object') {
validate.isObject(obj[object])
} else if (rule[rule].type === 'boolean' || rules[rule].type === 'Boolean') {
validate.isBoolean(obj[object])
} else if (rules[rule].type === 'number' || rules[rule].type === 'Number') {
validate.isNumber(obj[object])
} else if (rules[rule].type === 'integer' || rules[rule].type === 'Integer') {
validate.isInteger(obj[object])
} else if (rules[rule].type === 'function' || rules[rule].type === 'Function') {
// Not recommended to allow arbitrary functions to be passed as arguments
validate.isFunction(obj[object])
} else if (typeof obj[object] !== (rules[rule].type).toString()) {
let err = new Error(`Invalid input type. Expected ${rules[rule].type}. Instead found ${(typeof obj[object])}`)
return err
} else {
let error = new Error(`Invalid input type. Expected ${rules[rule].type}. Instead found ${(typeof obj[object])}`)
return error
}
}
}
}
if (!validate.isObj(obj) || !validate.isObj(rules)) {
const err = new Error(`Invalid Type Found. Cannot validate ${obj} against ${rules}`)
return err
} else {
let validationState = {}
// let's build the ruleset
for (let r in rules) {
// check to see if the attribute is required
if (rules[r] === 'required') {
constraints[rules[r]].presence = true
}
// now let's do local auth
for (let p = 0; p < secJson.accessControlsPolicy.authenticationPolicy.supportedMethods.length; p++) {
if (secJson.accessControlsPolicy.authenticationPolicy.supportedMethods[p] === 'uname/passwd' || secJson.accessControlsPolicy.authenticationPolicy.supportedMethods === 'local') {
constraints.password = authSchema().password
constraints.email = authSchema().email
}
}
// now more generic constraints
if (rules[r] === 'unique') {
constraints[rules[r]].unique = true
}
if (rules[r] === 'format') {
constraints[rules[r]].format = rules[r].format
}
if (rules[r] === 'length') {
constraints[rules[r]]['length'].is = rules[r].length
}
if (rules[r] === 'email') {
constraints[rules[r]].email = true
}
if (rules[r] === 'date' || rules[r] === 'datetime') {
constraints[rules[r]].datetime = true
}
if (rules[r] === 'matches') {
constraints[rules[r]].equality = rules[r].matches
}
}
syntaxCheck()
validate(obj, constraints)
return validationState
}
}
module.exports = {
validated: validated
}