From d46ac080ddc7319a356722ff6164ab6e228c93aa Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 13:02:00 -0800 Subject: [PATCH 01/15] make twitter photo higher resolution on login. remove twitter redirect middleware --- app.js | 4 +--- config/passport.js | 21 +++------------------ 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/app.js b/app.js index 12269cf30d..699067b4fb 100644 --- a/app.js +++ b/app.js @@ -293,13 +293,11 @@ app.get('/auth/twitter', passport.authenticate('twitter')); app.get( '/auth/twitter/callback', passport.authenticate('twitter', { - successRedirect: '/auth/twitter/middle', + successRedirect: '/', failureRedirect: '/login' }) ); -app.get('/auth/twitter/middle', passportConf.hasEmail); - app.get( '/auth/linkedin', passport.authenticate('linkedin', { diff --git a/config/passport.js b/config/passport.js index 34aec6fd55..11b1c8115e 100644 --- a/config/passport.js +++ b/config/passport.js @@ -15,7 +15,6 @@ var _ = require('lodash'), module.exports = { isAuthenticated: isAuthenticated, isAuthorized: isAuthorized, - hasEmail: hasEmail }; passport.serializeUser(function(user, done) { @@ -59,7 +58,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok 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.picture = user.profile.picture || profile._json.profile_image_url_https.replace('_normal', ''); user.save(function(err) { req.flash('info', { msg: 'Twitter account has been linked.' }); done(err, user); @@ -75,13 +74,13 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok // 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 + "@please_add_your_email_here.com"; + user.email = ''; user.profile.username = profile.username; 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.profile.picture = profile._json.profile_image_url_https.replace('_normal', ''); user.save(function(err) { done(err, user); }); @@ -477,20 +476,6 @@ function isAuthenticated(req, res, next) { res.redirect('/login'); } -function hasEmail(req, res) { - if (req.user) { - if (req.user.email) { - res.redirect('/'); - } else { - 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]; From 503d6ada8a8ad3560d96bedd9d1fa516dd5a394e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 13:54:38 -0800 Subject: [PATCH 02/15] remove redundant github auth --- config/passport.js | 494 ++++++++++++++++++--------------------------- 1 file changed, 202 insertions(+), 292 deletions(-) diff --git a/config/passport.js b/config/passport.js index 11b1c8115e..5870421045 100644 --- a/config/passport.js +++ b/config/passport.js @@ -9,12 +9,13 @@ var _ = require('lodash'), OAuthStrategy = require('passport-oauth').OAuthStrategy, OAuth2Strategy = require('passport-oauth').OAuth2Strategy, User = require('../models/User'), + nodemailer = require('nodemailer'), secrets = require('./secrets'); // Login Required middleware. module.exports = { isAuthenticated: isAuthenticated, - isAuthorized: isAuthorized, + isAuthorized: isAuthorized }; passport.serializeUser(function(user, done) { @@ -29,6 +30,21 @@ passport.deserializeUser(function(id, done) { }); }); +// 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.' }); + } + }); + }); +})); + /** * OAuth Strategy Overview * @@ -44,6 +60,147 @@ passport.deserializeUser(function(id, done) { * - Else create a new account. */ +// Sign in with Facebook. + +passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ facebook: profile.id }, 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 transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); + } + }); + }); + } +})); + +// Sign in with GitHub. + +passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { + if (req.user) { + User.findOne({ github: profile.id }, 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 transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); + } + }); + }); + } +})); + // Sign in with Twitter. passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) { @@ -71,10 +228,6 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok 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 = ''; user.profile.username = profile.username; user.twitter = profile.id; user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); @@ -128,6 +281,29 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre user.save(function(err) { done(err, user); }); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } + }); + var mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } + }); } }); }); @@ -176,53 +352,28 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r user.save(function(err) { done(err, user); }); - } - }); - }); - } -})); - -// Sign in with GitHub. - -passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - User.findOne({ github: profile.id }, 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); + var transporter = nodemailer.createTransport({ + service: 'Mandrill', + auth: { + user: secrets.mandrill.user, + pass: secrets.mandrill.password + } }); - }); - } - }); - } 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 mailOptions = { + to: user.email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + text: [ + 'Greetings from San Francisco!\n\n', + 'Thank you for joining our community.\n', + 'Feel free to email us at this address if you have any questions about Free Code Camp.\n', + "And if you have a moment, check out our blog: blog.freecodecamp.com.\n", + 'Good luck with the challenges!\n\n', + '- the Volunteer Camp Counselor Team' + ].join('') + }; + transporter.sendMail(mailOptions, function(err) { + if (err) { return err; } }); } }); @@ -230,247 +381,6 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre } })); -// Sign in with Facebook. - -passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - User.findOne({ facebook: profile.id }, 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); - }); - } - }); - }); - } -})); - -// Sign in using Email and Password. -passport.use( - new LocalStrategy( - { usernameField: 'email' }, function(email, password, done) { - User.findOne({ email: email }, function(err, user) { - if (err) { return done(err); } - - if (!user) { - return done(null, false, { message: 'Email ' + email + ' not found'}); - } - user.comparePassword(password, function(err, isMatch) { - if (err) { return done(err); } - - if (isMatch) { - return done(null, user); - } else { - return done(null, false, { message: 'Invalid email or password.' }); - } - }); - }); -})); - - -// Sign in with Facebook. -passport.use( - new FacebookStrategy( - secrets.facebook, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - User.findOne({ facebook: profile.id }, function(err, existingUser) { - if (err) { return done(err); } - - 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.' - ].join(' ') - }); - done(); - } else { - User.findById(req.user.id, function(err, user) { - if (err) { return done(err); } - - 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) { - if (err) { return done(err); } - - req.flash( - 'info', { msg: 'Facebook account has been linked.' }); - done(null, user); - }); - }); - } - }); - } else { - User.findOne({ facebook: profile.id }, function(err, existingUser) { - if (err) { return done(err); } - - if (existingUser) { return done(null, existingUser); } - - User.findOne( - { email: profile._json.email }, function(err, existingEmailUser) { - if (err) { return done(err); } - - var user = existingEmailUser || new User(); - user.email = user.email || profile._json.email; - 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.profile.location = - user.profile.location || - (profile._json.location) ? profile._json.location.name : ''; - - user.challengesComplete = user.challengesCompleted || []; - user.save(function(err) { - if (err) { return done(err); } - done(null, user); - }); - }); - }); - } -})); - -// Sign in with GitHub. - -passport.use( - new GitHubStrategy( - secrets.github, function(req, accessToken, refreshToken, profile, done) { - if (req.user) { - User.findOne({ github: profile.id }, function(err, existingUser) { - if (err) { return done(err); } - - 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.' - ].join(' ') - }); - done(); - } else { - User.findById(req.user.id, function(err, user) { - if (err) { return done(err); } - - 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) { - if (err) { return done(err); } - - req.flash('info', { msg: 'GitHub account has been linked.' }); - done(null, user); - }); - }); - } - }); - } else { - User.findOne({ github: profile.id }, function(err, existingUser) { - if (err) { return done(err); } - - if (existingUser) { return done(null, existingUser); } - User.findOne( - { email: profile._json.email }, function(err, existingEmailUser) { - if (err) { return done(err); } - - var user = existingEmailUser || new User(); - user.email = user.email || profile._json.email; - 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) { - if (err) { return done(err); } - done(null, user); - }); - }); - }); - } -})); - - - - function isAuthenticated(req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/login'); From d533e07edd08417bc243d74c6aba39868f7c9875 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 14:02:21 -0800 Subject: [PATCH 03/15] debug auth in production --- config/passport.js | 21 +++++++++------------ views/account/forgot.jade | 25 +++++++++++++------------ views/account/profile.jade | 10 +++++----- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/config/passport.js b/config/passport.js index 5870421045..d8a04146b7 100644 --- a/config/passport.js +++ b/config/passport.js @@ -12,20 +12,13 @@ var _ = require('lodash'), nodemailer = require('nodemailer'), secrets = require('./secrets'); -// Login Required middleware. -module.exports = { - isAuthenticated: isAuthenticated, - isAuthorized: isAuthorized -}; passport.serializeUser(function(user, done) { done(null, user.id); }); passport.deserializeUser(function(id, done) { - User.findOne({ - _id: id - }, '-password', function(err, user) { + User.findById(id, function(err, user) { done(err, user); }); }); @@ -381,12 +374,16 @@ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, r } })); -function isAuthenticated(req, res, next) { +// Login Required middleware. + +exports.isAuthenticated = function(req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/login'); -} +}; -function isAuthorized(req, res, next) { +// Authorization Required middleware. + +exports.isAuthorized = function(req, res, next) { var provider = req.path.split('/').slice(-1)[0]; if (_.find(req.user.tokens, { kind: provider })) { @@ -394,4 +391,4 @@ function isAuthorized(req, res, next) { } else { res.redirect('/auth/' + provider); } -} +}; \ No newline at end of file diff --git a/views/account/forgot.jade b/views/account/forgot.jade index a843a1e95b..cc7f131ccc 100644 --- a/views/account/forgot.jade +++ b/views/account/forgot.jade @@ -1,15 +1,16 @@ extends ../layout block content - .col-sm-8.col-sm-offset-2 - form(method='POST') - legend Forgot Password - input(type='hidden', name='_csrf', value=_csrf) - .form-group - p Enter your email address below and we will send you password reset instructions. - label.control-label(for='email') Email - input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true) - .form-group - button.btn.btn-primary(type='submit') - i.fa.fa-key - | Reset Password + .jumbotron + .col-sm-8.col-sm-offset-2 + form(method='POST') + h1 Forgot Password + input(type='hidden', name='_csrf', value=_csrf) + .form-group + p Enter your email address below and we will send you password reset instructions. + label.control-label(for='email') Email + input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true) + .form-group + button.btn.btn-primary(type='submit') + i.fa.fa-key + | Reset Password diff --git a/views/account/profile.jade b/views/account/profile.jade index b51b83b9f2..0c8ab97bb6 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -243,11 +243,11 @@ block content 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.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') From 372610e99221b0b7edc88279bd4a4fb9ab874c75 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 14:07:38 -0800 Subject: [PATCH 04/15] github auth still not working properly so pulling it again --- views/account/login.jade | 6 +++--- views/account/profile.jade | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/views/account/login.jade b/views/account/login.jade index 7118d2e931..b32eab5057 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 0c8ab97bb6..b51b83b9f2 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -243,11 +243,11 @@ block content 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.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') From 61204f61be9f4f0a7a29f3e973d3de49d3e1ad3a Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 14:31:49 -0800 Subject: [PATCH 05/15] auto populate twitter attribute if you log in with twitter --- config/passport.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/passport.js b/config/passport.js index d8a04146b7..635362d1b0 100644 --- a/config/passport.js +++ b/config/passport.js @@ -209,6 +209,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok 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.replace('_normal', ''); + user.profile.twitterHandle = user.profile.twitterHandle || profile.username; user.save(function(err) { req.flash('info', { msg: 'Twitter account has been linked.' }); done(err, user); @@ -227,6 +228,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok user.profile.name = profile.displayName; user.profile.location = profile._json.location; user.profile.picture = profile._json.profile_image_url_https.replace('_normal', ''); + user.profile.twitterHandle = user.profile.twitterHandle || profile.username; user.save(function(err) { done(err, user); }); From 5decaf13f0755ace9de7ac058c231f7dc7ba45e3 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 14:46:42 -0800 Subject: [PATCH 06/15] add .htaccess file --- public/.htaccess | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 public/.htaccess diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000000..0a72a55ebf --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,5 @@ + + + Header set Access-Control-Allow-Origin "*" + + \ No newline at end of file From 4e91e944631981ecced9aced0db584ad0c7464ee Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 15:10:34 -0800 Subject: [PATCH 07/15] fix firefox CDN issue and ensure twitter handle, email and username are lowercase --- app.js | 1 + config/passport.js | 7 ++++--- controllers/user.js | 2 +- public/js/main.js | 3 +++ views/layout.jade | 16 ++++++++-------- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index 699067b4fb..2e4aaeb981 100644 --- a/app.js +++ b/app.js @@ -102,6 +102,7 @@ app.use(flash()); app.disable('x-powered-by'); app.use(helmet.xssFilter()); +app.use(helmet.noSniff()); app.use(helmet.xframe()); var trusted = [ diff --git a/config/passport.js b/config/passport.js index 635362d1b0..197bbfb0f6 100644 --- a/config/passport.js +++ b/config/passport.js @@ -206,10 +206,11 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok User.findById(req.user.id, function(err, user) { user.twitter = profile.id; user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); + user.profile.username = user.profile.username || profile.username.toLowerCase(); 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.replace('_normal', ''); - user.profile.twitterHandle = user.profile.twitterHandle || profile.username; + user.profile.twitterHandle = user.profile.twitterHandle || profile.username.toLowerCase(); user.save(function(err) { req.flash('info', { msg: 'Twitter account has been linked.' }); done(err, user); @@ -222,13 +223,13 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok User.findOne({ twitter: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); var user = new User(); - user.profile.username = profile.username; + user.profile.username = profile.username.toLowerCase(); 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.replace('_normal', ''); - user.profile.twitterHandle = user.profile.twitterHandle || profile.username; + user.profile.twitterHandle = user.profile.twitterHandle || profile.username.toLowerCase(); user.save(function(err) { done(err, user); }); diff --git a/controllers/user.js b/controllers/user.js index ba4468e55c..9d6a8ad6b4 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -173,7 +173,7 @@ exports.getAccount = function(req, res) { */ exports.returnUser = function(req, res, next) { - User.find({'profile.username': req.params.username}, function(err, user) { + User.find({'profile.username': req.params.username.toLowerCase()}, function(err, user) { if (err) { debug('Username err: ', err); next(err); } if (user[0]) { var user = user[0]; diff --git a/public/js/main.js b/public/js/main.js index 95072493b9..56e9befd41 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -47,6 +47,9 @@ profileValidation.controller('profileValidationController', ['$scope', '$http', function($scope, $http) { $http.get('/account/api').success(function(data) { $scope.user = data.user; + $scope.user.profile.username = $scope.user.profile.username.toLowerCase(); + $scope.user.email = $scope.user.email.toLowerCase(); + $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle.toLowerCase(); }); } ]); diff --git a/views/layout.jade b/views/layout.jade index 3aaabbc3dd..8ff03c4c46 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,14 +1,14 @@ doctype html html(ng-app='profileValidation') head - script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") - script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js") - script(src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") - script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js') - link(rel='shortcut icon', href='https://s3.amazonaws.com/freecodecamp/favicon.ico') - link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') - link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') - link(rel='stylesheet', href='https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css') + script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") + script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js") + script(src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js") + script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js') + link(rel='shortcut icon', href='//s3.amazonaws.com/freecodecamp/favicon.ico') + link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') + link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css') + link(rel='stylesheet', href='//code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css') include partials/meta title #{title} | Free Code Camp meta(charset='utf-8') From 99253a7745fa4fa054218077a040b0d0cd7cb7aa Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 15:47:52 -0800 Subject: [PATCH 08/15] clean up failed firefox solutions and implement one that works --- app.js | 3 ++- public/.htaccess | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 public/.htaccess diff --git a/app.js b/app.js index 2e4aaeb981..baa56c1cf5 100644 --- a/app.js +++ b/app.js @@ -127,7 +127,8 @@ var trusted = [ 'localhost:3000', 'ws://localhost:3000/', 'http://localhost:3000', - '*.ionicframework.com' + '*.ionicframework.com', + 'https://syndication.twitter.com' ]; debug(trusted); diff --git a/public/.htaccess b/public/.htaccess deleted file mode 100644 index 0a72a55ebf..0000000000 --- a/public/.htaccess +++ /dev/null @@ -1,5 +0,0 @@ - - - Header set Access-Control-Allow-Origin "*" - - \ No newline at end of file From 3e7da5ae9899027e6f615642097a3d5b17361e20 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 17:52:19 -0800 Subject: [PATCH 09/15] start angularizing sign up form --- app.js | 3 ++ controllers/user.js | 33 +++++++++++++++++-- public/js/main.js | 32 ++++++++++++++++++ views/account/email-signin.jade | 52 ++++++++++++++--------------- views/account/email-signup.jade | 58 +++++++++++++++++++-------------- views/account/profile.jade | 2 +- 6 files changed, 126 insertions(+), 54 deletions(-) diff --git a/app.js b/app.js index baa56c1cf5..30bd4d17c9 100644 --- a/app.js +++ b/app.js @@ -258,6 +258,9 @@ app.get( ); app.all('/account', passportConf.isAuthenticated); app.get('/account/api', userController.getAccountAngular); +// Unique Check API route +app.get('/api/checkUniqueUsername/:username', userController.checkUniqueUsername); +app.get('/api/checkUniqueEmail/:email', userController.checkUniqueEmail); app.get('/account', userController.getAccount); app.post('/account/profile', userController.postUpdateProfile); app.post('/account/password', userController.postUpdatePassword); diff --git a/controllers/user.js b/controllers/user.js index 9d6a8ad6b4..ededc20fcf 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -70,7 +70,7 @@ exports.logout = function(req, res) { exports.getEmailSignin = function(req, res) { if (req.user) return res.redirect('/'); - res.render('account/email-signup', { + res.render('account/email-signin', { title: 'Sign in to your Free Code Camp Account' }); }; @@ -82,7 +82,7 @@ exports.getEmailSignin = function(req, res) { exports.getEmailSignup = function(req, res) { if (req.user) return res.redirect('/'); - res.render('account/email-signin', { + res.render('account/email-signup', { title: 'Create Your Free Code Camp Account' }); }; @@ -93,6 +93,7 @@ exports.getEmailSignup = function(req, res) { */ exports.postEmailSignup = function(req, res, next) { + console.log('post email signup called'); req.assert('email', 'Email is not valid').isEmail(); req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('confirmPassword', 'Passwords do not match') @@ -103,6 +104,7 @@ exports.postEmailSignup = function(req, res, next) { if (errors) { req.flash('errors', errors); return res.redirect('/email-signup'); + console.log(errors); } var user = new User({ @@ -166,6 +168,33 @@ exports.getAccount = function(req, res) { }); }; +/** + * Unique username check API Call + */ + +exports.checkUniqueUsername = function(req, res) { + User.count({'profile.username': req.params.username.toLowerCase()}, function (err, data) { + if (data == 1) { + return res.send(true); + } else { + return res.send(false); + } + }); +}; +/** + * Unique email check API Call + */ + +exports.checkUniqueEmail = function(req, res) { + User.count({'email': req.params.email.toLowerCase()}, function (err, data) { + if (data == 1) { + return res.send(true); + } else { + return res.send(false); + } + }); +}; + /** * GET /campers/:username diff --git a/public/js/main.js b/public/js/main.js index 56e9befd41..6e3385ddc4 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -54,3 +54,35 @@ profileValidation.controller('profileValidationController', ['$scope', '$http', } ]); +profileValidation.controller('emailSignUpController', ['$scope', + function($scope) { + + } +]); + +profileValidation.directive('uniqueUsername', function($http) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { + if (data) { + ngModel.$setValidity('unique', false); + } + }); + } + }); + } + }; +}); + + + +profileValidation.controller('emailSignInController', ['$scope', + function($scope) { + + } +]); \ No newline at end of file diff --git a/views/account/email-signin.jade b/views/account/email-signin.jade index ebc38961cc..860a9ce002 100644 --- a/views/account/email-signin.jade +++ b/views/account/email-signin.jade @@ -1,29 +1,27 @@ extends ../layout block content - .jumbotron.text-center - h2 Sign up with an email address here: - form.form-horizontal(method='POST') - input(type='hidden', name='_csrf', value=_csrf) - .form-group - .col-sm-6.col-sm-offset-3 - input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus) - .form-group - .col-sm-6.col-sm-offset-3 - input.form-control(type='password', name='password', id='password', placeholder='Password') - .form-group - .col-sm-6.col-sm-offset-3 - input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password') - .form-group - .col-sm-offset-3.col-sm-6 - button.btn.btn-success(type='submit') - span.ion-person-add - | Signup - br - br - br - br - br - br - br - br - br + .jumbotron.text-center(ng-controller="emailSignInController") + h2 Sign in with an email address here: + form(method='POST', action='/email-signin') + input(type='hidden', name='_csrf', value=_csrf) + .col-sm-6.col-sm-offset-3 + .form-group + input.form-control(type='email', name='email', id='email', placeholder='Email', ng-model='email', autofocus=true) + | {{ $scope.email }} + .form-group + input.form-control(type='password', name='password', id='password', placeholder='Password', ng-model='password') + .form-group + button.btn.btn-primary(type='submit') + span.ion-android-hand + | Login + span    + a.btn.btn-info(href='/forgot') Forgot your password? + br + br + br + br + br + br + br + br + br \ No newline at end of file diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 0764a2ee18..95923e53f4 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -1,26 +1,36 @@ extends ../layout block content - .jumbotron.text-center - h2 Sign in with an email address here: - form(method='POST') - input(type='hidden', name='_csrf', value=_csrf) - .col-sm-6.col-sm-offset-3 - .form-group - input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true) - .form-group - input.form-control(type='password', name='password', id='password', placeholder='Password') - .form-group - button.btn.btn-primary(type='submit') - span.ion-android-hand - | Login - span    - a.btn.btn-info(href='/forgot') Forgot your password? - br - br - br - br - br - br - br - br - br \ No newline at end of file + .jumbotron.text-center + h2 Sign up with an email address here: + form.form-horizontal(method='POST', action='/email-signup', name="signupForm", novalidate="novalidate") + input(type='hidden', name='_csrf', value=_csrf) + .form-group + .col-sm-6.col-sm-offset-3 + input.form-control(type='email', ng-model='email', name='email', id='email', placeholder='email', autofocus, required) + .form-group + .col-sm-6.col-sm-offset-3 + input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required) + .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.unique && !signupForm.username.$pristine") + alert(type='danger') + span.ion-close-circled + | This username is taken. + .form-group + .col-sm-6.col-sm-offset-3 + input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required) + .form-group + .col-sm-6.col-sm-offset-3 + input.form-control(type='password', ng-model='confirmPassword', name='confirmPassword', id='confirmPassword', placeholder='confirm password', required) + .form-group + .col-sm-offset-3.col-sm-6 + button.btn.btn-success(type='submit') + span.ion-person-add + | Signup + br + br + br + br + br + br + br + br + br diff --git a/views/account/profile.jade b/views/account/profile.jade index b51b83b9f2..0fb25cd99e 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -13,7 +13,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * .col-sm-4 - input.form-control(type='text', placeholder='Name', name='name', ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') + input.form-control(type='text', ng-keyup='keyPress()', placeholder='Name', name='name', ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && !profileForm.name.$pristine && profileForm.name.$error.required") alert(type='danger') span.ion-close-circled From 02eb3b59145dc4ccfc94f9a625dc65e3a549737e Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 18:09:49 -0800 Subject: [PATCH 10/15] committing what I have for Nathan to continue --- controllers/user.js | 4 +++- public/js/main.js | 32 +++++++++++++++++++++++++------- views/account/email-signup.jade | 6 +++++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index ededc20fcf..c2ecd1b890 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -186,7 +186,9 @@ exports.checkUniqueUsername = function(req, res) { */ exports.checkUniqueEmail = function(req, res) { - User.count({'email': req.params.email.toLowerCase()}, function (err, data) { + console.log(req.params.email); + User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { + console.log(User.findOne({'email': decodeURIComponent(req.params.email)})); if (data == 1) { return res.send(true); } else { diff --git a/public/js/main.js b/public/js/main.js index 6e3385ddc4..26983d7c9a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -60,6 +60,12 @@ profileValidation.controller('emailSignUpController', ['$scope', } ]); +profileValidation.controller('emailSignInController', ['$scope', + function($scope) { + + } +]); + profileValidation.directive('uniqueUsername', function($http) { return { restrict: 'A', @@ -79,10 +85,22 @@ profileValidation.directive('uniqueUsername', function($http) { }; }); - - -profileValidation.controller('emailSignInController', ['$scope', - function($scope) { - - } -]); \ No newline at end of file +profileValidation.directive('uniqueEmail', function($http) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + element.bind("keyup", function (event) { + ngModel.$setValidity('unique', true); + if (element.val()) { + console.log(encodeURIComponent(element.val())); + $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { + if (data) { + ngModel.$setValidity('unique', false); + } + }); + } + }); + } + }; +}); \ No newline at end of file diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 95923e53f4..86fb400d3b 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -6,7 +6,11 @@ block content input(type='hidden', name='_csrf', value=_csrf) .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='email', ng-model='email', name='email', id='email', placeholder='email', autofocus, required) + input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off") + .col-sm-6.col-sm-offset-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") + alert(type='danger') + span.ion-close-circled + | This email is taken. .form-group .col-sm-6.col-sm-offset-3 input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required) From 11bc776ffbc2f8d0247c26dac238fd75f0c92968 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 9 Jan 2015 21:33:50 -0500 Subject: [PATCH 11/15] Email signup for angularized, user.js controller modified so that username can be stored upon signup --- controllers/user.js | 5 ++++- views/account/email-signup.jade | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index c2ecd1b890..2315602bc3 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -109,7 +109,10 @@ exports.postEmailSignup = function(req, res, next) { var user = new User({ email: req.body.email, - password: req.body.password + password: req.body.password, + profile : { + username: req.body.username + } }); User.findOne({ email: req.body.email }, function(err, existingUser) { diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 86fb400d3b..765eed26da 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -6,7 +6,7 @@ block content input(type='hidden', name='_csrf', value=_csrf) .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off") + input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='') .col-sm-6.col-sm-offset-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") alert(type='danger') span.ion-close-circled From b4395e2b59a9dfdaacf92ec572e44166807c91f6 Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 9 Jan 2015 22:05:45 -0500 Subject: [PATCH 12/15] Cleaning up, enforcing username constraints to match profile page settings. Added uniqueness to profile username. --- controllers/user.js | 2 -- public/js/main.js | 2 +- views/account/email-signup.jade | 23 ++++++++++++++++------- views/account/profile.jade | 8 ++++++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index 2315602bc3..bc46b8553f 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -189,9 +189,7 @@ exports.checkUniqueUsername = function(req, res) { */ exports.checkUniqueEmail = function(req, res) { - console.log(req.params.email); User.count({'email': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) { - console.log(User.findOne({'email': decodeURIComponent(req.params.email)})); if (data == 1) { return res.send(true); } else { diff --git a/public/js/main.js b/public/js/main.js index 26983d7c9a..32785360c7 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -48,6 +48,7 @@ profileValidation.controller('profileValidationController', ['$scope', '$http', $http.get('/account/api').success(function(data) { $scope.user = data.user; $scope.user.profile.username = $scope.user.profile.username.toLowerCase(); + $scope.storedUsername = $scope.user.profile.username; $scope.user.email = $scope.user.email.toLowerCase(); $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle.toLowerCase(); }); @@ -93,7 +94,6 @@ profileValidation.directive('uniqueEmail', function($http) { element.bind("keyup", function (event) { ngModel.$setValidity('unique', true); if (element.val()) { - console.log(encodeURIComponent(element.val())); $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { if (data) { ngModel.$setValidity('unique', false); diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 765eed26da..356807d382 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -5,19 +5,28 @@ block content form.form-horizontal(method='POST', action='/email-signup', name="signupForm", novalidate="novalidate") input(type='hidden', name='_csrf', value=_csrf) .form-group - .col-sm-6.col-sm-offset-3 - input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='') - .col-sm-6.col-sm-offset-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") - alert(type='danger') - span.ion-close-circled - | This email is taken. + h3 + .col-sm-6.col-sm-offset-3 + input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='') + .col-sm-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") + alert(type='danger') + span.ion-close-circled + | This email is taken. .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required) + input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required, ng-minlength=5, ng-maxlength=20) .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.unique && !signupForm.username.$pristine") alert(type='danger') span.ion-close-circled | This username is taken. + .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.minlength && !signupForm.username.$pristine") + alert(type='danger') + span.ion-close-circled + | Usernames must be at least 5 characters. + .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.maxlength && !signupForm.username.$pristine") + alert(type='danger') + span.ion-close-circled + | Usernames must be 20 characters or less. .form-group .col-sm-6.col-sm-offset-3 input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required) diff --git a/views/account/profile.jade b/views/account/profile.jade index 0fb25cd99e..41c7102c36 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -13,7 +13,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * .col-sm-4 - input.form-control(type='text', ng-keyup='keyPress()', placeholder='Name', name='name', ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') + input.form-control(type='text', placeholder='Name', name='name', ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && !profileForm.name.$pristine && profileForm.name.$error.required") alert(type='danger') span.ion-close-circled @@ -30,7 +30,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) * .col-sm-4 - input.form-control(type='text', placeholder='username' name='username', id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20') + input.form-control(type='text', placeholder='username' name='username', id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='') .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.required && !profileForm.username.$pristine") alert(type='danger') span.ion-close-circled @@ -43,6 +43,10 @@ block content alert(type='danger') span.ion-close-circled | Your username must be fewer than 15 characters. + .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine") + alert(type='danger') + span.ion-close-circled + | This username is taken. .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') Email * From c90c61720ed936311b83c9ed43f38a1892de20da Mon Sep 17 00:00:00 2001 From: terakilobyte Date: Fri, 9 Jan 2015 22:07:20 -0500 Subject: [PATCH 13/15] Tidying up --- views/account/email-signup.jade | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 356807d382..58d993a29b 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -5,13 +5,12 @@ block content form.form-horizontal(method='POST', action='/email-signup', name="signupForm", novalidate="novalidate") input(type='hidden', name='_csrf', value=_csrf) .form-group - h3 - .col-sm-6.col-sm-offset-3 - input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='') - .col-sm-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") - alert(type='danger') - span.ion-close-circled - | This email is taken. + .col-sm-6.col-sm-offset-3 + input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='') + .col-sm-6.col-sm-offset-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine") + alert(type='danger') + span.ion-close-circled + | This email is taken. .form-group .col-sm-6.col-sm-offset-3 input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required, ng-minlength=5, ng-maxlength=20) From e1ed00419f4d29605bb737bd2c3093e788904cdb Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 20:03:24 -0800 Subject: [PATCH 14/15] fix uniqueness and ability to detect original value --- public/js/main.js | 18 ++++++++------ views/account/profile.jade | 48 +++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index 32785360c7..3f81c3c571 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -24,7 +24,6 @@ $(document).ready(function() { l = location.pathname.split('/'); cn = l[l.length - 1]; - console.log(cn); $.ajax({ type: 'POST', data: {challengeNumber: cn}, @@ -48,7 +47,8 @@ profileValidation.controller('profileValidationController', ['$scope', '$http', $http.get('/account/api').success(function(data) { $scope.user = data.user; $scope.user.profile.username = $scope.user.profile.username.toLowerCase(); - $scope.storedUsername = $scope.user.profile.username; + $scope.storedUsername = data.user.profile.username; + $scope.storedEmail = data.user.email; $scope.user.email = $scope.user.email.toLowerCase(); $scope.user.profile.twitterHandle = $scope.user.profile.twitterHandle.toLowerCase(); }); @@ -76,14 +76,16 @@ profileValidation.directive('uniqueUsername', function($http) { ngModel.$setValidity('unique', true); if (element.val()) { $http.get("/api/checkUniqueUsername/" + element.val()).success(function (data) { - if (data) { + if (element.val() == scope.storedUsername) { + ngModel.$setValidity('unique', true); + } else if (data) { ngModel.$setValidity('unique', false); } }); } }); } - }; + } }); profileValidation.directive('uniqueEmail', function($http) { @@ -95,12 +97,14 @@ profileValidation.directive('uniqueEmail', function($http) { ngModel.$setValidity('unique', true); if (element.val()) { $http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) { - if (data) { + if (element.val() == scope.storedEmail) { + ngModel.$setValidity('unique', true); + } else if (data) { ngModel.$setValidity('unique', false); } }); - } + }; }); } - }; + } }); \ No newline at end of file diff --git a/views/account/profile.jade b/views/account/profile.jade index 41c7102c36..bf1bf83974 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -13,7 +13,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * .col-sm-4 - input.form-control(type='text', placeholder='Name', name='name', ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') + input.form-control(type='text', placeholder='Name', name='name', autocomplete="off", ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && !profileForm.name.$pristine && profileForm.name.$error.required") alert(type='danger') span.ion-close-circled @@ -30,7 +30,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) * .col-sm-4 - input.form-control(type='text', placeholder='username' name='username', id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='') + input.form-control(type='text', placeholder='username' name='username', autocomplete="off", id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='') .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.required && !profileForm.username.$pristine") alert(type='danger') span.ion-close-circled @@ -43,15 +43,15 @@ block content alert(type='danger') span.ion-close-circled | Your username must be fewer than 15 characters. - .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine") + .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine && $scope.storedUsername !== user.profile.username") alert(type='danger') span.ion-close-circled - | This username is taken. + | That username is taken. .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') Email * .col-sm-4 - input.form-control(type='email', name='email', id='email', ng-model='user.email', required='required') + input.form-control(type='email', name='email', id='email', autocomplete="off", ng-model='user.email', required='required', ng-keypress='', unique-email='') .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.required && !profileForm.email.$pristine") alert(type='danger') span.ion-close-circled @@ -60,11 +60,15 @@ block content alert(type='danger') span.ion-close-circled | Please enter a valid email format. + .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.unique && !profileForm.email.$pristine") + alert(type='danger') + span.ion-close-circled + | That email is taken. .form-group label.col-sm-3.col-sm-offset-2.control-label(for='location') Location .col-sm-4 - input.form-control(type='text', name='location', id='location', ng-model='user.profile.location') + input.form-control(type='text', name='location', autocomplete="off", id='location', ng-model='user.profile.location') .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') Link to Profile Photo (1:1 ratio) @@ -78,7 +82,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='bio') Bio (140 characters) .col-sm-4 - input.form-control(type='text', name='bio', ng-model='user.profile.bio', ng-maxlength='140', id='bio') + input.form-control(type='text', name='bio', autocomplete="off", ng-model='user.profile.bio', ng-maxlength='140', id='bio') .col-sm-4.col-sm-offset-5(ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine') alert(type='danger') span.ion-close-circled @@ -98,7 +102,7 @@ block content .col-sm-4 .input-group.twitter-input span.input-group-addon @ - input.form-control(type='text', name='twitterHandle', id='twitterHandle', ng-model='user.profile.twitterHandle', ng-maxlength='15', ng-pattern="/^[A-z0-9_]+$/") + input.form-control(type='text', name='twitterHandle', autocomplete="off", id='twitterHandle', ng-model='user.profile.twitterHandle', ng-maxlength='15', ng-pattern="/^[A-z0-9_]+$/") .col-sm-4.col-sm-offset-5(ng-show="profileForm.twitterHandle.$error.pattern") alert(type='danger') span.ion-close-circled @@ -110,7 +114,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') Github .col-sm-4 - input.form-control(type='url', name='githubProfile', id='githubProfile', ng-model='user.profile.githubProfile', placeholder='http://') + input.form-control(type='url', name='githubProfile', id='githubProfile', autocomplete="off", ng-model='user.profile.githubProfile', placeholder='http://') .col-sm-4.col-sm-offset-5(ng-show="profileForm.githubProfile.$error.url && !profileForm.githubProfile.$pristine") alert(type='danger') span.ion-close-circled @@ -119,7 +123,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') CodePen .col-sm-4 - input.form-control(type='url', name='codepenProfile', id='codepenProfile', ng-model='user.profile.codepenProfile', placeholder='http://') + input.form-control(type='url', name='codepenProfile', id='codepenProfile', autocomplete="off", ng-model='user.profile.codepenProfile', placeholder='http://') .col-sm-4.col-sm-offset-5(ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine") alert(type='danger') span.ion-close-circled @@ -128,7 +132,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') CoderByte .col-sm-4 - input.form-control(type='url', name='coderbyteProfile', id='coderbyteProfile', ng-model='user.profile.coderbyteProfile', placeholder='http://') + input.form-control(type='url', name='coderbyteProfile', id='coderbyteProfile', autocomplete="off", ng-model='user.profile.coderbyteProfile', placeholder='http://') .col-sm-4.col-sm-offset-5(ng-show="profileForm.coderbyteProfile.$error.url && !profileForm.coderbyteProfile.$pristine") alert(type='danger') span.ion-close-circled @@ -137,7 +141,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') LinkedIn .col-sm-4 - input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', ng-model='user.profile.linkedinProfile', placeholder='http://') + input.form-control(type='url', name='linkedinProfile', id='linkedinProfile', autocomplete="off", ng-model='user.profile.linkedinProfile', placeholder='http://') .col-sm-4.col-sm-offset-5(ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine") alert(type='danger') span.ion-close-circled @@ -158,7 +162,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website1Title') Title .col-sm-4 - input.form-control(type='text', name='website1Title', id='website1Title', ng-model='user.portfolio.website1Title', ng-maxlength='140') + input.form-control(type='text', name='website1Title', id='website1Title', autocomplete="off", ng-model='user.portfolio.website1Title', ng-maxlength='140') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Title.$error.maxlength && !profileForm.website1Title.$pristine") alert(type='danger') span.ion-close-circled @@ -167,12 +171,12 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website1Link') Link .col-sm-4 - input.form-control(type='text', name='website1Link', id='website1Link', ng-model='user.portfolio.website1Link', placeholder='http://') + input.form-control(type='text', name='website1Link', id='website1Link', autocomplete="off", ng-model='user.portfolio.website1Link', placeholder='http://') .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='text', name='website1Image', id='website1Image', ng-model='user.portfolio.website1Image', placeholder='http://') + input.form-control(type='text', name='website1Image', id='website1Image', autocomplete="off", ng-model='user.portfolio.website1Image', placeholder='http://') .col-sm-4.col-sm-offset-5.flat-top h3 Second Portfolio Project @@ -180,7 +184,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website2Title') Title .col-sm-4 - input.form-control(type='text', name='website2Title', id='website2Title', ng-model='user.portfolio.website2Title', ng-maxlength='140') + input.form-control(type='text', name='website2Title', id='website2Title', autocomplete="off", ng-model='user.portfolio.website2Title', ng-maxlength='140') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Title.$error.maxlength && !profileForm.website2Title.$pristine") alert(type='danger') span.ion-close-circled @@ -189,12 +193,12 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website2Link') Link .col-sm-4 - input.form-control(type='text', name='website2Link', id='website2Link', ng-model='user.portfolio.website2Link', placeholder='http://') + input.form-control(type='text', name='website2Link', id='website2Link', autocomplete="off", ng-model='user.portfolio.website2Link', placeholder='http://') .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='text', name='website2Image', id='website2Image', ng-model='user.portfolio.website2Image', placeholder='http://') + input.form-control(type='text', name='website2Image', id='website2Image', autocomplete="off", ng-model='user.portfolio.website2Image', placeholder='http://') .col-sm-4.col-sm-offset-5.flat-top h3 Third Portfolio Project @@ -202,7 +206,7 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website3Title') Title .col-sm-4 - input.form-control(type='text', name='website3Title', id='website3Title', ng-model='user.portfolio.website3Title', ng-maxlength='140') + input.form-control(type='text', name='website3Title', id='website3Title', autocomplete="off", ng-model='user.portfolio.website3Title', ng-maxlength='140') .col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Title.$error.maxlength && !profileForm.website3Title.$pristine") alert(type='danger') span.ion-close-circled @@ -211,12 +215,12 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website3Link') Link .col-sm-4 - input.form-control(type='text', name='website3Link', id='website3Link', ng-model='user.portfolio.website3Link', placeholder='http://') + input.form-control(type='text', name='website3Link', id='website3Link', autocomplete="off", ng-model='user.portfolio.website3Link', placeholder='http://') .form-group label.col-sm-3.col-sm-offset-2.control-label(for='website3Image') Image Link (4:3 ratio) .col-sm-4 - input.form-control(type='text', name='website3Image', id='website3Image', ng-model='user.portfolio.website3Image', placeholder='http://') + input.form-control(type='text', name='website3Image', id='website3Image', autocomplete="off", ng-model='user.portfolio.website3Image', placeholder='http://') .form-group .col-sm-offset-5.col-sm-4 @@ -233,7 +237,7 @@ block content a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/') Take me to my current challenge a.btn.btn-lg.btn-block.btn-warning.btn-link-social(href='/logout') Sign out br - - if (!user.google || !user.facebook || !user.github || !user.linkedin || !user.twitter) + - if (!user.google || !user.facebook || /*!user.github ||*/ !user.linkedin || !user.twitter) .panel.panel-primary .panel-heading.text-center Link other services to your account: .panel-body From 114f6598b9de5b510c95ca80ce771994f99e6fb4 Mon Sep 17 00:00:00 2001 From: Michael Q Larson Date: Fri, 9 Jan 2015 20:30:29 -0800 Subject: [PATCH 15/15] finish up angularizing forms --- views/account/email-signup.jade | 22 +++++++++++++++++----- views/account/profile.jade | 28 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/views/account/email-signup.jade b/views/account/email-signup.jade index 58d993a29b..826af1f806 100644 --- a/views/account/email-signup.jade +++ b/views/account/email-signup.jade @@ -13,7 +13,11 @@ block content | This email is taken. .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required, ng-minlength=5, ng-maxlength=20) + input.form-control(type='text', name='username', ng-keypress='', autocomplete="off", id='username', placeholder='username', ng-model='username', unique-username='', required, ng-minlength=5, ng-maxlength=20, ng-pattern="/^[A-z0-9_]+$/") + .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.pattern && !signupForm.username.$pristine") + alert(type='danger') + span.ion-close-circled + | Your username should only contain letters, numbers and underscores (az10_). .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.unique && !signupForm.username.$pristine") alert(type='danger') span.ion-close-circled @@ -21,17 +25,25 @@ block content .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.minlength && !signupForm.username.$pristine") alert(type='danger') span.ion-close-circled - | Usernames must be at least 5 characters. + | Your username must be at least 5 characters long. .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.maxlength && !signupForm.username.$pristine") alert(type='danger') span.ion-close-circled - | Usernames must be 20 characters or less. + | Your usernames must be 20 characters or fewer. .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required) + input.form-control(type='password', ng-model='password', name='password', id='password', placeholder='password', required, ng-minlength=5) + .col-sm-6.col-sm-offset-3(ng-show="signupForm.password.$error.minlength && !signupForm.password.$pristine") + alert(type='danger') + span.ion-close-circled + | Your password must be at least 8 characters long. .form-group .col-sm-6.col-sm-offset-3 - input.form-control(type='password', ng-model='confirmPassword', name='confirmPassword', id='confirmPassword', placeholder='confirm password', required) + input.form-control(type='password', ng-model='confirmPassword', name='confirmPassword', id='confirmPassword', placeholder='confirm password', required, ng-minlength=5) + .col-sm-6.col-sm-offset-3(ng-show="(confirmPassword !== password) && !signupForm.confirmPassword.$pristine") + alert(type='danger') + span.ion-close-circled + | Passwords must match. .form-group .col-sm-offset-3.col-sm-6 button.btn.btn-success(type='submit') diff --git a/views/account/profile.jade b/views/account/profile.jade index bf1bf83974..7fcef2ef01 100644 --- a/views/account/profile.jade +++ b/views/account/profile.jade @@ -14,9 +14,9 @@ block content label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * .col-sm-4 input.form-control(type='text', placeholder='Name', name='name', autocomplete="off", ng-model='user.profile.name', ng-minlength='3', ng-maxlength='50', required='required', id='name') - .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && !profileForm.name.$pristine && profileForm.name.$error.required") + .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && profileForm.name.$error.required") alert(type='danger') - span.ion-close-circled + span.ion-close-circled(id='#name-error') | Your name is required. .col-sm-4.col-sm-offset-5(ng-show='profileForm.name.$error.minlength && !profileForm.name.$pristine') alert(type='danger') @@ -30,11 +30,15 @@ block content .form-group label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) * .col-sm-4 - input.form-control(type='text', placeholder='username' name='username', autocomplete="off", id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='') - .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.required && !profileForm.username.$pristine") + input.form-control(type='text', placeholder='username' name='username', autocomplete="off", id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20', ng-keypress='', unique-username='', ng-pattern="/^[A-z0-9_]+$/") + .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.pattern") alert(type='danger') span.ion-close-circled - | Please enter a username. + | Your username should only contain letters, numbers and underscores (az10_). + .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.required") + alert(type='danger') + span.ion-close-circled + | Your username is required. .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.minlength && !profileForm.username.$pristine") alert(type='danger') span.ion-close-circled @@ -46,16 +50,16 @@ block content .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.unique && !profileForm.username.$pristine && $scope.storedUsername !== user.profile.username") alert(type='danger') span.ion-close-circled - | That username is taken. + | That username is already taken. .form-group label.col-sm-3.col-sm-offset-2.control-label(for='email') Email * .col-sm-4 input.form-control(type='email', name='email', id='email', autocomplete="off", ng-model='user.email', required='required', ng-keypress='', unique-email='') - .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.required && !profileForm.email.$pristine") + .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.required") alert(type='danger') span.ion-close-circled - | An email address is required. + | Your email address is required. .col-sm-4.col-sm-offset-5(ng-show="profileForm.$error.email && !profileForm.email.$pristine") alert(type='danger') span.ion-close-circled @@ -63,7 +67,7 @@ block content .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.unique && !profileForm.email.$pristine") alert(type='danger') span.ion-close-circled - | That email is taken. + | That email is already taken. .form-group label.col-sm-3.col-sm-offset-2.control-label(for='location') Location @@ -106,7 +110,7 @@ block content .col-sm-4.col-sm-offset-5(ng-show="profileForm.twitterHandle.$error.pattern") alert(type='danger') span.ion-close-circled - | Your Twitter handle should only contain letters, numbers and underscores (@az10_). + | Your Twitter handle should only contain letters, numbers and underscores (az10_). .col-sm-4.col-sm-offset-5(ng-show='profileForm.twitterHandle.$error.maxlength && !profileForm.twitterHandle.$pristine') alert(type='danger') span.ion-close-circled @@ -316,7 +320,3 @@ block content button.btn.btn-danger.btn-block(type='submit') span.ion-trash-b | Yes, Delete my account - - - -