diff --git a/bower.json b/bower.json index b00287d040..9c346a0e90 100644 --- a/bower.json +++ b/bower.json @@ -40,7 +40,8 @@ "ngInfiniteScroll": "1.0.0", "jquery-colorbox": "~1.4.36", "pnotify": "~1.3.1", - "jquery-ui": "~1.10.3" + "jquery-ui": "~1.10.3", + "angular-facebook": "~0.2.3" }, "devDependencies": { "angular-mocks": "1.3.0-beta.11" diff --git a/public/js/controllers/authCtrl.js b/public/js/controllers/authCtrl.js index 64bf201a9f..8293f48c69 100644 --- a/public/js/controllers/authCtrl.js +++ b/public/js/controllers/authCtrl.js @@ -5,26 +5,15 @@ */ angular.module('authCtrl', []) - .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrlService', '$modal', - function($scope, $rootScope, User, $http, $location, $window, ApiUrlService, $modal) { - var runAuth; - var showedFacebookMessage; - - $scope.useUUID = false; - $scope.toggleUUID = function() { - if (showedFacebookMessage === false) { - alert(window.env.t('untilNoFace')); - showedFacebookMessage = true; - } - $scope.useUUID = !$scope.useUUID; - }; + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrlService', '$modal', 'Facebook', + function($scope, $rootScope, User, $http, $location, $window, ApiUrlService, $modal, Facebook) { $scope.logout = function() { localStorage.clear(); window.location.href = '/logout'; }; - runAuth = function(id, token) { + var runAuth = function(id, token) { User.authenticate(id, token, function(err) { $window.location.href = '/'; }); @@ -59,14 +48,10 @@ angular.module('authCtrl', []) username: $scope.loginUsername || $('#login-tab input[name="username"]').val(), password: $scope.loginPassword || $('#login-tab input[name="password"]').val() }; - if ($scope.useUUID) { - runAuth($scope.loginUsername, $scope.loginPassword); - } else { - $http.post(ApiUrlService.get() + "/api/v2/user/auth/local", data) - .success(function(data, status, headers, config) { - runAuth(data.id, data.token); - }).error(errorAlert); - } + $http.post(ApiUrlService.get() + "/api/v2/user/auth/local", data) + .success(function(data, status, headers, config) { + runAuth(data.id, data.token); + }).error(errorAlert); }; $scope.playButtonClick = function(){ @@ -126,5 +111,24 @@ angular.module('authCtrl', []) $scope.hasNoNotifications = function() { return selectNotificationValue(false, false, false, false, true); } + + // ------ Facebook ---------- + // See https://developers.facebook.com/docs/facebook-login/login-flow-for-web/v2.2 for boilerplate + $scope.fbLogin = function(){ + var thenLogin = function(response){ + $http.post(ApiUrlService.get() + "/api/v2/user/auth/facebook", response.authResponse) + .success(function(data, status, headers, config) { + runAuth(data.id, data.token); + }).error(errorAlert); + } + Facebook.getLoginStatus(function(response) { + if (response.status === 'connected') { + thenLogin(response); + } else { + Facebook.login(thenLogin) + } + + }); + } } ]); diff --git a/public/js/static.js b/public/js/static.js index 84894ec5ab..606c7d18d7 100644 --- a/public/js/static.js +++ b/public/js/static.js @@ -1,18 +1,23 @@ "use strict"; -window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices', 'userServices', 'chieffancypants.loadingBar', 'authCtrl', 'ui.bootstrap']) +window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices', 'userServices', 'chieffancypants.loadingBar', 'authCtrl', 'ui.bootstrap', 'facebook']) .constant("API_URL", "") .constant("STORAGE_USER_ID", 'habitrpg-user') .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) -habitrpgStatic.controller("PlansCtrl", ['$rootScope', +.config(['FacebookProvider', function(FacebookProvider){ + FacebookProvider.init(window.env.FACEBOOK_KEY); +}]) + +.controller("PlansCtrl", ['$rootScope', function($rootScope) { $rootScope.clickContact = function(){ window.ga && ga('send', 'event', 'button', 'click', 'Contact Us (Plans)'); } } ]) + .controller('AboutCtrl',[function(){ $(document).ready(function(){ $('a.gallery').colorbox({ diff --git a/public/manifest.json b/public/manifest.json index e44d2b2d0a..59628b67aa 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -83,6 +83,7 @@ "bower_components/angular-bootstrap/ui-bootstrap-tpls.js", "bower_components/bootstrap/dist/js/bootstrap.js", "bower_components/jquery-colorbox/jquery.colorbox-min.js", + "bower_components/angular-facebook/lib/angular-facebook.js", "bower_components/angular-loading-bar/build/loading-bar.js", "js/env.js", diff --git a/src/controllers/auth.js b/src/controllers/auth.js index eae795a3ea..2a3c2fd83f 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -168,22 +168,43 @@ api.loginLocal = function(req, res, next) { /* POST /user/auth/facebook */ - - api.loginFacebook = function(req, res, next) { - var facebook_id = req.body.facebook_id; - if (!facebook_id) return res.json(401, {err: 'No facebook id provided'}); - User.findOne({'auth.facebook.id': facebook_id}, function(err, user) { - if (err) { - return res.json(401, {err: err}); - } else if (user) { - if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); - return res.json(200, {id: user.id,token: user.apiToken}); - } else { - /* FIXME: create a new user instead*/ - return res.json(403, {err: "Please register with Facebook on https://habitrpg.com, then come back here and log in."}); + var accessToken = req.body.accessToken; + async.waterfall([ + function(cb){ + // TODO is this private function here safe to use? + passport._strategies.facebook.userProfile(accessToken, cb); + }, + function(profile, cb) { + User.findOne({'auth.facebook.id': profile.id}, {_id:1, apiToken:1, auth:1}, function(err, user){ + if (err) return cb(err); + cb(null, {user:user, profile:profile}); + }); + }, + function(data, cb){ + if (data.user) return cb(null, data.user); + // Create new user + var prof = data.profile; + var user = new User({ + preferences: { + language: req.language // User language detected from browser, not saved + }, + auth: { + facebook: prof, + timestamps: {created: +new Date(), loggedIn: +new Date()} + } + }); + user.save(cb); + if(isProd && prof.emails && prof.emails[0] && prof.emails[0].value){ + emailUser((prof.displayName || prof.username), prof.emails[0].value, 'welcome'); + } + ga.event('register', 'Facebook').send(); } - }); + ], function(err, user){ + if (err) return res.json(401, {err: err.toString ? err.toString() : err}); + if (user.auth.blocked) return res.json(401, accountSuspended(user._id)); + return res.json(200, {id: user.id, token:user.apiToken}); + }) }; api.resetPassword = function(req, res, next){ @@ -271,66 +292,4 @@ api.setupPassport = function(router) { res.redirect('/'); }) - // GET /auth/facebook - // Use passport.authenticate() as route middleware to authenticate the - // request. The first step in Facebook authentication will involve - // redirecting the user to facebook.com. After authorization, Facebook will - // redirect the user back to this application at /auth/facebook/callback - router.get('/auth/facebook', - passport.authenticate('facebook', {scope: 'email'}), - i18n.getUserLanguage, - function(req, res){ - // The request will be redirected to Facebook for authentication, so this - // function will not be called. - }); - - // GET /auth/facebook/callback - // Use passport.authenticate() as route middleware to authenticate the - // request. If authentication fails, the user will be redirected back to the - // login page. Otherwise, the primary route function function will be called, - // which, in this example, will redirect the user to the home page. - router.get('/auth/facebook/callback', - passport.authenticate('facebook', { failureRedirect: '/login' }), - i18n.getUserLanguage, - function(req, res) { - //res.redirect('/'); - - async.waterfall([ - function(cb){ - User.findOne({'auth.facebook.id':req.user.id}, cb) - }, - function(user, cb){ - if (user) return cb(null, user); - - user = new User({ - preferences: { - language: req.language // User language detected from browser, not saved - }, - auth: { - facebook: req.user, - timestamps: {created: +new Date(), loggedIn: +new Date()} - } - }); - user.save(cb); - if(isProd && req.user.emails && req.user.emails[0] && req.user.emails[0].value){ - emailUser((req.user.displayName || req.user.username), req.user.emails[0].value, 'welcome'); - } - ga.event('register', 'Facebook').send() - } - ], function(err, saved){ - if (err) return res.redirect('/static/front?err=' + err); - req.session.userId = saved._id; - res.redirect('/static/front?_id='+saved._id+'&apiToken='+saved.apiToken); - }) - }); - - // Simple route middleware to ensure user is authenticated. - // Use this route middleware on any resource that needs to be protected. If - // the request is authenticated (typically via a persistent login session), - // the request will proceed. Otherwise, the user will be redirected to the - // login page. -// function ensureAuthenticated(req, res, next) { -// if (req.isAuthenticated()) { return next(); } -// res.redirect('/login') -// } }; diff --git a/src/middleware.js b/src/middleware.js index 1781ef2394..4f4bece349 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -192,6 +192,7 @@ module.exports.locals = function(req, res, next) { siteVersion: siteVersion, Content: shared.content, mods: require('./models/user').mods, + FACEBOOK_KEY: nconf.get('FACEBOOK_KEY'), tavern: tavern, // for world boss worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {} diff --git a/src/server.js b/src/server.js index e3703a6fb3..98e9fc0040 100644 --- a/src/server.js +++ b/src/server.js @@ -73,20 +73,15 @@ if (cluster.isMaster && (isDev || isProd)) { done(null, obj); }); - // Use the FacebookStrategy within Passport. - // Strategies in Passport require a `verify` function, which accept - // credentials (in this case, an accessToken, refreshToken, and Facebook - // profile), and invoke a callback with a user object. + // FIXME + // This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile) + // The proper fix would be to move to a general OAuth module simply to verify accessTokens passport.use(new FacebookStrategy({ clientID: nconf.get("FACEBOOK_KEY"), clientSecret: nconf.get("FACEBOOK_SECRET"), - callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" + //callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" }, function(accessToken, refreshToken, profile, done) { - // To keep the example simple, the user's Facebook profile is returned to - // represent the logged-in user. In a typical application, you would want - // to associate the Facebook account with a user record in your database, - // and return that user instead. done(null, profile); } )); diff --git a/views/static/login-modal.jade b/views/static/login-modal.jade index 5332b972b8..1382914049 100644 --- a/views/static/login-modal.jade +++ b/views/static/login-modal.jade @@ -3,8 +3,9 @@ script(id='modals/login.html', type='text/ng-template') button.close(type='button', ng-click='$close()') × h4.modal-title=env.t('loginAndReg') .modal-body(ng-controller='AuthCtrl') - a(href='/auth/facebook') + a(href='#', ng-click='fbLogin()') img(src='/bower_components/habitrpg-shared/img/facebook-login-register.jpeg', alt=env.t('loginFacebookAlt')) + //can we add in google auth? I like google auth h3=env.t('or') ul.nav.nav-tabs