diff --git a/config/passport.js b/config/passport.js index df37109678..fdfac47093 100755 --- a/config/passport.js +++ b/config/passport.js @@ -21,9 +21,7 @@ passport.deserializeUser(function(id, done) { }); }); -/** - * Sign in using Email and Password. - */ +// Sign in using Email and Password. passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) { User.findOne({ email: email }, function(err, user) { @@ -53,9 +51,7 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw * - Else create a new account. */ -/** - * Sign in with Facebook. - */ +// Sign in with Facebook. passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { if (req.user) { @@ -102,9 +98,7 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r } })); -/** - * Sign in with GitHub. - */ +// Sign in with GitHub. passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { if (req.user) { @@ -152,9 +146,7 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre } })); -/** - * Sign in with Twitter. - */ +// Sign in with Twitter. passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) { if (req.user) { @@ -197,9 +189,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok } })); -/** - * Sign in with Google. - */ +// Sign in with Google. passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) { if (req.user) { @@ -245,9 +235,7 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre } })); -/** - * Sign in with LinkedIn. - */ +// Sign in with LinkedIn. passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { if (req.user) { @@ -298,10 +286,7 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r } })); -/** - * Tumblr API - * Uses OAuth 1.0a Strategy. - */ +// Tumblr API setup. passport.use('tumblr', new OAuthStrategy({ requestTokenURL: 'http://www.tumblr.com/oauth/request_token', @@ -322,10 +307,7 @@ passport.use('tumblr', new OAuthStrategy({ } )); -/** - * Foursquare API - * Uses OAuth 2.0 Strategy. - */ +// Foursquare API setup. passport.use('foursquare', new OAuth2Strategy({ authorizationURL: 'https://foursquare.com/oauth2/authorize', @@ -345,10 +327,7 @@ passport.use('foursquare', new OAuth2Strategy({ } )); -/** - * Venmo API - * Uses OAuth 2.0 Strategy. - */ +// Venmo API setup. passport.use('venmo', new OAuth2Strategy({ authorizationURL: 'https://api.venmo.com/v1/oauth/authorize', diff --git a/generator.js b/generator.js new file mode 100644 index 0000000000..7e0f6104df --- /dev/null +++ b/generator.js @@ -0,0 +1,1099 @@ +var _ = require('underscore'); +var colors = require('colors'); +var fs = require('fs'); +var inquirer = require('inquirer'); +var M = require('mstring'); + +colors.setTheme({ + silly: 'rainbow', + input: 'grey', + verbose: 'cyan', + prompt: 'grey', + info: 'green', + data: 'grey', + help: 'cyan', + warn: 'yellow', + debug: 'blue', + error: 'red' +}); + +inquirer.prompt({ + type: 'list', + name: 'category', + message: 'Hackathon Starter:', + choices: ['☂ Authentication', '☱ Exit'] +}, function(answer) { + if (answer.category.match('Authentication')) { + inquirer.prompt({ + type: 'checkbox', + message: 'Select Authentication Providers', + name: 'auth', + choices: [ + new inquirer.Separator(M(function() { + /*** + + ╔═════════════════════════════════════════════════════════════════════╗ + ║ THIS TOOL IS STILL IN EXPERIMENTAL STAGE! USE AT YOUR OWN RISK. ║ + ║ ALWAYS USE VERSION CONTROL SYSTEM SO YOU COULD REVERT THE CHANGES. ║ + ╚═════════════════════════════════════════════════════════════════════╝ + + ***/ + })), + { name: 'Facebook', checked: true }, + { name: 'GitHub', checked: true }, + { name: 'Google', checked: true }, + { name: 'Twitter', checked: true }, + { name: 'Local', checked: true }, + { name: 'LinkedIn', checked: true }, + { name: 'Instagram' }, + new inquirer.Separator('Press ctrl+ to quit') + ], + validate: function(answer) { + if (answer.length < 1) return 'You must choose at least one authentication provider.'; + return true; + } + }, function(answer) { + var index; + + var passportConfigFile = 'config/passport.js'; + var userModelFile = 'models/User.js'; + var appFile = 'app.js'; + var secretsFile = 'config/secrets.js'; + var profileTemplateFile = 'views/account/profile.jade'; + var loginTemplateFile = 'views/account/login.jade'; + + var passportConfig = fs.readFileSync(passportConfigFile).toString().split('\n'); + var loginTemplate = fs.readFileSync(loginTemplateFile).toString().split('\n'); + var profileTemplate = fs.readFileSync(profileTemplateFile).toString().split('\n'); + var userModel = fs.readFileSync(userModelFile).toString().split('\n'); + var app = fs.readFileSync(appFile).toString().split('\n'); + var secrets = fs.readFileSync(secretsFile).toString().split('\n'); + + if (_.contains(answer.auth, 'Facebook')) { + var facebookStrategyRequire = "var FacebookStrategy = require('passport-facebook').Strategy;"; + var facebookStrategy = M(function() { + /*** + // Sign in with Facebook. + + passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [{ facebook: profile.id }, { email: profile.email }] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.facebook = profile.id; + user.tokens.push({ kind: 'facebook', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.gender = user.profile.gender || profile._json.gender; + user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; + user.save(function(err) { + req.flash('info', { msg: 'Facebook account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ facebook: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (existingEmailUser) { + req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' }); + done(err); + } else { + var user = new User(); + user.email = profile._json.email; + user.facebook = profile.id; + user.tokens.push({ kind: 'facebook', accessToken: accessToken }); + user.profile.name = profile.displayName; + user.profile.gender = profile._json.gender; + user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; + user.profile.location = (profile._json.location) ? profile._json.location.name : ''; + user.save(function(err) { + done(err, user); + }); + } + }); + }); + } + })); + + ***/ + }); + var facebookButton = M(function() { + /*** + a.btn.btn-block.btn-facebook.btn-social(href='/auth/facebook') + i.fa.fa-facebook + | Sign in with Facebook + ***/ + }); + var facebookLinkUnlink = M(function() { + /*** + + if user.facebook + p: a.text-danger(href='/account/unlink/facebook') Unlink your Facebook account + else + p: a(href='/auth/facebook') Link your Facebook account + ***/ + }); + var facebookModel = ' facebook: String,'; + + if (passportConfig.indexOf(facebookStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, facebookStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, facebookStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(facebookButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, facebookLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, facebookModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ Facebook authentication has been added.'.info); + } else { + console.log('✓ Facebook authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(facebookStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with Facebook.'); + passportConfig.splice(index, 47); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-facebook.btn-social(href='/auth/facebook')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(" if user.facebook"); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' facebook: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ Facebook authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'GitHub')) { + var githubStrategyRequire = "var GitHubStrategy = require('passport-github').Strategy;"; + var githubStrategy = M(function() { + /*** + // Sign in with GitHub. + + passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [{ github: profile.id }, { email: profile.email }] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.github = profile.id; + user.tokens.push({ kind: 'github', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.picture = user.profile.picture || profile._json.avatar_url; + user.profile.location = user.profile.location || profile._json.location; + user.profile.website = user.profile.website || profile._json.blog; + user.save(function(err) { + req.flash('info', { msg: 'GitHub account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ github: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (existingEmailUser) { + req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' }); + done(err); + } else { + var user = new User(); + user.email = profile._json.email; + user.github = profile.id; + user.tokens.push({ kind: 'github', accessToken: accessToken }); + user.profile.name = profile.displayName; + user.profile.picture = profile._json.avatar_url; + user.profile.location = profile._json.location; + user.profile.website = profile._json.blog; + user.save(function(err) { + done(err, user); + }); + } + }); + }); + } + })); + + ***/ + }); + + var githubButton = M(function() { + /*** + a.btn.btn-block.btn-github.btn-social(href='/auth/github') + i.fa.fa-github + | Sign in with GitHub + ***/ + }); + var githubLinkUnlink = M(function() { + /*** + + if user.github + p: a.text-danger(href='/account/unlink/github') Unlink your GitHub account + else + p: a(href='/auth/github') Link your GitHub account + ***/ + }); + var githubModel = ' github: String,'; + + if (passportConfig.indexOf(githubStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, githubStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, githubStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(githubButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, githubLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, githubModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ GitHub authentication has been added.'.info); + } else { + console.log('✓ GitHub authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(githubStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with GitHub.'); + passportConfig.splice(index, 48); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-github.btn-social(href='/auth/github')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' if user.github'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' github: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ GitHub authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'Google')) { + var googleStrategyRequire = "var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;"; + var googleStrategy = M(function() { + /*** + // Sign in with Google. + + passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [{ google: profile.id }, { email: profile.email }] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.google = profile.id; + user.tokens.push({ kind: 'google', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.gender = user.profile.gender || profile._json.gender; + user.profile.picture = user.profile.picture || profile._json.picture; + user.save(function(err) { + req.flash('info', { msg: 'Google account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ google: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { + if (existingEmailUser) { + req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' }); + done(err); + } else { + var user = new User(); + user.email = profile._json.email; + user.google = profile.id; + user.tokens.push({ kind: 'google', accessToken: accessToken }); + user.profile.name = profile.displayName; + user.profile.gender = profile._json.gender; + user.profile.picture = profile._json.picture; + user.save(function(err) { + done(err, user); + }); + } + }); + }); + } + })); + + ***/ + }); + + var googleButton = M(function() { + /*** + a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google') + i.fa.fa-google-plus + | Sign in with Google + ***/ + }); + var googleLinkUnlink = M(function() { + /*** + + if user.google + p: a.text-danger(href='/account/unlink/google') Unlink your Google account + else + p: a(href='/auth/google') Link your Google account + ***/ + }); + var googleModel = ' google: String,'; + + if (passportConfig.indexOf(googleStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, googleStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, googleStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(googleButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, googleLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, googleModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ Google authentication has been added.'.info); + } else { + console.log('✓ Google authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(googleStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with Google.'); + passportConfig.splice(index, 46); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' if user.google'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' google: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ Google authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'Twitter')) { + var twitterStrategyRequire = "var TwitterStrategy = require('passport-twitter').Strategy;"; + var twitterStrategy = M(function() { + /*** + // Sign in with Twitter. + + passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) { + if (req.user) { + User.findOne({ twitter: profile.id }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a Twitter account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.twitter = profile.id; + user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.location = user.profile.location || profile._json.location; + user.profile.picture = user.profile.picture || profile._json.profile_image_url; + user.save(function(err) { + req.flash('info', { msg: 'Twitter account has been linked.' }); + done(err, user); + }); + }); + } + }); + + } else { + User.findOne({ twitter: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + var user = new User(); + // Twitter will not provide an email address. Period. + // But a person’s twitter username is guaranteed to be unique + // so we can "fake" a twitter email address as follows: + user.email = profile.username + "@twitter.com"; + user.twitter = profile.id; + user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); + user.profile.name = profile.displayName; + user.profile.location = profile._json.location; + user.profile.picture = profile._json.profile_image_url; + user.save(function(err) { + done(err, user); + }); + }); + } + })); + ***/ + }); + + var twitterButton = M(function() { + /*** + a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google') + i.fa.fa-google-plus + | Sign in with Google + ***/ + }); + var twitterLinkUnlink = M(function() { + /*** + + if user.google + p: a.text-danger(href='/account/unlink/google') Unlink your Google account + else + p: a(href='/auth/google') Link your Google account + ***/ + }); + var twitterModel = ' twitter: String,'; + + if (passportConfig.indexOf(twitterStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, twitterStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, twitterStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(twitterButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, twitterLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, twitterModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ Twitter authentication has been added.'.info); + } else { + console.log('✓ Twitter authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(twitterStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with Twitter.'); + passportConfig.splice(index, 43); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-twitter.btn-social(href='/auth/twitter')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' if user.twitter'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' twitter: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ Twitter authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'LinkedIn')) { + var linkedinStrategyRequire = "var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;"; + var linkedinStrategy = M(function() { + /*** + // Sign in with LinkedIn. + + passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [ + { linkedin: profile.id }, + { email: profile._json.emailAddress } + ] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.linkedin = profile.id; + user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.location = user.profile.location || profile._json.location.name; + user.profile.picture = user.profile.picture || profile._json.pictureUrl; + user.profile.website = user.profile.website || profile._json.publicProfileUrl; + user.save(function(err) { + req.flash('info', { msg: 'LinkedIn account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ linkedin: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) { + if (existingEmailUser) { + req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' }); + done(err); + } else { + var user = new User(); + user.linkedin = profile.id; + user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); + user.email = profile._json.emailAddress; + user.profile.name = profile.displayName; + user.profile.location = profile._json.location.name; + user.profile.picture = profile._json.pictureUrl; + user.profile.website = profile._json.publicProfileUrl; + user.save(function(err) { + done(err, user); + }); + } + }); + }); + } + })); + ***/ + }); + + var linkedinButton = M(function() { + /*** + a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin') + i.fa.fa-linkedin + | Sign in with LinkedIn + ***/ + }); + var linkedinLinkUnlink = M(function() { + /*** + + if user.linkedin + p: a.text-danger(href='/account/unlink/linkedin') Unlink your LinkedIn account + else + p: a(href='/auth/linkedin') Link your LinkedIn account + ***/ + }); + var linkedinModel = ' linkedin: String,'; + + if (passportConfig.indexOf(linkedinStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, linkedinStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, linkedinStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(linkedinButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, linkedinLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, linkedinModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ LinkedIn authentication has been added.'.info); + } else { + console.log('✓ LinkedIn authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(linkedinStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with LinkedIn.'); + passportConfig.splice(index, 51); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' if user.linkedin'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' linkedin: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ LinkedIn authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'LinkedIn')) { + var linkedinStrategyRequire = "var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;"; + var linkedinStrategy = M(function() { + /*** + // Sign in with LinkedIn. + + passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [ + { linkedin: profile.id }, + { email: profile._json.emailAddress } + ] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.linkedin = profile.id; + user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.location = user.profile.location || profile._json.location.name; + user.profile.picture = user.profile.picture || profile._json.pictureUrl; + user.profile.website = user.profile.website || profile._json.publicProfileUrl; + user.save(function(err) { + req.flash('info', { msg: 'LinkedIn account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ linkedin: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) { + if (existingEmailUser) { + req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' }); + done(err); + } else { + var user = new User(); + user.linkedin = profile.id; + user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); + user.email = profile._json.emailAddress; + user.profile.name = profile.displayName; + user.profile.location = profile._json.location.name; + user.profile.picture = profile._json.pictureUrl; + user.profile.website = profile._json.publicProfileUrl; + user.save(function(err) { + done(err, user); + }); + } + }); + }); + } + })); + ***/ + }); + + var linkedinButton = M(function() { + /*** + a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin') + i.fa.fa-linkedin + | Sign in with LinkedIn + ***/ + }); + var linkedinLinkUnlink = M(function() { + /*** + + if user.linkedin + p: a.text-danger(href='/account/unlink/linkedin') Unlink your LinkedIn account + else + p: a(href='/auth/linkedin') Link your LinkedIn account + ***/ + }); + var linkedinModel = ' linkedin: String,'; + + if (passportConfig.indexOf(linkedinStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, linkedinStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, linkedinStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + loginTemplate.push(linkedinButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, linkedinLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, linkedinModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ LinkedIn authentication has been added.'.info); + } else { + console.log('✓ LinkedIn authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(linkedinStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with LinkedIn.'); + passportConfig.splice(index, 51); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' if user.linkedin'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' linkedin: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ LinkedIn authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'Local')) { + var localStrategyRequire = "var LocalStrategy = require('passport-local').Strategy;"; + var localStrategy = M(function() { + /*** + // Sign in using Email and Password. + + passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) { + User.findOne({ email: email }, function(err, user) { + if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); + user.comparePassword(password, function(err, isMatch) { + if (isMatch) { + return done(null, user); + } else { + return done(null, false, { message: 'Invalid email or password.' }); + } + }); + }); + })); + ***/ + }); + + var localLoginForm = M(function() { + /*** + form(method='POST') + legend Sign In + input(type='hidden', name='_csrf', value=_csrf) + .col-sm-8.col-sm-offset-2 + .form-group + label.control-label(for='email') Email + input.form-control(type='text', name='email', id='email', placeholder='Email', autofocus=true) + .form-group + label.control-label(for='password') Password + input.form-control(type='password', name='password', id='password', placeholder='Password') + .form-group + button.btn.btn-primary(type='submit') + i.fa.fa-unlock-alt + | Login + a.btn.btn-link(href='/forgot') Forgot your password? + hr + ***/ + }); + + var localChangePassword = M(function() { + /*** + .page-header + h3 Change Password + + form.form-horizontal(action='/account/password', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + .form-group + label.col-sm-3.control-label(for='password') New Password + .col-sm-4 + input.form-control(type='password', name='password', id='password') + .form-group + label.col-sm-3.control-label(for='confirmPassword') Confirm Password + .col-sm-4 + input.form-control(type='password', name='confirmPassword', id='confirmPassword') + .form-group + .col-sm-offset-3.col-sm-4 + button.btn.btn.btn-primary(type='submit') + i.fa.fa-keyboard-o + | Change Password + ***/ + }); + + var localModel = ' password: String,'; + + if (passportConfig.indexOf(localStrategyRequire) < 0) { + + // config/passport.js (+) + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, localStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, localStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (+) + index = profileTemplate.indexOf('block content'); + profileTemplate.splice(index + 1, 0, localLoginForm); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (+) + index = profileTemplate.indexOf(' | Update Profile'); + profileTemplate.splice(index + 1, 0, localChangePassword); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (+) + index = userModel.indexOf('email: { type: String, unique: true, lowercase: true },'); + userModel.splice(index, 0, localModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✓ Local authentication has been added.'.info); + } else { + console.log('✓ Local authentication is already active.'.data); + } + } else { + + // config/passport.js (-) + index = passportConfig.indexOf(localStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in using Email and Password.'); + passportConfig.splice(index, 15); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // views/account/login.jade (-) + index = loginTemplate.indexOf(" form(method='POST')"); + loginTemplate.splice(index, 15); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // views/account/profile.jade (-) + index = profileTemplate.indexOf(' h3 Change Password'); + profileTemplate.splice(index - 1, 19); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // models/User.js (-) + index = userModel.indexOf(' password: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ Local authentication has been removed.'.error); + } + + if (_.contains(answer.auth, 'Instagram')) { + var instagramRoutes = M(function() { + /*** + app.get('/auth/instagram', passport.authenticate('instagram')); + app.get('/auth/instagram/callback', passport.authenticate('instagram', { failureRedirect: '/login' }), function(req, res) { + res.redirect(req.session.returnTo || '/'); + }); + ***/ + }); + var instagramSecrets = M(function() { + /*** + instagram: { + clientID: process.env.INSTAGRAM_ID || 'Your Client ID', + clientSecret: process.env.INSTAGRAM_SECRET || 'Your Client Secret', + callbackURL: '/auth/instagram/callback', + passReqToCallback: true + }, + + ***/ + }); + var instagramStrategyRequire = "var InstagramStrategy = require('passport-instagram').Strategy;"; + var instagramStrategy = M(function() { + /*** + // Sign in with Instagram. + + passport.use(new InstagramStrategy(secrets.instagram,function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ $or: [{ instagram: profile.id }, { email: profile.email }] }, function(err, existingUser) { + if (existingUser) { + req.flash('errors', { msg: 'There is already an Instagram account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); + done(err); + } else { + User.findById(req.user.id, function(err, user) { + user.instagram = profile.id; + user.tokens.push({ kind: 'instagram', accessToken: accessToken }); + user.profile.name = user.profile.name || profile.displayName; + user.profile.picture = user.profile.picture || profile._json.data.profile_picture; + user.profile.website = user.profile.website || profile._json.data.website; + user.save(function(err) { + req.flash('info', { msg: 'Instagram account has been linked.' }); + done(err, user); + }); + }); + } + }); + } else { + User.findOne({ instagram: profile.id }, function(err, existingUser) { + if (existingUser) return done(null, existingUser); + + var user = new User(); + user.instagram = profile.id; + user.tokens.push({ kind: 'instagram', accessToken: accessToken }); + user.profile.name = profile.displayName; + user.email = ''; + user.profile.website = profile._json.data.website; + user.profile.picture = profile._json.data.profile_picture; + user.save(function(err) { + done(err, user); + }); + }); + } + })); + + ***/ + }); + + var instagramButton = M(function() { + /*** + a.btn.btn-block.btn-instagram.btn-social(href='/auth/instagram') + i.fa.fa-instagram + | Sign in with Instagram + ***/ + }); + var instagramLinkUnlink = M(function() { + /*** + + if user.instagram + p: a.text-danger(href='/account/unlink/instagram') Unlink your Instagram account + else + p: a(href='/auth/linkedin') Link your Instagram account + ***/ + }); + var instagramModel = ' instagram: String,'; + + if (passportConfig.indexOf(instagramStrategyRequire) < 0) { + + // Add Instagram to passport.js + index = passportConfig.indexOf("var passport = require('passport');"); + passportConfig.splice(index + 1, 0, instagramStrategyRequire); + index = passportConfig.indexOf('passport.deserializeUser(function(id, done) {'); + passportConfig.splice(index + 6, 0, instagramStrategy); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // Add Instagram to login.jade + loginTemplate.push(instagramButton); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // Add Instagram to profile.jade + index = profileTemplate.indexOf(' h3 Linked Accounts'); + profileTemplate.splice(index + 1, 0, instagramLinkUnlink); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // Add Instagram to User.js + index = userModel.indexOf(' tokens: Array,'); + userModel.splice(index - 1, 0, instagramModel); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + // Add Instagram to app.js + index = app.indexOf(' * OAuth routes for sign-in.'); + app.splice(index + 3, 0, instagramRoutes); + fs.writeFileSync(appFile, app.join('\n')); + + // Add Instagram to secrets.js + index = secrets.indexOf('module.exports = {'); + secrets.splice(index + 1, 0, instagramSecrets); + fs.writeFileSync(secretsFile, secrets.join('\n')); + + console.log('✓ Instagram authentication has been added.'.info); + } else { + console.log('✓ Instagram authentication is already active.'.data); + } + } else { + + // Remove Instagram from passport.js + index = passportConfig.indexOf(instagramStrategyRequire); + passportConfig.splice(index, 1); + index = passportConfig.indexOf('// Sign in with Instagram.'); + passportConfig.splice(index, 40); + fs.writeFileSync(passportConfigFile, passportConfig.join('\n')); + + // Remove Instagram from login.jade + index = loginTemplate.indexOf(" a.btn.btn-block.btn-instagram.btn-social(href='/auth/instagram')"); + loginTemplate.splice(index, 4); + fs.writeFileSync(loginTemplateFile, loginTemplate.join('\n')); + + // Remove Instagram from profile.jade + index = profileTemplate.indexOf(' if user.instagram'); + profileTemplate.splice(index - 1, 5); + fs.writeFileSync(profileTemplateFile, profileTemplate.join('\n')); + + // Remove Instagram from User.js + index = userModel.indexOf(' instagram: String,'); + userModel.splice(index, 1); + fs.writeFileSync(userModelFile, userModel.join('\n')); + + console.log('✗ Instagram authentication has been removed.'.error); + } + }); + } +}); \ No newline at end of file diff --git a/views/account/login.jade b/views/account/login.jade index dd73045a27..6171e469a8 100644 --- a/views/account/login.jade +++ b/views/account/login.jade @@ -5,36 +5,30 @@ block content legend Sign In input(type='hidden', name='_csrf', value=_csrf) .col-sm-8.col-sm-offset-2 - if secrets.localAuth - .form-group - label.control-label(for='email') Email - input.form-control(type='text', name='email', id='email', placeholder='Email', autofocus=true) - .form-group - label.control-label(for='password') Password - input.form-control(type='password', name='password', id='password', placeholder='Password') - .form-group - button.btn.btn-primary(type='submit') - i.fa.fa-unlock-alt - | Login - a.btn.btn-link(href='/forgot') Forgot your password? - hr - if secrets.facebookAuth - a.btn.btn-block.btn-facebook.btn-social(href='/auth/facebook') - i.fa.fa-facebook - | Sign in with Facebook - if secrets.twitterAuth - a.btn.btn-block.btn-twitter.btn-social(href='/auth/twitter') - i.fa.fa-twitter - | Sign in with Twitter - if secrets.googleAuth - a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google') - i.fa.fa-google-plus - | Sign in with Google - if secrets.githubAuth - a.btn.btn-block.btn-github.btn-social(href='/auth/github') - i.fa.fa-github - | Sign in with GitHub - if secrets.linkedinAuth - a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin') - i.fa.fa-linkedin - | Sign in with LinkedIn + .form-group + label.control-label(for='email') Email + input.form-control(type='text', name='email', id='email', placeholder='Email', autofocus=true) + .form-group + label.control-label(for='password') Password + input.form-control(type='password', name='password', id='password', placeholder='Password') + .form-group + button.btn.btn-primary(type='submit') + i.fa.fa-unlock-alt + | Login + a.btn.btn-link(href='/forgot') Forgot your password? + hr + a.btn.btn-block.btn-facebook.btn-social(href='/auth/facebook') + i.fa.fa-facebook + | Sign in with Facebook + a.btn.btn-block.btn-twitter.btn-social(href='/auth/twitter') + i.fa.fa-twitter + | Sign in with Twitter + a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google') + i.fa.fa-google-plus + | Sign in with Google + a.btn.btn-block.btn-github.btn-social(href='/auth/github') + i.fa.fa-github + | Sign in with GitHub + a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin') + i.fa.fa-linkedin + | Sign in with LinkedIn diff --git a/views/account/profile.jade b/views/account/profile.jade index 39c73cb2d9..3f5fdbe812 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -41,26 +41,24 @@ block content i.fa.fa-magnet | Update Profile + .page-header + h3 Change Password - if secrets.localAuth - .page-header - h3 Change Password - - form.form-horizontal(action='/account/password', method='POST') - input(type='hidden', name='_csrf', value=_csrf) - .form-group - label.col-sm-3.control-label(for='password') New Password - .col-sm-4 - input.form-control(type='password', name='password', id='password') - .form-group - label.col-sm-3.control-label(for='confirmPassword') Confirm Password - .col-sm-4 - input.form-control(type='password', name='confirmPassword', id='confirmPassword') - .form-group - .col-sm-offset-3.col-sm-4 - button.btn.btn.btn-primary(type='submit') - i.fa.fa-keyboard-o - | Change Password + form.form-horizontal(action='/account/password', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + .form-group + label.col-sm-3.control-label(for='password') New Password + .col-sm-4 + input.form-control(type='password', name='password', id='password') + .form-group + label.col-sm-3.control-label(for='confirmPassword') Confirm Password + .col-sm-4 + input.form-control(type='password', name='confirmPassword', id='confirmPassword') + .form-group + .col-sm-offset-3.col-sm-4 + button.btn.btn.btn-primary(type='submit') + i.fa.fa-keyboard-o + | Change Password .page-header h3 Delete Account @@ -75,32 +73,27 @@ block content .page-header h3 Linked Accounts - if secrets.googleAuth - if user.google - p: a.text-danger(href='/account/unlink/google') Unlink your Google account - else - p: a(href='/auth/google') Link your Google account + if user.google + p: a.text-danger(href='/account/unlink/google') Unlink your Google account + else + p: a(href='/auth/google') Link your Google account - if secrets.facebookAuth - if user.facebook - p: a.text-danger(href='/account/unlink/facebook') Unlink your Facebook account - else - p: a(href='/auth/facebook') Link your Facebook account + if user.facebook + p: a.text-danger(href='/account/unlink/facebook') Unlink your Facebook account + else + p: a(href='/auth/facebook') Link your Facebook account - if secrets.twitterAuth - if user.twitter - p: a.text-danger(href='/account/unlink/twitter') Unlink your Twitter account - else - p: a(href='/auth/twitter') Link your Twitter account + if user.twitter + p: a.text-danger(href='/account/unlink/twitter') Unlink your Twitter account + else + p: a(href='/auth/twitter') Link your Twitter account - if secrets.githubAuth - if user.github - p: a.text-danger(href='/account/unlink/github') Unlink your GitHub account - else - p: a(href='/auth/github') Link your GitHub account + if user.github + p: a.text-danger(href='/account/unlink/github') Unlink your GitHub account + else + p: a(href='/auth/github') Link your GitHub account - if secrets.linkedinAuth - if user.linkedin - p: a.text-danger(href='/account/unlink/linkedin') Unlink your LinkedIn account - else - p: a(href='/auth/linkedin') Link your LinkedIn account + if user.linkedin + p: a.text-danger(href='/account/unlink/linkedin') Unlink your LinkedIn account + else + p: a(href='/auth/linkedin') Link your LinkedIn account \ No newline at end of file