add validation and sanitization for auth/. posts

This commit is contained in:
Sorrel Bri 2020-01-15 13:52:01 -08:00 committed by sorrelbri
parent a5bee4129b
commit 923879e249
11 changed files with 294 additions and 138 deletions

View file

@ -2,12 +2,21 @@ const knex = require('../data/db')
const { hashPassword, compareHash } = require('../services/bcrypt'); const { hashPassword, compareHash } = require('../services/bcrypt');
const signToken = require('../services/signToken'); const signToken = require('../services/signToken');
const { validateSignup, validateLogin } = require('../services/userValidate');
const { validationResult } = require('express-validator');
const checkValidationErrors = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
}
const signup = async (req, res, next) => { const signup = async (req, res, next) => {
checkValidationErrors(req, res);
const user = req.body; const user = req.body;
if (!validateSignup(user)) return;
try { try {
@ -15,6 +24,7 @@ const signup = async (req, res, next) => {
const secureUser = { ...user, password: hashedPassword } const secureUser = { ...user, password: hashedPassword }
knex('user') knex('user')
.returning(['username', 'email'])
.insert(secureUser) .insert(secureUser)
.then(queryResults => { .then(queryResults => {
const newUser = queryResults[0]; const newUser = queryResults[0];
@ -28,9 +38,10 @@ const signup = async (req, res, next) => {
} }
const login = async (req, res, next) => { const login = async (req, res, next) => {
checkValidationErrors(req, res);
const user = req.body; const user = req.body;
if (!validateLogin(user)) return;
try { try {

View file

@ -0,0 +1,36 @@
const {check, sanitize, validationResult} = require('express-validator');
const signupValidationRules = () => {
return [
check('email', 'invalid email').normalizeEmail().isEmail(),
check('username', 'invalid username').isString(),
check('password', 'invalid password').isString().isLength({min: 8}),
sanitize('username').escape()
]
}
const loginValidationRules = () => {
return [
check('username', 'bad credentials').isString(),
check('password', 'bad credentials').isString()
]
}
const validate = (req, res, next) => {
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
}
const extractedErrors = []
errors.array().map(err => extractedErrors.push({ [err.param]: err.msg }))
return res.status(422).json({
errors: extractedErrors,
})
}
module.exports = {
signupValidationRules,
loginValidationRules,
validate
}

View file

@ -1043,6 +1043,15 @@
"vary": "~1.1.2" "vary": "~1.1.2"
} }
}, },
"express-validator": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.3.1.tgz",
"integrity": "sha512-YQHQKP/zlUTN6d38uWwXgK3At5phK6R24pOB/ImWisMUz/U/1AC3ZXMgiZYhtH4ViYJ6UAiV0/nj8s1Qs3kmvw==",
"requires": {
"lodash": "^4.17.15",
"validator": "^11.1.0"
}
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -3461,6 +3470,11 @@
"homedir-polyfill": "^1.0.1" "homedir-polyfill": "^1.0.1"
} }
}, },
"validator": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-11.1.0.tgz",
"integrity": "sha512-qiQ5ktdO7CD6C/5/mYV4jku/7qnqzjrxb3C/Q5wR3vGGinHTgJZN/TdFT3ZX4vXhX2R1PXx42fB1cn5W+uJ4lg=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View file

@ -17,6 +17,7 @@
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "~4.16.1", "express": "~4.16.1",
"express-validator": "^6.3.1",
"http-errors": "~1.6.3", "http-errors": "~1.6.3",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"knex": "^0.20.7", "knex": "^0.20.7",

View file

@ -1,9 +1,10 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const app = require('../server');
const authController = require('../controllers/auth'); const authController = require('../controllers/auth');
const { signupValidationRules, loginValidationRules, validate } = require('../middleware/userValidator');
router.post('/signup', authController.signup); router.post('/signup', signupValidationRules(), validate, authController.signup);
router.post('/login', authController.login); router.post('/login', loginValidationRules(), validate, authController.login);
module.exports = router; module.exports = router;

View file

@ -1,14 +0,0 @@
const validateSignup = (user) => {
if (!user.username) throw('no username');
if (!user.email) throw('no email');
if (!user.password) throw('no password');
return true
}
const validateLogin = (user) => {
if (!user.username) throw('no username');
if (!user.password) throw('no password');
return true;
}
module.exports = { validateLogin, validateSignup };

View file

@ -0,0 +1,13 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();
const verifyToken = (token) => {
const secret = process.env.NODE_ENV === 'test' ? process.env.TEST_SECRET : process.env.JWT_SECRET;
const decoded = jwt.verify(token, secret, (err, decoded) => {
if (err) return err;
return decoded;
});
return decoded;
}
module.exports = verifyToken;

View file

@ -0,0 +1,63 @@
const authSignupSpec = (chai, knex, server) => {
const newUserFormData = {
'username':'newUser',
'password':'password',
'email':'user@example.com'
}
const loginFormData = {
'username':'newUser',
'password':'password'
}
it('post to /login with non-registered user should return status 401 with bad creds err', done => {
chai.request(server)
.post('/auth/login')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(401);
res.body.err.should.equal('bad credentials');
done();
});
})
it('post to /login with non-registered user should return status 401 with bad creds err', done => {
chai.request(server)
.post('/auth/login')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(401);
res.body.err.should.equal('bad credentials');
done();
})
})
it('post to /login with registered user should return cookie', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
chai.request(server)
.post('/auth/login')
.type('form')
.send(loginFormData)
.end((err, res) => {
if(err) done(err);
res.should.status(200);
res.should.cookie('token');
done();
})
})
})
}
module.exports = authSignupSpec;

