fix email password reset

This commit is contained in:
Berkeley Martinez
2015-08-16 09:54:34 -07:00
parent 63be84f47f
commit de7eb3d440
4 changed files with 72 additions and 192 deletions

View File

@ -83,6 +83,39 @@ module.exports = function(User) {
next(); next();
}); });
User.on('resetPasswordRequest', function(info) {
const host = User.app.get('host');
// TODO(berks) get protocol as well
const url = `http://${host}/reset-password?access_token=` +
info.accessToken.id;
// the email of the requested user
debug(info.email);
// the temp access token to allow password reset
debug(info.accessToken.id);
// requires AccessToken.belongsTo(User)
var mailOptions = {
to: info.email,
from: 'Team@freecodecamp.com',
subject: 'Password Reset Request',
text: `
Hello,\n\n
This email is confirming that you requested to
reset your password for your Free Code Camp account.
This is your email: ${ info.email }.
Go to ${ url } to reset your password.
\n
Happy Coding!
\n
`
};
User.app.models.Email.send(mailOptions, function(err) {
if (err) { console.error(err); }
debug('email reset sent');
});
});
User.afterRemote('login', function(ctx, user, next) { User.afterRemote('login', function(ctx, user, next) {
var res = ctx.res; var res = ctx.res;
var req = ctx.req; var req = ctx.req;

View File

@ -1,11 +1,8 @@
var _ = require('lodash'), var _ = require('lodash'),
async = require('async'), async = require('async'),
crypto = require('crypto'),
nodemailer = require('nodemailer'),
moment = require('moment'), moment = require('moment'),
// debug = require('debug')('freecc:cntr:userController'), debug = require('debug')('freecc:cntr:userController');
secrets = require('../../config/secrets');
function calcCurrentStreak(cals) { function calcCurrentStreak(cals) {
const revCals = cals.slice().reverse(); const revCals = cals.slice().reverse();
@ -40,8 +37,8 @@ module.exports = function(app) {
router.get('/signout', signout); router.get('/signout', signout);
router.get('/forgot', getForgot); router.get('/forgot', getForgot);
router.post('/forgot', postForgot); router.post('/forgot', postForgot);
router.get('/reset/:token', getReset); router.get('/reset-password', getReset);
router.post('/reset/:token', postReset); router.post('/reset-password', postReset);
router.get('/email-signup', getEmailSignup); router.get('/email-signup', getEmailSignup);
router.get('/email-signin', getEmailSignin); router.get('/email-signin', getEmailSignin);
router.get('/account/api', getAccountAngular); router.get('/account/api', getAccountAngular);
@ -182,11 +179,6 @@ module.exports = function(app) {
); );
} }
/**
* POST /account/password
* Update current password.
*/
function postUpdatePassword(req, res, next) { function postUpdatePassword(req, res, next) {
req.assert('password', 'Password must be at least 4 characters long') req.assert('password', 'Password must be at least 4 characters long')
.len(4); .len(4);
@ -215,11 +207,6 @@ module.exports = function(app) {
}); });
} }
/**
* POST /account/delete
* Delete user account.
*/
function postDeleteAccount(req, res, next) { function postDeleteAccount(req, res, next) {
User.destroyById(req.user.id, function(err) { User.destroyById(req.user.id, function(err) {
if (err) { return next(err); } if (err) { return next(err); }
@ -229,11 +216,6 @@ module.exports = function(app) {
}); });
} }
/**
* GET /account/unlink/:provider
* Unlink OAuth provider.
*/
function getOauthUnlink(req, res, next) { function getOauthUnlink(req, res, next) {
var provider = req.params.provider; var provider = req.params.provider;
User.findById(req.user.id, function(err, user) { User.findById(req.user.id, function(err, user) {
@ -253,120 +235,38 @@ module.exports = function(app) {
}); });
} }
/** function getReset(req, res) {
* GET /reset/:token if (!req.accessToken) {
* Reset Password page. req.flash('errors', { msg: 'access token invalid' });
*/ return res.render('account/forgot');
function getReset(req, res, next) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
User.findOne(
{
where: {
resetPasswordToken: req.params.token,
resetPasswordExpires: { gte: Date.now() }
}
},
function(err, user) {
if (err) { return next(err); }
if (!user) {
req.flash('errors', {
msg: 'Password reset token is invalid or has expired.'
});
return res.redirect('/forgot');
} }
res.render('account/reset', { res.render('account/reset', {
title: 'Password Reset', title: 'Password Reset',
token: req.params.token accessToken: req.accessToken.id
});
}); });
} }
/**
* POST /reset/:token
* Process the reset password request.
*/
function postReset(req, res, next) { function postReset(req, res, next) {
var errors = req.validationErrors(); const errors = req.validationErrors();
const { password } = req.body;
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('back'); return res.redirect('back');
} }
async.waterfall([ User.findById(req.accessToken.userId, function(err, user) {
function(done) {
User.findOne(
{
where: {
resetPasswordToken: req.params.token,
resetPasswordExpires: { gte: Date.now() }
}
},
function(err, user) {
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { user.updateAttribute('password', password, function(err) {
req.flash('errors', {
msg: 'Password reset token is invalid or has expired.'
});
return res.redirect('back');
}
user.password = req.body.password;
user.resetPasswordToken = null;
user.resetPasswordExpires = null;
user.save(function(err) {
if (err) { return done(err); }
req.logIn(user, function(err) {
done(err, user);
});
});
});
},
function(user, done) {
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: 'Your Free Code Camp password has been changed',
text: [
'Hello,\n\n',
'This email is confirming that you requested to',
'reset your password for your Free Code Camp account.',
'This is your email:',
user.email,
'\n'
].join(' ')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return done(err); }
req.flash('success', {
msg: 'Success! Your password has been changed.'
});
done();
});
}
], function(err) {
if (err) { return next(err); } if (err) { return next(err); }
debug('password reset processed successfully');
req.flash('info', { msg: 'password reset processed successfully' });
res.redirect('/'); res.redirect('/');
}); });
});
} }
/**
* GET /forgot
* Forgot Password page.
*/
function getForgot(req, res) { function getForgot(req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return res.redirect('/'); return res.redirect('/');
@ -381,83 +281,29 @@ module.exports = function(app) {
* Create a random token, then the send user an email with a reset link. * Create a random token, then the send user an email with a reset link.
*/ */
function postForgot(req, res, next) { function postForgot(req, res) {
var errors = req.validationErrors(); const errors = req.validationErrors();
const email = req.body.email.toLowerCase();
if (errors) { if (errors) {
req.flash('errors', errors); req.flash('errors', errors);
return res.redirect('/forgot'); return res.redirect('/forgot');
} }
async.waterfall([ User.resetPassword({
function(done) { email: email
crypto.randomBytes(16, function(err, buf) { }, function(err) {
if (err) { return done(err); } if (err) {
var token = buf.toString('hex'); req.flash('errors', err);
done(null, token);
});
},
function(token, done) {
User.findOne({
where: { email: req.body.email.toLowerCase() }
}, function(err, user) {
if (err) { return done(err); }
if (!user) {
req.flash('errors', {
msg: 'No account with that email address exists.'
});
return res.redirect('/forgot'); return res.redirect('/forgot');
} }
user.resetPasswordToken = token;
// 3600000 = 1 hour
user.resetPasswordExpires = Date.now() + 3600000;
user.save(function(err) {
if (err) { return done(err); }
done(null, token, user);
});
});
},
function(token, user, done) {
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: 'Reset your Free Code Camp password',
text: [
'You are receiving this email because you (or someone else)\n',
'requested we reset your Free Code Camp account\'s password.\n\n',
'Please click on the following link, or paste this into your\n',
'browser to complete the process:\n\n',
'http://',
req.headers.host,
'/reset/',
token,
'\n\n',
'If you did not request this, please ignore this email and\n',
'your password will remain unchanged.\n'
].join('')
};
transporter.sendMail(mailOptions, function(err) {
if (err) { return done(err); }
req.flash('info', { req.flash('info', {
msg: 'An e-mail has been sent to ' + msg: 'An e-mail has been sent to ' +
user.email + email +
' with further instructions.' ' with further instructions.'
}); });
done(null, 'done'); res.render('account/forgot');
});
}
], function(err) {
if (err) { return next(err); }
res.redirect('/forgot');
}); });
} }

View File

@ -27,6 +27,7 @@ var passportConfigurator = new PassportConfigurator(app);
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); app.set('view engine', 'jade');
app.use(loopback.token());
app.disable('x-powered-by'); app.disable('x-powered-by');
// adds passport initialization after session middleware phase is complete // adds passport initialization after session middleware phase is complete

View File

@ -2,7 +2,7 @@ extends ../layout
block content block content
.col-sm-8.col-sm-offset-2.jumbotron .col-sm-8.col-sm-offset-2.jumbotron
form(action='/reset/#{token}', method='POST') form(action='/reset-password?access_token=#{accessToken}', method='POST')
h1 Reset Password h1 Reset Password
input(type='hidden', name='_csrf', value=_csrf) input(type='hidden', name='_csrf', value=_csrf)
.form-group .form-group