Related
The problem start when I use the bcrypt middleware to encrypt my password.
Whitout bcrypt I could save the users, but with it not now.
My users.js file
'use strict'
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const bcrypt = require('bcrypt')
const UserSchema = new Schema({
email: { type: String, unique: true, lowercase: true },
displayName: String,
password: { type: String, select: false }
})
UserSchema.pre('save', (next) => {
let user = this
if (!user.isModified('password')) {
return next();
}
bcrypt.genSalt(10, (err, salt) => {
if (err) return next(err)
bcrypt.hash(user.password, salt, null, (err, hash) => {
if (err) return next(err)
user.password = hash
next();
})
})
})
module.exports = mongoose.model('User', UserSchema)
My router.js file:
const express = require('express')
const router = express.Router()
const mongoose = require('mongoose')
const User = require('./model/user')
const bcrypt = require('bcrypt')
router.post('/user', (req, res) => {
console.log(req.body)
let user = new User()
user.email = req.body.email
user.displayName = req.body.displayName
user.password = req.body.password
user.save((err, stored) => {
res.status(200).send({
user: stored
})
})
})
This is the server response:
{}
My db is not affected...
I can see two mistakes in the provided code:
1. this in the pre-save middleware is not a user document instance
Arrow functions do not provide their own this binding:
In arrow functions, this retains the value of the enclosing lexical context's this. [source]
Change your code to the following:
UserSchema.pre('save', function (next) {
const user = this;
// ... code omitted
});
2. Not handling duplicate key MongoError
The request might fail with MongoError: E11000 duplicate key error collection since email field is unique. You are ignoring such fail and since stored in your user.save() is undefined the response from the server is going to be an empty object.
To fix this issue you need to add a handler in the following code:
user.save((err, stored) => {
if (err) {
throw err; // some handling
}
res.status(200).send({user: stored});
});
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
}
I am using passportjs to handle auth of my app.
Once the user is logged in, I want to add the possibility to change the password from inside the app.
this is in my controller:
$http.post('/change-my-password',{oldPassword: $scope.user.oldpassword, newPassword: $scope.user.newpassword})
.then(function (res) {
if (res.data.success) {
// password has been changed.
} else {
// old password was wrong.
}
});
and this is my route handler in express nodejs in backend:
router.post('/change-my-password', function (req, res) {
if (!req.isAuthenticated()) {
return res.status(403).json({
success: false
});
}
UserSchema.findById(req.user._id, function(err, user){
if (err) return res.status(200).json({success: false});
user.validatePassword(req.body.oldPassword, function(err) {
if (err){
return res.status(200).json({
success: false
});
}
user.setPassword(req.body.newPassword, function() {
if (err || !user) {
return res.status(200).json(
{
success: false
}
)
}
user.save(function(err) {
if (err) return res.status(200).json({success: false});
req.login(user, function (err) {
if (err) return res.status(200).json({success: false});
return res.status(200).json({success: true});
});
});
});
});
});
});
here is my user schema model:
// user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema({
email: String,
password: String,
confirmStatus: Boolean,
token: String,
registerAt: Number
});
UserSchema.methods.validatePassword = function (password, callback) {
this.authenticate(password, callback);
};
UserSchema.plugin(passportLocalMongoose,
{
usernameField: 'email'
});
module.exports = mongoose.model('users', UserSchema);
the problem:
I find my user by Id in my mongoose schema UserSchema then I should check if the oldPassword is valid or not, and then I set the new password.
I successfully find the user and the set the new password. But the part that should check for comparison of the old password field, doesn't work at all. Whatever I enter in the old password field gets accepts as OK and that step is skipped. Whereas, it should throws an error saying that the old password is wrong.
I am also advised to use sanitizedUser in order not to show my salt and etc.
Question is: how can I first do the comparison check of the old password and then do the set new password step? If possible, how can I use the sanitize? And how can I check if the user is not entering the same password as the new password? or if possible, saying that the new password is very similar to the old one?
You can implement the it using the new feature added 3 days ago:
just use the changePassword method, and it handles it through this:
schema.methods.changePassword = function(oldPassword, newPassword, cb) {
if (!oldPassword || !newPassword) {
return cb(new errors.MissingPasswordError(options.errorMessages.MissingPasswordError));
}
var self = this;
this.authenticate(oldPassword, function(err, authenticated) {
if (err) { return cb(err); }
if (!authenticated) {
return cb(new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError));
}
self.setPassword(newPassword, function(setPasswordErr, user) {
if (setPasswordErr) { return cb(setPasswordErr); }
self.save(function(saveErr) {
if (saveErr) { return cb(saveErr); }
cb(null, user);
});
});
});
};
so in your code, you need to replace the validatePassword method by this:
user.changePassword(req.body.oldPassword,req.body.newPassword, function(err) {
if (err){
return res.status(200).json({
success: false
});
}
hope this works for you.
I'm making a User Authentication with passport. First I created a default Admin User. Now this Admin must able to create users but not any other users. For this I created a Admin user in Database. Now my Question is how to create the other users by the Admin and as well only this Admin should have access to all API's routes but not for any other Users how to protect the API's? In server.js file i created middleware function as
//Catch unauthorized errors
app.use(function (err, req, res, next) {
if(err.name === 'UnauthorizedError') {
res.status(401);
res.json({"message": err.name + ":" + err.message});
}
});
Please help with this. I hope you guys don't mind for posting such a long files.
'authentication.js'
'use strict';
var passport = require('passport'),
mongoose = require('mongoose'),
Users = mongoose.model('Users');
var authentication = {
register: function(req, res, name, email, password) {
var userData = req.body;
var user = new Users({
email: userData.email,
name: userData.name,
});
user.setPassword(userData.password);
if(!user) {
res.status(400).send({error: 'All fields required'});
}
user.save(function(err, result) {
if(err) {
console.log('Could not save the User');
res.status(500).send({error: 'Could not save the User'});
}else {
res.send('New User Created Successfully');
}
});
},
login: function (req, res) {
if(!req.body.email || !req.body.password) {
res.status(400).send({"message": "All fields required"});
return;
}
passport.authenticate('local', function (err, user, info) {
var token;
if (err) {
res.status(404).send({err: 'An Error Occured'});
return;
}
if(user) {
token = user.generateJwt();
res.status(300).send({"token": token});
}else {
res.status(401).send('Unauthorized User');
}
});
}
};
module.exports = authentication;
'user-model.js'
'use strict';
var mongoose = require('mongoose'),
crypto = require('crypto'),
jwt = require('jsonwebtoken'),
Schema = mongoose.Schema;
var userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
name: {
type: String,
required: true
},
hash: String,
salt: String
});
userSchema.methods.setPassword = function (password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};
//Validating a submitted password
userSchema.methods.validPassword = function (password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
};
//Generating a JSON Web Token
userSchema.methods.generateJwt = function () {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
return jwt.sign({
_id: this._id,
email: this.email,
name: this.name,
exp: parseInt(expiry.getTime() / 1000)
}, process.env.JWT_SECRET);
};
var User = mongoose.model('Users', userSchema);
var user = new User();
user.name = 'Arjun Kumar';
user.email = 'arjun#kumar.com';
user.setPassword('myPassword');
user.save();
'user-route.js'
'use strict';
var express = require('express'),
userRoute = express.Router(),
jwt = require('express-jwt'),
authentication = require('../controllers/authentication');
var auth = jwt({
secret: process.env.JWT_SECRET,
userProperty: 'payload'
});
userRoute.post('/:adminuserid/register', auth, authentication.register)
.post('/login', authentication.login);
module.exports = userRoute;
'passport.js'
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
mongoose = require('mongoose'),
Users = mongoose.model('Users');
passport.use(new LocalStrategy({usernameField: 'email'}, function (username, password, done) {
Users.findOne({ email: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Incorrect username.'
})
}
if (!user.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.'
});
}
return done(null, user);
});
}));
One thing you can do it to put all your functions in a conditional like this to give access only to admin:
If(req.user.email === your admin email) {
Your function
}
This should go under the routes that you want only the admin have access to.
Or if you have several admins , then you should alter your schema a bit and add an admin : Number which you can later declare for example any user with admin:1 are system administrators otherwise not .
I hope I understood your question correctly.
Good luck
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 });