I have implemented a login with hashed password authentication via Bcrypt in my Express Backend. Registration and login, as well as authenticated routes, work fine in the app, but not in the test environment. The following registration test succeeds, but the login test fails. When I log the password inside the user model's compare function, it is logged as plain text instead of the hash. I don't understand why though.
Any help is appreciated.
This is the users.test.js:
import { expect, server, BASE_URL } from './setup'
describe('User', () => {
it('registers a user', (done) => {
const data = { email: 'chester@benning.ton', name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body).to.have.property('user')
expect(res.body.user).to.have.property('id')
expect(res.body.user).to.have.property('name', data.name)
done()
})
})
it('logs in a user', (done) => {
const data = { name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users/login`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body.user).to.have.property('id')
done()
})
})
})
The user model looks like this:
const { Model, Op } = require('sequelize')
const bcrypt = require('bcrypt')
const config = require('../../config/config')
function hashPassword(user) {
const saltRounds = config.saltRounds
if (!user.changed('password')) {
return false
}
return bcrypt.hash(user.password, saltRounds).then((hashedPassword) => {
user.setDataValue('password', hashedPassword)
})
}
module.exports = (sequelize, DataTypes) => {
class User extends Model {}
User.init({
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
email: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('E-Mail is already registered')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
name: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
name: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('User name already exists')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
password: {
allowNull: false,
type: DataTypes.STRING
}
}, {
hooks: {
beforeCreate: hashPassword,
beforeSave: hashPassword,
beforeUpdate: hashPassword
},
modelName: 'User',
sequelize,
tableName: 'Users'
})
User.prototype.comparePassword = async function comparePassword(password) {
return bcrypt.compare(password, this.password).then((res) => {
return res
}).catch((err) => {
return false
})
}
User.prototype.toJSON = function toJSON() {
const userObj = { ...this.get() }
delete userObj.password
return userObj
}
return User
}
And this is my UserController.js:
const { Op } = require('sequelize')
const jwt = require('jsonwebtoken')
const { User } = require('../models')
const config = require('../../config/config')
function jwtSignUser(user) {
const ONE_DAY = 60 * 60 * 24
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_DAY
})
}
module.exports = {
findAll(req, res) {
const options = {
attributes: {
exclude: [ 'password' ]
},
order: [
[ 'name', 'ASC' ]
]
}
return User.findAll(options).then((users) => res.status(200).send(users)).catch(() => {
res.status(404).send({
message: 'Could not find users'
})
})
},
login(req, res) {
const { name, password } = req.body
User.findOne({
where: {
name: {
[Op.iLike]: name
}
}
}).then(async (user) => {
if (!user) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const passwordIsValid = await user.comparePassword(password)
if (!passwordIsValid) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch(() => {
res.status(500).send({
message: 'Login was not successful'
})
})
},
register(req, res) {
return User.create(req.body).then((user) => {
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch((error) => {
res.status(422).send({
message: error.errors[0].message
})
})
}
}