Passport.js async vs sync deserialization - node.js

I'm implementing a payment feature on my app, and my provider requres AccountId (identification of the user in my system) and Email. I've noticed some strange behaviour I cannot explain with Passport.js. First of all, deserialization looks like in docs:
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
Now, I get that this is an async operation, however when I form the data for the request, I do:
var data = {
AccountId: toString(req.user._id),
Email: req.user.auth.local.email
// ...
}
For some reason Email gets set correctly, but AccountId always returns [object Undefined]. Well, I thought that maybe it's due to Passport's async nature of deserialization (since it requires time to User.findById()), but why does Email gets set correctly then?
I've found a way of setting AccountId to req.session.passport.user, but it seems like a hack to me.

Your problem is here:
AccountId: toString(req.user._id),
toString is a method of an object. In the browser, simply calling toString assumes that you mean window.toString() and it returns [object Window]. In Node, since the global window does not exist, you get [object Undefined].
I think the way you meant to call that function would be like so:
AccountId: req.user._id.toString(),

Related

Associating user data between multiple passport strategies

I'm using node, express, express-session, mongoose and passport-discord to authenticate users. But I also wish to use passport-steam to optionally link a user's steam account to their discord account.
The problem is, in order to link their steam account, I would also need a way of knowing which discord user is trying to do so, but the authentication callback doesn't include any request/response headers to access the current session, and the database I'm storing discord users in doesn't have any steam data in it until steam's authentication callback is called, at which point I have no idea which discord user is trying to authenticate their steam account.
To help remedy this, I've setup 3 mongoose models to have 3 separate database collections:
DiscordUser (stores discord user information, including their discordId)
SteamUser (stores steam user information, including their steamId)
User (stores required discordId and optional steamId)
I then try to serialize and deserialize the User object, but the passport.deserializeUser(obj, done) method seems to pass in an id instead of a User object, and I can't be sure if it's a discordId or a steamId, or which discord user I'm deserializing a SteamUser for, so I end up back at square one.
What's the best way to go about associating these users?
If needed, I can supply some code snippets upon request.
I found an article which helped solve this problem.
Here's what I did to fix it:
Using passReqToCallback: true in the new Strategy(...) options
In both strategy callback functions, adding req as the first parameter(ie., async function callback(req, accessToken, refreshToken, profile, done) { ... })
And then changing this:
router.get('/steam', passport.authenticate('steam'), (req, res) => {
//...
});
To this:
// Note that the route here is the same as the callback/redirect
router.get('/steam/redirect', (req, res, next) => {
passport.authenticate('steam', (err, user, { nextRoute }) => {
if (err)
return next(err);
if (nextRoute) {
// user was authorized with provider?
// send em back to profile
return res.redirect(nextRoute);
} else {
// give em some cookies
req.logIn(user, err => {
if (err)
return next(err);
return res.redirect('/');
});
}
})(req, res, next)
});
And then finally, modifying the User model schema to include the sessionID, which can be accessed via req.sessionID (or req.session.sessionID) from within the callback functions of both strategies.
This allows me to update the database upon successful login with my primary strategy(s) (discord, in this case), and then reference it from secondary strategies (steam, in this case), providing enough time to update the User database to have linked information between each strategy for as long as the session is still valid. After which, you can always simply reference the User database anywhere in code to check for valid primary and secondary strategy ids, allowing you to cross-reference other database collections for information, update the session id if the current session id doesn't match (and possibly forcing the user to re-authenticate?), etc.

Node.js: Using passport, is it possible to customize the temporary value saved by the serialize function?

We know that the passport will keep the authenticate result in the req.user by using
passport.serializeUser(function(user, cb) { cb(null,user); })
I'm trying to return the result and the status code from the server. In order to get both of them, I'm trying to find a way to save those two in different vessel. For example down below.
passport.serializer____(function(statuscode, user, cb) { cb(null, user, statuscode); } )
In the customized serializer above, it saved the result in req.user and the statuscode in req.___ (save status code). However, the way I used now is to saved them both in req.user.

Passport.js - serializeUser doesn't find user properties after lodash _.merge

