diff --git a/app.js b/app.js index 15c384da18..c063783b7a 100644 --- a/app.js +++ b/app.js @@ -76,7 +76,13 @@ app.use(connectAssets({ app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); -app.use(expressValidator()); +app.use(expressValidator({ + customValidators: { + matchRegex: function(param, regex) { + return regex.test(param); + } + } +})); app.use(methodOverride()); app.use(cookieParser()); app.use(session({ @@ -264,13 +270,12 @@ app.get('/auth/twitter', passport.authenticate('twitter')); app.get( '/auth/twitter/callback', passport.authenticate('twitter', { - successRedirect: '/', - failureRedirect: '/auth/twitter/middle' + successRedirect: '/auth/twitter/middle', + failureRedirect: '/login' }) ); -app.get('/auth/twitter/middle', function(req, res, next) { -}); +app.get('/auth/twitter/middle', passportConf.hasEmail); app.get( '/auth/linkedin', diff --git a/config/passport.js b/config/passport.js index 6157ffa1c6..669876f1fc 100644 --- a/config/passport.js +++ b/config/passport.js @@ -14,7 +14,8 @@ var _ = require('lodash'), // Login Required middleware. module.exports = { isAuthenticated: isAuthenticated, - isAuthorized: isAuthorized + isAuthorized: isAuthorized, + hasEmail: hasEmail }; passport.serializeUser(function(user, done) { @@ -107,12 +108,6 @@ passport.use( } else { User.findOne({ twitter: profile.id }, function(err, existingUser) { if (err) { return done(err); } - - //if (existingUser) return done(null, existingUser); - // 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"; var user = existingUser || new User(); user.twitter = profile.id; user.email = user.email || ''; @@ -132,15 +127,6 @@ passport.use( if (err) { return done(err); } done(null, user); }); - //TODO: Twitter redirect to capture user email. - //if (!user.email) { - // req.redirect('/account'); - // req.flash('errors', { - // msg: - // 'OK, you are signed in. ' + - // 'Please add your email address to your profile.' - // }); - //} }); } }) @@ -513,6 +499,21 @@ function isAuthenticated(req, res, next) { res.redirect('/login'); } +function hasEmail(req, res) { + if (req.user) { + console.log('started'); + if (req.user.email) { + res.redirect('/'); + } else { + console.log('hit'); + req.flash('info', { + msg: 'Please add your email address before starting our challenges.' + }); + res.redirect('/account'); + } + } +} + // Authorization Required middleware. function isAuthorized(req, res, next) { var provider = req.path.split('/').slice(-1)[0]; diff --git a/controllers/user.js b/controllers/user.js index 69b769294b..590f6e43f1 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -170,10 +170,6 @@ exports.updateProgress = function(req, res) { }); }; - - - - /** * POST /account/profile * Update profile information. @@ -182,16 +178,56 @@ exports.updateProgress = function(req, res) { exports.postUpdateProfile = function(req, res, next) { User.findById(req.user.id, function(err, user) { if (err) return next(err); - user.email = req.body.email || ''; - user.profile.name = req.body.name || ''; - user.profile.username = req.body.username || ''; - user.profile.location = req.body.location || ''; - user.profile.website = req.body.website || ''; - user.save(function(err) { - if (err) return next(err); - req.flash('success', { msg: 'Profile information updated.' }); - res.redirect('/account'); + req.assert('email', 'Email is required').notEmpty(); + req.assert('email', 'Please enter a valid email address.').isEmail(); + req.assert('username', 'Your username must be between 3 and 20 characters').len(3, 20); + req.assert('username', 'Your username can only use letters, numbers or underscores').matchRegex(/[A-Za-z0-9_]+/); + var errors = req.validationErrors(); + if (errors) { + req.flash('errors', errors); + return res.redirect('/account'); + } + + User.findOne({ email: req.body.email }, function(err, existingEmail) { + if (err) { + return next(err); + } + var user = req.user; + if (existingEmail && existingEmail.email != user.email) { + console.log(user.email); + console.log(existingEmail.email) + req.flash('errors', { + msg: "An account with that email address already exists." + }); + return res.redirect('/account'); + } + User.findOne({ username: req.body.username }, function(err, existingUsername) { + if (err) { + return next(err); + } + var user = req.user; + if (existingUsername && existingUsername.profile.username != user.profile.username) { + console.log(existingUsername.profile.username) + console.log(user.profile.username) + req.flash('errors', { + msg: 'An account with that username already exists.' + }); + return res.redirect('/account'); + } + var user = req.user; + user.email = req.body.email || ''; + user.profile.name = req.body.name || ''; + user.profile.username = req.body.username || ''; + user.profile.location = req.body.location || ''; + user.profile.website = req.body.website || ''; + + user.save(function (err) { + if (err) return next(err); + req.flash('success', {msg: 'Profile information updated.'}); + res.redirect('/account'); + }); + }); }); }); }; diff --git a/models/User.js b/models/User.js index 5d789e0e9c..222d9d1b48 100644 --- a/models/User.js +++ b/models/User.js @@ -3,15 +3,13 @@ var crypto = require('crypto'); var mongoose = require('mongoose'); var userSchema = new mongoose.Schema({ - /*email: { + email: { type: String, unique: true, lowercase: true, - match: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/ - },*/ - email: String, + trim: true + }, password: String, - facebook: String, twitter: String, google: String, @@ -283,13 +281,14 @@ var userSchema = new mongoose.Schema({ picture: { type: String, default: '' - } - /*username: { + }, + username: { type: String, default: '', unique: true, - match: /^[a-zA-Z0-9_]+$/ - }*/ + lowercase: true, + trim: true + } }, resetPasswordToken: String, diff --git a/package.json b/package.json index 107a6286b3..67ebda643c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "express": "^4.10.4", "express-flash": "^0.0.2", "express-session": "^1.9.2", - "express-validator": "^2.7.0", + "express-validator": "^2.8.0", "fbgraph": "^0.3.0", "github-api": "^0.7.0", "helmet": "^0.5.3", diff --git a/public/css/main.less b/public/css/main.less index 338278b65b..c8c06264e1 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -259,6 +259,11 @@ ul { margin: auto; } +.btn-link-social { + max-width: 400px; + margin-bottom: 10px; +} + .navbar { background-color: #4a2b0f; } @@ -356,4 +361,9 @@ thead { .nowrap { white-space: nowrap; +} + +.big-break { + margin-top: 30px; + margin-bottom: 30px; } \ No newline at end of file diff --git a/views/account/login.jade b/views/account/login.jade index 04983d112b..025c807488 100644 --- a/views/account/login.jade +++ b/views/account/login.jade @@ -8,9 +8,9 @@ block content a.btn.btn-lg.btn-block.btn-facebook.btn-social(href='/auth/facebook') i.fa.fa-facebook | Sign in with Facebook - //a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github') - // i.fa.fa-github - // | Sign in with GitHub + a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github') + i.fa.fa-github + | Sign in with GitHub a.btn.btn-lg.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 422cb6f5b5..0bbdefad7c 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -11,8 +11,8 @@ block content label.col-sm-2.control-label(for='name') Name .col-sm-4 input.form-control(type='text', name='name', id='name', value='#{user.profile.name}') - //.form-group - label.col-sm-2.control-label(for='username') Username + .form-group + label.col-sm-2.control-label(for='username') Username (no spaces) .col-sm-4 input.form-control(type='text', name='username', id='username', value='#{user.profile.username}') .form-group @@ -32,47 +32,81 @@ block content button.btn.btn.btn-primary(type='submit') span.ion-edit | Update my profile - - h1 Completed Challenges - .col-xs-12 - table.table.table-striped - thead - tr - th Challenge - th Date Finished - for challenge in cc - if ch[challenge.challengeNumber] > 0 - tr - td= cc[challenge.challengeNumber].name - td= moment(ch[challenge.challengeNumber], 'X').format("MMM DD, YYYY") - + - if (!user.google || !user.facebook || !user.github || !user.linkedin || !user.twitter) + .panel + .container + h1 Link other services to your account: + - if (!user.google) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-google-plus.btn-link-social(href='/auth/google') + i.fa.fa-google-plus + | Link Google with your account + - if (!user.facebook) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-facebook.btn-link-social(href='/auth/facebook') + i.fa.fa-facebook + | Link Facebook with your account + - if (!user.github) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/auth/github') + i.fa.fa-github + | Link GitHub with your account + - if (!user.linkedin) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-linkedin.btn-link-social(href='/auth/linkedin') + i.fa.fa-linkedin + | Link LinkedIn with your account + - if (!user.twitter) + .col-xs-12 + a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/auth/twitter') + i.fa.fa-twitter + | Link Twitter with your account + br + - if (ch[0] > 0) + .panel + .container + h1 Completed Challenges + .col-xs-12 + table.table.table-striped + thead + tr + th Challenge + th Date Finished + for challenge in cc + if ch[challenge.challengeNumber] > 0 + tr + td= cc[challenge.challengeNumber].name + td= moment(ch[challenge.challengeNumber], 'X').format("MMM DD, YYYY") + br + .panel + .container h3 Danger Zone button.btn.btn-danger.confirm-deletion span.ion-trash-b | I want to delete my account + br script. $('.confirm-deletion').on("click", function() { $('#modal-dialog').modal('show'); }); - #modal-dialog.modal.animated.wobble - .modal-dialog - .modal-content - .modal-header - a.close(href='#', data-dismiss='modal', aria-hidden='true') × - h3 Are you really leaving us? - .modal-body - p Pro Tip: If you tweet feedback to  - a(href="https://twitter.com/intent/tweet?text=Hey%20@freecodecamp") @FreeCodeCamp - | , we'll act quickly on it! - .modal-footer - a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true') - span.ion-happy - | Nevermind, I'll stick around - br - form(action='/account/delete', method='POST') - input(type='hidden', name='_csrf', value=_csrf) - button.btn.btn-danger.btn-block(type='submit') - span.ion-trash-b - | Yes, Delete my account - br - br \ No newline at end of file + br +#modal-dialog.modal.animated.wobble + .modal-dialog + .modal-content + .modal-header + a.close(href='#', data-dismiss='modal', aria-hidden='true') × + h3 Are you really leaving us? + .modal-body + p Pro Tip: If you tweet feedback to  + a(href="https://twitter.com/intent/tweet?text=Hey%20@freecodecamp") @FreeCodeCamp + | , we'll act quickly on it! + .modal-footer + a.btn.btn-success.btn-block(href='#', data-dismiss='modal', aria-hidden='true') + span.ion-happy + | Nevermind, I'll stick around + br + form(action='/account/delete', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + button.btn.btn-danger.btn-block(type='submit') + span.ion-trash-b + | Yes, Delete my account