View file

@ -0,0 +1,144 @@
const verifyToken = require('../services/verifyToken');
const authSignupSpec = (chai, knex, server) => {
const newUserFormData = {
'username':'newUser',
'password':'password',
'email':'user@example.com'
}
const invalidEmailFormData = {
'username':'newUser',
'email': 'useremail',
'password':'password'
}
const scriptInjectionFormData = {
'username': '<script> alert("hello, there");</script>',
'password':'password',
'email':'user@example.com'
}
const sqlInjectionFormData = {
'username': '; DROP TABLE user;',
'password':'password',
'email':'user@example.com'
}
it('post to /signup should return 200 status', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(200);
done();
});
});
it('post to /signup should return token', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.cookie('token');
done();
});
});
it('post to /signup should add user to db', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
knex('user').where({'username': newUserFormData.username}).then(results => {
const newUser = results[0];
if (newUser.username === newUserFormData.username) done();
})
});
})
it('post to /signup should add user to db with password', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
knex('user').where({'username': newUserFormData.username}).then(results => {
const newUser = results[0];
if (newUser.password !== newUserFormData.password) done();
})
});
});
it('post to /signup with invalid email should return 422', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(invalidEmailFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(422);
done();
});
})
it('post to /signup should return cookie with jwt for user', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
const cookie = res.headers['set-cookie'][0];
const token = cookie.split(';')[0].split('token=')[1]
const verifiedToken = verifyToken(token);
const userAssertion = verifiedToken.should.have.property('user');
userAssertion.with.property('username');
userAssertion.with.property('email');
userAssertion.not.with.property('password');
done()
})
})
it('post to /signup should sanitize inputs for script injection', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(scriptInjectionFormData)
.end((err, res) => {
if (err) done(err);
const cookie = res.headers['set-cookie'][0];
const token = cookie.split(';')[0].split('token=')[1]
const verifiedToken = verifyToken(token);
verifiedToken.should.have.property('user')
.with.property('username')
.to.not.equal('<script> alert("hello, there");</script>')
done()
})
})
it('post to /signup should sanitize inputs for sql injection', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(sqlInjectionFormData)
.end((err, res) => {
if (err) done(err);
knex('user')
.where('id', 1)
.select('id','username','email')
.then(results => {
const newUser = results[0];
if (newUser) done();
})
})
})
}
module.exports = authSignupSpec;

View file

@ -1,114 +0,0 @@
const authSpec = (chai, knex, server) => {
const newUserFormData = {
'username':'newUser',
'password':'password',
'email':'user@example.com'
}
const loginFormData = {
'username':'newUser',
'password':'password'
}
it('post to sign up should return 200 status', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(200);
done();
});
});
it('post to sign up should return token', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.cookie('token');
done();
});
});
it('post to sign up should add user to db', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
knex('user').where({'username': newUserFormData.username}).then(results => {
const newUser = results[0];
if (newUser.username === newUserFormData.username) done();
})
});
})
it('post to sign up should add user to db with password', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
knex('user').where({'username': newUserFormData.username}).then(results => {
const newUser = results[0];
if (newUser.password !== newUserFormData.password) done();
})
});
});
it('post to login with non-registered user should return status 401 with bad creds err', done => {
chai.request(server)
.post('/auth/login')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(401);
res.body.err.should.equal('bad credentials');
done();
});
})
it('post to login with non-registered user should return status 401 with bad creds err', done => {
chai.request(server)
.post('/auth/login')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
res.should.status(401);
res.body.err.should.equal('bad credentials');
done();
})
})
it('post to login with registered user should return cookie', done => {
chai.request(server)
.post('/auth/signup')
.type('form')
.send(newUserFormData)
.end((err, res) => {
if (err) done(err);
chai.request(server)
.post('/auth/login')
.type('form')
.send(loginFormData)
.end((err, res) => {
if(err) done(err);
res.should.status(200);
res.should.cookie('token');
done();
})
})
})
}
module.exports = authSpec;

View file

@ -8,7 +8,8 @@ const server = require('../server');
const should = chai.should(); const should = chai.should();
const authSpec = require('./auth.spec'); const authSignupSpec = require('./auth.signup.spec');
const authLoginSpec = require('./auth.login.spec');
chai.use(chaiHttp); chai.use(chaiHttp);
// ! to run tests from other testing modules // ! to run tests from other testing modules
@ -29,8 +30,8 @@ const setupDb = () => {
describe('Auth Routes', function() { describe('Auth Routes', function() {
setupDb(); setupDb();
authSpec(chai, knex, server) authSignupSpec(chai, knex, server);
authLoginSpec(chai, knex, server);
}); });
describe('API Routes', function() { describe('API Routes', function() {