Following on from this question.
I feel like I'm almost there, but my incomplete understanding of async is preventing me from solving this. I'm basically trying to just hash a password using bcrypt and have decided to seperate out the hashPassword function so that I can potentially use it in other parts of the app.
hashedPassword keeps returning undefined though...
userSchema.pre('save', async function (next) {
let user = this
const password = user.password;
const hashedPassword = await hashPassword(user);
user.password = hashedPassword
next()
})
async function hashPassword (user) {
const password = user.password
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds, function(err, hash) {
if (err) {
return err;
}
return hash
});
return hashedPassword
}
await dosent wait for bcrypt.hash because bcrypt.hash does not
return a promise. Use the following method, which wraps bcrypt in a promise in order to use await.
async function hashPassword (user) {
const password = user.password
const saltRounds = 10;
const hashedPassword = await new Promise((resolve, reject) => {
bcrypt.hash(password, saltRounds, function(err, hash) {
if (err) reject(err)
resolve(hash)
});
})
return hashedPassword
}
Update:-
The library has added code to return a promise which will make the use
of async/await possible, which was not available
earlier. the new way of usage would be as follows.
const hashedPassword = await bcrypt.hash(password, saltRounds)
By default, bcrypt.hash(password,10) will return as promise. please check here
Example: Run the code,
var bcrypt= require('bcrypt');
let password = "12345";
var hashPassword = async function(){
console.log(bcrypt.hash(password,10));
var hashPwd = await bcrypt.hash(password,10);
console.log(hashPwd);
}
hashPassword();
Output:
Promise { <pending> }
$2b$10$8Y5Oj329TeEh8weYpJA6EOE39AA/BXVFOEUn1YOFC.sf1chUi4H8i
When you use await inside the async function, it will wait untill it get resolved from the promise.
use The method bcrypt.hashSync(), It is Synchronous out of the box.
const hashedPassword = bcrypt.hashSync(password,saltRounds);
Hashing bcrypt asynchronously should be like this
bcrypt.hash(password, saltRounds, function(err, hash) {
if (err) {
throw err;
}
// Do whatever you like with the hash
});
If you are confused with sync and async. You need to read more about them.
There are a lot of good articles out there.
You need to look here in the documentation.
Async methods that accept a callback, return a Promise when callback
is not specified if Promise support is available.
So, if your function call takes in a callback then you can't use await on it since this function signature doesn't return a Promise. In order to use await you need to remove the callback function. You can also wrap it in a Promise and await on it but that's a bit overkill since the library already provides a mechanism to do so.
Code refactor:
try {
// I removed the callbackFn argument
const hashedPassword = await bcrypt.hash(password, saltRounds)
} catch (e) {
console.log(e)
}
const hashedPassword = (password, salt) => {
return new Promise((resolve, reject) => {
bcrpyt.hash(password, salt, (err, hash) => {
if (err) reject();
resolve(hash);
});
});
};
hashedPassword('password', 10).then((passwordHash) => {
console.log(passwordHash);
});
Had same issue...
solved by assigning the Boolean value from a function:
compareHash = (password, hashedPassword) => {
if (!password || !hashedPassword) {
return Promise.resolve(false);
}
return bcrypt.compare(password, hashedPassword);
};
Here the 2 arguments will not be undefined, which is the cause of issue.
And calling the function:
let hashCompare = this.compareHash(model.password, entity.password);
Related
I have a signup user function that I dont really understand how it's working...
module.exports.signupUser = async (req, res) => {
let email = req.body.email;
let password = req.body.password;
if(!validator.isEmail(email))
{
res.status(400).json({
"message": "Please enter a valid email!"
}).end();
}
else if(!validator.isLength(password, {min:6})){
res.status(400).json({
"message": "Password must have at least 6 characters!"
}).end();
}
else {
const salt = bcrypt.genSaltSync(10);
const hashedPassword = await bcrypt.hash(password, salt);
let params = [email, hashedPassword];
console.log(hashedPassword);
let query = 'insert into users (email, password) values (?,?)';
connection.query(query, params, (err, result, fields) => {
if(err && err.code === 'ER_DUP_ENTRY') {
res.status(400).json({
"message": "There is an account already associated with this email adress!"
}).end();
}
else {
res.status(200).json({
"message": "User created!"
}).end();
}
});
}
}
So in the last else, I use await bcrypt.hash(password, salt) to encrypt my password.
I'm new to JS but still I understand that await executes code asyncronously, so console.log(hashedPassword) should not return me the hashed password because this log will execute before the actual hashing. Anyone can explain me what is really happening?
await executes the code synchronously. So in your case await will wait for the execution of bcrypt.hash.
For more clarity bcrypt.hash function returns a promise and now we have two choices here.
1. Await for the promise to resolve
const hashedPassword = await bcrypt.hash(password, salt);
console.log(hashedPassword);
the hashedPassword will contain the actual output we need.
2. Handle promise resolution using then
bcrypt.hash(password, salt).then(hashedPassword => {
console.log(hashedPassword);
})
As the bcrypt.hash function returns a promise, we need to handle the rest of the code inside a then block as it'll only return the value once the function resolves the promise.
In case you want to execute your code asynchronously, you need to remove the await keyword which waits for the promise to resolve before moving to the next line.
const hashedPassword = await bcrypt.hash(password, salt);
console.log(hashedPassword);
If you use the await keyword, nodejs will wait for promise to resolve before moving to the next line in the async function.
Your console.log's result will be the hashed password.
If you don't use the await keyword, node will start resolving the promise but will not wait for it to finish resolving before moving to next line.
Your console.log's result will be Promise { pending }
I've stumbled upon an interesting but rather hacked way to write linear code with promises in Javascript, as follows:
const return_by_death = new Promise((resolve) => {});
and
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(async (err) => {
res.sendStatus(500);
await return_by_death;
});
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
and from my experimentation, it seems to work in the way that if the promise from generate_hash is rejected, it will go into my error handler without moving on to the new_account line. My questions are as follows:
Does this waste memory, by generating promises or execution threads that just hang indefinitely?
Does expressjs hold on to functions passed into app.get(path, func) where it would be keeping track of the signup promise that might never resolve?
Is there a better way to do this?
EDIT: based on the answer/information from #CertainPerformance I came up with the following solution
class PromiseRunError {
constructor(obj) {
this.obj = obj;
}
}
Promise.prototype.run = function() {
return this.catch((err) => {return new PromiseRunError(err)}).then((data) => {
if(data instanceof PromiseRunError) {
return [undefined, data.obj];
}
return [data, undefined];
});
};
and
const [hash_data, hash_gen_err] = await generate_hash(pass).run();
if(hash_gen_err) {
res.sendStatus(500);
return;
}
Yes, using perpetually unresolved Promises here is a problem.
If signup gets called many times, and many Promises that hang are generated, more and more memory will get used over time; each call of signup results in a new closure with user, pass, req, and res variables allocated, which can't be garbage collected until the function ends. (But it'll never end if there's an error.)
It's extremely confusing from a readability standpoint.
If you want to keep things as flat as possible, without surrounding everything in a try/catch, assuming that generate_hash returns a Promise that, if it resolves, will resolve to something truthy, just don't return anything inside the .catch handler, then check if hash_data is truthy or not before continuing:
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(() => {});
if (!hash_data) {
res.sendStatus(500);
return;
}
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
I'd prefer try/catch, though:
const signup = async (req, res) => {
try {
const { user, pass } = req.body;
if (!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass)
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
} catch (e) {
res.sendStatus(500);
return;
}
};
I am trying to validate a user's password using bcryptjs. I have this function which returns a Promise, however when I get to bycrypt.hash, all i get is Promise { <pending> } Therefore .then() will not execute on undefined. Please help, I've been stuck on this a while
userSchema.methods.verifyPassword = function (password, err, next) {
const saltSecret = this.saltSecret;
const a = async function(resolve, reject) {
console.log('hi4')
console.log('this.saltSecret2', saltSecret);
console.log(password);
const hashed_pass = await bcrypt.hash(password, saltSecret);
console.log('hash', hashed_pass);
const valid = await bcrypt.compare(password, hashed_pass);
if(valid){
console.log('GOOD');
}
};
a();
};
I like to use async-await syntax to handle promises. It is less confusing. and gives the ability of quickly understanding someone else code.
you can make your function an async one. wait until bcrypt does its job
const password = await bcrypt.hash(password, saltSecret);
However bcrypt library provides a function to compare password and the hash
const valid = await bcrypt.compare(password, hashed_pass);
try this
async function(resolve, reject) {
console.log('hi4')
console.log(this.saltSecret);
console.log(password);
const hashed_pass = await bcrypt.hash(password, saltSecret);
console.log('hash', hashed_pass);
const valid = await bcrypt.compare(password, hashed_pass);
if(valid){
console.log('GOOD');
}
};
This line will always return a Promise.
console.log(bcrypt.hash(password, this.saltSecret));
You could always do something like this.
return new Promise(async (resolve, reject) => {
const hash = await bcrypt.hash(password, this.saltSecret);
if (hash == this.password) {
return resolve(true);
}
return reject();
});
bcrypt.hash uses a callback, not a promise (that's what the .then is doing)
You should use it like so:
bcrypt.hash(password, this.saltSecret, (err, hash) => {
...
});
I've got into the pattern of using async await in my aws nodejs lambda functions, and I common thing I run into is the need to await the result of a promise and use the response in the next async/await promise, and sort of repeat this pattern until I've run all my logic.
let userId;
await GetUserId({AccessToken: headers.accesstoken}).then(function(res){
userId = res;
},function(err){
});
let username;
await GetUserName(userId).then(function(res){
username = res;
},function(err){
});
Is there anyway I can declare and assign userId a value in the same line as invoking the function.
sudo code:
let userId = await GetUserId().then(()=>{ //bubble response up to userId })
The reason I'm asking is that it just sort of messing/wrong initializing a variable separately. Maybe I need a different pattern, or it's just something I'll have to live with.
Solution
var ExampleFunction = async() => {
try {
const userId = await GetUserId('token');
const username = await GetUserName(userId);
console.log(`userId: ${userId}`);
console.log(`username: ${username}`);
} catch (err) {
console.log(err);
console.log(`Exit Function`);
}
function GetUserId(token) {
return new Promise(function(resolve, reject) {
if (!token)
reject('no token');
resolve('ID');
});
}
function GetUserName(userId) {
return new Promise(function(resolve, reject) {
if (!userId)
reject('no userId');
resolve('NAME');
});
}
}
ExampleFunction();
The await is supposed to replace the then syntax (except you really need to distinguish fulfillment from rejection with it). The await expression either throws the rejection reason as an exception to catch, or results in the fulfilment value of the promise. You can directly use that:
const userId = await GetUserId({AccessToken: headers.accesstoken});
const username = await GetUserName(userId);
(I assume that it was unintentional that your current code ignored errors and continued with undefined values).
The keyword await makes JavaScript wait until that promise resolves and returns its result.
let userId = await GetUserId({AccessToken: headers.accesstoken});
if you assign the result of await to var it will be the promise resolve value
so you can
let userId = await GetUserId({AccessToken: headers.accesstoken});
and
let username = await GetUserName(userId);
PS. don't forget error handling using try/catch.
Calling an async function in a pre save hook is returning me undefined for the password. Am I fundamentally misunderstanding async here? I've used it successfully in other areas of my app an it seems to be working fine...
userSchema.pre('save', function (next) {
let user = this
const saltRounds = 10;
const password = hashPassword(user)
user.password = password;
next();
})
async hashPassword(user) {
let newHash = await bcrypt.hash(password, saltRounds, function(err, hash) {
if (err) {
console.log(err)
}
return hash
});
return newHash
}
I think you'd need to handle the promise returned by hashPassword:
hashPassword(user)
.then(password => {user.password = password
next()}
)
I don't think you can turn userSchema.pre into an async function.
Mongoose hooks allow async functions in them. It worked for me. Note that the "next" callback parameter is not needed in async functions as the function executes synchronously.
Here is what a correct async/await version of the code posted in the question would be. Please note that the corrections are marked in bold:
userSchema.pre('save', async function () {
let user = this;
const saltRounds = 10;
const hashed_password = await hashPassword(user.password, saltRounds);
user.password = hashed_password;
}); // pre save hook ends here
async hashPassword(password, saltRounds) {
try {
let newHash = await bcrypt.hash(password, saltRounds);
} catch(err){
// error handling here
}
return newHash;
}
Just to clean things up a bit:
userSchema.pre('save', function(next) {
if (!this.isModified('password')) {
return next();
}
this.hashPassword(this.password)
.then((password) => {
this.password = password;
next();
});
});
userSchema.methods = {
hashPassword(password) {
return bcrypt.hash(password, 10);
},
}
let user = this can be dropped when using an arrow function in then.
When using bcrypt.hash() with no callback, it returns a promise.
async for hashPassword is redundant when using .then when calling