Merge pull request #14 from FreeCodeCamp/angularize-login

Angularize login
This commit is contained in:
Free Code Camp
2015-01-09 20:33:57 -08:00
10 changed files with 471 additions and 443 deletions

11
app.js
View File

@ -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 = [
@ -126,7 +127,8 @@ var trusted = [
'localhost:3000',
'ws://localhost:3000/',
'http://localhost:3000',
'*.ionicframework.com'
'*.ionicframework.com',
'https://syndication.twitter.com'
];
debug(trusted);
@ -256,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);
@ -293,13 +298,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', {

View File

@ -9,27 +9,35 @@ 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,
hasEmail: hasEmail
};
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);
});
});
// 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
*
@ -45,137 +53,69 @@ passport.deserializeUser(function(id, done) {
* - Else create a new account.
*/
// Sign in with Twitter.
// Sign in with Facebook.
passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) {
passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ twitter: profile.id }, function(err, existingUser) {
User.findOne({ facebook: 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.' });
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.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.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 persons 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.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.save(function(err) {
done(err, user);
});
});
}
}));
// Sign in with Google.
passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) {
req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
} else {
User.findById(req.user.id, function(err, user) {
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.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 || profile._json.picture;
user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.save(function(err) {
req.flash('info', { msg: 'Google account has been linked.' });
req.flash('info', { msg: 'Facebook account has been linked.' });
done(err, user);
});
});
}
});
} else {
User.findOne({ google: profile.id }, function(err, existingUser) {
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 Google manually from Account Settings.' });
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.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
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 = profile._json.picture;
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 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);
var transporter = nodemailer.createTransport({
service: 'Mandrill',
auth: {
user: secrets.mandrill.user,
pass: secrets.mandrill.password
}
});
});
}
});
} else {
User.findOne({ linkedin: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' });
done(err);
} else {
var user = new User();
user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
user.email = profile._json.emailAddress;
user.profile.name = profile.displayName;
user.profile.location = profile._json.location.name;
user.profile.picture = profile._json.pictureUrl;
user.profile.website = profile._json.publicProfileUrl;
user.save(function(err) {
done(err, user);
var 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; }
});
}
});
@ -225,273 +165,228 @@ passport.use(new GitHubStrategy(secrets.github, 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; }
});
}
});
});
}
}));
// Sign in with Facebook.
// Sign in with Twitter.
passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) {
passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) {
if (req.user) {
User.findOne({ facebook: profile.id }, function(err, existingUser) {
User.findOne({ twitter: 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.' });
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.facebook = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken });
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.toLowerCase();
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();
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.toLowerCase();
user.save(function(err) {
done(err, user);
});
});
}
}));
// Sign in with Google.
passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) {
if (req.user) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) {
req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
done(err);
} else {
User.findById(req.user.id, function(err, user) {
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender;
user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.picture = user.profile.picture || profile._json.picture;
user.save(function(err) {
req.flash('info', { msg: 'Facebook account has been linked.' });
req.flash('info', { msg: 'Google account has been linked.' });
done(err, user);
});
});
}
});
} else {
User.findOne({ facebook: profile.id }, function(err, existingUser) {
User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' });
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' });
done(err);
} else {
var user = new User();
user.email = profile._json.email;
user.facebook = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken });
user.google = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.name = profile.displayName;
user.profile.gender = profile._json.gender;
user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large';
user.profile.location = (profile._json.location) ? profile._json.location.name : '';
user.profile.picture = profile._json.picture;
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 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); }
// Sign in with LinkedIn.
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);
});
});
}
});
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.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);
});
User.findById(req.user.id, function(err, user) {
user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location.name;
user.profile.picture = user.profile.picture || profile._json.pictureUrl;
user.profile.website = user.profile.website || profile._json.publicProfileUrl;
user.save(function(err) {
req.flash('info', { msg: 'LinkedIn account has been linked.' });
done(err, user);
});
});
}
});
} else {
User.findOne({ linkedin: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' });
done(err);
} else {
var user = new User();
user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
user.email = profile._json.emailAddress;
user.profile.name = profile.displayName;
user.profile.location = profile._json.location.name;
user.profile.picture = profile._json.pictureUrl;
user.profile.website = profile._json.publicProfileUrl;
user.save(function(err) {
done(err, user);
});
var 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; }
});
}
});
});
}
}));
// Login Required middleware.
function isAuthenticated(req, res, next) {
exports.isAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) return 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) {
exports.isAuthorized = function(req, res, next) {
var provider = req.path.split('/').slice(-1)[0];
if (_.find(req.user.tokens, { kind: provider })) {
@ -499,4 +394,4 @@ function isAuthorized(req, res, next) {
} else {
res.redirect('/auth/' + provider);
}
}
};

View File

@ -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,11 +104,15 @@ exports.postEmailSignup = function(req, res, next) {
if (errors) {
req.flash('errors', errors);
return res.redirect('/email-signup');
console.log(errors);
}
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) {
@ -166,6 +171,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': decodeURIComponent(req.params.email).toLowerCase()}, function (err, data) {
if (data == 1) {
return res.send(true);
} else {
return res.send(false);
}
});
};
/**
* GET /campers/:username
@ -173,7 +205,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];

View File

@ -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},
@ -47,7 +46,65 @@ 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.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();
});
}
]);
profileValidation.controller('emailSignUpController', ['$scope',
function($scope) {
}
]);
profileValidation.controller('emailSignInController', ['$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 (element.val() == scope.storedUsername) {
ngModel.$setValidity('unique', true);
} else if (data) {
ngModel.$setValidity('unique', false);
}
});
}
});
}
}
});
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()) {
$http.get("/api/checkUniqueEmail/" + encodeURIComponent(element.val())).success(function (data) {
if (element.val() == scope.storedEmail) {
ngModel.$setValidity('unique', true);
} else if (data) {
ngModel.$setValidity('unique', false);
}
});
};
});
}
}
});

View File

@ -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

View File

@ -1,26 +1,60 @@
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
.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', 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, 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
| 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
| 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
| 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, 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, 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')
span.ion-person-add
| Signup
br
br
br
br
br
br
br
br
br

View File

@ -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

View File

@ -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

View File

@ -13,10 +13,10 @@ 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')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && !profileForm.name.$pristine && profileForm.name.$error.required")
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.$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', id='username', ng-model='user.profile.username', required='required', ng-minlength='5', ng-maxlength='20')
.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
@ -43,24 +47,32 @@ 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 && $scope.storedUsername !== user.profile.username")
alert(type='danger')
span.ion-close-circled
| 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', ng-model='user.email', required='required')
.col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.required && !profileForm.email.$pristine")
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")
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
| 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 already 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)
@ -74,7 +86,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
@ -94,11 +106,11 @@ 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
| 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
@ -106,7 +118,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
@ -115,7 +127,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
@ -124,7 +136,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
@ -133,7 +145,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
@ -154,7 +166,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
@ -163,12 +175,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
@ -176,7 +188,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
@ -185,12 +197,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
@ -198,7 +210,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
@ -207,12 +219,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
@ -229,7 +241,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
@ -308,7 +320,3 @@ block content
button.btn.btn-danger.btn-block(type='submit')
span.ion-trash-b
| Yes, Delete my account

View File

@ -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')