From fa9fde43fb1e3d9a67000b7f69261d8eb150f3d5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 13 Apr 2015 17:29:43 -0400 Subject: [PATCH] closes #2971 --- public/language/en_GB/error.json | 1 + public/src/client/account/edit.js | 2 + src/database/redis/main.js | 16 +++-- src/socket.io/admin/user.js | 6 +- src/socket.io/user.js | 3 +- src/user/create.js | 2 +- src/user/email.js | 101 ++++++++++++++++-------------- src/user/profile.js | 2 +- 8 files changed, 75 insertions(+), 58 deletions(-) diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index a8d4c042b5..11f507b168 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -26,6 +26,7 @@ "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email", "email-confirm-failed": "We could not confirm your email, please try again later.", + "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minutes to send another one.", "username-too-short": "Username too short", "username-too-long": "Username too long", diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index 4b2e0ff93d..e44d9f3f81 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -220,7 +220,9 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator'], function handleEmailConfirm() { $('#confirm-email').on('click', function() { + var btn = $(this).attr('disabled', true); socket.emit('user.emailConfirm', {}, function(err) { + btn.removeAttr('disabled'); if (err) { return app.alertError(err.message); } diff --git a/src/database/redis/main.js b/src/database/redis/main.js index b09574e10f..59ae28827f 100644 --- a/src/database/redis/main.js +++ b/src/database/redis/main.js @@ -105,18 +105,26 @@ module.exports = function(redisClient, module) { }; module.expire = function(key, seconds, callback) { - redisClient.expire(key, seconds, callback); + redisClient.expire(key, seconds, function(err) { + callback(err); + }); }; module.expireAt = function(key, timestamp, callback) { - redisClient.expireat(key, timestamp, callback); + redisClient.expireat(key, timestamp, function(err) { + callback(err); + }); }; module.pexpire = function(key, ms, callback) { - redisClient.pexpire(key, ms, callback); + redisClient.pexpire(key, ms, function(err) { + callback(err); + }); }; module.pexpireAt = function(key, timestamp, callback) { - redisClient.pexpireat(key, timestamp, callback); + redisClient.pexpireat(key, timestamp, function(err) { + callback(err); + }); }; }; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 0fa583d742..454a51741a 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -1,7 +1,7 @@ "use strict"; -var async = require('async'), +var async = require('async'), db = require('../../database'), groups = require('../../groups'), user = require('../../user'), @@ -132,7 +132,7 @@ User.sendValidationEmail = function(socket, uids, callback) { if (!Array.isArray(uids)) { return callback(new Error('[[error:invalid-data]]')); } - + if (parseInt(meta.config.requireEmailConfirmation, 10) !== 1) { return callback(new Error('[[error:email-confirmations-are-disabled]]')); } @@ -144,7 +144,7 @@ User.sendValidationEmail = function(socket, uids, callback) { async.eachLimit(usersData, 50, function(userData, next) { if (userData.email && userData.uid) { - user.email.verify(userData.uid, userData.email, next); + user.email.sendValidationEmail(userData.uid, userData.email, next); } else { next(); } diff --git a/src/socket.io/user.js b/src/socket.io/user.js index e395418142..02a7aee65e 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -53,8 +53,7 @@ SocketUser.emailConfirm = function(socket, data, callback) { return; } - user.email.verify(socket.uid, email); - callback(); + user.email.sendValidationEmail(socket.uid, email, callback); }); } }; diff --git a/src/user/create.js b/src/user/create.js index d11669099f..db85c81508 100644 --- a/src/user/create.js +++ b/src/user/create.js @@ -116,7 +116,7 @@ module.exports = function(User) { if (userData.email) { db.setObjectField('email:uid', userData.email.toLowerCase(), userData.uid, next); if (parseInt(userData.uid, 10) !== 1 && parseInt(meta.config.requireEmailConfirmation, 10) === 1) { - User.email.verify(userData.uid, userData.email); + User.email.sendValidationEmail(userData.uid, userData.email); } } else { next(); diff --git a/src/user/email.js b/src/user/email.js index 973a7e844e..b4c6460683 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -27,61 +27,68 @@ var async = require('async'), }); }; - UserEmail.verify = function(uid, email, callback) { + UserEmail.sendValidationEmail = function(uid, email, callback) { callback = callback || function() {}; var confirm_code = utils.generateUUID(), confirm_link = nconf.get('url') + '/confirm/' + confirm_code; - plugins.fireHook('filter:user.verify.code', confirm_code, function(err, confirm_code) { - if (err) { - return callback(err); - } + var emailInterval = 10; - async.series([ - function(next) { - db.setObject('confirm:' + confirm_code, { - email: email.toLowerCase(), + async.waterfall([ + function(next) { + db.get('uid:' + uid + ':confirm:email:sent', next); + }, + function(sent, next) { + if (sent) { + return next(new Error('[[error:confirm-email-already-sent, ' + emailInterval + ']]')); + } + db.set('uid:' + uid + ':confirm:email:sent', 1, next); + }, + function(next) { + db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000), next); + }, + function(next) { + plugins.fireHook('filter:user.verify.code', confirm_code, next); + }, + function(_confirm_code, next) { + confirm_code = _confirm_code; + db.setObject('confirm:' + confirm_code, { + email: email.toLowerCase(), + uid: uid + }, next); + }, + function(next) { + db.expireAt('confirm:' + confirm_code, Math.floor(Date.now() / 1000 + 60 * 60 * 2), next); + }, + function(next) { + user.getUserField(uid, 'username', next); + }, + function(username, next) { + var title = meta.config.title || meta.config.browserTitle || 'NodeBB'; + translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function(subject) { + var data = { + site_title: title, + username: username, + confirm_link: confirm_link, + confirm_code: confirm_code, + + subject: subject, + template: 'welcome', uid: uid - }, next); - }, - function(next) { - db.expireAt('confirm:' + confirm_code, Math.floor(Date.now() / 1000 + 60 * 60 * 2), next); - } - ], function(err) { - if (err) { - return callback(err); - } - user.getUserField(uid, 'username', function(err, username) { - if (err) { - return winston.error(err.stack); + }; + + if (plugins.hasListeners('action:user.verify')) { + plugins.fireHook('action:user.verify', {uid: uid, data: data}); + next(); + } else if (plugins.hasListeners('action:email.send')) { + emailer.send('welcome', uid, data, next); + } else { + winston.warn('No emailer to send verification email!'); + next(); } - - var title = meta.config.title || meta.config.browserTitle || 'NodeBB'; - translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function(subject) { - var data = { - site_title: title, - username: username, - confirm_link: confirm_link, - confirm_code: confirm_code, - - subject: subject, - template: 'welcome', - uid: uid - }; - - if (plugins.hasListeners('action:user.verify')) { - plugins.fireHook('action:user.verify', {uid: uid, data: data}); - callback(); - } else if (plugins.hasListeners('action:email.send')) { - emailer.send('welcome', uid, data, callback); - } else { - winston.warn('No emailer to send verification email!'); - callback(); - } - }); }); - }); - }); + } + ], callback); }; UserEmail.confirm = function(code, callback) { diff --git a/src/user/profile.js b/src/user/profile.js index a284165061..1eb66893ca 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -162,7 +162,7 @@ module.exports = function(User) { }, function(next) { if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && newEmail) { - User.email.verify(uid, newEmail); + User.email.sendValidationEmail(uid, newEmail); } User.setUserField(uid, 'email:confirmed', 0, next); },