Mongoose middleware schema.pre('save', ...) - node.js

I am making a REST API in NodeJS using the Mongoose Driver. I want to hash the passwords before saving them. For the same, I am using Mongoose Schema, where I made a userSchema for my user model. And for hashing I used the following function.
userSchema.pre('save', async (next) => {
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
But on creating a user or modifying it I am getting Error 500, and {} is getting returned. My routers are as follows.
router.post('/users', async (req, res) => {
const user = User(req.body);
try {
await user.save();
res.status(201).send(user);
} catch (e) {
res.status(400).send(e);
}
});
router.patch('/users/:id', async (req, res) => {
const updateProperties = Object.keys(req.body);
const allowedUpdateProperties = [
'name', 'age', 'email', 'password'
];
const isValid = updateProperties.every((property) => allowedUpdateProperties.includes(property));
if (!isValid) {
return res.status(400).send({error: "Invalid properties to update."})
}
const _id = req.params.id;
try {
const user = await User.findById(req.params.id);
updateProperties.forEach((property) => user[property] = req.body[property]);
await user.save();
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(400).send(e);
}
});
And the following is my console output.
Server running on port 3000
{}
undefined
On commenting out the userSchema.pre('save', ...) everything is working as expected. Please can you help me figure out where am I going wrong.

Using function definition instead of arrow function for mongoose pre save middleware:
userSchema.pre('save', async function(next) { // this line
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
Update:
The difference is this context, if you use arrow function in this line const user = this;, this now is your current file (schema file, I guess).
When you use function keyword, this context will belong to the caller object (user instance).

Related

How do I go about making the change password route in my restAPI?

I am currently doing this to register users:
const register = async (req, res) => {
const seller = await Seller.create({ ...req.body });
res.status(StatusCodes.CREATED).json({ seller });
};
I am using the pre-save function to hash the passwords:
SellerSchema.pre('save', async function () {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
But when making the password change function which I have made like this:
const updatePassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;
const seller = await Seller.findOne({ _id: id });
if(!seller) throw new NotFoundError(`No seller with id:${id} exists`)
const isPasswordCorrect = await seller.comparePassword(oldPassword);
if (!isPasswordCorrect) {
throw new UnauthenticatedError('Invalid Credentials');
}
seller.update({ password: newPassword });
await seller.save();
res.status(StatusCodes.OK).json({ seller });
};
But what happens is the pre-save function hashes the existing password before saving; how do I make it hash the incoming password? I know I can remove the pre-save function and just use it in the controller. But is there any way to do this by using the pre-save function?
const updatePassword = async (req, res) => {
const { id } = req.params;
const { oldPassword, newPassword } = req.body;
const seller = await Seller.findOne({ _id: id });
if(!seller) throw new NotFoundError(`No seller with id:${id} exists`)
const isPasswordCorrect = await seller.comparePassword(oldPassword);
if (!isPasswordCorrect) {
throw new UnauthenticatedError('Invalid Credentials');
}
// Correct this line of code
seller.password=newPassword
await seller.save();
res.status(StatusCodes.OK).json({ seller });
};

automatic logout after change any user info passport-local-mongoose

i tried req.login which is a passport method which i think its used when we sign up only but it didnt work and i see this solution Passport-Local-Mongoose – When I Update A Record's Username, I'm Logged Out, Why? but it didn`t work and
async (req, res, next) => {
const {id} = req.params;
if (req.file) {
const {path, filename} = req.file;
const foundUser = await User.findByIdAndUpdate(id, {
profileImage: {profileUrl: path, filename},
});
}
const foundUser = await User.findByIdAndUpdate(id, req.body.user);
req.logIn(foundUser, function (err) {
if (err) {
return next(err);
}
return res.redirect("/user/" + foundUser.username);
});
}
);
i found the solution the problem happens when you change username and passport is saving your username already on the session {user: ''} so when u change with new username we don`t update passport user with new username
so try this
try {
const foundUser = await User.findByIdAndUpdate(id, req.body.user);
req.session.passport.user = req.body.user.username;
return res.redirect("/users/" + id);
} catch (e) {
const duplicated = Object.keys(e.keyPattern);
return next(
new AppError(
`this ${duplicated} already in use, you can enter another ${duplicated} `,
406
)
);
}

deleting key from object but still showing in response

I am experimenting with node authentication, I have managed to store a username and a hashed password into my database, but I want to return the json back without the hashed password.
I am deleting the password key before sending the JSON back but the password still shows in the returned result.
router.post("/signup", async (req, res, next) => {
const user = await User.exists({ username: req.body.username });
if (user) {
const error = new Error("Username already exists");
next(error);
} else {
const newUser = new User({
username: req.body.username,
password: req.body.password,
});
try {
const result = await newUser.save();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}
}
});
the User model has a pre hook to hash the password before save:
userSchema.pre("save", async function save(next) {
const user = this;
if (!user.isModified("password")) return next();
try {
user.password = await bcrypt.hash(user.password, 12);
return next();
} catch (err) {
return next(err);
}
});
Here is the solution thanks to Mahan for pointing it out.
result returns a Mongoose object so needs turning into a normal Javascript object first.
try {
let result = await newUser.save();
result = result.toObject();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}

async / await doesn't seem to work nodejs passport reset passwd

For some reason i get this Error :
Error: req#login requires a callback function
at IncomingMessage.req.login.req.logIn (/home/project/node_modules/passport/lib/http/request.js:47:44)
at exports.update (/home/project/controllers/authController.js:92:13)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
with this code:
exports.update = async (req, res) => {
const user = await User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: { $gt: Date.now() }
});
if (!user) {
req.flash('error', 'Password reset is invalid or has expired');
return res.redirect('/login');
}
const setPassword = promisify(user.setPassword, user);
await setPassword(req.body.password);
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
const updatedUser = await user.save();
await req.login(updatedUser);
req.flash('success', 'Your password has been reset! You are now
logged in!');
res.redirect('/');
};
What am i doing wrong ? I can't figure it out ..
As per the error message, req.login(updatedUser); expects a callback as the second argument. Basically, you'd do something like this:
req.login(updatedUser, function(err, data) {
// handle err and data
});
However, since you wish to await on this async operation, you need something that returns a promise. Basically, you can await on any value that is a promise (and login does not return one.
You can build a promise around this call like this:
await new Promise(function(res, rej) {
req.login(updatedUser, function(err, data) {
if (err) rej(err);
else res(data);
});
})
Node.JS ships with a helper function named promisify to help constructing this behavior if you do not wish to do this manually everytime.
Exactly as Ekin Konc answered you can only async/await only functions that returns a Promise.
You can write some middleware to alias your req.login to promisified function.
E.g
loginPromisifier.js
const { promisify } = require('util');
const loginPromisifier = (req,res,next)=>{
req.login = promisify(req.login);
next();
}
module.exports = loginPromisifier;
and then app.js
const loginPromisifier = require(./loginPromisifier) // your path to it
app.use(loginPromisifier);
I hope that helped.

Node.js. mongodb return promise

I am creating REST api and want to authenticate user by token. I've found tutorial and wrote function based on it. But it's on callbacks and I want to return Promise from mongoose model and in route use then rather than do callbacks. Here is my function:
UserSchema.statics.authenticate = function(login, password, fn) {
var token;
this.findOne({login: login}, function(err, user) {
var _token;
if (err)
token = ( false);
if (!user){
token = ( false);
}
else if (user)
{
if (user.password != password)
token = ( false);
else
{
token = jwt.sign(user, secret);
user.update(
{ $set: {
token: token ,
lastActive: new Date()
}}
);
}
}
fn(token);
});
};
module.exports = mongoose.model('User', UserSchema);
I know that to return promise from find function i have to usee exec() but what I want to achive is to return token do I have to var q = new Promise in function and return this q object?
This is my route
router.post('/authenticate', function(req, res, next) {
User.authenticate( req.body.login,req.body.password, function(response){
if(response)
res.status(200)
.send({'success': true, token: response, msg: "Successfuly authenticated"});
else
res.status(200)
.send({'success': false, token: null, msg: "Wrong username or password"});
})
});
Bluebird is a great library to handle this.
You can define a promise before a query, resolve it in the response, and yield it after.
For example:
var Promise = require('bluebird');
var defer = Promise.defer();
collection.find({name: 'name here'}).exec(function(err, result) {
if(err){
defer.reject(err);
} else {
defer.resolve(result);
}
});
return defer.promise
The best way to use mongoose with promises is to used the bluebird npm package :
npm install --save bluebird
and to make that on your models declaration :
const Promise = require('bluebird');
//...
let Model = mongoose.model('User', UserSchema);
module.exports = Promise.promisifyAll(Model);
Now you can use all mongoose methods with promises :
const User = require('./User');
User.find().then((users) => {
if (users) {
reply(null, users);
return;
}
reply(null, []);
}).catch((err) => {
reply.boom(500, err);
});

Resources