passport-local-mongoose changePassword function - node.js

I want to have the functionality that user can change their password.
I've implemented a route ('/resetPasswd') like this:
UserRouter.route('/resetPasswd')
.post(function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
user.changePassword(req.body.oldPassword, req.body.newPassword, function (err, user) {
if (err) next(err);
res.json('password changes successfully !');
})
})(req, res, next);
});
this is what I send as the body:
{
"oldpassword": "secret",
"newPassword": "new"
}
But I get this error as response:
{
"message": "user.changePassword is not a function",
"error": {}
}
and this is a picture of my schema:
user schema:
I don't think I should declare the changePassword function in my schema (since it is provided by the passport-local-mongoose, although I added it but still get the same error) What mistake am I making here?

Someone had the same issue last night actually. Their problem was the package needed to be updated. I would check that you're on the latest version.

Here is what i did in my controller handling the reset password,
exports.editPassword = async (req, res) => {
const user = await User.findOne({
username: req.user.username
});
await user.setPassword(req.body.password);
const updatedUser = await user.save();
req.login(updatedUser);
req.flash('success', 'Password Changed Successfully') res.redirect('back')
}
from the Documentation Passport-local-mongoose ,you first need to get the specific user to update the password , here in my case the current login user , which is available on the req.user which we are exposed to , you can use any of the return property to query your collection, using async await i made a variable to hold the return object, in my case 'user', thereafter i chained the setProperty on it passing in the new password(req.body.password) since it return a promise i await it and assign a variable to it. from here you are good ...Note: since it is a promise it either resolved of reject, handling error can be done by rapping your code in a safe blanket, try..catch . You can read more Here

Since changePassword is a schema method, it must be used on an instance of a model, not the model itself or the imported passportLocalMongoose.
UserModel.findById(req.user._id)
// I assume you already have authentication and the req.user is generated
.then(foundUser => {
foundUser.changePassword(req.body.old, req.body.new)
.then(() => {
console.log('password changed');
})
.catch((error) => {
console.log(error);
})
})
.catch((error) => {
console.log(error);
});
the user object passport sends in callback function is just an object and not a schema instance document object, thus it does not have the changePassword function.

Related

TypeError: Cannot read property 'then' of undefined when using JWT token and refresh token

Sorry I'm new to backend (nodejs) and I'm trying to build a JWT token and refresh token but I stumbled across an error that I cannot fix when I try to run an endpoint in insomnia.
TypeError: Cannot read property 'then' of undefined
This is the code snippet from the area the error came from in my app.js file
app.post('/users/login', (req, res) => {
let email = req.body.email;
let password = req.body.password;
User.findByCredentials(email, password)
.then((user) => {
return user.createSession()
.then((refreshToken) => {
return user.generateAccessAuthToken()
.then((accessToken) => {
return { accessToken, refreshToken };
});
})
.then((authTokens) => {
res
.header('x-refresh-token', authTokens.refreshToken)
.header('x-access-token', authTokens.accessToken)
.send(user);
})
}).catch((e) => {
res.status(400).send(e);
});
});
And this is the code for the method "findByCredentials" in my user.model.js file
UserSchema.statics.findByCredentials = function(email, password) {
let User = this;
User.findOne({ email }).then((user) => {
if(!user) return Promise.reject();
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, res) => {
if (res) resolve(user);
else {
reject();
}
})
})
});
}
Please help me
Error solved: I just needed to return the value of the findByCredentials method to solve the problem
Since the error isn't posted, it's hard to pinpoint the error line & cause.
But I would suggest using .catch blocks along with the end of every .then chain and get the spot/line of error
Recommendation:
You are creating a callback hell by using nested .then chains what is call back hell.
Try using async/await syntax to achieve the same functionality in a simplified way - Ref.
Regards,
Muhamed
As if you didn't send any code snippet, it is quite clear. But after going through your code it seems after resolving the "user" you are calling two methods createSession and generateAccessAuthToken. And as you also didn't send any code snippet of these methods I'm assuming you are having error from one of the functions.
And a piece of advice:
Practice best error handling patterns. It will help you in your journey.
https://www.youtube.com/watch?v=cFTFtuEQ-10
And async js:
https://www.youtube.com/watch?v=PoRJizFvM7s

User.findOne returns null

router.post("/login", async (req, res) =>
{
try
{
console.log(req.body.email)
const user = await User.findOne({email: req.body.email, password: req.body.password})
if(user)
{
return res.redirect("/")
}
console.log(user)
res.status(200).send(user)
}
catch(e)
{
res.status(400).send(e)
}
})
I am trying to look for a user in MongoDB but the user variable returns null and I am getting status code 200 with an empty object.
Thanks for answers. I found the missing point. I stored the hashed password in database but I forget to search for hashed password
First of all, you should consider not storing passwords as plaintext in your database and when finding a user querying them. I recommend using a hashing algorithm like bcrypt to validate them.
Regarding your question, you have to know that mongoose does not throw an error when it finds no entries and you only send 404 when an error is thrown. I would recommend you changing your code to the following:
router.post("/login", async (req, res) =>
{
try
{
console.log(req.body.email)
const user = await User.findOne({email: req.body.email})
if (user) {
// TODO: Use hashing algorithm to verify password
console.log(user)
res.status(200).send(user)
return res.redirect("/")
} else {
res.status(400).send("User not found")
}
}
catch(e)
{
res.status(500).send("Server error: " + e.message)
}
})
This didn't match the query. Are you sure the e-mail and password are correct? The syntax is correct. It shows status code of 200, that means your code is perfect, but your email pr password is wrong or there is no data in the database with that email and password. Like #Adwirawien said you should hash your password.
looks like your condition:
if(user)
should be written
if(!user)
Or am I wrong ?

