using passport module to get facebook profile events - node.js

i am trying to get future events user attended to using passport module but im clearly missing something. i followed the Facebook API and got the permission i needed like so :
router.get('/auth/facebook', passport.authenticate('facebook', { scope: [ 'public_profile', 'user_events' ] }));
router.get('/auth/facebook/callback', passport.authenticate('facebook', {
successRedirect:'/welcome',
failureRedirect:'/'
}))
but when i am trying to print an event like this:
passport.use(new FacebookStrategy({
clientID: config.fb.appID,
clientSecret: config.fb.appSecret,
callbackURL: config.fb.callbackURL,
profileFields: ['id', 'displayName', 'photos', 'birthday', 'events', 'profileUrl', 'emails', 'likes']
}, function(accessToken, refershToken, profile, done){
//Check if the user exists in our Mongo DB database
//if not, create one and return the profile
//if exists, return profile
userModel.findOne({'profileID':profile.id}, function(err, result){
if(result){
done(null,result);
} else {
// Create a new user in our mongoLab account
var newFbUSer = new userModel({
profileID: profile.id,
fullname: profile.displayName,
profilePic:profile.photos[0].value || '',
birthday:profile.birthday,
events:profile.events[0].name,
profileUrl:profile.profileUrl,
});
console.log(newFbUSer.profilePic);
newFbUSer.save(function(err){
done(null,newFbUSer);
})
}
})
i get this error:
events:profile.events[0].name,
^
TypeError: Cannot read property '0' of undefined
i tried to replace name with value, as it is working perfectly fine with the photos, and i tried many other ways but i simply can't get this one to work...any help please?

Related

Passport JS Google oauth20 callback missing req object

I have seen some similar questions here but the answer is irrelevant to mine, as I have declared passReqToCallback.
I am trying to create an authentication system with passport. I have successfully integrated passport-local, which register/logs in user and creates a session but am having an issue in the logic of the google strategy:
passport.use(new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
passReqToCallback: true
},
(req, accessToken, refreshToken, profile, done) => {
/**
*
* This if statement is failing, as far as I can tell there is no req object being passed despite declaring it above
*
*/
// If user is logged in, proceed to simply link account
if (req.user) {
req.user.googleid = profile.id;
req.user.googleEmail = profile.emails[0].value;
req.user.googleDisplayName = profile.displayname;
pool.query('UPDATE users SET googleid = ?, google_email = ?, google_display_name = ? WHERE id = ?', [
req.user.googleid,
req.user.googleEmail,
req.user.googleDisplayName,
req.id
],
function(err, rows) {
// If google account is duplicate (linked to different account) will return error
if (err) {
return done(err, false, {message: "The Google account you tried to link is associated with another account."});
}
return done(null, req.user);
})
}
// Check if google account is registered
pool.query('SELECT * FROM users WHERE googleid = ?', [profile.id], function(err, rows) {
if (err) {
return done(err);
}
// If not logged in but user already registered, log in
if (rows.length) {
return done(null, rows[0]);
}
// If no existing record, register the user.
else {
let newUser = {
email: profile.emails[0].value,
// Google account specific fields
googleid: profile.id,
googleDisplayName: profile.displayName,
method: "gl", // This field ties this new user to the google account
// General fields (taken from the stuff google gives us)
firstName: profile.name.givenName,
lastName: profile.name.familyName,
googleEmail: profile.emails[0].value
}
let insertQuery = "INSERT INTO users (email, googleid, google_display_name, method, first_name, last_name, google_email) VALUES (?,?,?,?,?,?,?)";
pool.query(insertQuery, [
newUser.email,
newUser.googleid,
newUser.googleDisplayName,
newUser.method,
newUser.firstName,
newUser.lastName,
newUser.googleEmail
],
function(err, rows) {
if (err) return done(err, null);
newUser.id = rows.insertId;
return done(null, newUser);
})
}
})}));
So essentially the first if is supposed to see if the user is already authenticated and then just link the account as so. However, in practice, it will skip this even when authenticated and proceed to the rest of the logic, which works absolutely fine. It will go on to create a new account at the else statement (or return error if email is taken, I still need to implement that part).
But, interestingly, if I am logged in while using it, it doesn't log me in as the new user as it otherwise would, instead it keeps me logged in as the current session.
Where have I gone wrong? Why is the req.user not detected? I have also tried using req.isAuthenticated() with the same result.
Below is my callback route, if helpful:
// Google
router.get('/oauth/google', passport.authenticate('google', {
scope: ['email', 'profile']
}));
// Callback
router.get('/oauth/google/redirect', passport.authenticate('google', {
successRedirect: '/account',
failureFlash: 'Something went wrong. Please enable third party cookies to allow Google to sign in.',
failureRedirect: '/login'
}
));
UPDATE 1: If I try (!req.user), same result, skips to below, not sure what that means is happening

email field is optional in passportjs facebook strategy

I wrote the code for login with facebook, everything works and I'm getting the user's email address. But there is another option on facebook which lets the user select the data my application is going to have access to.
If user clicks on that, he'll see the name and everything marked as required, but email is optional and the user can remove it from the data that is going to be provided to the app.
On the application, email is required. So how I can mark the email as required on facebook?
This is the snippet I'm using in the code.
passport.use(new FacebookStrategy({
clientID: config.social.facebook.clientID,
clientSecret: config.social.facebook.clientSecret,
callbackURL: config.url+'auth/facebook/cb',
enableProof: false,
profileFields:['id', 'name', 'emails'],
scope: "email"
}, function(accessToken, refreshToken, profile, done) {
// doing the rest of the thing
}
));
// ...
app.get('/auth/facebook', passport.authenticate('facebook', {scope: ['email']}));
app.get('/auth/facebook/cb', passport.authenticate('facebook'), function(req, res, next) {
res.redirect("/");
});
I solved the problem by re-requesting the permission.
Turns out I can add authType: 'rerequest' to passport.authenticate('facebook', {scope: ['email'], authType: 'rerequest'}).
What I did is to check if the emails field is present in the result, if not, I call done with an error.
function(accessToken, refreshToken, profile, done) {
if (profile.emails === undefined) {
done('email-required')
return;
}
// doing the rest of the thing
}
Then to catch the error, I had to write a custom callback for passport.authenticate('facebook').
app.get('/auth/facebook/cb', function(req, res, next) {
passport.authenticate('facebook', function (err, user, info) {
if (err) {
if (err == 'email-required') res.redirect('/auth/facebook/rerequest');
// check for other kinds of errors and show proper messages
return;
}
req.user = user;
// do the rest of the thing
})(req, res, next)
});
As you see, I redirect the user to another route /auth/facebook/rerequest in case of error.
app.get('/auth/facebook/rerequest',
passport.authenticate('facebook', {
scope: ['email'],
authType: 'rerequest' // this is important
}
));
This will redirect the user to the same page on FB again and this time email field is required. I couldn't do this in the same route; apparently it was using the same generated code to communicate to fb which was not acceptable by fb.
And that's how I managed to solve the issue.
you need to specify email in Strategy in profileFields property
passport.use('facebook', new FacebookStrategy({
clientID: config.facebook.appId,
clientSecret: config.facebook.appSecret,
callbackURL: config.facebook.callbackURL,
profileFields: ['emails', 'first_name', 'last_name', 'locale', 'timezone']
}, function (token, refreshToken, profile, done) {
// you will get emails in profile.emails
}));

Mongo create user error using passport authentication

I am using MEAN.IO stack (default template with Mongoose) and Passport.js authentication to create new users. Whenever user.save(...) is called in the below code:
// Use Facebook strategy
passport.use(new FacebookStrategy({
clientID: config.strategies.facebook.clientID,
clientSecret: config.strategies.facebook.clientSecret,
callbackURL: config.strategies.facebook.callbackURL
},
function(accessToken, refreshToken, profile, done) {
User.findOne({
'facebook.id': profile.id
}, function(err, user) {
if (user) {
return done(err, user);
}
user = new User({
name: profile.displayName,
email: profile.emails[0].value,
provider: 'facebook',
facebook: profile._json,
roles: ['authenticated']
});
user.save(function(err) {
if (err) {
console.log(err);
return done(null, false, {message: 'facebook login failed, email already used by other login strategy'});
} else {
return done(err, user);
}
});
});
}
));
The console logs the following error:
MongoError: attempt to use unsupported textIndexVersion 2, only textIndexVersion 1 supported
I've looked online and through the MEAN.IO stack code and can't find anything about how to change the textIndexVersion.
Any ideas how to change the version or fix this error in general?
According to the bug reports for Mongo:
mongod v2.4.9 will correctly forbid changes to collections that have a
text index which is incompatible with 2.4. Attempts to insert, update,
or remove documents in these collections will return the error message
"attempt to use unsupported textIndexVersion 2, only textIndexVersion
1 supported".
https://jira.mongodb.org/browse/SERVER-11494
I would suggest updating your Mongo version to be compatible with v2.4.9

getting info about facebook user using passport

this are my first steps in node.js in general and passport in particular and i came across a really annoying issue. i am trying to get the events user attended to with hes Facebook profile but no matter what i tried it simply didn't work. So i thought "ok, lets and get other data" but except for the basic display name and profile pic any other attempt (birthday,events,friends list etc..) ends up with no data. i tried using Facebook's api alot in the last few days (for the first time) and simply couldnt figure it out...this is my last attempt:
passport.use(new FacebookStrategy({
clientID: config.fb.appID,
clientSecret: config.fb.appSecret,
callbackURL: config.fb.callbackURL,
profileFields: ['id', 'displayName', 'photos', 'birthday', 'events', 'profileUrl', 'emails', 'likes']
}, function(accessToken, refershToken, profile, done){
//Check if the user exists in our Mongo DB database
//if not, create one and return the profile
//if exists, return profile
userModel.findOne({'profileID':profile.id}, function(err, result){
if(result){
done(null,result);
} else {
// Create a new user in our mongoLab account
var newFbUSer = new userModel({
profileID: profile.id,
fullname: profile.displayName,
profilePic:profile.photos[0].value || '',
birthday:profile.birthday,
//friends:profile.user.friends[0],
profileUrl:profile.profileUrl
});
newFbUSer.save(function(err){
done(null,newFbUSer);
console.log(newFbUSer.displayName);
})
}
})
}))
any help with how can i get and use user's friends list/ events??
Maybe you haven't passed the details of information you need to facebook while calling the Facebook login. While calling the facebook login you need to specify what all information you need in the scope. For example if you need public_profile,email,user_friends following is the code which you will add in routes:
app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'public_profile,email,user_friends' }));
Try this for the full name:
fullName: profile.name.givenName + ' ' + profile.name.familyName

passport-facebook in nodejs - profile fields

some of the profile fields i am trying to get from Facebook when logging in a user, are not going through.
I am using passportjs in node. this is the facebook strategy:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL,
profileFields: ['id', 'displayName', 'link', 'about_me', 'photos', 'email']
},
routes.handleLogin
));
being used with:
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['user_about_me', 'email'] }));
the result is that 'link', 'about_me' and 'email' are not getting pulled while the other fields are.
The profileFields parameter adheres to the Portable Contacts convention. This means you would want to use 'emails' instead of 'email'. As for the "about_me" field, it does not appear as if passport-facebook supports the OpenSocial protocol fully. This means you're out of luck if you want to use the "profileFields" parameter for both of these profile elements. The following code snippet, taken from the master branch, illustrates this limitation:
Strategy.prototype._convertProfileFields = function(profileFields) {
var map = {
'id': 'id',
'username': 'username',
'displayName': 'name',
'name': ['last_name', 'first_name', 'middle_name'],
'gender': 'gender',
'profileUrl': 'link',
'emails': 'email',
'photos': 'picture'
};
...
The fields listed in this mapping are the only ones supported right now.
Fortunately all is not lost. If you choose not to use the profileFields parameter then oddly enough you will be sent the "about_me" content you are interested in via a property called "bio". Here's how you could access that:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL
},
function(accessToken, refreshToken, profile, done) {
console.log("bio: " + profile._json.bio);
}));
Unfortunately this doesn't give you the other data you were interested in. I'm guessing that in your situation you are probably looking at gathering the supported convention fields during the passport-facebook callback and then grabbing the extended profile fields in a followup call using the facebook api directly. Either that or poke the passport-facebook maintainers to extend their field support.
aturkelson is correct. about_me is not supported yet. As far as email it comes with the profile as long as you request it. I also have a console log to confirm I am not crazy.
//Passport facebook strategy
exports.passportInit= passport.use(new facebookStrategy({
clientID: process.env.FACEBOOK_APP_ID ,
clientSecret: process.env.FACEBOOK_SECRET_ID,
callbackURL: '/api/auth/facebook/callback',
profileFields: ['id', 'displayName', 'emails', 'photos']
},
function(accessToken, refreshToken, profile, done) {
console.log(profile);
db.User.findOne({facebook_id: profile.id}, function(err, oldUser){
if(oldUser){
done(null,oldUser);
}else{
var newUser = new db.User({
facebook_id : profile.id,
facebook_photo : profile.photos[0].value,
email : profile.emails[0].value,
display_name : profile.displayName,
// picture: profile.picture
}).save(function(err,newUser){
if(err) throw err;
done(null, newUser);
});
}
});
}
));
According to my knowledge FB is providing you the info...
try this piece of code..
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: FACEBOOK_CALLBACK_URL,
profileFields: ['id', 'displayName', 'email'] },
function(accessToken, refreshToken, profile, done) {
// asynchronous
process.nextTick(function() {
FACEBOOK_TOKEN = accessToken;
FACEBOOK_USER = profile._json;
// facebook can return multiple emails so we'll take the first
profile.emails[0].value;
console.log(FACEBOOK_USER.email);
done(null, profile);
});
}));

Resources