From 00f3940d8f82db5dceafd812d62cb4dc82751db7 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Wed, 9 Apr 2014 10:10:22 +0200 Subject: [PATCH 1/9] Add joining of multiple accounts --- app/controllers/users.js | 94 +++++++++++++++++++++++++++++++++++ app/models/user.js | 7 ++- config/strategies/facebook.js | 45 ++++++----------- config/strategies/google.js | 42 +++++----------- config/strategies/linkedin.js | 41 +++++---------- config/strategies/twitter.js | 39 ++++----------- 6 files changed, 150 insertions(+), 118 deletions(-) diff --git a/app/controllers/users.js b/app/controllers/users.js index e2ff8f4b..407b6faa 100755 --- a/app/controllers/users.js +++ b/app/controllers/users.js @@ -247,4 +247,98 @@ exports.hasAuthorization = function(req, res, next) { } next(); +}; + +/** + * Helper function to save or update a user. + * When the user is logged in, it joins the user data to the existing one. + * Otherwise it creates a new user. + * + * @author Kentaro Wakayama + * + * @date 2014-04-09 + * + * @param {Object} req This is the request object which contains the user when he is signed in. + * @param {String} token This is the accesstoken. + * @param {String} tokenSecret This is the refreshtoken. + * @param {Object} profile This is the user profile of the current provider. + * @param {Function} done Callback to supply Passport with the user that authenticated. + * + * @param {Object} providerData This Object contains all data which is specific for the provider + * @param {String} providerData.provider This is the passport provider name. + * @param {String} providerData.idKey This is the Key / Attribute name for saving / retrieving the provider id. + * @param {String} providerData.name This is the user's name. + * @param {String} [providerData.email] This is the user's email. + * @param {String} providerData.username This is the user's username. + * + * @return {[type]} [description] + */ +exports.saveOrUpdate = function(req, token, tokenSecret, profile, done, providerData) { + var provider = providerData.provider; + var idKey = providerData.idKey; + var searchProviderKey = provider + '.' + idKey; + var searchObject = {}; + searchObject[searchProviderKey] = profile.id; + + if (!req.user) { + // no user active, this is a fresh login + User.findOne(searchObject, function(err, user) { + if (err) { + return done(err); + } + if (!user) { + + var possibleUsername = ''; + if (providerData.email) { + possibleUsername = providerData.email.split('@')[0]; + } else { + possibleUsername = profile.username; + } + + User.findUniqueUsername(possibleUsername, null, function(availableUsername) { + user = new User({ + firstName: providerData.firstName, + lastName: providerData.lastName, + username: availableUsername, + displayName: providerData.displayName, + email: providerData.email, + provider: provider, + }); + + user[provider] = profile._json; + user[provider].token = token; + user[provider].tokenSecret = tokenSecret; + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + }); + } else { + user[provider].token = token; + user[provider].tokenSecret = tokenSecret; + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } + }); + } else { + // a user is already logged in, join the provider data to the existing user. + User.findById( req.user._id, function(err, user) { + if (err) { + return done(err); + } + if (user) { + user[provider] = profile._json; + user[provider].token = token; + user[provider].tokenSecret = tokenSecret; + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } else { + return done(err, user); + } + }); + } }; \ No newline at end of file diff --git a/app/models/user.js b/app/models/user.js index 30d12bb1..2eebcbdb 100755 --- a/app/models/user.js +++ b/app/models/user.js @@ -73,7 +73,12 @@ var UserSchema = new Schema({ created: { type: Date, default: Date.now - } + }, + facebook: {}, + twitter: {}, + github: {}, + google: {}, + linkedin: {} }); /** diff --git a/config/strategies/facebook.js b/config/strategies/facebook.js index 4802a46f..80f684b7 100644 --- a/config/strategies/facebook.js +++ b/config/strategies/facebook.js @@ -3,7 +3,8 @@ var passport = require('passport'), FacebookStrategy = require('passport-facebook').Strategy, User = require('mongoose').model('User'), - config = require('../config'); + config = require('../config'), + users = require('../../app/controllers/users'); module.exports = function() { // Use facebook strategy @@ -14,36 +15,18 @@ module.exports = function() { passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - return done(new Error('User is already signed in'), req.user); - } else { - User.findOne({ - 'provider': 'facebook', - 'providerData.id': profile.id - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - User.findUniqueUsername(profile.username, null, function(availableUsername) { - user = new User({ - firstName: profile.name.givenName, - lastName: profile.name.familyName, - displayName: profile.displayName, - email: profile.emails[0].value, - username: availableUsername, - provider: 'facebook', - providerData: profile._json - }); - user.save(function(err) { - return done(err, user); - }); - }); - } else { - return done(err, user); - } - }); - } + + var providerData = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + provider: 'facebook', + idKey: 'id', + email: profile.emails[0].value, + username: profile.username, + }; + users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + } )); }; \ No newline at end of file diff --git a/config/strategies/google.js b/config/strategies/google.js index d86e0255..fcbac8b2 100644 --- a/config/strategies/google.js +++ b/config/strategies/google.js @@ -3,7 +3,8 @@ var passport = require('passport'), GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, User = require('mongoose').model('User'), - config = require('../config'); + config = require('../config'), + users = require('../../app/controllers/users'); module.exports = function() { // Use google strategy @@ -14,35 +15,18 @@ module.exports = function() { passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - return done(new Error('User is already signed in'), req.user); - } else { - User.findOne({ - 'provider': 'google', - 'providerData.id': profile.id - }, function(err, user) { - if (!user) { - var possibleUsername = profile.emails[0].value.split('@')[0]; - User.findUniqueUsername(possibleUsername, null, function(availableUsername) { - user = new User({ - firstName: profile.name.givenName, - lastName: profile.name.familyName, - displayName: profile.displayName, - email: profile.emails[0].value, - username: availableUsername, - provider: 'google', - providerData: profile._json - }); - user.save(function(err) { - return done(err, user); - }); - }); - } else { - return done(err, user); - } - }); - } + var providerData = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + provider: 'google', + idKey: 'id', + email: profile.emails[0].value, + username: profile.username + }; + users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + } )); }; \ No newline at end of file diff --git a/config/strategies/linkedin.js b/config/strategies/linkedin.js index cbb460a6..80994fbf 100644 --- a/config/strategies/linkedin.js +++ b/config/strategies/linkedin.js @@ -3,7 +3,8 @@ var passport = require('passport'), LinkedInStrategy = require('passport-linkedin').Strategy, User = require('mongoose').model('User'), - config = require('../config'); + config = require('../config'), + users = require('../../app/controllers/users'); module.exports = function() { // Use linkedin strategy @@ -15,35 +16,17 @@ module.exports = function() { profileFields: ['id', 'first-name', 'last-name', 'email-address'] }, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - return done(new Error('User is already signed in'), req.user); - } else { - User.findOne({ - 'provider': 'linkedin', - 'providerData.id': profile.id - }, function(err, user) { - if (!user) { - var possibleUsername = profile.emails[0].value.split('@')[0]; - User.findUniqueUsername(possibleUsername, null, function(availableUsername) { - user = new User({ - firstName: profile.name.givenName, - lastName: profile.name.familyName, - displayName: profile.displayName, - email: profile.emails[0].value, - username: availableUsername, - provider: 'linkedin', - providerData: profile._json - }); - user.save(function(err) { - return done(err, user); - }); - }); - } else { - return done(err, user); - } - }); - } + var providerData = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + provider: 'linkedin', + idKey: 'id', + username: profile.displayName, + }; + users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + } )); }; \ No newline at end of file diff --git a/config/strategies/twitter.js b/config/strategies/twitter.js index 62301d44..44e7b4d4 100644 --- a/config/strategies/twitter.js +++ b/config/strategies/twitter.js @@ -3,7 +3,8 @@ var passport = require('passport'), TwitterStrategy = require('passport-twitter').Strategy, User = require('mongoose').model('User'), - config = require('../config'); + config = require('../config'), + users = require('../../app/controllers/users'); module.exports = function() { // Use twitter strategy @@ -14,33 +15,15 @@ module.exports = function() { passReqToCallback: true }, function(req, token, tokenSecret, profile, done) { - if (req.user) { - return done(new Error('User is already signed in'), req.user); - } else { - User.findOne({ - 'provider': 'twitter', - 'providerData.id_str': profile.id - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - User.findUniqueUsername(profile.username, null, function(availableUsername) { - user = new User({ - displayName: profile.displayName, - username: availableUsername, - provider: 'twitter', - providerData: profile._json - }); - user.save(function(err) { - return done(err, user); - }); - }); - } else { - return done(err, user); - } - }); - } + + var providerData = { + displayName: profile.displayName, + provider: 'twitter', + idKey: 'id_str', + username: profile.username, + }; + + users.saveOrUpdate(req, token, tokenSecret, profile, done, providerData); } )); }; \ No newline at end of file From 56b0c3112015ed7695e95460e5aa1df0f022a032 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Wed, 9 Apr 2014 10:11:24 +0200 Subject: [PATCH 2/9] Add account buttons to join more accounts --- public/modules/users/views/settings/profile.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/public/modules/users/views/settings/profile.html b/public/modules/users/views/settings/profile.html index 5006dc9f..aae41fff 100644 --- a/public/modules/users/views/settings/profile.html +++ b/public/modules/users/views/settings/profile.html @@ -31,4 +31,19 @@ +

Add an other Account

+ \ No newline at end of file From 8479e46f1e3d102b8aa4d222b04f5c0c00845f00 Mon Sep 17 00:00:00 2001 From: Michael Cole Date: Mon, 14 Apr 2014 16:25:51 -0500 Subject: [PATCH 3/9] Move license to it's own file. Easier to see what the license is when first encountering the repo --- LICENSE.md | 21 +++++++++++++++++++++ README.md | 24 +----------------------- 2 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..b366c5dc --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +## License +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 69dbe6fe..90434603 100644 --- a/README.md +++ b/README.md @@ -71,26 +71,4 @@ In the docs we'll try to explain both general concepts of MEAN components and gi Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com). ## Credits -Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) - -## License -(The MIT License) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) \ No newline at end of file From 2f6427aa42486a2ddb31e520852fce6e929ef996 Mon Sep 17 00:00:00 2001 From: Michael Cole Date: Mon, 14 Apr 2014 17:06:13 -0500 Subject: [PATCH 4/9] Update README.md to include where to download the MEAN.JS boilerplate --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 90434603..38ab836d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ $ sudo npm install -g grunt-cli ## Quick Install Once you've installed all the prerequisites, you're just a few steps away from starting to develop you MEAN application. +The easiest way to use MEAN.JS as a boilerplate is to download a copy from the [master branch on github][https://github.com/meanjs/mean/archive/master.zip]. +``` +$ wget https://github.com/meanjs/mean/archive/master.zip -O MEANJS.zip; unzip MEANJS.zip; rm MEANJS.zip +``` +Rename `mean-master` after your project and create your own git repo. If you'd like to contribute code back to MEAN.JS from your project, see here (tbd). + The first thing you should do is install the Node.js dependencies. The boilerplate comes pre-bundled with a package.json file that contains the list of modules you need to start your application, to learn more about the modules installed visit the NPM & Package.json section. To install Node.js dependencies you're going to use npm again, in the application folder run this in the command-line: From 956ec259820e3f197639260e6b21a3985adf032d Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Tue, 15 Apr 2014 15:44:10 +0300 Subject: [PATCH 5/9] Fixing License and Readme --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 38ab836d..a59a30e3 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,28 @@ $ npm install -g bower $ sudo npm install -g grunt-cli ``` -## Quick Install -Once you've installed all the prerequisites, you're just a few steps away from starting to develop you MEAN application. +## Downloading MEAN.JS +There are several ways you can get the MEAN.JS boilerplate: -The easiest way to use MEAN.JS as a boilerplate is to download a copy from the [master branch on github][https://github.com/meanjs/mean/archive/master.zip]. +### Yo Generator +The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html) which will generate the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles. + +### Cloning The GitHub Repository +You can also use Git to directly clone the MEAN.JS repository: +``` +$ git clone https://github.com/meanjs/mean.git meanjs +``` +This will clone the latest version of the MEAN.JS repository to a **meanjs** folder. + +### Downloading The Repository Zip File +Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on github](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: ``` $ wget https://github.com/meanjs/mean/archive/master.zip -O MEANJS.zip; unzip MEANJS.zip; rm MEANJS.zip ``` -Rename `mean-master` after your project and create your own git repo. If you'd like to contribute code back to MEAN.JS from your project, see here (tbd). +You'll be able to rename **mean-master** after your project name and create your own git repo. + +## Quick Install +Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop you MEAN application. The first thing you should do is install the Node.js dependencies. The boilerplate comes pre-bundled with a package.json file that contains the list of modules you need to start your application, to learn more about the modules installed visit the NPM & Package.json section. @@ -77,4 +91,26 @@ In the docs we'll try to explain both general concepts of MEAN components and gi Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com). ## Credits -Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) \ No newline at end of file +Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) + +## License +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 08a2c0fc72924986c928bbe03d588a4131022ea4 Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Tue, 15 Apr 2014 16:08:02 +0300 Subject: [PATCH 6/9] Fixed Comments and README --- README.md | 4 ++-- config/config.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a59a30e3..d8d04f6e 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ This will clone the latest version of the MEAN.JS repository to a **meanjs** fol ### Downloading The Repository Zip File Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on github](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: ``` -$ wget https://github.com/meanjs/mean/archive/master.zip -O MEANJS.zip; unzip MEANJS.zip; rm MEANJS.zip +$ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip ``` -You'll be able to rename **mean-master** after your project name and create your own git repo. +Don't forget to rename **mean-master** after your project name. ## Quick Install Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop you MEAN application. diff --git a/config/config.js b/config/config.js index 1823cae4..afe7eb09 100644 --- a/config/config.js +++ b/config/config.js @@ -3,6 +3,7 @@ var _ = require('lodash'), utilities = require('./utilities'); +// Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV process.env.NODE_ENV = ~utilities.walk('./config/env', /(.*)\.js$/).map(function(file) { return file.split('/').pop().slice(0, -3); }).indexOf(process.env.NODE_ENV) ? process.env.NODE_ENV : 'development'; @@ -11,4 +12,4 @@ process.env.NODE_ENV = ~utilities.walk('./config/env', /(.*)\.js$/).map(function module.exports = _.extend( require('./env/all'), require('./env/' + process.env.NODE_ENV) || {} -); +); \ No newline at end of file From 279eb396941ac221e81b5903d7b6076ebc56e6c7 Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Wed, 16 Apr 2014 02:00:23 +0300 Subject: [PATCH 7/9] Finalizing the User Accounts Module --- app/controllers/users.js | 175 ++++++++++-------- app/models/user.js | 8 +- app/routes/users.js | 1 + config/env/development.js | 18 +- config/strategies/facebook.js | 16 +- config/strategies/google.js | 20 +- config/strategies/linkedin.js | 17 +- config/strategies/twitter.js | 20 +- public/modules/core/views/header.html | 3 + public/modules/users/config/routes.js | 4 + public/modules/users/controllers/settings.js | 32 +++- public/modules/users/css/users.css | 13 +- .../modules/users/views/settings/profile.html | 15 -- 13 files changed, 208 insertions(+), 134 deletions(-) diff --git a/app/controllers/users.js b/app/controllers/users.js index 407b6faa..9faaca57 100755 --- a/app/controllers/users.js +++ b/app/controllers/users.js @@ -8,6 +8,9 @@ var mongoose = require('mongoose'), User = mongoose.model('User'), _ = require('lodash'); +/** + * Get the error message from error object + */ var getErrorMessage = function(err) { var message = ''; @@ -153,7 +156,6 @@ exports.changePassword = function(req, res, next) { }); } }); - } else { res.send(400, { message: 'Passwords do not match' @@ -197,9 +199,8 @@ exports.me = function(req, res) { */ exports.oauthCallback = function(strategy) { return function(req, res, next) { - passport.authenticate(strategy, function(err, user, email) { + passport.authenticate(strategy, function(err, user, redirectURL) { if (err || !user) { - console.log(err); return res.redirect('/#!/signin'); } req.login(user, function(err) { @@ -207,7 +208,7 @@ exports.oauthCallback = function(strategy) { return res.redirect('/#!/signin'); } - return res.redirect('/'); + return res.redirect(redirectURL || '/'); }); })(req, res, next); }; @@ -250,94 +251,112 @@ exports.hasAuthorization = function(req, res, next) { }; /** - * Helper function to save or update a user. - * When the user is logged in, it joins the user data to the existing one. - * Otherwise it creates a new user. - * - * @author Kentaro Wakayama - * - * @date 2014-04-09 - * - * @param {Object} req This is the request object which contains the user when he is signed in. - * @param {String} token This is the accesstoken. - * @param {String} tokenSecret This is the refreshtoken. - * @param {Object} profile This is the user profile of the current provider. - * @param {Function} done Callback to supply Passport with the user that authenticated. - * - * @param {Object} providerData This Object contains all data which is specific for the provider - * @param {String} providerData.provider This is the passport provider name. - * @param {String} providerData.idKey This is the Key / Attribute name for saving / retrieving the provider id. - * @param {String} providerData.name This is the user's name. - * @param {String} [providerData.email] This is the user's email. - * @param {String} providerData.username This is the user's username. - * - * @return {[type]} [description] + * Helper function to save or update a OAuth user profile */ -exports.saveOrUpdate = function(req, token, tokenSecret, profile, done, providerData) { - var provider = providerData.provider; - var idKey = providerData.idKey; - var searchProviderKey = provider + '.' + idKey; - var searchObject = {}; - searchObject[searchProviderKey] = profile.id; - +exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { if (!req.user) { - // no user active, this is a fresh login - User.findOne(searchObject, function(err, user) { + // Define a search query fields + var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; + var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; + + // Define main provider search query + var mainProviderSearchQuery = {}; + mainProviderSearchQuery.provider = providerUserProfile.provider; + mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define additional provider search query + var additionalProviderSearchQuery = {}; + additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define a search query to find existing user with current provider profile + var searchQuery = { + $or: [mainProviderSearchQuery, additionalProviderSearchQuery] + }; + + User.findOne(searchQuery, function(err, user) { if (err) { return done(err); - } - if (!user) { - - var possibleUsername = ''; - if (providerData.email) { - possibleUsername = providerData.email.split('@')[0]; - } else { - possibleUsername = profile.username; - } - - User.findUniqueUsername(possibleUsername, null, function(availableUsername) { - user = new User({ - firstName: providerData.firstName, - lastName: providerData.lastName, - username: availableUsername, - displayName: providerData.displayName, - email: providerData.email, - provider: provider, - }); - - user[provider] = profile._json; - user[provider].token = token; - user[provider].tokenSecret = tokenSecret; - user.save(function(err) { - if (err) console.log(err); - return done(err, user); - }); - }); } else { - user[provider].token = token; - user[provider].tokenSecret = tokenSecret; - user.save(function(err) { - if (err) console.log(err); + if (!user) { + var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); + + User.findUniqueUsername(possibleUsername, null, function(availableUsername) { + user = new User({ + firstName: providerUserProfile.firstName, + lastName: providerUserProfile.lastName, + username: availableUsername, + displayName: providerUserProfile.displayName, + email: providerUserProfile.email, + provider: providerUserProfile.provider, + providerData: providerUserProfile.providerData + }); + + // And save the user + user.save(function(err) { + return done(err, user); + }); + }); + } else { return done(err, user); - }); + } } }); } else { - // a user is already logged in, join the provider data to the existing user. - User.findById( req.user._id, function(err, user) { + // User is already logged in, join the provider data to the existing user + User.findById(req.user.id, function(err, user) { if (err) { return done(err); - } - if (user) { - user[provider] = profile._json; - user[provider].token = token; - user[provider].tokenSecret = tokenSecret; - user.save(function(err) { - if (err) console.log(err); + } else { + // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured + if (user && user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { + // Add the provider data to the additional provider data field + if (!user.additionalProvidersData) user.additionalProvidersData = {}; + user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + + // And save the user + user.save(function(err) { + return done(err, user, '/#!/settings/accounts'); + }); + } else { return done(err, user); + } + } + }); + } +}; + +/** + * Remove OAuth provider + */ +exports.removeOAuthProvider = function(req, res, next) { + var user = req.user; + var provider = req.param('provider'); + + if (user && provider) { + // Delete the additional provider + if (user.additionalProvidersData[provider]) { + delete user.additionalProvidersData[provider]; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + } + + user.save(function(err) { + if (err) { + return res.send(400, { + message: getErrorMessage(err) }); } else { - return done(err, user); + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.jsonp(user); + } + }); } }); } diff --git a/app/models/user.js b/app/models/user.js index 2eebcbdb..cfd122a0 100755 --- a/app/models/user.js +++ b/app/models/user.js @@ -67,18 +67,14 @@ var UserSchema = new Schema({ required: 'Provider is required' }, providerData: {}, + additionalProvidersData: {}, updated: { type: Date }, created: { type: Date, default: Date.now - }, - facebook: {}, - twitter: {}, - github: {}, - google: {}, - linkedin: {} + } }); /** diff --git a/app/routes/users.js b/app/routes/users.js index 96e9903d..23f8126b 100644 --- a/app/routes/users.js +++ b/app/routes/users.js @@ -11,6 +11,7 @@ module.exports = function(app) { app.get('/users/me', users.me); app.put('/users', users.update); app.post('/users/password', users.changePassword); + app.del('/users/accounts', users.removeOAuthProvider); // Setting up the users api app.post('/auth/signup', users.signup); diff --git a/config/env/development.js b/config/env/development.js index dc583724..0e2e2871 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -6,23 +6,23 @@ module.exports = { title: 'MEAN.JS - Development Environment' }, facebook: { - clientID: 'APP_ID', - clientSecret: 'APP_SECRET', - callbackURL: 'http://localhost:3000/auth/facebook/callback' + clientID: '588647347851720', + clientSecret: 'd2870185a0b41ab0ec32ac9d023be5b0', + callbackURL: 'http://local.meanjs.herokuapp.com:3000/auth/facebook/callback' }, twitter: { - clientID: 'CONSUMER_KEY', - clientSecret: 'CONSUMER_SECRET', + clientID: 'f9JcCc0xSzEUkwF5E5ZKLQ', + clientSecret: 'E9zzKZhZlZuy5T1qMsu3c75EkGf9yVwp0uAIOwtI0oM', callbackURL: 'http://localhost:3000/auth/twitter/callback' }, google: { - clientID: 'APP_ID', - clientSecret: 'APP_SECRET', + clientID: '751147574067-kt9q7nnkvns3b8cg742nsddk9d77k0bt.apps.googleusercontent.com', + clientSecret: '-7acCDhnsbf22HoHB_8CkAHi', callbackURL: 'http://localhost:3000/auth/google/callback' }, linkedin: { - clientID: 'APP_ID', - clientSecret: 'APP_SECRET', + clientID: '77f1ywm1byjwpm', + clientSecret: 'K6D9cufcuNIjcqUr', callbackURL: 'http://localhost:3000/auth/linkedin/callback' } }; \ No newline at end of file diff --git a/config/strategies/facebook.js b/config/strategies/facebook.js index 80f684b7..113cb74b 100644 --- a/config/strategies/facebook.js +++ b/config/strategies/facebook.js @@ -2,7 +2,6 @@ var passport = require('passport'), FacebookStrategy = require('passport-facebook').Strategy, - User = require('mongoose').model('User'), config = require('../config'), users = require('../../app/controllers/users'); @@ -15,18 +14,25 @@ module.exports = function() { passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; - var providerData = { + // Create the user OAuth profile + var providerUserProfile = { firstName: profile.name.givenName, lastName: profile.name.familyName, displayName: profile.displayName, - provider: 'facebook', - idKey: 'id', email: profile.emails[0].value, username: profile.username, + provider: 'facebook', + providerIdentifierField: 'id', + providerData: providerData }; - users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); } )); }; \ No newline at end of file diff --git a/config/strategies/google.js b/config/strategies/google.js index fcbac8b2..f06c0998 100644 --- a/config/strategies/google.js +++ b/config/strategies/google.js @@ -2,7 +2,6 @@ var passport = require('passport'), GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, - User = require('mongoose').model('User'), config = require('../config'), users = require('../../app/controllers/users'); @@ -15,18 +14,25 @@ module.exports = function() { passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { - - var providerData = { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var providerUserProfile = { firstName: profile.name.givenName, lastName: profile.name.familyName, displayName: profile.displayName, - provider: 'google', - idKey: 'id', email: profile.emails[0].value, - username: profile.username + username: profile.username, + provider: 'google', + providerIdentifierField: 'id', + providerData: providerData }; - users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); } )); }; \ No newline at end of file diff --git a/config/strategies/linkedin.js b/config/strategies/linkedin.js index 80994fbf..2ba5736d 100644 --- a/config/strategies/linkedin.js +++ b/config/strategies/linkedin.js @@ -2,7 +2,6 @@ var passport = require('passport'), LinkedInStrategy = require('passport-linkedin').Strategy, - User = require('mongoose').model('User'), config = require('../config'), users = require('../../app/controllers/users'); @@ -16,17 +15,25 @@ module.exports = function() { profileFields: ['id', 'first-name', 'last-name', 'email-address'] }, function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; - var providerData = { + // Create the user OAuth profile + var providerUserProfile = { firstName: profile.name.givenName, lastName: profile.name.familyName, displayName: profile.displayName, + email: profile.emails[0].value, + username: profile.username, provider: 'linkedin', - idKey: 'id', - username: profile.displayName, + providerIdentifierField: 'id', + providerData: providerData }; - users.saveOrUpdate(req, accessToken, refreshToken, profile, done, providerData); + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); } )); }; \ No newline at end of file diff --git a/config/strategies/twitter.js b/config/strategies/twitter.js index 44e7b4d4..cf3e6cc2 100644 --- a/config/strategies/twitter.js +++ b/config/strategies/twitter.js @@ -2,7 +2,6 @@ var passport = require('passport'), TwitterStrategy = require('passport-twitter').Strategy, - User = require('mongoose').model('User'), config = require('../config'), users = require('../../app/controllers/users'); @@ -15,15 +14,22 @@ module.exports = function() { passReqToCallback: true }, function(req, token, tokenSecret, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.token = token; + providerData.tokenSecret = tokenSecret; - var providerData = { + // Create the user OAuth profile + var providerUserProfile = { displayName: profile.displayName, - provider: 'twitter', - idKey: 'id_str', - username: profile.username, - }; + username: profile.username, + provider: 'twitter', + providerIdentifierField: 'id_str', + providerData: providerData + }; - users.saveOrUpdate(req, token, tokenSecret, profile, done, providerData); + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); } )); }; \ No newline at end of file diff --git a/public/modules/core/views/header.html b/public/modules/core/views/header.html index 8cf3169b..918c882b 100644 --- a/public/modules/core/views/header.html +++ b/public/modules/core/views/header.html @@ -32,6 +32,9 @@
  • Edit Profile
  • +
  • + Manage Social Accounts +
  • Change Password
  • diff --git a/public/modules/users/config/routes.js b/public/modules/users/config/routes.js index 12d8a0a0..f01bd699 100755 --- a/public/modules/users/config/routes.js +++ b/public/modules/users/config/routes.js @@ -13,6 +13,10 @@ angular.module('users').config(['$stateProvider', url: '/settings/password', templateUrl: 'modules/users/views/settings/password.html' }). + state('accounts', { + url: '/settings/accounts', + templateUrl: 'modules/users/views/settings/accounts.html' + }). state('signup', { url: '/signup', templateUrl: 'modules/users/views/signup.html' diff --git a/public/modules/users/controllers/settings.js b/public/modules/users/controllers/settings.js index 1715b2bb..c030e18c 100644 --- a/public/modules/users/controllers/settings.js +++ b/public/modules/users/controllers/settings.js @@ -4,9 +4,39 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l function($scope, $http, $location, Users, Authentication) { $scope.user = Authentication.user; - //If user is not signed in then redirect back home + // If user is not signed in then redirect back home if (!$scope.user) $location.path('/'); + // Check if there are additional accounts + $scope.hasConnectedAdditionalSocialAccounts = function(provider) { + for (var i in $scope.user.additionalProvidersData) { + return true; + } + + return false; + }; + + // Check if provider is already in use with current user + $scope.isConnectedSocialAccount = function(provider) { + return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); + }; + + $scope.removeUserSocialAccount = function(provider) { + $scope.success = $scope.error = null; + + $http.delete('/users/accounts', { + params: { + provider: provider + } + }).success(function(response) { + // If successful show success message and clear form + $scope.success = true; + $scope.user = Authentication.user = response; + }).error(function(response) { + $scope.error = response.message; + }); + }; + $scope.updateUserProfile = function() { $scope.success = $scope.error = null; var user = new Users($scope.user); diff --git a/public/modules/users/css/users.css b/public/modules/users/css/users.css index 487df40a..7dd49e21 100644 --- a/public/modules/users/css/users.css +++ b/public/modules/users/css/users.css @@ -2,4 +2,15 @@ .nav-users { position: fixed; } -} \ No newline at end of file +} + +.remove-account-container { + display: inline-block; + position: relative; +} + +.btn-remove-account { + top: 10px; + right: 10px; + position: absolute; +} diff --git a/public/modules/users/views/settings/profile.html b/public/modules/users/views/settings/profile.html index aae41fff..5006dc9f 100644 --- a/public/modules/users/views/settings/profile.html +++ b/public/modules/users/views/settings/profile.html @@ -31,19 +31,4 @@ -

    Add an other Account

    - \ No newline at end of file From 7ae98d5e2155de8054f7d0ce55f8ac6ad82beb1a Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Wed, 16 Apr 2014 02:44:55 +0300 Subject: [PATCH 8/9] Adding Comments --- config/env/development.js | 18 +++++++++--------- package.json | 4 ++-- public/modules/users/controllers/settings.js | 3 +++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/config/env/development.js b/config/env/development.js index 0e2e2871..dc583724 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -6,23 +6,23 @@ module.exports = { title: 'MEAN.JS - Development Environment' }, facebook: { - clientID: '588647347851720', - clientSecret: 'd2870185a0b41ab0ec32ac9d023be5b0', - callbackURL: 'http://local.meanjs.herokuapp.com:3000/auth/facebook/callback' + clientID: 'APP_ID', + clientSecret: 'APP_SECRET', + callbackURL: 'http://localhost:3000/auth/facebook/callback' }, twitter: { - clientID: 'f9JcCc0xSzEUkwF5E5ZKLQ', - clientSecret: 'E9zzKZhZlZuy5T1qMsu3c75EkGf9yVwp0uAIOwtI0oM', + clientID: 'CONSUMER_KEY', + clientSecret: 'CONSUMER_SECRET', callbackURL: 'http://localhost:3000/auth/twitter/callback' }, google: { - clientID: '751147574067-kt9q7nnkvns3b8cg742nsddk9d77k0bt.apps.googleusercontent.com', - clientSecret: '-7acCDhnsbf22HoHB_8CkAHi', + clientID: 'APP_ID', + clientSecret: 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/google/callback' }, linkedin: { - clientID: '77f1ywm1byjwpm', - clientSecret: 'K6D9cufcuNIjcqUr', + clientID: 'APP_ID', + clientSecret: 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/linkedin/callback' } }; \ No newline at end of file diff --git a/package.json b/package.json index 52695071..2261bfaa 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "meanjs", "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.", - "version": "0.2.2", + "version": "0.2.3", "private": false, "author": "https://github.com/meanjs/mean/graphs/contributors", "repository": { @@ -31,7 +31,7 @@ "passport-linkedin": "~0.1.3", "passport-google-oauth": "~0.1.5", "lodash": "~2.4.1", - "forever": "~0.10.11", + "forever": "~0.11.00", "bower": "~1.3.1", "grunt-cli": "~0.1.13" }, diff --git a/public/modules/users/controllers/settings.js b/public/modules/users/controllers/settings.js index c030e18c..02fd1fdd 100644 --- a/public/modules/users/controllers/settings.js +++ b/public/modules/users/controllers/settings.js @@ -21,6 +21,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); }; + // Remove a user social account $scope.removeUserSocialAccount = function(provider) { $scope.success = $scope.error = null; @@ -37,6 +38,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l }); }; + // Update a user profile $scope.updateUserProfile = function() { $scope.success = $scope.error = null; var user = new Users($scope.user); @@ -49,6 +51,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l }); }; + // Change user password $scope.changeUserPassword = function() { $scope.success = $scope.error = null; From ac3ffca366c6726250b97e0ed3de7a9dc214119f Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Wed, 16 Apr 2014 02:45:26 +0300 Subject: [PATCH 9/9] Adding Accounts View --- .../users/views/settings/accounts.html | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 public/modules/users/views/settings/accounts.html diff --git a/public/modules/users/views/settings/accounts.html b/public/modules/users/views/settings/accounts.html new file mode 100644 index 00000000..ffc1c8f0 --- /dev/null +++ b/public/modules/users/views/settings/accounts.html @@ -0,0 +1,26 @@ +
    +

    Connected social accounts:

    +
    + +
    +

    Connect other social accounts:

    + +
    \ No newline at end of file