Bcrypt hash function returns undefined result - javascript

In a controller I'm trying to create a hash and save it with the user, using this section of code.
router.post('/', (req, res)=>{
console.log(req.body.password, process.env.SALT_ROUNDS);
bcrypt.hash(req.body.password, process.env.SALT_ROUNDS, function (err, hash) {
if(err) {
res.status(500);
}
console.log("hash ", hash, req.body.password);
// await req.context.models.User.create({
// username: req.body.username,
// email: req.body.email,
// password: hash })
// .then(function(data) {
// console.log("user saved")
// if (data) {
// res.send(data);
// }
// });
});
});
But the result of the callback function inside hash argument is undefined. Here are the logs.
asd123 10
hash undefined asd123
Where is the problem, that hash is undefined ?

It turned out that process.env.SALT_ROUNDS was a string and I didn't quite understood that from the returned result. When I parse it to number +process.env.SALT_ROUNDS it worked.
asd123 10
hash $2b$10$Ty3JNeIBXUizrS85MsH8H.oTNsRtXzdiv9ZdamxnG7CmPD2CjSZG2 asd123

Related

Mongoose bcrypt set password and saving asynchronously

I have a mongoose schema:
UserSchema.methods.setPassword = function (password) {
bcrypt.hash(password, saltRounds).then(function (hash) {
this.hash = hash;
});
};
and here is how I create the user object:
router.post('/signup', function(req, res, next){
var user = new User();
user.username = req.body.user.username;
user.email = req.body.user.email;
user.setPassword(req.body.user.password);
user.save().then(function(){
return res.json({user: user.toAuthJSON()});
}).catch(next);
});
However, it saves the user without the hashed password. I guess it's because the bcrypt.hash callback didn't run before user.save is called. How can I best resolve this issue?
On the bcrypt doc it says not to use bcrypt.hashSync, would it be an appropriate here?
UserSchema.methods.setPassword = function (password) {
return new Promise((resolve, reject) => {
bcrypt.hash(password, saltRounds, (error, hash) => {
if (error) {
reject(error);
} else {
this.password = hash;
resolve(true);
}
})
})
}
and then call
await user.setPassword(req.body.user.password);
or maybe catch the error, idk

how doe export work in javaScript wold. hash undefined bcryptjs

