Mongoose schema async method doesn't wait - node.js

this is my code :
const user = await User.findOne({ email }).select('+password');
console.log(' user value : ', user);
const boolean = await user.comparePassword(password, user.password);
console.log('boolean value : ', boolean);
The results of my flags in console :
user value : {
role: 'user',
passwordChangedAt: 2021-04-14T02:19:54.689Z,
_id: 6074b03b28f33810a528d61f,
name: 'Javier',
password: '$2a$12$cSsURwyoGGd2j9kreuwnGur3pYaTmnY3K2vjXRSJpDhptDwy0t4lG',
__v: 0
}
boolean value : undefined
So, boolean var is not waiting, and i don't know why. These is my function in my userschema :
userSchema.methods.comparePassword = catchAsync(async function comparePassword(
password,
userPassword
) {
const aux = await bcrypt.compare(password, userPassword);
return aux;
});
```
How can i force boolean to wait for the result of comparePassword function ?

userSchema.methods.comparePassword = async (password, userPassword) => {
return new Promise((resolve, reject) => {
bcrypt.compare(password, userPassword, (err, result) => {
if (err) {
resolve(false);
}
if (result == true) {
resolve(true);
} else {
resolve(false);
}
})
});
}

Related

Check if user exists in router before creation in database with Sequelize

I have a function to check if a user exists, and a function to create a new user in my User model.
What I want to do is call them in the router to check if a user with the email adress in req.body already exists.
If it does, I want to return a message, and if not, I want to create the user.
When I try to call the route in Postman, I get this error in node console :
node_modules/express/lib/response.js:257
var escape = app.get('json escape')
TypeError: Cannot read properties of undefined (reading 'get')
User model :
const Sequelize = require("sequelize");
const connexion = require("../database");
const User = connexion.define(
"users",
{
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
email: {
type: Sequelize.STRING(100),
allowNull: false,
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
},
{
freezeTableName: true
}
);
function checkUser(userEmail) {
const findUser = User.findOne({ where: { userEmail } }).catch((err) => {
console.log(err);
});
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
}
function createUser(userData) {
console.log(userData);
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
module.exports = { createUser, checkUser };
user controller :
const createUser = require("../models/User");
const bcrypt = require("bcrypt");
const saltRounds = 10;
addUser = async (req, res) => {
try {
const userData = req.body;
console.log(req.body);
bcrypt.hash(userData.password, saltRounds, async function (err, hash) {
userData.password = hash;
const newUser = await createUser(req.body);
res.status(201).json({ newUser });
});
} catch (err) {
console.log(err);
res.status(500).json("Server error");
}
};
module.exports = addUser;
user router :
const express = require("express");
const router = express.Router();
const addUser = require("../controllers/userController");
const { checkUser } = require("../models/User");
router.post("/", async (req, res) => {
const { email } = req.body;
const alreadyExists = await checkUser(email);
if (!alreadyExists) {
addUser(req.body);
}
});
module.exports = router;
EDIT : Finally I'm trying a more simple way. I will do the check part directly into the createUser function.
But now, it creates the user even if the email already exists ^^
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
i think the problem is with this part you are trying to use res but it doesn't exist in your checkUser function
if (findUser) {
return res.json({ message: "Cette adresse email est déjà enregistrée" });
} else {
return false;
}
try this instead
if (findUser) {
return true });
} else {
return false;
}
UPDATE to fix the problem of user creation if it already exists
async function createUser(userData) {
console.log(userData);
const findUser = await User.findOne({ where: userData.email }).catch(
(err) => {
console.log(err);
}
);
if(!findUser){
findUser
? console.log(findUser)
: User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}
Problem solved by doing this (thanks super sub for your help):
async function createUser(userData) {
console.log(userData);
const email = userData.email;
const findUser = await User.findOne({ where: { email } }).catch((err) => {
console.log(err);
});
if (!findUser) {
User.create(userData)
.then((user) => {
console.log(user);
})
.catch((err) => {
console.log(err);
});
}
}

Can't seem to fix undefined await result

I need to compare passwords with Bcrypt library.
here's my code:
bcrypt.js
const bcrypt = require('bcrypt');
const saltRounds = 10;
var Bcrypt = () => {
}
Bcrypt.encrypt = async function(password) {
const hashedPassword = await bcrypt.hash(password, saltRounds)
return hashedPassword
}
Bcrypt.compare = async function(password, hashed_password) {
await bcrypt.compare(password, hashed_password, function(err, result) {
return result;
});
}
module.exports = Bcrypt;
userMethods.js
const Bcrypt = require('../../global-functions/bcrypt');
var login = async(req) => {
var user = {
username: req.body.username,
password: req.body.password
}
if (!user.username || !user.password) {
return ({ error: "Login details are required to continue." });
}
return new Promise((resolve, reject) => {
db.query("SELECT name,username,email,password FROM users WHERE username = ?", [req.body.username], function(err, rows) {
if (err) {
console.log("error: ", err);
reject(err);
} else {
var compared_result = Bcrypt.compare(user.password, rows[0].password); // returns **undefined**
}
if (compared_result) {
resolve({ success: compared_result, username: rows[0].username });
} else {
resolve({ error: "wrong username or password!" });
}
});
});
}
when I try to add await before Bcrypt.compare I get an "await is only valid in async function" error.
I would appreciate any help.
Do you want to put it before this Bcrypt.compare
Bcrypt.compare = async function(password, hashed_password) {
await bcrypt.compare(password, hashed_password, function(err, result) {
return result;
});
}
Then you have to write this code into a outer function and give async there then use await inside
async function()
{
await Bcrypt.compare = async function(password, hashed_password) {
await bcrypt.compare(password, hashed_password, function(err, result) {
return result;
});
}
}
I think this might solve your problem

problem with koa, mongoose await not returning ctx.body

I'm using koa to reset a password, wanting to use .save in order to fire the schema.pre('save' ).
data was returning with findOneAndUpdate, but not when I use .save.
what's the magic combination to make this return the .save doc properly with the await/asyncs?
r.post("/public/auth/resetpass", async (ctx, next) => {
const values = ctx.request.body;
const query = {
email: values.email,
resetPasswordToken: values.resetPasswordToken,
resetPasswordExpires: {
$gt: new Date(new Date())
}
};
const update = {
password: values.password,
resetPasswordToken: null,
resetPasswordExpires: null
};
// let userFound = null;
await User.findOne(query,async function(err, user) {
if (err) {
console.log("*** err");
next(err);
} else {
if (_.isEmpty(user)) {
ctx.status = 200;
ctx.body = {
error: true,
message: "token is incorrect or time has expired for password reset"
};
} else {
user.password = values.password;
await user.save(function(err, doc) {
if (err) {
console.log('***err saving');
next(err);
} else {
//console.log fires, but ctx body doesn't return
console.log ('***saved, writing poco');
ctx.body = userToPoco(doc);
}
});
}
}
});
});
ultimately switched to a promise.
await user.save().then (doc =>{
ctx.body = doc;
});

ReferenceError: nome is not defined

I'm trying to make a function retrieve a callback. First the error was that callback parameter was not a function, than I tried to fix my syntax its saying that a parameter is not defined.
controller: (ERROR AT LINE 10):
//Tried to declare like "function registraU(nome, ... ())
const registraUsuario = (nome, email, password, (e, usuarioCriado) => {
UsuarioModel.findOne({ email: email }, (e, match) => {
if (e) { return callback(e); }
if (match !== null) {
return callback(null, null);
} else {
var hash = bcrypt.hashSync(password, 10);
password = hash;
novoUsuario = {
nome: nome,
email: email,
password: password
}
var temp = new UsuarioModel(novoUsuario);
temp.save(function(e, usuarioCriado){
if(e){console.log(e)};
return callback(null, usuarioCriado);
});
}
});
});
And this is the code that is calling it:
passport.use('local-registro', new LocalStrategy({
nomeField: 'nome',
emailField: 'email',
passwordField: 'password',
passReqToCallback : true
},
(req, nome, email, password, done) => {
UsuarioController.registraUsuario(nome, email, password, (e, callback) => {
if(e) {return done(e); }
if(!novoUsuario){
return done(null, false, req.flash({"erroRegistro": "Email já cadastrado"}));
} else {
return done(null, novoUsuario);
}
});
}
));
You're declaring as a kind of es6 arrow function but you forgot to add the function body...
const registraUsuario = (nome, email, password, (e, usuarioCriado) => {...});
But where is the rest of you function body ??
const registraUsuario = (nome, email, password, (e, usuarioCriado) => {...}) => {
// your function body
}
That's why you code is broken... It's not interpreted as a function declaration.

Mongoose password hashing

I am looking for a good way to save an Account to MongoDB using mongoose.
My problem is: The password is hashed asynchronously. A setter wont work here because it only works synchronous.
I thought about 2 ways:
Create an instance of the model and save it in the callback of the
hash function.
Creating a pre hook on 'save'
Is there any good solution on this problem?
The mongodb blog has an excellent post detailing how to implement user authentication.
http://blog.mongodb.org/post/32866457221/password-authentication-with-mongoose-part-1
The following is copied directly from the link above:
User Model
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10;
var UserSchema = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true }
});
UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('User', UserSchema);
Usage
var mongoose = require(mongoose),
User = require('./user-model');
var connStr = 'mongodb://localhost:27017/mongoose-bcrypt-test';
mongoose.connect(connStr, function(err) {
if (err) throw err;
console.log('Successfully connected to MongoDB');
});
// create a user a new user
var testUser = new User({
username: 'jmar777',
password: 'Password123'
});
// save the user to database
testUser.save(function(err) {
if (err) throw err;
});
// fetch the user and test password verification
User.findOne({ username: 'jmar777' }, function(err, user) {
if (err) throw err;
// test a matching password
user.comparePassword('Password123', function(err, isMatch) {
if (err) throw err;
console.log('Password123:', isMatch); // -> Password123: true
});
// test a failing password
user.comparePassword('123Password', function(err, isMatch) {
if (err) throw err;
console.log('123Password:', isMatch); // -> 123Password: false
});
});
For those who are willing to use ES6+ syntax can use this -
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const { isEmail } = require('validator');
const { Schema } = mongoose;
const SALT_WORK_FACTOR = 10;
const schema = new Schema({
email: {
type: String,
required: true,
validate: [isEmail, 'invalid email'],
createIndexes: { unique: true },
},
password: { type: String, required: true },
});
schema.pre('save', async function save(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
this.password = await bcrypt.hash(this.password, salt);
return next();
} catch (err) {
return next(err);
}
});
schema.methods.validatePassword = async function validatePassword(data) {
return bcrypt.compare(data, this.password);
};
const Model = mongoose.model('User', schema);
module.exports = Model;
TL;DR - Typescript solution
I have arrived here when I was looking for the same solution but using typescript. So for anyone interested in TS solution to the above problem, here is an example of what I ended up using.
imports && contants:
import mongoose, { Document, Schema, HookNextFunction } from 'mongoose';
import bcrypt from 'bcryptjs';
const HASH_ROUNDS = 10;
simple user interface and schema definition:
export interface IUser extends Document {
name: string;
email: string;
password: string;
validatePassword(password: string): boolean;
}
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});
user schema pre-save hook implementation
userSchema.pre('save', async function (next: HookNextFunction) {
// here we need to retype 'this' because by default it is
// of type Document from which the 'IUser' interface is inheriting
// but the Document does not know about our password property
const thisObj = this as IUser;
if (!this.isModified('password')) {
return next();
}
try {
const salt = await bcrypt.genSalt(HASH_ROUNDS);
thisObj.password = await bcrypt.hash(thisObj.password, salt);
return next();
} catch (e) {
return next(e);
}
});
password validation method
userSchema.methods.validatePassword = async function (pass: string) {
return bcrypt.compare(pass, this.password);
};
and the default export
export default mongoose.model<IUser>('User', userSchema);
note: don't forget to install type packages (#types/mongoose, #types/bcryptjs)
I think this is a good way by user Mongoose and bcrypt!
User Model
/**
* Module dependences
*/
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt');
const SALT_WORK_FACTOR = 10;
// define User Schema
const UserSchema = new Schema({
username: {
type: String,
unique: true,
index: {
unique: true
}
},
hashed_password: {
type: String,
default: ''
}
});
// Virtuals
UserSchema
.virtual('password')
// set methods
.set(function (password) {
this._password = password;
});
UserSchema.pre("save", function (next) {
// store reference
const user = this;
if (user._password === undefined) {
return next();
}
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) console.log(err);
// hash the password using our new salt
bcrypt.hash(user._password, salt, function (err, hash) {
if (err) console.log(err);
user.hashed_password = hash;
next();
});
});
});
/**
* Methods
*/
UserSchema.methods = {
comparePassword: function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
}
module.exports = mongoose.model('User', UserSchema);
Usage
signup: (req, res) => {
let newUser = new User({
username: req.body.username,
password: req.body.password
});
// save user
newUser.save((err, user) => {
if (err) throw err;
res.json(user);
});
}
Result
Result
The Mongoose official solution requires the model to be saved before using the verifyPass method, which can cause confusion. Would the following work for you? (I am using scrypt instead of bcrypt).
userSchema.virtual('pass').set(function(password) {
this._password = password;
});
userSchema.pre('save', function(next) {
if (this._password === undefined)
return next();
var pwBuf = new Buffer(this._password);
var params = scrypt.params(0.1);
scrypt.hash(pwBuf, params, function(err, hash) {
if (err)
return next(err);
this.pwHash = hash;
next();
});
});
userSchema.methods.verifyPass = function(password, cb) {
if (this._password !== undefined)
return cb(null, this._password === password);
var pwBuf = new Buffer(password);
scrypt.verify(this.pwHash, pwBuf, function(err, isMatch) {
return cb(null, !err && isMatch);
});
};
Another way to do this using virtuals and instance methods:
/**
* Virtuals
*/
schema.virtual('clean_password')
.set(function(clean_password) {
this._password = clean_password;
this.password = this.encryptPassword(clean_password);
})
.get(function() {
return this._password;
});
schema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* #param {String} plainText
* #return {Boolean}
* #api public
*/
authenticate: function(plainPassword) {
return bcrypt.compareSync(plainPassword, this.password);
},
/**
* Encrypt password
*
* #param {String} password
* #return {String}
* #api public
*/
encryptPassword: function(password) {
if (!password)
return '';
return bcrypt.hashSync(password, 10);
}
};
Just save your model like, the virtual will do its job.
var user = {
username: "admin",
clean_password: "qwerty"
}
User.create(user, function(err,doc){});
const bcrypt = require('bcrypt');
const saltRounds = 5;
const salt = bcrypt.genSaltSync(saltRounds);
module.exports = (password) => {
return bcrypt.hashSync(password, salt);
}
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const hashPassword = require('../helpers/hashPassword')
const userSchema = new Schema({
name: String,
email: {
type: String,
match: [/^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, `Please fill valid email address`],
validate: {
validator: function() {
return new Promise((res, rej) =>{
User.findOne({email: this.email, _id: {$ne: this._id}})
.then(data => {
if(data) {
res(false)
} else {
res(true)
}
})
.catch(err => {
res(false)
})
})
}, message: 'Email Already Taken'
}
},
password: {
type: String,
required: [true, 'Password required']
}
});
userSchema.pre('save', function (next) {
if (this.password) {
this.password = hashPassword(this.password)
}
next()
})
const User = mongoose.model('User', userSchema)
module.exports = User
const mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
SALT_WORK_FACTOR = 10;
const userDataModal = mongoose.Schema({
username: {
type: String,
required : true,
unique:true
},
password: {
type: String,
required : true
}
});
userDataModal.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
userDataModal.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
// Users.index({ emaiId: "emaiId", fname : "fname", lname: "lname" });
const userDatamodal = module.exports = mongoose.model("usertemplates" , userDataModal)
//inserting document
userDataModel.findOne({ username: reqData.username }).then(doc => {
console.log(doc)
if (doc == null) {
let userDataMode = new userDataModel(reqData);
// userDataMode.password = userDataMode.generateHash(reqData.password);
userDataMode.save({new:true}).then(data=>{
let obj={
success:true,
message: "New user registered successfully",
data:data
}
resolve(obj)
}).catch(err=>{
reject(err)
})
}
else {
resolve({
success: true,
docExists: true,
message: "already user registered",
data: doc
}
)
}
}).catch(err => {
console.log(err)
reject(err)
})
//retriving and checking
// test a matching password
user.comparePassword(requestData.password, function(err, isMatch) {
if (err){
reject({
'status': 'Error',
'data': err
});
throw err;
} else {
if(isMatch){
resolve({
'status': true,
'data': user,
'loginStatus' : "successfully Login"
});
console.log('Password123:', isMatch); // -> Password123: true
}
I guess it would be better to use the hook, after some research i found
http://mongoosejs.com/docs/middleware.html
where it says:
Use Cases:
asynchronous defaults
I prefer this solution because i can encapsulate this and ensure that an account can only be saved with a password.
I used .find({email}) instead of .findOne({email}).
Make sure to use .findOne(...) to get a user.
Example:
const user = await <user>.findOne({ email });

Resources