I am using the Passport node module in conjunction with Express to enable users to login via Facebook, Twitter, and Google. I have setup Facebook and Twitter so far and users can login successfully, but it creates a new user every time even though I have tried to make it recognize when a user has already created an account. Here is the code I am using:
passport.use(new FacebookStrategy({
clientID: '********************',
clientSecret: '***********************',
callbackURL: "http://www.wundertutor.com:3000/auth/facebook/callback"
}, function(accessToken, refreshToken, profile, done) {
processProfile(profile, function(err, user) {
if (err) throw err;
done(null, user);
});
}));
function processProfile(profile, callback) {
if (profile.emails) {
profile.emails.forEach(function(email) {
findUserByEmail(email.value, function(err, user) {
if (user) {
return callback(null, user);
}
});
});
var newUser = {
id: profile.id,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value
};
user.create(newUser, profile.provider, function(err, user) {
if (err) throw err;
return callback(null, user);
});
}
}
function findUserByID(id, callback) {
pool.getConnection(function(err, connection) {
var query = connection.query("SELECT * FROM users WHERE id = ?", id, function(err, rows) {
connection.end();
var user;
if (rows.length == 1) {
user = {
id: rows[0].id,
role: rows[0].role,
firstName: rows[0].firstName,
lastName: rows[0].lastName,
email: rows[0].email
};
}
return callback(null, user);
});
});
}
function findUserByEmail(email, callback) {
if (email) {
pool.getConnection(function(err, connection) {
if (err) throw err;
var query = connection.query('SELECT * FROM users WHERE email = ?', email, function(err, rows) {
connection.end();
if (err) throw err;
if (rows.length == 1) {
console.log("C");
return callback(null, rows[0]);
} else {
return callback(null, null);
}
});
});
} else {
return callback(null, null);
}
}
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
findUserByID(id, function(err, user) {
done(null, user);
});
});
In processProfile, I have it loop through each email associated with a user profile and check to see whether that user is already in our database. If they are, then it gets that user's info and passes it to the 'done' callback to be serialized and log the user in. The strange thing is that when a user who is already in our database is logged in, it does get to this part of the code and returns the user successfully (determined by a series of console.logs), but for some reason it still creates a new user in the database each time they try to log in.
Also, on a side note, when I log in normally (via username and password), I am redirected to our learn page '/learn', but when I log in via Facebook, I am redirected to the learn page with some extra stuff afterwards '/learn#='. Any ideas?
Try rewriting your processProfile function to something like this:
function processProfile(profile, callback) {
var result;
if (profile.emails) {
profile.emails.forEach(function(email) {
findUserByEmail(email.value, function(err, user) {
if (user) {
result = user;
}
});
});
if( !user ) {
var newUser = {
id: profile.id,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value
};
user.create(newUser, profile.provider, function(err, user) {
if (err) throw err;
result = user;
callback( null, result );
});
} else {
callback( null, result );
}
}
}
Related
UserModelVk.findOne({vkontakteId: profile.id}, function(err, vkUser){
if(err){ return done(err) }
if(!vkUser){
const user = new UserModelVk({
vkontakteId: profile.id,
name: profile.displayName,
access_token: params.access_token
})
user.save(function(err) {
if (err){ log.error(err) }
localStorage.setItem('username', user.vkontakteId);
localStorage.setItem('key', user.access_token)
return done(err, user);
});
} else {
vkUser = { access_token: params.access_token }
vkUser.save(function(err) {
if (err){ log.error(err) }
localStorage.setItem('key', vkUser.access_token)
return done(err, vkUser);
});
localStorage.setItem('username', vkUser.vkontakteId);
return done(err, vkUser);
}
})
Here, I check have user in my DB or not. If not: register user (and send to him his token), if have: update token (and send to him his updated token).
Why ".save is not a function"?
P.S: And how correctly send data to user localStorage?
You are overriding vkUser by assigning it vkUser = { access_token: params.access_token }. The save function is lost therefore.
Change it to:
vkUser.access_token = params.access_token
I'm trying to implement the passport-facebook strategy into my application, however I seem to be running into some issues.
I can successfully call Facebook and get a response back, however when trying to save the user to my database, nothing happens.
In my code below, I want to check to see if the user already exists in the database, if they do, return their details, if they don't create a new entry.
passport.use(new FacebookStrategy({
clientID: '123',
clientSecret: '123',
callbackURL: 'http://localhost:1337/auth/facebook/callback',
profileFields: ['email']
},
function(accessToken, refreshToken, profile, done) {
User.findOne({ email: profile.emails[0].value}, function(err, user){
if (err) { return done(err); }
if (!user || user == 'false') {
console.log('No user found');
console.log(profile.id);
User.create({facebookId: profile.id, email: profile.emails[0].value}, function(err, user){
return done(null, user, { message: 'Facebook user created'});
})
} else {
console.log(user);
return done(null, user, { message: 'User already registered'});
}
})
}
));
I can log out the profile id fine, as well as the email, however the User.create statement does not run.
I also have passport-local implemented, and if I post the data to my user model, it creates the user just fine.
For reference, my user model looks like:
module.exports = {
connection: 'someMongodbServer',
attributes: {
facebookId: {
type: 'integer',
unique: true
},
email: {
type: 'email',
required: true,
unique: true,
},
password: {
type: 'string',
minLength: 6,
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(user, cb) {
if(user.password){
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(user.password, salt, function(err, hash){
if(err) {
console.log(err);
cb(err)
} else {
user.password = hash;
cb();
}
});
});
}
}
};
Does anyone know where I may be going wrong?
Managed to solve it, was a silly mistake on my behalf.
The User model invokes the beforeCreate function to hash the password. As I was using the Facebook strategy for Passport, there is no password for a Facebook user so I added an if statement to check for this.
if(user.password){
What I had missed was the callback in this function should their not be a password. Solution for the beforeCreate function is:
beforeCreate: function(user, cb) {
if(user.password){
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(user.password, salt, function(err, hash){
if(err) {
console.log(err);
cb(err)
} else {
user.password = hash;
cb();
}
});
});
}
cb();
}
first off thank you for your help. I am trying to make a call to the facebook api to gather all of my users friends.
passport.use(new FacebookStrategy({
clientID: config.fb.id,
clientSecret: config.fb.secret,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOne({
"facebook.id" : profile.id
}, function(err, user) {
if (err) {
return done(err);
}
//No user was found... so create a new user with values from Facebook (all the profile. stuff)
if (!user) {
this is the call back I am made to return the data
function viewback(err, data, user) {
if(err) {
console.log("Error: " + JSON.stringify(err));
} else {
return data;
}
}
below is where I am making the call for friends, the data variable is not getting set before the call to create the user is being completed and it is not saving the data.
var fbapi = require('facebook-api');
var client = fbapi.user(accessToken); // do not set an access token
process.nextTick(function(){
var data = client.me.friends(viewback);
});
user = new User({
name: profile.displayName,
username: profile.username,
profileUrl: profile.profileUrl,
provider: 'facebook',
token: accessToken,
//now in the future searching on User.findOne({'facebook.id': profile.id } will match because of this next line
facebook: profile._json
fb_friends: data
});
user.save(function(err) {
if (err) console.log(err);
console.log('making a new user')
return done(err, user);
});
} else {
//found user. Return
console.log('found the user')
return done(err, user);
}
});
}));
I don't receive any errors and the user saves in the DB, but without the friend data. Any help would be great. Even better would be something I could read to pick up on this concept.
Here's the code for a new user:
var User = mongoose.model('User', userSchema);
var usr = new User({ username: 'bob', email: 'bob#example.com', password: 'secret' });
Here's the code for checking login.
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
If the username doesn't exist, it says "Unknown user __________"
Instead of saying unknown user, I want to create a new user in the database. How do I modify this code to do that?
I'd like to create a new user with the login info they entered if that login name doesn't already exist.
Update
I'm trying this and it's not working. bob5 isn't saving to the database.
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { usr = new User({ username: 'bob5', email: 'bob5#example.com', password: 'secret' });
usr.save(function(err) {
if(err) {
console.log(err);
} else {
console.log('user: ' + usr.username + " saved.");
}
});
If I type this, bob99 gets saved to the database. So I can create a user... I just need to pass the arguments to it within the if statement (I think).
usr = new User({ username: 'bob99', email: 'bob99#example.com', password: 'secret' });
usr.save(function(err) {
if(err) {
console.log(err);
} else {
console.log('user: ' + usr.username + " saved.");
}
});
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
usr = new User({ username: 'bob99', email: 'bob99#example.com', password: 'secret' });
usr.save(function(err) {
if(err) {
console.log(err);
} else {
console.log('user: ' + usr.username + " saved.");
}
});
}
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
I have two model in my node/express app :
User model (simple user authenticated)
Manager model (user with different rights and different fields)
I want to use passport to authenticated them :
User with couple : Username / Password
Manager with couple : Email / Password
I correctly implemented PassportJS for the User model but now I try to do the same for Manager model.
exports.postlogin = function(req, res, next) {
passport.authenticate('user', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/login')
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
exports.postlogin = function(req, res, next) {
passport.authenticate('manager', function(err, manager, info) {
if (err) { return next(err) }
if (!manager) {
req.session.messages = [info.message];
return res.redirect('/manager_signup')
}
req.logIn(manager, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
The two strategies :
passport.use('user', new LocalStrategy(function(username, password, done) {
UserModel.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
passport.use('manager', new LocalStrategy({usernameField: 'manager_signin_email', passwordField: 'manager_signin_password'},function(manager_signin_email, manager_signin_password, done) {
ManagerModel.findOne({ email: manager_signin_email }, function(err, manager) {
if (err) { return done(err); }
if (!manager) { return done(null, false, { message: 'Unknown manager ' + manager_signin_email }); }
manager.comparePassword(manager_signin_password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
console.log('Manager login OK : ' + manager_signin_email);
return done(null, manager);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
The problem is for Serialize/Deserialize.
For User I have this :
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
UserModel.findById(id, function (err, user) {
done(err, user);
});
});
But I don't know how to do for Manager model.
You could maybe do something like this when serializing:
if (isUser(user)) {
// serialize user
done(null, "user_"+user.id );
} else if (isManager(user)) {
// serialize manager
done(null, "manager_"+user.id );
}
And then check the prefix when deserializing.
I think there is an open issue for what you want (https://github.com/jaredhanson/passport/issues/148) .
Alternatively you could change you serialize method to include information if it's user or manager not only user id and when deserializing read that info and load user/manager from proper model.
In case anyone is still stumbling upon this. You can check the type of the object you are serializing. Note all there of by objects are generalized in to a PrincipleInfo object.
function PrincipleInfo(principleId, principleType, details) {
this.principleId = principleId;
this.principleType = principleType;
this.details = details;
}
passport.serializeUser(function (userObjectThing, done) {
//userObjectThing could be a User or a Sponsor
var principleType = "user";
var userPrototype = Object.getPrototypeOf(userObjectThing);
if (userPrototype === User.prototype) {
principleType = "user";
} else if (userPrototype === Sponsor.prototype) {
principleType = "sponsor";
} else if (userPrototype === Admin.prototype) {
principleType = "admin";
}
var principleInfo = new PrincipleInfo(userObjectThing.id, principleType, '');
done(null,principleInfo);
});
passport.deserializeUser(function (principleInfo, done) {
if (principleInfo.principleType == 'user') {
User.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'sponsor') {
Sponsor.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'admin') {
Admin.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
}
});