Let's say we have two file, user.js users.js in user.js we have. Why can we do module.exports.. we can use in it diff .js file? what does "#returns Promise If callback has been omitted" means it is from the bcrypt.genSalt function?
I also have a github repo, so please take a look if you have a bit of time. after cloning it
stuck in terminal
result { error: null,
value:
{ email: 'max#mail.com',
username: 'max',
password: '1234',
confirmationPassword: '1234' },
then: [Function: then],
catch: [Function: catch] }
hash undefined
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcryptjs');
const userSchema = new Schema({
email: String,
username: String,
password: String
});
const User = mongoose.model('user', userSchema);
module.exports = User;
module.exports.hashPassword = (password) => {
return hash = bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
});
});
};
in users.js we have have
const express = require('express');
const router = express.Router();
const Joi = require('joi');
const User = require('../models/user');
const userSchema = Joi.object().keys({
email:Joi.string().email().required(),
username:Joi.string().required(),
password:Joi.string().regex(/^[a-zA-Z0-9]{3,15}$/).required(),
confirmationPassword:Joi.any().valid(Joi.ref('password')).required()
});
router.route('/register')
.get((req, res) => {
res.render('register');
})
.post(async (req, res, next) => {
try{
const result = Joi.validate(req.body,userSchema);
console.log('result',result);
if(result.error) {
req.flash('error', 'Data is not valid, please try again');
res.redirect('/users/register');
return;
//console.log('result',result);
}
// checking if email is already taken
const user = await User.findOne({'email':result.value.email });
if (user){
req.flash('error','Email is already in use');
res.redirect('/users/register');
return;
}
// console.log('hash',hash);
// Hash the password
const hash = await User.hashPassword(result.value.password);
console.log('hash',hash);
} catch(error) {
next(error);
}
});
module.exports = router;
based the example given by bcrypt
var bcrypt = require('bcryptjs');
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash("B4c0/\/", salt, function(err, hash) {
// Store hash in your password DB.
});
});
adding pic for mongo db
I think the problem lies in this line:
const hash = await User.hashPassword(result.value.password);
This implies that User.hashPassword(result.value.password) should be returning a promise (but it returns a reference to the wrong promise).
module.exports.hashPassword = (password) => {
return hash = bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(password, salt, function (err, hash) {});
});
};
Perhaps modifying the above to return a promise may help.. Like so:
module.exports.hashPassword = (password) => {
var salt = await bcrypt.genSalt(10);
return bcrypt.hash(password, salt);
};
To answer your question about #returns Promise If callback has been omitted:
Bcrypt methods are asynchronous. Which means they return immediately and process in the background. When the result is available, the function makes this available to the calling code either via a callback function or a promise.
Consider the following API for genSalt from the docs:
genSalt(rounds, minor, cb)
rounds - [OPTIONAL] - the cost of processing the data. (default - 10)
minor - [OPTIONAL] - minor version of bcrypt to use. (default - b)
cb - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.
err - First parameter to the callback detailing any errors.
salt - Second parameter to the callback providing the generated salt.
What that says is genSalt can take three arguments: genSalt(rounds, minor, cb)
Sample using callbacks
If the calling code wants the result via a callback, it can pass a function which looks like function(err, salt){} as the cb parameter.
bcrypt.genSalt(rounds, minor, function(err, salt){
if(err){
//Handle error
return;
}
// Salt is available here
console.log(salt);
});
Sample using promises
If the cb parameter is not passed (null or undefined) the function returns a Promise instead.
var promise = bcrypt.genSalt(rounds, minor);
promise
.then(function(salt){
// Salt is available here
console.log(salt);
})
.catch(function(err){
// Handle error
});

"this" is not refering the the user object in mongoose model

I have the following code to save user object to database from express,
api.post('/signup', function (req, res) {
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.setPassword(req.body.password);
user.save(function (err) {
err ? res.send(err) : res.json({ message: 'User Created!'})
})
})
and below here for the user schema,
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var SALT_WORK_FACTOR = 10;
var Schema = mongoose.Schema;
var userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
},
password: String,
})
userSchema.methods.setPassword = function (password) {
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return console.log(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err) return console.log(err);
this.password = hash; // <= `this` will not be saved to mongoDB
})
})
}
module.exports = mongoose.model('User', userSchema);
When it performed the save function, it will show that the password was undefined and will save the object to mongodb without password value.
I've checked the this question as well and changed all my functions to not using arrow method but it still got the same error.
The same issue when i was using middleware hook that this reference was not referring to user object. Below here for my another approach,
userSchema.pre('save', (next) => {
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if(err) return next(err);
bcrypt.hash(this.password, salt, function (err, hash) {
if (err) return next(err);
this.password = hash; // <= `this` will not be saved to mongoDB
next();
})
})
})
Any idea for this value to be saved to database when perform save?
You should try below code, Hope it will work for you:
userSchema.methods.setPassword = function (password) {
var pswd=password;
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return console.log(err);
bcrypt.hash(pswd, salt, function (err, hash) {
if (err) return console.log(err);
pswd = hash;
console.log(pwsd); // your decripted password
})
})
}
I will elaborate more in the next days (I want to test a couple of things) but the error, I think, is because the meaning of this in JS is tricky.
Basically this is not a reference to the function's lexical scope, it is binding done in the context where the function is called.
So, as a fast solution while I elaborate more, I would recommend you to encrypt the password in:
user.setPassword(encrypt(req.body.password)); and simplify the mongoose's setPassword function
Hope that helps.
If you want this to refer to the userSchema context, you can use arrow functions for the bcrypt callbacks. Arrow functions will not create a new function scope, so it will preserve the original context.
userSchema.methods.setPassword = function (password) {
bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
if (err) return console.log(err);
bcrypt.hash(password, salt, (err, hash) => {
if (err) return console.log(err);
this.password = hash; // <= `this` will be the user object
})
})
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