I have a strange problem with implementing serializeUser() on my express app. My data model has called for a single type of "user" to exist in two models, basically an Identity (username, password, role, etc) and then role-specific data, such as Author (data specific to the Author role). I solve this by having a ref (ObjectID) of an Identity stored inside my Author model. Thus, my signup function appears as follows (simplified):
// Mongoose will just take what it needs for each model
var identity = new Identity(req.body);
var author = new Author(req.body);
identity.save(function(err) {
if (!err) {
// Save reference to identity
author._identity = identity._id;
// Save author document, then login
author.save(function (err) {
// create new "user" object from both identity and author objects
var user = _.merge(identity, author);
req.login(user, function(err) { res.json(user); });
});
}
});
This is working fine and everything is stored nicely in mongo. However, for serializeUser(), I want to store user._identity, not user._id (or user.id as it is strangely used), in order to access the ObjectID of the Identity document, not the Author document:
passport.serializeUser(function(user, done) {
console.log('user', user); // returns full, concatenated object
console.log('_id', user._id); // returns ObjectID value
console.log('_iden', user._identity); // returns undefined
// breaks with a "Failed to serialize user into session" error
done(null, user._identity);
});
What is strange is that console.log(user) gives the full (concatenated object), and user._id returns a value, but user._identity returns undefined (and no other value is returned for that matter. Any insight into why this might be happening or a different approach I should take?
NOTE: This seems to be caused by the _.merge call. I'm using lodash v.2.4.2
Turns out mongoose doesn't like attempting to _.merge two documents - simply using the document.toObject() method resolved this issue:
var user = _.merge(identity.toObject(), author.toObject());
Not bad at all!

nodejs express profile property in request

I got very confused for one usage:
In the route file:
app.param('userId', users.load);
And the users.load function:
exports.load = function (req, res, next, id) {
var options = {
criteria: { _id : id }
};
User.load(options, function (err, user) {
if (err) return next(err);
if (!user) return next(new Error('Failed to load User ' + id));
req.profile = user;
next();
});
};
Here, route should have the userId to response but why does the author use req.profile here. profile is not a property.
Anyone can help?
Thanks.
What the code does is this: for routes that have a userId parameter (that is, routes that look similar to this: /user/:userId), Express will call the load() function before the route handler is called.
The load function loads the user profile belonging to the userId from the database, and adds it to req as a newly created property req.profile.
The .profile property name is arbitrarily named by the author and demonstrates the fact that it's perfectly valid to add properties to req (or res, for that matter, but convention is to add these properties to req).
In the route handler, you can then use req.profile. It's basically a way of propagating data from middleware and app.param() implementations to other parts of the route handling.
the line req.profile = users; think of it this way, 'i want to take all the powers of the users and paste them to req.profile' why? remember this part is sort of a middleware if you want to target any of the read, update and delete code it has to pass through here, it only makes sense if it involves the req, because you are practically requesting to access the said pages (read, edit and delete or any other:userId page) now the profile name doesn't matter you could use any name but its sort of a convention in the community to use the profile name.

Tidy callbacks node.js

Trying to think of a logical way of structuring this. For simplicity, I am creating a user registration page utilising Node.js, Redis and Express.js.
User posts details to page.
Username is confirmed to be valid then Redis checks username is unique. If it is, we continue, if it isn't we return an error message and all the previous details.
Password is confirmed to be valid. If it isn't an error is returned and we don't continue.
Email is confirmed to be unique with Redis. If it is we continue, if it isn't we return an error message and stop.
If no errors at this point, the data is inserted into Redis.
This seems very simple, but using callbacks has generated a total mess - particularly when it comings to returning an error.
How can I structure this in a tidy way?
What you've experienced is callback hell. There are a variety of things you could do like name your callback functions instead of inlining them, follow the same pattern etc...
However, I would recommend you have a look at the async module.
In your, very typical, case I would use async.series like this:
var validateUserName = function(username, callback){
if(username!='valid'){
return callback('invalid username');
}
};
var checkRedis = function(username, callback){
// check to redis
};
var checkPassword = function(password, callback){
// if password valid callback();
// else callback('invalid password');
}
etc...
async.series([checkUserName, checkRedis, checkPassword, etc...], next);

Resources