fix email password reset
This commit is contained in:
@ -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;
|
||||||
|
@ -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');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user