bcrypt error with passport and mongoose

Bcrypt is throwing an Incorrect arguments error which I traced back to this function in user.js
userSchema.methods.comparePassword = (candidatePassword, callback) => {
bcrypt.compare(candidatePassword, this, (err, isMatch) => {
console.log('candidatePassword= ', candidatePassword, '& this= ', this);
if (err) { return callback(err); }
callback(null, isMatch);
});
};
/*
candidatePassword= bird
this= {}
this.password= undefined */
The user object is coming back as an empty object, and therefore this.password is undefined. I assume the this parameter in bcrypt.compare refers to the userSchema instance. The userSchema is declared in passport.js
const passport = require('passport');
const ExtractJwt = require('passport-jwt').ExtractJwt;
const JwtStrategy = require('passport-jwt').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user');
const config = require('../config');
var localOptions = {
usernameField: 'email',
};
// Verifies user by checking if a password matches the specified email during signin
var localStrategy = new LocalStrategy(localOptions, function (email, password, done) {
User.findOne({ email:email.toLowerCase()}, function (err, user) {
console.log('/passport.js/localStrategy- user object: ', user)
if (err) { return done(err); }
if (!user) { return done(null, false); }
user.comparePassword(password, function (err, isMatch) {
console.log('/passport.js/localStrategy- password: ', password)
if (err) { return done(err); }
if (!isMatch) { return done(err, false); }
return done(null, user);
});
});
});
// ... jwt strategy ...
passport.use(localStrategy);
/*
user object: { _id: 58a1018dc3f89eb5955b8638,
email: 'bird#bird.com',
password: '$2a$10$lAJ9hoGKt9ggfk1TISfkOedxDIs/waLB5e4PccHAKt286XCKCY0/q',
__v: 0 } */
I'm not sure quite what the issue as it seems a user object is returned with an encrypted password field from mongodb, and user.comparepassword() is called...
I signed the user up with the same Schema object as well.
Any help / tips appreciated!
You are only setting up your model so that it pulls in the candidatePassword but never finds the stored password from the database. Since this is returning an empty object, either the email is not being matched or the password is not being compared to what is stored. Try simplifying the comparePassword function and adding 'sync' to the bcrypt.compare which removes the need for a callback.
In models:
userSchema.methods.comparePassword = (candidatePassword) => {
return bcrypt.compareSync(candidatePassword, this.password);
};

Node.js: crypto.pbkdf2 password to hex

I currently use following set up to register new users:
// creates a new user
app.post('/users', function(req, res) {
// create new user
var user = new User();
// assign post
user.username = req.body.username;
user.email = req.body.email;
crypto.randomBytes(32, function(err, buf) {
if (err) throw err;
user.salt = buf.toString('hex');
crypto.pbkdf2(req.body.password, user.salt, 25000, 512, function(err, encodedPassword) {
if (err) throw err;
user.password = (encodedPassword.toString('hex')); // this line
user.save(function(err, user) {
if (!err) return res.send(err, 500);
return res.json(user);
});
}.bind(this));
});
});
Take a closer look at this line:
user.password = (encodedPassword.toString('hex'));
This should encode the password string (which looks like a binary one) into a hex string.
For some reason this doesn't work.
Why not?
Byside:
What encoding is recommand for salt and password storage (hex, binary, base64)?
It appears that if it's already a String, the toString('hex') won't work.
What I did was something like Buffer(encodedPassword, 'binary').toString('hex').

Categories

Resources