spartan-shield
Version:
nodejs project to package and configure common security middleware.
359 lines (356 loc) • 12.2 kB
JavaScript
let chai = require('chai'),
expect = chai.expect,
assert = chai.assert,
chaiHttp = require('chai-http'),
fs = require('fs'),
path = require('path'),
{ app } = require('../app'),
{ checkConnection,
checkHeaders,
checkBrowser,
checkData,
checkUploads,
upload } = require('../validation'),
headers = {
'content-type': 'application/json',
'host': 'localhost:5000',
'connection': 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
'dnt': '1'
},
file = {
foto:
{
name: 'baddies.gif',
data:
`<Buffer 47 49 46 38 39 61 f4 01 0c 01 f7 ff 00 24 42 46 99 63 5c 05 08 10 b3 d5 ef 86 84 89 72 7a 85 22 39 43 09 18 22 a8 a6 aa 23 35 37 76 75 79 cc dc ed 72 ... >`,
size: 1048382,
encoding: '7bit',
tempFilePath: '',
truncated: false,
mimetype: 'image/gif',
md5: 'e7a6e5242a4bd8141d7227c38b32f684'
// mv: [Function: mv]
}
}
chai.use(chaiHttp)
describe('VALIDATION OF INPUT', () => {
describe('validate connection', () => {
let allowed = { allowedSchemes: ['ftp', '*'] },
connRequest
before(() => {
connRequest = chai.request(app)
})
it('should be able to parse and match request\'s protocol against a predetermined list', () => {
connRequest.get('/').then(response => {
checkConnection(response.req.agent, allowed, (err, success) => {
if (err) {
console.log(`${err.message}`)
expect(success).to.be.equal(false)
}
else {
console.log(`${response.req.agent.protocol} was matched in ${allowed.allowedSchemes}`)
expect(success).to.equal(true)
}
})
}).catch(e => {
console.log(e.stack)
})
})
})
describe('validate headers', () => {
let headerChecks
beforeEach(() => {
headerChecks = chai.request(app)
})
it('should have headers', done => {
headerChecks.get('/').end((err, response) => {
expect(response.req, 'bad bad not good').to.have.headers
done()
})
})
it('should check that headers match whitelist', done => {
headerChecks.get('/').end((err, response) => {
if (err) {
console.log(err.message)
expect(err).to.throw(err.message, 'There was a problem')
} else {
response.req.headers = headers
checkHeaders(response.req, null, (error, success) => {
if (error) {
console.log(error.message)
expect(success).to.be.false
} else {
console.log('we did it!')
expect(success).to.be.true
}
})
}
done()
})
})
it('should allow whitelist to be overridden', done => {
headerChecks.get('/').end((err, response) => {
if (err) {
console.log(err.message)
expect(err).to.throw(err.message, 'There was a problem')
} else {
response.req.headers = headers
checkHeaders(response.req, response.req.headers, (error, success) => {
if (error) {
console.log(error.message)
expect(success).to.be.false
} else {
console.log('we did it!')
expect(success).to.be.true
}
})
}
done()
})
})
it('should fail if the whitelist is blank', done => {
headerChecks.get('/').end((err, response) => {
if (err) {
console.log(err.message)
expect(err).to.throw(err.message, 'There was a problem')
} else {
response.req.headers = headers
checkHeaders(response.req, " ", (error, success) => {
if (error) {
console.log(error.message)
expect(success).to.be.false
} else {
console.log('we did it!')
expect(success).to.be.true
}
})
}
done()
})
})
})
describe('validate browser', () => {
let browserChecks
let whitelists = require('../security/.whitelists.json')
beforeEach(async () => {
browserChecks = await chai.request(app)
})
after(async () => {
whitelists['user-agent'] = []
fs.writeFile(path.join(__dirname, '../security/.whitelists.json'), JSON.stringify(whitelists), err => {
if (err) { console.log(err.message) }
})
})
it('should have user-agent header', done => {
browserChecks.get('/').end((err, response) => {
expect(response.req, 'bad bad not good').to.have.header('user-agent')
done()
})
})
it('should add new user-agents to the whitelist in monitor mode', done => {
browserChecks.get('/').end((err, response) => {
response.req.headers = headers
checkBrowser(response.req, 'monitor', (error, success, message) => {
if (error) {
console.log(error.code, error.message)
expect(success).to.be.false
} else {
expect(whitelists['user-agent'].length).to.equal(1)
assert.include(whitelists['user-agent'], 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36', `${message}`)
expect(success).to.be.true
}
})
done()
})
})
it('should block user-agents NOT on the whitelist in enforce mode', done => {
browserChecks.get('/').end((err, response) => {
response.req.headers = headers
response.req.headers['user-agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0'
checkBrowser(response.req, 'enforce', (error, success) => {
if (error) {
console.log(error.code, error.message)
expect(success).to.be.false
} else {
console.log(`Found user agent ${headers['user-agent']} on the list of allowed browsers`)
assert.notInclude(whitelists['user-agent'], 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0', 'value not in array')
expect(success).to.be.true
}
})
done()
})
})
})
describe('validate request', () => {
// stretch goal
it('should validate that the request is properly formmated')
it('should detect & prevent use of and execution of code in the browser')
})
describe('validate data', () => {
let data,
rules,
dCheck
beforeEach(() => {
dCheck = chai.request(app)
data = {
firstname: 'John',
lastname: 'Doe',
email: 'jdoe@email.com'
},
rules = {
firstname: {
presence: true,
format: {
pattern: "[a-zA-Z'-]+",
flags: "i",
message: 'only letters apostrophes and dashes allowed'
},
length: {
minimum: 10
}
},
lastname: {
presence: {
allowEmpty: true
},
format: {
pattern: "[a-zA-Z'-]+",
flags: "i",
message: 'only letters apostrophes and dashes allowed'
},
length: {
maximum: 10,
tooLong: 'Sorry, the last name is too long'
}
},
email: {
email: true,
presence: true
}
}
})
it('should check that the data matches a given ruleset', () => {
dCheck.get('/').then(response => {
response.req.headers = headers
checkData(response.req, data, rules, (error, success) => {
if (error) {
console.log(error.code + " : " + error.message)
expect(success).to.be.false
} else {
console.log('no problem, bro')
expect(success).to.be.true
}
})
}).catch(e => console.log(e.message))
})
it('data and ruleset MUST be non-empty objects', () => {
dCheck.get('/thanks').then(response => {
response.req.headers = headers
checkData(response.req, {}, {}, (err, success) => {
if (err) {
console.log(err.code + ' : ' + err.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
}).catch(e => console.log(e.message))
})
it('for every data value, there MUST be a matching rule', () => {
dCheck.get('/register').then(response => {
response.req.headers = headers
checkData(response.req, data, rules.firstname, (err, success) => {
if (err) {
console.log(err.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
}).catch(e => console.log(e.message))
})
})
describe('validate uploads', () => {
let uploadCheck
beforeEach(() => {
uploadCheck = chai.request(app)
if (fs.existsSync('../uploads/fake-file.jpg')) {
fs.unlink('../uploads/fake-file.jpg', err => {
if (err) console.log(err.message)
})
}
})
it('should restrict file size', () => {
uploadCheck.post('/register')
.attach('files', fs.readFileSync(path.join(__dirname + '/fake-file.jpg')), 'fake-file.jpg')
.then((result) => {
result.req.headers = headers
checkUploads(result.req, { saveLocation: '../uploads/', acceptableTypes: ['*'] }, (err, success) => {
if (err) {
console.log(err.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
})
.catch(error => {
console.log(error)
})
})
it('should restrict file type', done => {
uploadCheck.post('/register')
.attach('fake-foto', fs.readFileSync(path.join(__dirname + '/fake-file.jpg')), 'fake-file.jpg')
.end((err, response) => {
response.req.headers = headers
checkUploads(response.req, { saveLocation: '../uploads/', acceptableTypes: ['gif', 'pdf', 'png'] }, (error, success) => {
if (error) {
console.log(error.code + ' : ' + error.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
})
done()
})
it('should strip inapporpriate characters from filename', done => {
uploadCheck.post('/register')
.attach('fake-foto', fs.readFileSync(path.join(__dirname + '/fake-file.jpg')), 'fake-file.jpg')
.end((err, response) => {
response.req.headers = headers
checkUploads(response.req, { saveLocation: '../uploads/', acceptableTypes: ['jpg', '*'] }, (error, success) => {
if (error) {
console.log(error.code + " : " + error.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
})
done()
})
// it('should check file\'s magic number')
it('should NOT upload files which fail conditions', done => {
uploadCheck.post('/register')
.attach('security-audit', fs.readFileSync(path.join(__dirname + '/security-audit.jpg')), 'security-audit.jpg')
.end((err, response) => {
response.req.headers = headers
checkUploads(response.req, { saveLocation: '../uploads/', acceptableTypes: ['*'] }, (error, success) => {
if (error) {
console.log(error.code + " : " + error.message)
expect(success).to.be.false
} else {
expect(success).to.be.true
}
})
})
done()
})
})
})