having problem with Node.js and ES6 promise - node.js

this is my piece of code:
i declared a variable(newSeller) and i expect to use it in the process
let newSeller = '';
if (req.body.selectSeller == '') {
User.findOne({email: req.body.sellerEmail}).then(userEx => {
if (!userEx) {
const newUser = new User({
firstName: req.body.sellerName,
lastName: req.body.sellerLastName,
title: req.body.sellerTitle,
phoneNumber: req.body.sellerPhNum,
email: req.body.sellerEmail,
password: req.body.password,
address: req.body.sellerAddress
});
bcrypt.genSalt(10, (err, salt)=>{
bcrypt.hash(newUser.password, salt, (err, hash)=>{
newUser.password = hash;
});
});
newUser.save().then(savedSeller => {
newSeller = savedSeller.id;
});
} else if (userEx) {
req.flash('error_message', 'this email already exists. try another one')
res.redirect('/admin/invoice/incoming');
}
});
} else {
newSeller = req.body.selectSeller;
}
this piece of code actually saves the expected document successfully but when i assign the variable (newSeller) to the value of ES6 promise (after then() ) it doesn't work!
could you please help me with this?
how can i fetch the saved user values?

Basically you are using async and sync functions together in various places which messes up everything. Basically you cannot use sync functions if you use even one async function in the entire module. But then again in async functions, Try to use promise based syntaxes or async-await
Assuming you are using the code in some express route here is how you can simplify the code(commented for understanding):
app.post('/someroute', async (req, res) => { //<<-Async handler
let newSeller = '';
if (req.body.selectSeller == '') {
try { //<<--need to catch `async-await` errors
const userEx = await User.findOne({ email: req.body.sellerEmail });//<<awaiting the result
if (!userEx) {
const newUser = new User({
firstName: req.body.sellerName,
lastName: req.body.sellerLastName,
title: req.body.sellerTitle,
phoneNumber: req.body.sellerPhNum,
email: req.body.sellerEmail,
password: req.body.password,
address: req.body.sellerAddress
});
const salt = await bcrypt.genSalt(10); // await
const hash = await bcrypt.hash(newUser.password, salt);// await
newUser.password = hash;
const savedSeller = await newUser.save(); //await
newSeller = savedSeller.id;
} else {
req.flash('error_message', 'this email already exists. try another one')
res.redirect('/admin/invoice/incoming');
}
} catch (err) { //<<--if there is an error
req.flash(...something...)
res.redirect(...somewhere...);
}
} else {
newSeller = req.body.selectSeller;
}
//do something with newSeller
})

Related

Router doesn't get response from database functions

