From 7c1ae2f4506fbc9b60ec197f458609cf577ac2f4 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Wed, 19 Nov 2014 15:30:36 -0800 Subject: [PATCH] Adds auth check to linked path. Removes unused routes. --- app.js | 196 ++++++++++++++++++++-------------- config/passport.js | 259 +++++++++++++++++++++++---------------------- 2 files changed, 247 insertions(+), 208 deletions(-) diff --git a/app.js b/app.js index a45e9818b9..e15629c411 100644 --- a/app.js +++ b/app.js @@ -11,7 +11,7 @@ var logger = require('morgan'); var errorHandler = require('errorhandler'); var csrf = require('lusca').csrf(); var methodOverride = require('method-override'); -var bodyParser = require('body-parser') +var bodyParser = require('body-parser'); var _ = require('lodash'); var MongoStore = require('connect-mongo')({ session: session }); @@ -27,8 +27,8 @@ var connectAssets = require('connect-assets'); */ var homeController = require('./controllers/home'); -var challengesController = require('./controllers/challenges') -var resourcesController = require('./controllers/resources') +var challengesController = require('./controllers/challenges'); +var resourcesController = require('./controllers/resources'); var userController = require('./controllers/user'); var apiController = require('./controllers/api'); var contactController = require('./controllers/contact'); @@ -89,22 +89,25 @@ app.use(session({ secret: secrets.sessionSecret, store: new MongoStore({ url: secrets.db, - auto_reconnect: true + 'auto_reconnect': true }) })); app.use(passport.initialize()); app.use(passport.session()); app.use(flash()); + app.use(function(req, res, next) { // CSRF protection. - if (_.contains(csrfExclude, req.path)) return next(); + if (_.contains(csrfExclude, req.path)) { return next(); } csrf(req, res, next); }); + app.use(function(req, res, next) { // Make user object available in templates. res.locals.user = req.user; next(); }); + app.use(function(req, res, next) { // Remember original destination before login. var path = req.path.split('/')[1]; @@ -114,15 +117,17 @@ app.use(function(req, res, next) { req.session.returnTo = req.path; next(); }); + app.use(express.static(path.join(__dirname, 'public'), { maxAge: week })); /** * Main routes. */ - app.get('/', homeController.index); -app.get('/challenges/:challengeNumber', challengesController.returnChallenge); -app.get('/resources/interview-questions', resourcesController.interviewQuestions); + +app.get( + '/resources/interview-questions', + resourcesController.interviewQuestions); app.get('/learn-to-code', resourcesController.learnToCode); app.get('/login', userController.getLogin); app.post('/login', userController.postLogin); @@ -135,15 +140,110 @@ app.get('/signup', userController.getSignup); app.post('/signup', userController.postSignup); app.get('/nonprofits', contactController.getContact); app.post('/nonprofits', contactController.postContact); -app.get('/account', passportConf.isAuthenticated, userController.getAccount); -app.post('/account/profile', passportConf.isAuthenticated, userController.postUpdateProfile); -app.post('/account/password', passportConf.isAuthenticated, userController.postUpdatePassword); -app.post('/account/delete', passportConf.isAuthenticated, userController.postDeleteAccount); -app.get('/account/unlink/:provider', passportConf.isAuthenticated, userController.getOauthUnlink); -app.post('/update-progress', userController.updateProgress); + +// # Protected routes, user must be logged in. +app.all('/account', passportConf.isAuthenticated); +app.get('/account', userController.getAccount); +app.post('/account/profile', userController.postUpdateProfile); +app.post('/account/password', userController.postUpdatePassword); +app.post('/account/delete', userController.postDeleteAccount); +app.get('/account/unlink/:provider', userController.getOauthUnlink); + +app.post( + '/update-progress', + passportConf.isAuthenticated, + userController.updateProgress); + +app.get( + '/challenges/:challengeNumber', + passportConf.isAuthenticated, + challengesController.returnChallenge); + /** * API examples routes. */ +app.post('/completed_challenge', function(req, res) { + req.user.challengesCompleted.push(parseInt(req.body.cn)); + req.user.save(); +}); + +/** + * OAuth sign-in routes. + */ +app.get('/auth/twitter', passport.authenticate('twitter')); +app.get( + '/auth/twitter/callback', + passport.authenticate('twitter', { + successRedirect: '/', + failureRedirect: '/login' + }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); + +app.get( + '/auth/linkedin', + passport.authenticate('linkedin', { + state: 'SOME STATE' + })); + +app.get( + '/auth/linkedin/callback', + passport.authenticate('linkedin', { + successRedirect: '/', + failureRedirect: '/login' + }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); + +/** + * 500 Error Handler. + */ +app.use(errorHandler()); + +/** + * Start Express server. + */ +app.listen(app.get('port'), function() { + console.log( + 'FreeCodeCamp server listening on port %d in %s mode', + app.get('port'), + app.get('env') + ); +}); + +module.exports = app; + + +/* :TODO: Add these. +app.get('/auth/instagram', passport.authenticate('instagram')); +app.get('/auth/instagram/callback', passport.authenticate('instagram', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); +app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'user_location'] })); +app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); +app.get('/auth/github', passport.authenticate('github')); +app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); +app.get('/auth/google', passport.authenticate('google', { scope: 'profile email' })); +app.get('/auth/google/callback', passport.authenticate('google', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { + res.redirect(req.session.returnTo || '/'); +}); + +app.get('/auth/foursquare', passport.authorize('foursquare')); +app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), function(req, res) { + res.redirect('/api/foursquare'); +}); +app.get('/auth/tumblr', passport.authorize('tumblr')); +app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { + res.redirect('/api/tumblr'); +}); +app.get('/auth/venmo', passport.authorize('venmo', { scope: 'make_payments access_profile access_balance access_email access_phone' })); +app.get('/auth/venmo/callback', passport.authorize('venmo', { failureRedirect: '/api' }), function(req, res) { + res.redirect('/api/venmo'); +}); app.get('/api', apiController.getApi); app.get('/api/lastfm', apiController.getLastfm); @@ -168,70 +268,4 @@ app.post('/api/venmo', passportConf.isAuthenticated, passportConf.isAuthorized, app.get('/api/linkedin', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getLinkedin); app.get('/api/instagram', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getInstagram); app.get('/api/yahoo', apiController.getYahoo); -app.post('/completed_challenge', function(req, res){ - req.user.challengesCompleted.push(parseInt(req.body.cn)); - req.user.save(); -}); - -/** - * OAuth sign-in routes. - */ - -app.get('/auth/instagram', passport.authenticate('instagram')); -app.get('/auth/instagram/callback', passport.authenticate('instagram', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); -app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'user_location'] })); -app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); -app.get('/auth/github', passport.authenticate('github')); -app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); -app.get('/auth/google', passport.authenticate('google', { scope: 'profile email' })); -app.get('/auth/google/callback', passport.authenticate('google', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); -app.get('/auth/twitter', passport.authenticate('twitter')); -app.get('/auth/twitter/callback', passport.authenticate('twitter', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); -app.get('/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' })); -app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { successRedirect: '/',failureRedirect: '/login' }), function(req, res) { - res.redirect(req.session.returnTo || '/'); -}); - -/** - * OAuth authorization routes for API examples. - */ - -app.get('/auth/foursquare', passport.authorize('foursquare')); -app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), function(req, res) { - res.redirect('/api/foursquare'); -}); -app.get('/auth/tumblr', passport.authorize('tumblr')); -app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { - res.redirect('/api/tumblr'); -}); -app.get('/auth/venmo', passport.authorize('venmo', { scope: 'make_payments access_profile access_balance access_email access_phone' })); -app.get('/auth/venmo/callback', passport.authorize('venmo', { failureRedirect: '/api' }), function(req, res) { - res.redirect('/api/venmo'); -}); - -/** - * 500 Error Handler. - */ - -app.use(errorHandler()); - -/** - * Start Express server. - */ - -app.listen(app.get('port'), function() { - console.log('Express server listening on port %d in %s mode', app.get('port'), app.get('env')); -}); - -module.exports = app; - +*/ diff --git a/config/passport.js b/config/passport.js index 3f6a98c9cd..8ec41ce4e6 100644 --- a/config/passport.js +++ b/config/passport.js @@ -7,8 +7,8 @@ var TwitterStrategy = require('passport-twitter').Strategy; var GitHubStrategy = require('passport-github').Strategy; var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy; -var OAuthStrategy = require('passport-oauth').OAuthStrategy; // Tumblr -var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; // Venmo, Foursquare +var OAuthStrategy = require('passport-oauth').OAuthStrategy; +var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; var User = require('../models/User'); var secrets = require('./secrets'); @@ -22,8 +22,136 @@ passport.deserializeUser(function(id, done) { }); }); -// Sign in with Instagram. +/** + * OAuth Strategy Overview + * + * - User is already logged in. + * - Check if there is an existing account with a id. + * - If there is, return an error message. (Account merging not supported) + * - Else link new OAuth account with currently logged-in user. + * - User is not logged in. + * - Check if it's a returning user. + * - If returning user, sign in and we are done. + * - Else check if there is an existing account with user's email. + * - If there is, return an error message. + * - Else create a new account. + */ +// 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_https; + user.profile.username = profile.username; + 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_https; + user.save(function(err) { + done(err, user); + }); + }); + } +})); + +// Sign in with LinkedIn. +passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ linkedin: profile.id }, 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); + }); + } + }); + }); + } +})); + + +// Login Required middleware. + +module.exports = { + isAuthenticated: isAuthenticated, + isAuthorized: isAuthorized +}; + +function isAuthenticated(req, res, next) { + if (req.isAuthenticated()) return next(); + res.redirect('/login'); +} + +// Authorization Required middleware. +function isAuthorized(req, res, next) { + var provider = req.path.split('/').slice(-1)[0]; + + if (_.find(req.user.tokens, { kind: provider })) { + next(); + } else { + res.redirect('/auth/' + provider); + } +} + +/* passport.use(new InstagramStrategy(secrets.instagram,function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ instagram: profile.id }, function(err, existingUser) { @@ -66,7 +194,6 @@ passport.use(new InstagramStrategy(secrets.instagram,function(req, accessToken, })); // 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'}); @@ -80,20 +207,6 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, passw }); })); -/** - * OAuth Strategy Overview - * - * - User is already logged in. - * - Check if there is an existing account with a id. - * - If there is, return an error message. (Account merging not supported) - * - Else link new OAuth account with currently logged-in user. - * - User is not logged in. - * - Check if it's a returning user. - * - If returning user, sign in and we are done. - * - Else check if there is an existing account with user's email. - * - If there is, return an error message. - * - Else create a new account. - */ // Sign in with Facebook. @@ -190,49 +303,6 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre } })); -// 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_https; - user.profile.username = profile.username; - 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_https; - user.save(function(err) { - done(err, user); - }); - }); - } -})); // Sign in with Google. @@ -280,53 +350,6 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre } })); -// Sign in with LinkedIn. - -passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - User.findOne({ linkedin: profile.id }, 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); - }); - } - }); - }); - } -})); // Tumblr API setup. @@ -388,22 +411,4 @@ passport.use('venmo', new OAuth2Strategy({ }); } )); - -// Login Required middleware. - -exports.isAuthenticated = function(req, res, next) { - if (req.isAuthenticated()) return next(); - res.redirect('/login'); -}; - -// Authorization Required middleware. - -exports.isAuthorized = function(req, res, next) { - var provider = req.path.split('/').slice(-1)[0]; - - if (_.find(req.user.tokens, { kind: provider })) { - next(); - } else { - res.redirect('/auth/' + provider); - } -}; \ No newline at end of file +*/