Permalink
Browse files

Auth with Passport and providers (GitHub, Facebook, Twitter)

  • Loading branch information...
jorgecasar committed Jan 23, 2014
1 parent 3780c24 commit c3e544e2ca9a890151959129f347667d43eb5209
@@ -81,7 +81,8 @@ module.exports = {
create: function(req, res, next) {
// Create an user using all params.
// Schema is true, then we will save that we need.
- User.create( req.params.all(), function createdUser(err, user){
+ var params = _.extend({}, req.session.tempUser, req.params.all());
+ User.create( params, function createdUser(err, user){
if (err) return next(err);
req.login(user, function(err){
if (err) return res.redirect('/user/auth');
@@ -156,20 +157,111 @@ module.exports = {
* Actions that proccess info.
*/
login: function(req, res, next) {
- // Use Passport LocalStrategy
- require('passport').authenticate('local', function(err, user, info){
- if ((err) || (!user)) next(err);
- req.login(user, function(err){
- if (err) return res.redirect('/user/auth');
- // Redirect to the user page.
- return res.redirect('/user/' + user.id);
- });
- })(req, res);
+ var provider = req.param('provider') || 'local';
+ sails.log.verbose('AuthController.login:', isProvider);
+ if ( provider === 'local' || isProvider(provider) ) return linkProfile(provider, req, res, next);
+ return res.redirect('/user/auth');
},
- logout: function(req, res){
+ logout: function(req, res, next){
+ sails.log.verbose('AuthController.login');
+ var provider = req.param('provider');
+ if ( isProvider(provider) ) return unlinkProfile(provider, req, res, next);
// Call Passport method to destroy the session.
req.logout();
// Redirect to home page.
return res.redirect('/');
}
-};
+};
+
+function isProvider(provider){
+ return sails.config.providers[provider];
+}
+
+function linkProfile(provider, req, res, next){
+ sails.log.verbose('AuthController linkProfile');
+ process.nextTick(function () {
+ require('passport').authenticate(
+ provider,
+ function (err, user) {
+ sails.log.verbose('authenticate callback');
+ // If there are a logged user.
+ if( req.user ){
+ sails.log.verbose('req.user');
+ // If there are a user in our DB.
+ if ( user.id ) {
+ sails.log.verbose('user.id');
+ // If the ids match.
+ if( req.user.id === user.id ){
+ sails.log.verbose('req.user.id === user.id');
+ // Return to logeduser page
+ return res.redirect('/user/' + req.user.id);
+ }
+ // Otherwise, the ids are differents.
+ else {
+ sails.log.verbose('req.user.id !== user.id');
+ // There are another user with this account.
+ // TODO: Solve the conflict.
+ return res.redirect('/user/' + req.user.id);
+ }
+ }
+ // There aren't a user in our DB.
+ else {
+ sails.log.verbose('!user.id');
+ // Save the new profile.
+ User.findOne(req.user.id).done(function(err, localUser){
+ if( err ) res.serverError(err);
+ sails.log.verbose('Adding profile', user.profiles[0]);
+ localUser.profiles.push(user.profiles[0]);
+ localUser.save(function(err){
+ if( err ) {
+ // TODO: Notify error.
+ return res.redirect('/user/' + req.user.id);
+ }
+ return res.redirect('/user/' + req.user.id);
+ });
+ });
+ }
+ } else {
+ sails.log.verbose('!req.user');
+ if( user.id ) {
+ sails.log.verbose('user.id');
+ req.login(user, function (err) {
+ if (err) return res.serverError([err]);
+ return res.redirect('/user/' + user.id);
+ });
+ } else {
+ sails.log.verbose('!user.id');
+ // Save the temporal user in the session.
+ req.session.tempUser = user;
+ //TODO: Notify that the user need a new account.
+ return res.redirect('/user/new');
+ }
+ }
+ }
+ )(req, res, next);
+ });
+}
+
+function unlinkProfile(provider, req, res){
+ User.findOne(req.user.id).done(function(err, user){
+ var foundProfile = false;
+ var i = user.profiles.length;
+ while( !foundProfile && i >= 0){
+ i--;
+ foundProfile = user.profiles[i].provider === provider;
+ }
+ if( foundProfile ) {
+ user.profiles.splice(i, 1);
+ user.save(function(err, user){
+ if( err ) {
+ // TODO: Notify error.
+ return res.redirect('/user/' + user.id);
+ }
+ return res.redirect('/user/' + user.id);
+ });
+ } else {
+ // TODO: Notify provider not found.
+ return res.redirect('/user/' + user.id);
+ }
+ });
+}
View
@@ -24,6 +24,20 @@ module.exports = {
type: 'string',
required: true
},
+ profiles: {
+ type: 'array',
+ defaultsTo: []
+ },
+ // Profiles object look like this:
+ // provider: 'string',
+ // id: 'string',
+ // displayName: 'string',
+ // name_familyName: 'string',
+ // name_givenName: 'string',
+ // name_middleName: 'string',
+ // emails: 'array',
+ // photos: 'array'
+
// Override toJSON instance method to remove password value.
toJSON: function() {
var obj = this.toObject();
@@ -32,33 +46,37 @@ module.exports = {
},
// Check a password with the saved one.
validPassword: function(password, callback) {
- var obj = this.toObject();
- // If there are a callback, compare async.
- if (callback) {
- //callback (err, res)
- return bcrypt.compare(password, obj.password, callback);
- }
- // Otherwise, compare sync.
- return bcrypt.compareSync(password, obj.password);
- }
+ var obj = this.toObject();
+ // If there are a callback, compare async.
+ if (callback) {
+ //callback (err, res)
+ return bcrypt.compare(password, obj.password, callback);
+ }
+ // Otherwise, compare sync.
+ return bcrypt.compareSync(password, obj.password);
+ },
+ hasPassword: function(){
+ return !!this.password;
+ }
},
// Lifecycle Callbacks.
beforeCreate: function(values, next) {
- hashPassword(values, next);
+ if( values.password) hashPassword(values, next);
+ else next();
},
beforeValidation: function(values, next) {
- if( values.password && values.new_password && values.confirm_password) {
- // If we recive a password. We will try to change for the new one.
- if ( values.new_password === values.confirm_password ) {
- // If new password and confirm password is the same.
+ if( values.new_password && values.confirm_password ) {
+ var newPasswordMatch = values.new_password === values.confirm_password;
+ if( newPasswordMatch ) {
User.findOne(values.id).done(function(err, user) {
if (err) return next(err);
- if( user.validPassword(values.password) ){
+ if( !values.password || user.validPassword(values.password) ){
// If old password is valid.
// Ovewrite password with the new password.
values.password = values.new_password;
- // delete password confirmation.
+ // delete new password and confirmation.
delete values.confirm_password;
+ delete values.new_password;
// Hash the password.
hashPassword(values, next);
}
@@ -78,6 +96,15 @@ module.exports = {
} else {
next();
}
+ },
+ afterCreate: function(values, next) {
+ if( values.username ) return next();
+ else {
+ User.update({id: values.id}, {username: values.id}).done(function(err, user) {
+ if (err) return next(err);
+ else return next();
+ });
+ }
}
};
@@ -0,0 +1,65 @@
+var passport = require('passport'),
+ GitHubStrategy = require('passport-github').Strategy,
+ FacebookStrategy = require('passport-facebook').Strategy,
+ TwitterStrategy = require('passport-twitter').Strategy;
+
+function providersHandler(token, tokenSecret, profile, done) {
+ sails.log.verbose('config/passport providersHandler');
+ process.nextTick(function () {
+ User.findOne()
+ .where({'profiles.id': profile.id})
+ .done(function (err, user) {
+ if (user) return done(null, user);
+
+ var user = {}
+ user.profiles = [];
+
+ if(profile.emails) {
+ if( profile.emails[0] && profile.emails[0].value ) {
+ user.email = profile.emails[0].value;
+ }
+ }
+ if(profile.username) {
+ user.username = profile.username;
+ }
+ if(profile.displayName) {
+ user.displayName = profile.displayName;
+ }
+ if(profile.gender) {
+ user.gender = profile.gender;
+ }
+
+ user.profiles.push(profile);
+ delete profile._raw;
+ delete profile._json;
+ return done(err, user);
+ });
+ });
+};
+
+module.exports = {
+ configProviders: function(options, next) {
+ passport.use(new GitHubStrategy(
+ {
+ clientID: process.env.GITHUB_CLIENT_ID || sails.config.providers.github.clientID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET || sails.config.providers.github.clientSecret,
+ callbackURL: process.env.GITHUB_CALLBACK_URL || sails.config.providers.github.callbackURL
+ },
+ providersHandler
+ ));
+ passport.use(new FacebookStrategy({
+ clientID: process.env.FACEBOOK_CLIENT_ID || sails.config.providers.facebook.clientID,
+ clientSecret: process.env.FACEBOOK_CLIENT_SECRET || sails.config.providers.facebook.clientSecret,
+ callbackURL: process.env.FACEBOOK_CALLBACK_URL || sails.config.providers.facebook.callbackURL
+ },
+ providersHandler
+ ));
+ passport.use(new TwitterStrategy({
+ consumerKey: process.env.TWITTER_CONSUMER_KEY || sails.config.providers.twitter.consumerKey,
+ consumerSecret: process.env.TWITTER_CONSUMER_SECRET || sails.config.providers.twitter.consumerSecret,
+ callbackURL: process.env.TWITTER_CALLBACK_URL || sails.config.providers.twitter.callbackURL
+ },
+ providersHandler
+ ));
+ }
+};
View
@@ -8,9 +8,9 @@
* http://sailsjs.org/#documentation
*/
-module.exports.bootstrap = function (cb) {
-
- // It's very important to trigger this callack method when you are finished
- // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap)
- cb();
+module.exports.bootstrap = function (next) {
+ PassportService.configProviders();
+ // It's very important to trigger this callack method when you are finished
+ // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap)
+ next();
};
View
@@ -67,6 +67,22 @@ module.exports = {
// Config by connection URI.
// url: mongodb://localhost/building-realtime-webapp
}
+ },
+ providers: {
+ 'github': {
+ clientID: process.env.GITHUB_CLIENT_ID || 'GITHUB_CLIENT_ID',
+ clientSecret: process.env.GITHUB_CLIENT_SECRET || 'GITHUB_CLIENT_SECRET',
+ callbackURL: process.env.GITHUB_CALLBACK_URL || 'http://localhost:1337/user/login/github'
+ },
+ 'facebook': {
+ clientID: process.env.FACEBOOK_CLIENT_ID || 'FACEBOOK_CLIENT_ID',
+ clientSecret: process.env.FACEBOOK_CLIENT_SECRET || 'FACEBOOK_CLIENT_SECRET',
+ callbackURL: process.env.FACEBOOK_CALLBACK_URL || 'http://localhost:1337/user/login/facebook'
+ },
+ 'twitter': {
+ consumerKey: process.env.TWITTER_CONSUMER_KEY || 'TWITTER_CONSUMER_KEY',
+ consumerSecret: process.env.TWITTER_CONSUMER_SECRET || 'TWITTER_CONSUMER_SECRET',
+ callbackURL: process.env.TWITTER_CALLBACK_URL || 'http://localhost:1337/user/login/twitter'
+ }
}
-
};
View
@@ -1,6 +1,6 @@
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
- bcrypt = require('bcrypt');
+ bcrypt = require('bcrypt');
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
@@ -34,17 +34,28 @@ passport.use(new LocalStrategy(
]
}).done(function(err, user) {
if (err) { return done(null, err); }
- if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
+ if (!user) {
+ var user = {};
+ var re_email = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ // If username is an email, save as mail, else as username.
+ user[re_email.test(username)?'email':'username'] = username;
+ return done(null, user, { message: 'Unknown user ' + username });
+ }
bcrypt.compare(password, user.password, function(err, res) {
if (!res) return done(null, false, { message: 'Invalid Password'});
return done(null, user, { message: 'Logged In Successfully'} );
});
- })
+ });
});
}
));
module.exports = {
+ providers: {
+ 'github': {},
+ 'facebook': {},
+ 'twitter': {}
+ },
express: {
customMiddleware: function(app){
console.log('Express midleware for passport');
@@ -58,4 +69,4 @@ module.exports = {
});
}
}
-};
+};
Oops, something went wrong.

0 comments on commit c3e544e

Please sign in to comment.