when running the app and testing the /signup route the data gets written to the db and i can console.log it from the database/models.js file, but in the routes/index.js file it returns "Something went wrong." and postman shows nothing, not even an empty array or object.
routes/index.js
var express = require('express');
var router = express.Router();
const database = require('../database/models');
router.post('/signup', function(req, res, next) {
if (!req.body.email || !isValidEmail(req.body.email))
res.status(400).send('Email invalid.');
else if (!req.body.username || !isValidCredential(req.body.username) || !req.body.password || !isValidCredential(req.body.password))
res.status(400).send('Username/password invalid.');
else {
const result = database.createUser(req.body.email, req.body.username, req.body.password);
if (result)
res.status(200).send(result);
else
res.send('Something went wrong.');
}
});
function isValidEmail (email) {
if (email.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"))
return true;
else
return false;
}
function isValidCredential (credential) {
if (credential.length < 6)
return false;
else if (credential.match(/^[a-z0-9]+$/i))
return true;
else
return false;
}
module.exports = router;
database/models.js
const tools = require('./tools');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
email: String,
username: String,
hashedPassword: String,
salt: String,
accessToken: { type: String, default: "" }
});
const User = mongoose.model('User', userSchema);
function createUser(email, username, password) {
const hashPass = tools.generatePassword(password);
const newUser = new User({
email: email,
username: username,
hashedPassword: hashPass.hash,
salt: hashPass.salt
});
newUser.save(function (error, result) {
if (error)
return handleError(error);
return { email: result.email, username: result.username };
});
}
module.exports.createUser = createUser;
In your code you are not returning anything when calling the createUser function. Here a couple of considerations:
// index.js
const result = database.createUser(req.body.email, req.body.username, req.body.password);
since the createUser is an operation performed on a database, it will be probably asynchronous, and therefore also its result. I suggest the usage of async/await to be sure of the returned result. Also, you need to change the code of your models.js file to return a Promise and await for it.
function createUser(email, username, password) {
const hashPass = tools.generatePassword(password);
const newUser = new User({
email: email,
username: username,
hashedPassword: hashPass.hash,
salt: hashPass.salt
});
return new Promise((resolve, reject)=> {
newUser.save(function (error, result) {
if (error) reject(error);
resolve({ email: result.email, username: result.username });
});
});
}
and than you will have to await for your result. You can do it in the following way:
// index.js
// Add async here
router.post('/signup', async function(req, res, next) {
// ...other code
// Add await here
const result = await database.createUser(req.body.email, req.body.username, req.body.password);
I figured it out.
in routes/index.js use async/await like this:
router.post('/signup', async function(req, res, next) {
try {
if (!req.body.email || !isValidEmail(req.body.email))
res.status(400).send('Email invalid.');
else if (!req.body.username || !isValidCredential(req.body.username) || !req.body.password || !isValidCredential(req.body.password))
res.status(400).send('Username/password invalid.');
else {
const result = await database.createUser(req.body.email, req.body.username, req.body.password);
if (result)
res.status(200).send(result);
else
res.status(403).send(result);
}
} catch (error) { return error; }
});
and in database/models.js use async/await as well, but also rewrite mongoose methods into ones without callbacks, with returns into variables, like this:
async function createUser(email, username, password) {
try {
const hashPass = tools.generatePassword(password);
const newUser = new User({
email: email,
username: username,
hashedPassword: hashPass.hash,
salt: hashPass.salt
});
const result = await newUser.save();
return { email: result.email, username: result.username };
} catch (error) { console.log (error); return error; }
}

How to use async await with another API's implementation code

I have a user register function which is using async/await and I need to implement some code from another API. When I try to integrate it I get an error that I can't use await outside an async function.
exports.register = async (req, res) => {
// some logic here
nexmo.verify.request(
{
number: formattedMobile,
brand: "My Brand",
code_length: "4",
},
(err, result) => {
if (err) {
// If there was an error, return it to the client
return res.status(500).send(err.error_text);
}
// Otherwise, send back the request id. This data is integral to the next step
const requestId = result.request_id;
const salt = await bcrypt.genSalt(12);
const hashedPassword = await bcrypt.hash(password, salt);
const createdUser = new User({
name: name,
email: email,
mobile: formattedMobile,
password: hashedPassword,
});
try {
await createdUser.save();
res.status(200).send({ user: createdUser._id, otp: requestId });
} catch (err) {
res.status(500).send(err);
}
}
You need to make the callback function async, and most likely wrap the entire code in a try catch block to handle errors.
async (err, result) => {
if (err) {
// If there was an error, return it to the client
return res.status(500).send(err.error_text);
}
try {
// Otherwise, send back the request id. This data is integral to the next step
const requestId = result.request_id;
const salt = await bcrypt.genSalt(12);
const hashedPassword = await bcrypt.hash(password, salt);
const createdUser = new User({
name: name,
email: email,
mobile: formattedMobile,
password: hashedPassword,
});
try {
await createdUser.save();
res.status(200).send({ user: createdUser._id, otp: requestId });
} catch (err) {
res.status(500).send(err);
}
} catch(err) {
console.log(err);//do whatever error handling here
}
}

Using bcrypt on Node.js results in validation failed

I am writing code in Node.js to encrypt passwords using bcrypt.
However, if you use bcrypt, you will get an ValidationError: User validation failed: password: Cast to String failed for value "Promise { <pending> }" at path "password"
I do not get this error if I save it as plain text without encryption.
Is there a secret of bcrypt I do not know?
bcrypt (not working)
const bcrypt = require('bcrypt');
sign_up = (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
const hash = bcrypt.hash(password, 8);
const user = new User({
email: email,
password: hash
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};
no bcrypt (working)
sign_up = (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
const user = new User({
email: email,
password: password
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};
To elaborate on Chris's comment:
It appears that bcrypt.hash is asynchronous, and is returning a Promise.
To fix this, I would recommend using an async function and awaiting the result. MDN page
This may require a newer version of NodeJS than what you are running.
const bcrypt = require('bcrypt');
// Async function allows us to use await
sign_up = async (req, res, next) => {
const { email, password } = req.body;
const User = User.findOne({ email: email });
if (exUser) {
return res.send('exist user');
}
// We await the result of the hash function
const hash = await bcrypt.hash(password, 8);
const user = new User({
email: email,
password: hash
});
user.save((err) => {
if (err) {
return next(err);
}
res.send('signup success');
});
};
Do not use the bcrypt.hashSync function, as while it is running your server will not be able to do anything else.

Mongoose create() resolved before async middleware is finished

var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
email: {
type: string,
unique: true,
required: true,
trim: true
},
password: {
type: string,
required: true
},
authtokens: {
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'AuthToken' }]
}
});
//hashing a password before saving it to the database
UserSchema.pre('save', function (next) {
if (this.isNew) {
bcrypt.gensalt(10, function(err, salt) {
if (err) return next(err);
bcrypt.hash(this.password, salt, null, function (err, hash){
if (err) return next(err);
this.password = hash;
console.log('user.password ', this.password);
next();
});
});
} else next();
});
I call this from a controller:
'use strict';
var mongoose = require('mongoose'),
User = mongoose.model('User'),
AuthToken = mongoose.model('AuthToken');
exports.createUser = function(req, res, next) {
if (req.body.email && req.body.password && req.body.passwordConf) {
var userData = {
email: req.body.email,
password: req.body.password,
passwordConf: req.body.passwordConf
};
//use schema.create to insert data into the db
User.create(userData, function (err, user) {
console.log('user created ', user.password);
if (err) {
return next(err);
} else {
return res.redirect('/profile');
}
});
} else {
var err = new Error("Missing parameters");
err.status = 400;
next(err);
}
};
When a createUser is called with email user#email.com, password password, I get the output:
user.password $2a$10$wO.6TPUm5b1j6lvHdCi/JOTeEXHWhYernWU.ZzA3hfYhyWoOeugcq
user created password
Also, looking directly in the database, I see this user with plain text password -> password.
Why is user having plaintext password in the database. How can I store the hash instead?
In short, you forgot you were going into a callback which has a different functional scope and you're still referring to this, which is at that time not actually the "model" instance.
To correct this, take a copy of this before you do anything like launching another function with a callback:
UserSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
An alternate approach of course is to modernize things and use Promise results with async/await. The bcrypt library which is actually the "core" and not a fork does this right out of the box:
UserSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
Aside from the modern approach being generally cleaner code, you also don't need to change the scope of this since we don't "dive in" to another function call. Everything gets changed in the same scope, and of course awaits the async calls before continuing.
Full Example - Callback
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Full Example - Promise async/await
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Both show the password correctly encrypted, since we actually set the value in the model instance:
{
"_id": "5aec65f4853eed12050db4d9",
"email": "ted#example.com",
"password": "$2b$10$qAovc0m0VtmtpLg7CRZmcOXPDNi.2WbPjSFkfxSUqh8Pu5lyN4p7G",
"__v": 0
}

Nodejs export not returning value

I have a nodejs export like this
exports.add = function(req){
var newUser = new User({
email: req.body.email,
password: req.body.password,
});
// Attempt to save the user
newUser.save(function(err) {
if (err) {
return true;
}
return false;
});
}
But it's giving as undefined when i call the function like this
var value = instance.add(req);
Here instance is the imported instance of the javascript file
As stated in comments by #Ben Fortune you couldn't simply return a value from an asynchronous function call. you should use callbacks or promises:
The callback way:
exports.add = function (req, callback) {
var newUser = new User({
email: req.body.email,
password: req.body.password,
});
// Attempt to save the user
newUser.save(function(err) {
if (err) {
callback(err, null);
}
callback(null, newUser.toJSON()) ;
});
}
Then:
instance.add(req, function(err, value) {
if (err) throw err;
// use value here
});
Read More: How do I return the response from an asynchronous call? And implement promise way if you prefer.

Resources