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.disable('x-powered-by');
app.use(helmet.xssFilter()); app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.xframe()); app.use(helmet.xframe());
var trusted = [ var trusted = [
@ -126,7 +127,8 @@ var trusted = [
'localhost:3000', 'localhost:3000',
'ws://localhost:3000/', 'ws://localhost:3000/',
'http://localhost:3000', 'http://localhost:3000',
'*.ionicframework.com' '*.ionicframework.com',
'https://syndication.twitter.com'
]; ];
debug(trusted); debug(trusted);
@ -256,6 +258,9 @@ app.get(
); );
app.all('/account', passportConf.isAuthenticated); app.all('/account', passportConf.isAuthenticated);
app.get('/account/api', userController.getAccountAngular); 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.get('/account', userController.getAccount);
app.post('/account/profile', userController.postUpdateProfile); app.post('/account/profile', userController.postUpdateProfile);
app.post('/account/password', userController.postUpdatePassword); app.post('/account/password', userController.postUpdatePassword);
@ -293,13 +298,11 @@ app.get('/auth/twitter', passport.authenticate('twitter'));
app.get( app.get(
'/auth/twitter/callback', '/auth/twitter/callback',
passport.authenticate('twitter', { passport.authenticate('twitter', {
successRedirect: '/auth/twitter/middle', successRedirect: '/',
failureRedirect: '/login' failureRedirect: '/login'
}) })
); );
app.get('/auth/twitter/middle', passportConf.hasEmail);
app.get( app.get(
'/auth/linkedin', '/auth/linkedin',
passport.authenticate('linkedin', { passport.authenticate('linkedin', {

View File

@ -9,27 +9,35 @@ var _ = require('lodash'),
OAuthStrategy = require('passport-oauth').OAuthStrategy, OAuthStrategy = require('passport-oauth').OAuthStrategy,
OAuth2Strategy = require('passport-oauth').OAuth2Strategy, OAuth2Strategy = require('passport-oauth').OAuth2Strategy,
User = require('../models/User'), User = require('../models/User'),
nodemailer = require('nodemailer'),
secrets = require('./secrets'); secrets = require('./secrets');
// Login Required middleware.
module.exports = {
isAuthenticated: isAuthenticated,
isAuthorized: isAuthorized,
hasEmail: hasEmail
};
passport.serializeUser(function(user, done) { passport.serializeUser(function(user, done) {
done(null, user.id); done(null, user.id);
}); });
passport.deserializeUser(function(id, done) { passport.deserializeUser(function(id, done) {
User.findOne({ User.findById(id, function(err, user) {
_id: id
}, '-password', function(err, user) {
done(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 * OAuth Strategy Overview
* *
@ -45,137 +53,69 @@ passport.deserializeUser(function(id, done) {
* - Else create a new account. * - 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) { if (req.user) {
User.findOne({ twitter: profile.id }, function(err, existingUser) { User.findOne({ facebook: profile.id }, function(err, existingUser) {
if (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); done(err);
} else { } else {
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
user.twitter = profile.id; user.facebook = profile.id;
user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); user.tokens.push({ kind: 'facebook', accessToken: accessToken });
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.profile.name = user.profile.name || profile.displayName; user.profile.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender; 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) { 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); done(err, user);
}); });
}); });
} }
}); });
} else { } else {
User.findOne({ google: profile.id }, function(err, existingUser) { User.findOne({ facebook: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser); if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (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); done(err);
} else { } else {
var user = new User(); var user = new User();
user.email = profile._json.email; user.email = profile._json.email;
user.google = profile.id; user.facebook = profile.id;
user.tokens.push({ kind: 'google', accessToken: accessToken }); user.tokens.push({ kind: 'facebook', accessToken: accessToken });
user.profile.name = profile.displayName; user.profile.name = profile.displayName;
user.profile.gender = profile._json.gender; 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) { user.save(function(err) {
done(err, user); done(err, user);
}); });
} var transporter = nodemailer.createTransport({
}); service: 'Mandrill',
}); auth: {
} user: secrets.mandrill.user,
})); pass: secrets.mandrill.password
}
// 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 mailOptions = {
} to: user.email,
}); from: 'Team@freecodecamp.com',
} else { subject: 'Welcome to Free Code Camp!',
User.findOne({ linkedin: profile.id }, function(err, existingUser) { text: [
if (existingUser) return done(null, existingUser); 'Greetings from San Francisco!\n\n',
User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) { 'Thank you for joining our community.\n',
if (existingEmailUser) { 'Feel free to email us at this address if you have any questions about Free Code Camp.\n',
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.' }); "And if you have a moment, check out our blog: blog.freecodecamp.com.\n",
done(err); 'Good luck with the challenges!\n\n',
} else { '- the Volunteer Camp Counselor Team'
var user = new User(); ].join('')
user.linkedin = profile.id; };
user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); transporter.sendMail(mailOptions, function(err) {
user.email = profile._json.emailAddress; if (err) { return err; }
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);
}); });
} }
}); });
@ -225,273 +165,228 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre
user.save(function(err) { user.save(function(err) {
done(err, user); 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) { if (req.user) {
User.findOne({ facebook: profile.id }, function(err, existingUser) { User.findOne({ twitter: profile.id }, function(err, existingUser) {
if (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); done(err);
} else { } else {
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
user.facebook = profile.id; user.twitter = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken }); 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.name = user.profile.name || profile.displayName;
user.profile.gender = user.profile.gender || profile._json.gender; 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) { 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); done(err, user);
}); });
}); });
} }
}); });
} else { } else {
User.findOne({ facebook: profile.id }, function(err, existingUser) { User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser); if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (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); done(err);
} else { } else {
var user = new User(); var user = new User();
user.email = profile._json.email; user.email = profile._json.email;
user.facebook = profile.id; user.google = profile.id;
user.tokens.push({ kind: 'facebook', accessToken: accessToken }); user.tokens.push({ kind: 'google', accessToken: accessToken });
user.profile.name = profile.displayName; user.profile.name = profile.displayName;
user.profile.gender = profile._json.gender; user.profile.gender = profile._json.gender;
user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.profile.picture = profile._json.picture;
user.profile.location = (profile._json.location) ? profile._json.location.name : '';
user.save(function(err) { user.save(function(err) {
done(err, user); 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. // Sign in with LinkedIn.
passport.use(
new LocalStrategy(
{ usernameField: 'email' }, function(email, password, done) {
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) { passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) {
return done(null, false, { message: 'Email ' + email + ' not found'}); if (req.user) {
} User.findOne({ linkedin: profile.id }, function(err, existingUser) {
user.comparePassword(password, function(err, isMatch) { if (existingUser) {
if (err) { return done(err); } 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);
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 { } else {
User.findOne({ facebook: profile.id }, function(err, existingUser) { User.findById(req.user.id, function(err, user) {
if (err) { return done(err); } user.linkedin = profile.id;
user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
if (existingUser) { return done(null, existingUser); } user.profile.name = user.profile.name || profile.displayName;
user.profile.location = user.profile.location || profile._json.location.name;
User.findOne( user.profile.picture = user.profile.picture || profile._json.pictureUrl;
{ email: profile._json.email }, function(err, existingEmailUser) { user.profile.website = user.profile.website || profile._json.publicProfileUrl;
if (err) { return done(err); } user.save(function(err) {
req.flash('info', { msg: 'LinkedIn account has been linked.' });
var user = existingEmailUser || new User(); done(err, 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);
});
}); });
}); });
} }
});
} 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.
exports.isAuthenticated = function(req, res, next) {
function isAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next(); if (req.isAuthenticated()) return next();
res.redirect('/login'); 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. // Authorization Required middleware.
function isAuthorized(req, res, next) {
exports.isAuthorized = function(req, res, next) {
var provider = req.path.split('/').slice(-1)[0]; var provider = req.path.split('/').slice(-1)[0];
if (_.find(req.user.tokens, { kind: provider })) { if (_.find(req.user.tokens, { kind: provider })) {
@ -499,4 +394,4 @@ function isAuthorized(req, res, next) {
} else { } else {
res.redirect('/auth/' + provider); res.redirect('/auth/' + provider);
} }
} };

View File

@ -70,7 +70,7 @@ exports.logout = function(req, res) {
exports.getEmailSignin = function(req, res) { exports.getEmailSignin = function(req, res) {
if (req.user) return res.redirect('/'); 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' title: 'Sign in to your Free Code Camp Account'
}); });
}; };
@ -82,7 +82,7 @@ exports.getEmailSignin = function(req, res) {
exports.getEmailSignup = function(req, res) { exports.getEmailSignup = function(req, res) {
if (req.user) return res.redirect('/'); if (req.user) return res.redirect('/');
res.render('account/email-signin', { res.render('account/email-signup', {
title: 'Create Your Free Code Camp Account' title: 'Create Your Free Code Camp Account'
}); });
}; };
@ -93,6 +93,7 @@ exports.getEmailSignup = function(req, res) {
*/ */
exports.postEmailSignup = function(req, res, next) { exports.postEmailSignup = function(req, res, next) {
console.log('post email signup called');
req.assert('email', 'Email is not valid').isEmail(); req.assert('email', 'Email is not valid').isEmail();
req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('password', 'Password must be at least 4 characters long').len(4);
req.assert('confirmPassword', 'Passwords do not match') req.assert('confirmPassword', 'Passwords do not match')
@ -103,11 +104,15 @@ exports.postEmailSignup = function(req, res, next) {
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/email-signup'); return res.redirect('/email-signup');
console.log(errors);
} }
var user = new User({ var user = new User({
email: req.body.email, 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) { 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 * GET /campers/:username
@ -173,7 +205,7 @@ exports.getAccount = function(req, res) {
*/ */
exports.returnUser = function(req, res, next) { 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 (err) { debug('Username err: ', err); next(err); }
if (user[0]) { if (user[0]) {
var user = user[0]; var user = user[0];

View File

@ -24,7 +24,6 @@ $(document).ready(function() {
l = location.pathname.split('/'); l = location.pathname.split('/');
cn = l[l.length - 1]; cn = l[l.length - 1];
console.log(cn);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
data: {challengeNumber: cn}, data: {challengeNumber: cn},
@ -47,7 +46,65 @@ profileValidation.controller('profileValidationController', ['$scope', '$http',
function($scope, $http) { function($scope, $http) {
$http.get('/account/api').success(function(data) { $http.get('/account/api').success(function(data) {
$scope.user = data.user; $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 extends ../layout
block content block content
.jumbotron.text-center .jumbotron.text-center(ng-controller="emailSignInController")
h2 Sign up with an email address here: h2 Sign in with an email address here:
form.form-horizontal(method='POST') form(method='POST', action='/email-signin')
input(type='hidden', name='_csrf', value=_csrf) input(type='hidden', name='_csrf', value=_csrf)
.form-group .col-sm-6.col-sm-offset-3
.col-sm-6.col-sm-offset-3 .form-group
input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus) input.form-control(type='email', name='email', id='email', placeholder='Email', ng-model='email', autofocus=true)
.form-group | {{ $scope.email }}
.col-sm-6.col-sm-offset-3 .form-group
input.form-control(type='password', name='password', id='password', placeholder='Password') input.form-control(type='password', name='password', id='password', placeholder='Password', ng-model='password')
.form-group .form-group
.col-sm-6.col-sm-offset-3 button.btn.btn-primary(type='submit')
input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password') span.ion-android-hand
.form-group | Login
.col-sm-offset-3.col-sm-6 span   
button.btn.btn-success(type='submit') a.btn.btn-info(href='/forgot') Forgot your password?
span.ion-person-add br
| Signup br
br br
br br
br br
br br
br br
br br
br br
br
br

View File

@ -1,26 +1,60 @@
extends ../layout extends ../layout
block content block content
.jumbotron.text-center .jumbotron.text-center
h2 Sign in with an email address here: h2 Sign up with an email address here:
form(method='POST') form.form-horizontal(method='POST', action='/email-signup', name="signupForm", novalidate="novalidate")
input(type='hidden', name='_csrf', value=_csrf) input(type='hidden', name='_csrf', value=_csrf)
.col-sm-6.col-sm-offset-3 .form-group
.form-group .col-sm-6.col-sm-offset-3
input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true) input.form-control(type='email', ng-model='email', ng-keypress='', name='email', id='email', placeholder='email', autofocus, required, autocomplete="off", unique-email='')
.form-group .col-sm-6.col-sm-offset-3(ng-show="signupForm.email.$error.unique && !signupForm.email.$pristine")
input.form-control(type='password', name='password', id='password', placeholder='Password') alert(type='danger')
.form-group span.ion-close-circled
button.btn.btn-primary(type='submit') | This email is taken.
span.ion-android-hand .form-group
| Login .col-sm-6.col-sm-offset-3
span    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_]+$/")
a.btn.btn-info(href='/forgot') Forgot your password? .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.pattern && !signupForm.username.$pristine")
br alert(type='danger')
br span.ion-close-circled
br | Your username should only contain letters, numbers and underscores (az10_).
br .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.unique && !signupForm.username.$pristine")
br alert(type='danger')
br span.ion-close-circled
br | This username is taken.
br .col-sm-6.col-sm-offset-3(ng-show="signupForm.username.$error.minlength && !signupForm.username.$pristine")
br 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 extends ../layout
block content block content
.col-sm-8.col-sm-offset-2 .jumbotron
form(method='POST') .col-sm-8.col-sm-offset-2
legend Forgot Password form(method='POST')
input(type='hidden', name='_csrf', value=_csrf) h1 Forgot Password
.form-group input(type='hidden', name='_csrf', value=_csrf)
p Enter your email address below and we will send you password reset instructions. .form-group
label.control-label(for='email') Email p Enter your email address below and we will send you password reset instructions.
input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true) label.control-label(for='email') Email
.form-group input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
button.btn.btn-primary(type='submit') .form-group
i.fa.fa-key button.btn.btn-primary(type='submit')
| Reset Password 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') a.btn.btn-lg.btn-block.btn-facebook.btn-social(href='/auth/facebook')
i.fa.fa-facebook i.fa.fa-facebook
| Sign in with Facebook | Sign in with Facebook
a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github') //a.btn.btn-lg.btn-block.btn-github.btn-social(href='/auth/github')
i.fa.fa-github // i.fa.fa-github
| Sign in with GitHub // | Sign in with GitHub
a.btn.btn-lg.btn-block.btn-linkedin.btn-social(href='/auth/linkedin') a.btn.btn-lg.btn-block.btn-linkedin.btn-social(href='/auth/linkedin')
i.fa.fa-linkedin i.fa.fa-linkedin
| Sign in with LinkedIn | Sign in with LinkedIn

View File

@ -13,10 +13,10 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='name') Name * label.col-sm-3.col-sm-offset-2.control-label(for='name') Name *
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.name.$invalid && profileForm.name.$error.required")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled(id='#name-error')
| Your name is required. | Your name is required.
.col-sm-4.col-sm-offset-5(ng-show='profileForm.name.$error.minlength && !profileForm.name.$pristine') .col-sm-4.col-sm-offset-5(ng-show='profileForm.name.$error.minlength && !profileForm.name.$pristine')
alert(type='danger') alert(type='danger')
@ -30,11 +30,15 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) * label.col-sm-3.col-sm-offset-2.control-label(for='username') Username (path to public profile) *
.col-sm-4 .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', 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.required && !profileForm.username.$pristine") .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.pattern")
alert(type='danger') alert(type='danger')
span.ion-close-circled 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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.username.$error.minlength && !profileForm.username.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -43,24 +47,32 @@ block content
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
| Your username must be fewer than 15 characters. | 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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Email * label.col-sm-3.col-sm-offset-2.control-label(for='email') Email *
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.email.$error.required")
alert(type='danger') alert(type='danger')
span.ion-close-circled 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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.$error.email && !profileForm.email.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
| Please enter a valid email format. | 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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='location') Location label.col-sm-3.col-sm-offset-2.control-label(for='location') Location
.col-sm-4 .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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Link to Profile Photo (1:1 ratio) 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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='bio') Bio (140 characters) label.col-sm-3.col-sm-offset-2.control-label(for='bio') Bio (140 characters)
.col-sm-4 .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') .col-sm-4.col-sm-offset-5(ng-show='profileForm.bio.$error.maxlength && !profileForm.bio.$pristine')
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -94,11 +106,11 @@ block content
.col-sm-4 .col-sm-4
.input-group.twitter-input .input-group.twitter-input
span.input-group-addon @ 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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.twitterHandle.$error.pattern")
alert(type='danger') alert(type='danger')
span.ion-close-circled 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') .col-sm-4.col-sm-offset-5(ng-show='profileForm.twitterHandle.$error.maxlength && !profileForm.twitterHandle.$pristine')
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -106,7 +118,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') Github label.col-sm-3.col-sm-offset-2.control-label(for='email') Github
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.githubProfile.$error.url && !profileForm.githubProfile.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -115,7 +127,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') CodePen label.col-sm-3.col-sm-offset-2.control-label(for='email') CodePen
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.codepenProfile.$error.url && !profileForm.codepenProfile.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -124,7 +136,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') CoderByte label.col-sm-3.col-sm-offset-2.control-label(for='email') CoderByte
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.coderbyteProfile.$error.url && !profileForm.coderbyteProfile.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -133,7 +145,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='email') LinkedIn label.col-sm-3.col-sm-offset-2.control-label(for='email') LinkedIn
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.linkedinProfile.$error.url && !profileForm.linkedinProfile.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -154,7 +166,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Title') Title label.col-sm-3.col-sm-offset-2.control-label(for='website1Title') Title
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.website1Title.$error.maxlength && !profileForm.website1Title.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -163,12 +175,12 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Link') Link label.col-sm-3.col-sm-offset-2.control-label(for='website1Link') Link
.col-sm-4 .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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio) label.col-sm-3.col-sm-offset-2.control-label(for='website1Image') Image Link (4:3 ratio)
.col-sm-4 .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 .col-sm-4.col-sm-offset-5.flat-top
h3 Second Portfolio Project h3 Second Portfolio Project
@ -176,7 +188,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Title') Title label.col-sm-3.col-sm-offset-2.control-label(for='website2Title') Title
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.website2Title.$error.maxlength && !profileForm.website2Title.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -185,12 +197,12 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Link') Link label.col-sm-3.col-sm-offset-2.control-label(for='website2Link') Link
.col-sm-4 .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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio) label.col-sm-3.col-sm-offset-2.control-label(for='website2Image') Image Link (4:3 ratio)
.col-sm-4 .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 .col-sm-4.col-sm-offset-5.flat-top
h3 Third Portfolio Project h3 Third Portfolio Project
@ -198,7 +210,7 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Title') Title label.col-sm-3.col-sm-offset-2.control-label(for='website3Title') Title
.col-sm-4 .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") .col-sm-4.col-sm-offset-5(ng-show="profileForm.website3Title.$error.maxlength && !profileForm.website3Title.$pristine")
alert(type='danger') alert(type='danger')
span.ion-close-circled span.ion-close-circled
@ -207,12 +219,12 @@ block content
.form-group .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Link') Link label.col-sm-3.col-sm-offset-2.control-label(for='website3Link') Link
.col-sm-4 .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 .form-group
label.col-sm-3.col-sm-offset-2.control-label(for='website3Image') Image Link (4:3 ratio) label.col-sm-3.col-sm-offset-2.control-label(for='website3Image') Image Link (4:3 ratio)
.col-sm-4 .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 .form-group
.col-sm-offset-5.col-sm-4 .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-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 a.btn.btn-lg.btn-block.btn-warning.btn-link-social(href='/logout') Sign out
br 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.panel-primary
.panel-heading.text-center Link other services to your account: .panel-heading.text-center Link other services to your account:
.panel-body .panel-body
@ -308,7 +320,3 @@ block content
button.btn.btn-danger.btn-block(type='submit') button.btn.btn-danger.btn-block(type='submit')
span.ion-trash-b span.ion-trash-b
| Yes, Delete my account | Yes, Delete my account

View File

@ -1,14 +1,14 @@
doctype html doctype html
html(ng-app='profileValidation') html(ng-app='profileValidation')
head head
script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js") script(src="//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="//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="//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') script(src='//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='shortcut icon', href='//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='//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='//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') link(rel='stylesheet', href='//code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css')
include partials/meta include partials/meta
title #{title} | Free Code Camp title #{title} | Free Code Camp
meta(charset='utf-8') meta(charset='utf-8')