Check for existing user using Mongoose

I'm trying to write a middleware function that (when a POST request is made with a username/password) checks to see if the user being created already exists in the database. I don't know if I'm doing this properly though.
User.find({ username: req.body.username }) returns an object which contains (or does not contain) the user if it exists...but how to properly return to exit if a user under the same username is found? Whenever I test this with Mocha, res.body.msg comes up as undefined.
Code:
module.exports = exports = function(req, res, next) {
User.find({ username: req.body.username }, (err, user) => {
if (err) return handleDBError(err, res);
if (user) return res.status(200).json({ msg: 'an account with this username already exists' });
});
next();
};
User Schema:
var userSchema = new mongoose.Schema({
username: String,
authentication: {
email: String,
password: String
}
});
give it a try very initial create a function to get the user response
function findUser(arg, callback) {
/* write your query here */
return callback(pass response here)
}
And then use it where you want
findUser(arg,function(callbackResponse) { /*do something*/ })
Since nodejs is asynchronous, chances are that the response is being sent after you are trying to read it. Make sure to keep the reading process waiting untill response is sent. I personaly use passport for handling that.

Return value from one function to another with Node.JS

I am working on a login interface using the MEAN stack. I have managed to get it to work using PassportJS. My problem now is I need a way to let my client-side know whether the person logging in is an admin or user(user role). These info are available from my MongoDB.
The flow of my API call is as follow :
app.post('/login', passport.authenticate('local'), authRoutes.loginCheck);
First, it runs the passport.authenticate where it calls the function below
function verifyCredentials(username, password, done) // username & password from what user provide when logging in
{
console.log('VC');
User.findOne({username: username}, function(err, user) //query Mongo
{
console.log(user); // User role is available here, in JSON format
if(user === null) // if no username in database, do this
{
console.log('Username does not exist in database');
}
else
{
user.comparePassword(password, function(err, match) // function written to compare hashed password in Mongo & password provided by user
{
if(match)
{
done(null, {id: username, name: username});
return user; // this is not the correct syntax, but the idea is, I want to send over the user details here, so I can access the role later
}
else
{
done(null, null);
}
});
}
});
}
The verifyFunction is called with this syntax.
passport.use(new LocalStrategy(verifyCredentials));
Once that function is successfully called, the server executes the 2nd part of it which is the loginCheck.
module.exports.loginCheck = function(req, res)
{
console.log('Calling loginCheck route');
// I generate some sort of jwt token here
// payload, body, blah blah blah ...
console.log(req.body);
res.json({
authenticated: req.isAuthenticated(), //built-in authentication function, returns true or false
token: token // sends over token
role: user.role // want to send over something like this
}); // sends all these to client side as JSON
}
Since both functions are in different files, I am unclear if I have to require something or simply just pass an extra parameter to the loginCheck function. I have tried the latter though and it did not work.
One way that I could think of is do another Mongo query in the loginCheck function, but that would be kinda redundant.
Even a specific keyword for me to google up would definitely be of big help as don't I know what I should be looking for. The reason is because I am new to NodeJS, thus I am not familiarize with most of the terms yet.
I think these codes should suffice but if I am needed to provide more, let me know and I will do so. Thanks in advance !!
To pass control to next matching route you need to use next that passes as third argument in the routes:
function verifyCredentials(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) //query Mongo
{
if(user === null) {
return next(new Error('Username does not exist in database'));
} else {
user.comparePassword(req.body.password, function(err, match) {
if(match) {
next(null, {id: username, name: username});
} else {
next(new Error('not match'));
}
});
}
});
}
app.post('/login', verifyCredentials, authRoutes.loginCheck);

sails session writing bug

I'm using sails 0.10.4 and stumbled with one pretty annoying bug. When user logs in I write his data into the req.session.user then in policies I can retrieve his data such as his role, password etc. But the req.session.user becomes undefined when I go out of the login action. Do you have any ideas how to handle this? Here's the code:
api/controllers/User.js :
module.exports = {
login: function (req, res) {
Users.findOneByEmail(req.param('email'))
.exec(function (err, user) {
if ((err) || (!user)) {
res.send({
error: 'User not found'
});
return;
}
if (!passwordHash.verify(req.param('password'), user.password)) {
res.send({
error: 'Incorrect passwpord'
});
return;
}
req.session.user = user;//I write user into the session
res.send({
user: user
});
});
}
}
api/policies/isLoggedIn.js
module.exports = function (req, res, next) {
if (req.headers.authentication) {
var credentials = JSON.parse(req.headers.authentication);
if(req.session.user.login === credentials.login)//User doesn't exist in session
return next();
}
}
In a testing environment , this issue can happen when testing with Supertest and not defining an agent
var agent = request.agent(app);
agent.post('/api/login',{email:'foo#bar.com',password:'foobar})
.end(function(err,res){...; done();});
It is the correct way to work with sessions, simply using request.post would not work as it would reinit the session variable as soon as the response is sent, even if we are chaining requests inside the same test.
Learnt it the hard way, so I hope it can help some lost developper.

Resources