Update logged in user details in session - node.js

I am using PassportJS with ExpressJS.
I need to update the logged in user details. While I do update this in the DB, how do I update it in the session too so that request.user contains the updated user details?
That is, after updating the database, how do I update the session info on the user as well?
I tried directly assigning the updated details to request.user but it did not work.
I then tried request.session.passport.user - this worked but there is a delay of around 5 to 10 seconds before it gets updated in request.user too.
Is there a function that I need to call that updates the user information stored in the session? Or is there some other object that I can update where the change does not have a delay

I've been hunting down an answer for this too. Never mentioned in any docs or tutorials!
What seems to work is, after saving your newly updated user, do req.login(user)...
// "user" is the user with newly updated info
user.save(function(err) {
if (err) return next(err)
// What's happening in passport's session? Check a specific field...
console.log("Before relogin: "+req.session.passport.user.changedField)
req.login(user, function(err) {
if (err) return next(err)
console.log("After relogin: "+req.session.passport.user.changedField)
res.send(200)
})
})
The clue was here... https://github.com/jaredhanson/passport/issues/208

User.findById(req.user._id,function(err,doc){
req.logIn(doc,function(err1){
if(err1){ console.log("Error : "+err1) }
else{
res.render("abc.ejs",{user:req.user});
console.log('Item Removed Successfully!!!');
}
});
});
Here we are re-login the user
User => Mongoose Model

I had similar problem today and decided to share my findings, since i couldn't find similar answer.
The problem was that (copied from passport documentation) i was getting the user data directly from the token, that the user sent in the request. Which was of course outdated.
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : CONFIG.JWT_SECRET
},
function (jwtPayload, cb) {
return cb(null, jwtPayload);
}
));
while i should get the fresh user object from the database instead:
return User.findById(jwtPayload.id)
.then(user => {
return cb(null, user);
})
.catch(err => {
return cb(err);
});

Related

How do I check for an existing value in MongoDB with Axios? [duplicate]

This question already has answers here:
How to query MongoDB to test if an item exists?
(10 answers)
Closed 3 years ago.
I'm learning MERN stack development and I'm making a practice app with user login/registration. I have the Node server and MongoDB up and running, and I'm able to register new users, update usernames & passwords, retrieve a list of users, and retrieve info about a single user. I'm doing this using post/get requests to localhost:4000/credentials-test/etc with Postman (I haven't actually connected the front end yet).
I'm able to get info on a single user with the following code in my server.js file:
credsRoutes.route('/user/:id').get(function(req, res) {
let id = req.params.id;
User.findById(id, function(err, user) {
res.json(user);
});
});
I figured I'd be able to do something similar to check if a username already exists (to prevent duplicate usernames on registration or username changing), with the following code:
credsRoutes.route('/check-user/:username').get(function(req, res) {
let username = req.params.username;
User.find(username, function(err, user) {
if (!user)
res.json('User not found :(');
else
res.json('User found! Details:');
res.json(user);
});
});
But the response from localhost:4000/credentials-test/check-username/testuser is always User not found :(, even when the username definitely belongs to an existing user.
Any ideas why this might be happening and how I can implement a working solution?
You need to pass a query object into the Mongoose model. Also, it's wise to return out of the function when you call res.json() because the rest of the function could still run, .end() is a method that will explicitly end the client's request.
try this:
credsRoutes.route("/check-username/:username").get(function(req, res) {
const username = req.params.username;
User.findOne({ username }, function(err, user) {
if (!user) {
return res.json("User not found :(").end();
}
return res.json(`User found! Details: ${user}`).end();
});
});
`

Passport jwt and res.render

It is my first experience with passport-jwt. I configured the strategy and all works fine. I send token to client and extract it from the header with fromAuthHeaderAsBearerToken() method. But I got one issue, I want to render one specific page in case if Authorization header is empty or it doesn't set. How can I do it, except set up failureRedirect: '/specific-page' property?
The code of strategy is pretty standard:
JwtStrategy(jwtOptions, (payload, done) => {
User.findOne({fmn_id: payload.data.id})
.then( (obj) => {
if(!obj) throw new Error('No user with such id');
done(null, obj);
})
.catch( (err) => {
res.render('specificPage'); //In case of error I want to render this page. Current code doesn't work.
done(null, err);
});
}
You can render a specific template using res.render('path_to_template')
Suppose you have a template in your views directory called myfile.ejs then you can render to this by res.render('myfile')
Or you can also redirect user to another route using res.redirect('/route');

Refresh passport.js session data using req.login()

I'm using passport.js. The problem is when I change user information such as user name, It's not changed until user re-login. If user logout and login again, the information changed. but I want to change in the session without re-logging in.
My deserializeUser function like below, It looks different original passport's example because it is pickup data from session instead of querying database. I want to up loading speed so I didn't use database query on deserialize function.
passport.deserializeUser(function(userSession, done){
var member = { id : userSession[0], name : userSession[1] };
done(null, member);
});
So I think I have to use req.login() like below, but the session wouldn't changed.
Members.findOne({_id : someId }, function(err, member){
member[username] = req.body.username;
member.save(function (err){
if(err) return next();
console.log('The original session :'+req.sessionId);
req.login(req.user, {}, function(err){
if (err) return next();
console.log('The new session :'+req.sessionId);
res.send(200);
});
})
})
The problem is original session and new session is same, How can I force to refresh req.user object?
I red this document but didn't get solution.
https://github.com/jaredhanson/passport/issues/208

Passport session not updating

I'm trying to update the passport session after user info change using:
user.save(function(err) {
if (err) return next(err)
// What's happening in passport's session? Check a specific field...
console.log("Before relogin: "+req.session.passport.user.changedField)
req.login(user, function(err) {
if (err) return next(err)
console.log("After relogin: "+req.session.passport.user.changedField)
res.send(200)
})
})
But even after re-login, with no errors my passport session remains the same. I have to manually logout and log back in for the session info to update. Can anyone explain what's happening?
Passport sets req.user using the function you have provided with passport.deserializeUser. You are using req.session.passport.user which is incorrect. There is no need to re-login after change of user information - deserializeUser is called on every request, and this is where you fetch the user information from your database. Then you access it via req.user.

Node.js API - Allow users to only update and delete their own object

I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})

Resources