add validation and sanitization for auth/. posts
This commit is contained in:
parent
add9e49bb6
commit
02d408adba
11 changed files with 294 additions and 138 deletions
|
@ -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 {
|
||||||
|
|
||||||
|
|
36
packages/server/server/middleware/userValidator.js
Normal file
36
packages/server/server/middleware/userValidator.js
Normal 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
|
||||||
|
}
|
14
packages/server/server/package-lock.json
generated
14
packages/server/server/package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
|
13
packages/server/server/services/verifyToken.js
Normal file
13
packages/server/server/services/verifyToken.js
Normal 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;
|
63
packages/server/server/test/auth.login.spec.js
Normal file
63
packages/server/server/test/auth.login.spec.js
Normal 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;
|
144
packages/server/server/test/auth.signup.spec.js
Normal file
144
packages/server/server/test/auth.signup.spec.js
Normal 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;
|
|
@ -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;
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue