sequelize promise always return false - node.js

I'm creating a react-native app.
The flow works like this, a customer has to input an email and password to signup and the data will be saved in the database. Before the data is saved, I've used the pre-hook beforeValidate to hash the password using bcrypt.
Until here, everything worked fine, but I can't seem to return true when the promise from instanceMethod comparePassword is made.
I have a customer model Customer.js file like below:
const Sequelize = require('sequelize');
const bcrypt = require('bcrypt');
const db = require('../config/database');
const Customer = db.define('customer', {
id : {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
email : {
type: Sequelize.STRING,
unique: true,
allowNull: false
},
password : {
type: Sequelize.STRING,
allowNull: false
},
createdAt : {
type: Sequelize.NOW
},
updatedAt : {
type: Sequelize.NOW
}
}, {
hooks: {
afterValidate: (customer) => {
customer.password = bcrypt.hashSync(customer.password, 10);
}
},
instanceMethods: {
comparePassword: (candidatePassword) => {
return new Promise((resolve, reject) => {
bcrypt.compareSync(candidatePassword, this.password, (err, isMatch) => {
if(err) {
return reject(err);
}
if(!isMatch) {
return reject(false);
}
resolve(true);
});
});
}
}
});
module.exports = Customer;
and a snippet of authRoutes.js file like below:
router.post('/login', async (req, res) => {
const { email, password } = req.body;
if ( !email || !password ) {
return res.status(422).send({error: 'Must provide email and password!'});
}
const customer = await Customer.findOne({ where: {email} });
if(!customer) {
return res.status(422).send({error: '1. Invalid email or password!'});
}
try {
await customer.comparePassword(password);
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
res.send({ email, token });
} catch(err) {
return res.status(422).send({error: '2. Invalid email or password!'});
}
});
There's no error or anything but it always catches the "2. invalid email or password" error even tho I've input the correct credentials. Any kind of help is appreciated. Thank you.

I have created a function (comparePassword) to compare password with hashed password Which use bcrypt to compare password.
const bcrypt = require('bcryptjs');
const customer = await Customer.findOne({ where: { email } });
const comparePassword = (hashedPassword, password) => {
return bcrypt.compareSync(password, hashedPassword);
};
try {
if (!comparePassword(customer.password, password) {
return res.status(422).send({ error: '2. Invalid email or password!' });
}
else {
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
return res.status(200).send({ email, token });
}
} catch (err) {
console.log(err)
return res.status(500).send({ error: 'something bad happened on server' });
}

Customer can be defined as a class in Sequelize 4+. Then instance methods can be added as regular class instance methods.
class Customer extends Sequelize.Model {
static table_schema = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
...
}
static table_options = {
...
}
static init(sequelize){
return super.init(this.table_schema, { this.table_options, ...sequelize })
}
static associate(models) {
}
async comparePassword(candidatePassword){
return bcrypt.compare(candidatePassword, this.password)
}
}
Customer.addHook('afterValidate', async function(customer){
customer.password = await bcrypt.hash(customer.password, 10);
})
Then you should be able to make use of the async comparePassword function in your route, similar to Arya's answer
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if ( !email || !password ) {
return res.status(422).send({error: 'Must provide email and password!'});
}
const customer = await Customer.findOne({ where: {email} });
if (!customer) {
console.log('Failed login [%s] not found', email)
return res.status(422).send({error: 'Invalid email or password!'});
}
const auth = await customer.comparePassword(password);
if (!auth) {
console.log('Failed login [%s] bad password', email)
return res.status(422).send({error: 'Invalid email or password!'});
}
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
res.send({ email, token });
}
catch(err) {
console.error('Failed to process request', err)
return res.status(500).send({error: 'Internal Server Error'});
}
});

Related

How to test login with Bcrypt in Express with Mocha [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
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
})
})
}
}

How to use bcrypt.compare in sails js schema?

I have a user model like this:
module.exports = {
attributes: {
email: {
type: 'string',
isEmail: true,
unique: true,
required: true
},
password: {
type: 'string',
required: true
}
},
beforeCreate: (value, next) => {
bcrypt.hash(value.password, 10, (err, hash) => {
if (err){
throw new Error(err);
}
value.password = hash;
next();
});
},
};
Now when I want to match the password during login, how do I decrypt the password, if possible I would prefer to perform it in the user model file.
controller/ login.js
module.exports = {
login: async (req, res) => {
try{
const user = await User.findOne({email: req.body.email});
if (!user){
throw new Error('Failed to find User');
}
// here I want to match the password by calling some compare
//function from userModel.js
res.status(201).json({user: user});
}catch(e){
res.status(401).json({message: e.message});
}
},
};
first try to find the user with the given username by user
const find = Users.find(user=>user.username===req.body.username)
if(!find){
res.send('User Not Found')
}
else{
if( await bcrypt.compare(req.body.password,find.password)){
//now your user has been found
}
else{
//Password is Wrong
}
}
You must use bcrypt.compare(a,b)
a = given password by user
b = original password if username exist
hope it solve your problem

TypeError: Cannot read property 'authenticate' of null

I am working in user signin authentication in backend. Whenever I hit send request from postman it shows error as
TypeError: Cannot read property 'authenticate' of null
at /home/saru/mernbootcamp/projbackend/controllers/auth.js
I had check this error in stackoverflow but the solution doesn't match my case
controllers/auth.js
`const User = require("../models/user");
//express-validator
const { check, validationResult } = require('express-validator');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
const dotenv = require("dotenv")
const config = dotenv.config({ path: './routes/.env' });
//user object creation for class/model User
const user = new User(req.body);
exports.signin = (req, res) => {
const errors = validationResult(req);
const { email, password } = req.body;
if (!errors.isEmpty()) {
return res.status(422).json({
error: errors.array()[0].msg
});
}
User.findOne({ email }, (err, user) => {
if (err) {
return res.status(400).json({
error: "USER email does not exists"
});
}
console.log(password);
if (!user.authenticate(password)) {
return res.status(401).json({
error: "Email and password do not match"
});
}
//create token
const token = jwt.sign({ _id: user._id }, process.env.SECRET);
//put token in cookie
res.cookie("token", token, { expire: new Date() + 9999 });
//send response to front end
const { _id, name, email, role } = user;
return res.json({ token, user: { _id, name, email, role } });
});
};
`
models/user.js
`var mongoose = require("mongoose");
const crypto = require("crypto");
const uuidv1 = require("uuid/v1");
var userSchema = new mongoose.Schema(
{
email: {
type: String,
trim: true,
required: true,
unique: true
},
encry_password: {
type: String,
required: true
},
salt: String,
},
{ timestamps: true }
);
userSchema
.virtual("password")
.set(function (password) {
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function () {
return this._password;
});
userSchema.method = {
authenticate: function (plainpassword) {
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword) {
if (!password) return "";
try {
return crypto
.createHmac("sha256", this.salt)
.update(plainpassword)
.digest("hex");
} catch (err) {
return "";
}
}
};
module.exports = mongoose.model("User", userSchema);
`
In controllers/auth.js
Replace the if(err) with if(err || !user) in the findOne() method
You can use this code:
User.findOne({email}, (err, user) => {
if (err || !user) {
return res.status(400).json({
error: "USER email does not exists"
})
}
});
Replace if (err) with if (err || !user) and use return before res.status

my nodejs bcrypt compare not working properly

am building an app with nodes qraphQl using apollo, am trying to do a login page, but ater signing up and and i try to sign in, my bcrypt would always return false,
in my user model
import bcrypt from 'bcryptjs';
const user = (sequelize, DataTypes) => {
const User = sequelize.define('user', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
unique: true,
primaryKey: true,
field: 'id'
},
fullname: DataTypes.STRING,
username: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
email: {
type: DataTypes.STRING,
allowedNull: false,
validate: {
notEmpty: true,
isEmail: true,
}
},
password: {
type: DataTypes.STRING,
allowedNull: false,
validate: {
notEmpty: true,
len: [7, 42],
},
},
role: {
type: DataTypes.ENUM,
values: ['ADMIN', 'INSTRUCTOR', 'STUDENT'],
defaultValue: 'STUDENT'
}
});
User.beforeCreate(async user => {
user.password = await user.generatePasswordHash()
});
User.beforeSave(async user => {
user.password = await user.generatePasswordHash()
});
User.prototype.generatePasswordHash = async function() {
const saltRounds = 10;
return await bcrypt.hash(this.password, saltRounds)
};
User.prototype.validatePassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
User.associate = models => {
User.hasMany(models.Message, { onDelete: 'CASCADE' });
};
User.findByLogin = async login => {
let user = await User.findOne({
where: { username: login },
});
if (!user) {
user = await User.findOne({
where: { email: login },
});
}
return user;
};
return User;
};
export default user;
And in my users resolver, here is the code
import { combineResolvers } from 'graphql-resolvers';
import Joi from 'joi'
import { isAuthenticated, isAdmin } from './authorization';
import {SignUp, SignIn} from '../functions/joi'
import {createToken} from '../functions/jwt'
export default {
Mutation: {
signUp: async (parent, { username, fullname, email, password, Rpassword}, { models, secret }) => {
if(password !== Rpassword){
return new Error('Password did not match')
}
var thejoi = { username, fullname, email, password }
const checkUserEm = await models.User.find({ where: { email: email }})
if (checkUserEm) {
return new Error('Email address already Exist')
}
const checkUserUs = await models.User.find({ where: { username: username }})
if (checkUserUs) {
return new Error('Username already Exist')
}
await Joi.validate(thejoi, SignUp, {abortEarly:false})
const user = await models.User.create({
username,
fullname,
email,
password,
role:'STUDENT'
});
return { token: createToken(user) };
},
signIn: async (parent, { login, password }, { models, secret }, ) => {
var varrh = { password }
await Joi.validate(varrh, SignIn, {abortEarly:false})
const user = await models.User.findByLogin(login);
if (!user) {
return new Error('No user found with this login credentials.');
}
const isValid = await user.validatePassword(password);
if (!isValid) {
return new Error('Invalid password .');
}
return { token: createToken(user) };
}
},
User: {
messages: async (user, args, { models }) => {
return await models.Message.findAll({
where: {
userId: user.id
}
});
},
},
}
pls am really confused because its it suppose to work, i have searched google but it didnt help me, pls how can i solve this issue thanks
inside generatePasswordHash you referencing to this, but you also using arrow functions for beforeCreate and beforeSave hooks.
Two options:
Do not use arrow function for beforeCreate and beforeSave hooks.
User.beforeCreate(async function (user) {
user.password = await user.generatePasswordHash()
});
User.beforeSave(async function (user) {
user.password = await user.generatePasswordHash()
});
Provide user object to generatePasswordHash and replace this with user.
User.beforeCreate(async user => {
user.password = await user.generatePasswordHash(user)
});
User.beforeSave(async user => {
user.password = await user.generatePasswordHash(user)
});
User.prototype.generatePasswordHash = async function(user) {
const saltRounds = 10;
return await bcrypt.hash(user.password, saltRounds)
};

Mongoose Ignore Required

I have this User schema:
email: {
type: String,
required: true
},
name: {
type: String,
required: true
},
password: {
type: String,
required: true
}
When you do a POST (/api/user-add), I want all the fields to be required. But when I do a login (/api/login) then I only need the email and password fields. My problem is, in my login code I eventually get to this function:
staffSchema.methods.generateToken = function(callback) {
var token = jwt.sign(this._id.toHexString(), config.SECRET);
this.token = token;
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
});
}
And here it thows an error because the name field is required. How do I bypass this. I am looking for something like this I assume:
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
}).ignoreRequired('name');
When You Login using JWT token this is a basic example to generate token and authenticate user without store token
Note :
Example to authenticate the user without store token in DB
*Login Method
const jwt = require('./jwt');
userCtr.authenticate = (req, res) => {
const {
email, password,
} = req.body;
const query = {
email: email,
};
User.findOne(query)
.then((user) => {
if (!user) {
//return error user not found.
} else {
if (passwordHash.verify(password, user.password)) { // verify password
const token = jwt.getAuthToken({ id: user._id });
const userData = _.omit(user.toObject(), ['password']); // return user data
return res.status(200).json({ token, userData });
}
//return error password not match
}
})
.catch((err) => {
});
};
*jwt.js
const jwt = require('jwt-simple');
const logger = require('./logger');
const jwtUtil = {};
jwtUtil.getAuthToken = (data) => {
return jwt.encode(data, process.env.JwtSecret);
};
jwtUtil.decodeAuthToken = (token) => {
if (token) {
try {
return jwt.decode(token, process.env.JwtSecret);
} catch (err) {
logger.error(err);
return false;
}
}
return false;
};
module.exports = jwtUtil;
*use middleware to prevent another route to access.
userRouter.post('/update-profile', middleware.checkUser, userCtr.updateProfile);
*middleWare.js
middleware.checkUser = (req, res, next) => {
const { headers } = req;
if (_.isEmpty(headers.authorization)) {
//return error
} else {
const decoded = jwt.decodeAuthToken(headers.authorization.replace('Bearer ', ''));
if (decoded) {
User.findOne({ _id: decoded.id })
.then((user) => {
if (user) {
req.user = user;
next();
} else {
//error
}
})
.catch((err) => {
//errror
});
req.user = decoded;
} else {
//error
}
}
};

Resources