diff --git a/public/language/en_GB/email.json b/public/language/en_GB/email.json index efee400748..33fd28377b 100644 --- a/public/language/en_GB/email.json +++ b/public/language/en_GB/email.json @@ -13,6 +13,10 @@ "reset.text2": "To continue with the password reset, please click on the following link:", "reset.cta": "Click here to reset your password", + "reset.notify.subject": "Password successfully changed", + "reset.notify.text1": "We are notifying you that on %1, your password was changed successfully.", + "reset.notify.text2": "If you did not authorise this, please notify an administrator immediately.", + "digest.notifications": "You have unread notifications from %1:", "digest.latest_topics": "Latest topics from %1", "digest.cta": "Click here to visit %1", diff --git a/public/language/en_GB/reset_password.json b/public/language/en_GB/reset_password.json index 27537ffdf2..96ba318a8a 100644 --- a/public/language/en_GB/reset_password.json +++ b/public/language/en_GB/reset_password.json @@ -10,5 +10,7 @@ "enter_email": "Please enter your email address and we will send you an email with instructions on how to reset your account.", "enter_email_address": "Enter Email Address", "password_reset_sent": "Password Reset Sent", - "invalid_email": "Invalid Email / Email does not exist!" + "invalid_email": "Invalid Email / Email does not exist!", + "password_too_short": "The password entered is too short, please pick a different password.", + "passwords_do_not_match": "The two passwords you've entered do not match." } diff --git a/public/src/client/reset_code.js b/public/src/client/reset_code.js index 9a7fc80075..f690a2b152 100644 --- a/public/src/client/reset_code.js +++ b/public/src/client/reset_code.js @@ -11,44 +11,39 @@ define('forum/reset_code', function() { resetEl.on('click', function() { if (password.val().length < 6) { - $('#error').addClass('hide').hide(); - noticeEl.find('strong').html('Invalid Password'); - noticeEl.find('p').html('The password entered is too short, please pick a different password.'); - noticeEl.removeClass('hide').css({display: 'block'}); - } else if (password.value !== repeat.value) { - $('#error').hide(); - noticeEl.find('strong').html('Invalid Password'); - noticeEl.find('p').html('The two passwords you\'ve entered do not match.'); - noticeEl.removeClass('hide').css({display: 'block'}); + app.alertError('[[reset_password:password_too_short]]'); + } else if (password.val() !== repeat.val()) { + app.alertError('[[reset_password:passwords_do_not_match]]'); } else { + resetEl.prop('disabled', true).html(' Changing Password'); socket.emit('user.reset.commit', { code: reset_code, password: password.val() }, function(err) { - if(err) { + if (err) { + ajaxify.refresh(); return app.alertError(err.message); } - $('#error').addClass('hide').hide(); - $('#notice').addClass('hide').hide(); - $('#success').removeClass('hide').addClass('show').show(); + + window.location.href = RELATIVE_PATH + '/login'; }); } }); - socket.emit('user.reset.valid', reset_code, function(err, valid) { - if(err) { - return app.alertError(err.message); - } + // socket.emit('user.reset.valid', reset_code, function(err, valid) { + // if(err) { + // return app.alertError(err.message); + // } - if (valid) { - resetEl.prop('disabled', false); - } else { - var formEl = $('#reset-form'); - // Show error message - $('#error').show(); - formEl.remove(); - } - }); + // if (valid) { + // resetEl.prop('disabled', false); + // } else { + // var formEl = $('#reset-form'); + // // Show error message + // $('#error').show(); + // formEl.remove(); + // } + // }); }; return ResetCode; diff --git a/src/controllers/index.js b/src/controllers/index.js index 797d30e22d..ff337250f4 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -106,9 +106,12 @@ Controllers.home = function(req, res, next) { Controllers.reset = function(req, res, next) { if (req.params.code) { - res.render('reset_code', { - reset_code: req.params.code ? req.params.code : null, - breadcrumbs: helpers.buildBreadcrumbs([{text: '[[reset_password:reset_password]]', url: '/reset'}, {text: '[[reset_password:update_password]]'}]) + user.reset.validate(req.params.code, function(err, valid) { + res.render('reset_code', { + valid: valid, + reset_code: req.params.code ? req.params.code : null, + breadcrumbs: helpers.buildBreadcrumbs([{text: '[[reset_password:reset_password]]', url: '/reset'}, {text: '[[reset_password:update_password]]'}]) + }); }); } else { res.render('reset', { diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 28feb5616f..4df8915994 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -13,6 +13,8 @@ var async = require('async'), websockets = require('./index'), meta = require('../meta'), events = require('../events'), + emailer = require('../emailer'), + db = require('../database'), SocketUser = {}; SocketUser.exists = function(socket, data, callback) { @@ -81,23 +83,34 @@ SocketUser.reset.send = function(socket, email, callback) { } }; -SocketUser.reset.valid = function(socket, code, callback) { - if (code) { - user.reset.validate(code, callback); - } -}; - SocketUser.reset.commit = function(socket, data, callback) { if(data && data.code && data.password) { - user.reset.commit(data.code, data.password, function(err) { + async.series([ + async.apply(db.getObjectField, 'reset:uid', data.code), + async.apply(user.reset.commit, data.code, data.password) + ], function(err, data) { if (err) { return callback(err); } + + var uid = data[0], + now = new Date(), + parsedDate = now.getFullYear() + '/' + (now.getMonth()+1) + '/' + now.getDate(); + + user.getUserField(uid, 'username', function(err, username) { + emailer.send('reset_notify', uid, { + username: username, + date: parsedDate, + site_title: meta.config.title || 'NodeBB', + subject: '[[email:reset.notify.subject]]' + }); + }); events.log({ type: 'password-reset', uid: socket.uid, ip: socket.ip }); + callback(); }); } }; diff --git a/src/upgrade.js b/src/upgrade.js index ed82dcd20a..52347bcb40 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -21,7 +21,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2015, 0, 30); + latestSchema = Date.UTC(2015, 1, 8); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -807,6 +807,26 @@ Upgrade.upgrade = function(callback) { winston.info('[2015/01/30] Adding group member counts skipped'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2015, 1, 8); + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2015/02/08] Clearing reset tokens'); + + db.deleteAll(['reset:expiry', 'reset:uid'], function(err) { + if (err) { + winston.error('[2015/02/08] Error encountered while Clearing reset tokens'); + return next(err); + } + + winston.info('[2015/02/08] Clearing reset tokens done'); + Upgrade.update(thisSchemaDate, next); + }); + } else { + winston.info('[2015/02/08] Clearing reset tokens skipped'); + next(); + } } // Add new schema updates here diff --git a/src/user/reset.js b/src/user/reset.js index c65e77a570..e49c077180 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -1,4 +1,3 @@ - 'use strict'; var async = require('async'), @@ -21,19 +20,13 @@ var async = require('async'), return callback(err, false); } - db.getObjectField('reset:expiry', code, function(err, expiry) { + db.sortedSetScore('reset:issueDate', code, function(err, issueDate) { + // db.getObjectField('reset:expiry', code, function(err, expiry) { if (err) { return callback(err); } - if (parseInt(expiry, 10) >= Date.now() / 1000) { - callback(null, true); - } else { - // Expired, delete from db - db.deleteObjectField('reset:uid', code); - db.deleteObjectField('reset:expiry', code); - callback(null, false); - } + callback(null, parseInt(issueDate, 10) > (Date.now() - (1000*60*120))); }); }); }; @@ -46,7 +39,7 @@ var async = require('async'), var reset_code = utils.generateUUID(); db.setObjectField('reset:uid', reset_code, uid); - db.setObjectField('reset:expiry', reset_code, (60 * 60) + Math.floor(Date.now() / 1000)); + db.sortedSetAdd('reset:issueDate', Date.now(), reset_code); var reset_link = nconf.get('url') + '/reset/' + reset_code; @@ -85,7 +78,7 @@ var async = require('async'), user.setUserField(uid, 'password', hash); db.deleteObjectField('reset:uid', code); - db.deleteObjectField('reset:expiry', code); + db.sortedSetRemove('reset:issueDate', code); user.auth.resetLockout(uid, callback); }); diff --git a/src/views/emails/reset_notify.tpl b/src/views/emails/reset_notify.tpl new file mode 100644 index 0000000000..34c26aa481 --- /dev/null +++ b/src/views/emails/reset_notify.tpl @@ -0,0 +1,10 @@ +

[[email:greeting_with_name, {username}]],

+ +

[[email:reset.notify.text1, {date}]]

+ +

[[email:reset.notify.text2]]

+ +

+ [[email:closing]]
+ {site_title} +

\ No newline at end of file diff --git a/src/views/emails/reset_notify_plaintext.tpl b/src/views/emails/reset_notify_plaintext.tpl new file mode 100644 index 0000000000..788618f9d5 --- /dev/null +++ b/src/views/emails/reset_notify_plaintext.tpl @@ -0,0 +1,8 @@ +[[email:greeting_with_name, {username}]], + +[[email:reset.notify.text1, {date}]] + +[[email:reset.notify.text2]] + +[[email:closing]] +{site_title} \ No newline at end of file