diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index f40a756600..5c5970f1d9 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -200,13 +200,20 @@ module.exports = function(db, module) { }; module.deleteObjectField = function(key, field, callback) { + module.deleteObjectFields(key, [field], callback); + }; + + module.deleteObjectFields = function(key, fields, callback) { callback = callback || helpers.noop; - if (!key || !field) { + if (!key || !Array.isArray(fields) || !fields.length) { return callback(); } var data = {}; - field = helpers.fieldToString(field); - data[field] = ''; + fields.forEach(function(field) { + field = helpers.fieldToString(field); + data[field] = ''; + }); + db.collection('objects').update({_key: key}, {$unset : data}, function(err, res) { callback(err); }); diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 92ae7017c0..ce79d0bc6f 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -94,6 +94,12 @@ module.exports = function(redisClient, module) { }); }; + module.deleteObjectFields = function(key, fields, callback) { + helpers.multiKeyValues(redisClient, 'hdel', key, fields, function(err, results) { + callback(err); + }); + }; + module.incrObjectField = function(key, field, callback) { redisClient.hincrby(key, field, 1, callback); }; diff --git a/src/user/reset.js b/src/user/reset.js index 069ab4dd62..7434c4fc30 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -10,91 +10,109 @@ var async = require('async'), db = require('../database'), meta = require('../meta'), - events = require('../events'), emailer = require('../emailer'); (function(UserReset) { - UserReset.validate = function(code, callback) { - db.getObjectField('reset:uid', code, function(err, uid) { - if (err || !uid) { - return callback(err, false); - } + var twoHours = 7200000; - db.sortedSetScore('reset:issueDate', code, function(err, issueDate) { - if (err) { - return callback(err); + UserReset.validate = function(code, callback) { + async.waterfall([ + function(next) { + db.getObjectField('reset:uid', code, next); + }, + function(uid, next) { + if (!uid) { + return callback(null, false); } - - callback(null, parseInt(issueDate, 10) > (Date.now() - (1000*60*120))); - }); - }); + db.sortedSetScore('reset:issueDate', code, next); + }, + function(issueDate, next) { + next(null, parseInt(issueDate, 10) > Date.now() - twoHours); + } + ], callback); }; UserReset.send = function(email, callback) { - user.getUidByEmail(email, function(err, uid) { - if (err || !uid) { - return callback(err || new Error('[[error:invalid-email]]')); - } - - var reset_code = utils.generateUUID(); - db.setObjectField('reset:uid', reset_code, uid); - db.sortedSetAdd('reset:issueDate', Date.now(), reset_code); - - var reset_link = nconf.get('url') + '/reset/' + reset_code; + var reset_code = utils.generateUUID(); + var uid; + async.waterfall([ + function(next) { + user.getUidByEmail(email, next); + }, + function(_uid, next) { + if (!_uid) { + return next(new Error('[[error:invalid-email]]')); + } - translator.translate('[[email:password-reset-requested, ' + (meta.config.title || 'NodeBB') + ']]', meta.config.defaultLang, function(subject) { + uid = _uid; + async.parallel([ + async.apply(db.setObjectField, 'reset:uid', reset_code, uid), + async.apply(db.sortedSetAdd, 'reset:issueDate', Date.now(), reset_code) + ], next); + }, + function(results, next) { + translator.translate('[[email:password-reset-requested, ' + (meta.config.title || 'NodeBB') + ']]', meta.config.defaultLang, function(subject) { + next(null, subject); + }); + }, + function(subject, next) { + var reset_link = nconf.get('url') + '/reset/' + reset_code; emailer.send('reset', uid, { site_title: (meta.config.title || 'NodeBB'), reset_link: reset_link, subject: subject, template: 'reset', uid: uid - }); - callback(null, reset_code); - }); - }); + }, next); + }, + function(next) { + next(null, reset_code); + } + ], callback); }; UserReset.commit = function(code, password, callback) { - UserReset.validate(code, function(err, validated) { - if(err) { - return callback(err); - } - - if (!validated) { - return callback(new Error('[[error:reset-code-not-valid]]')); - } - - db.getObjectField('reset:uid', code, function(err, uid) { - if (err) { - return callback(err); + var uid; + async.waterfall([ + function(next) { + UserReset.validate(code, next); + }, + function(validated, next) { + if (!validated) { + return next(new Error('[[error:reset-code-not-valid]]')); + } + db.getObjectField('reset:uid', code, next); + }, + function(_uid, next) { + uid = _uid; + if (!uid) { + return next(new Error('[[error:reset-code-not-valid]]')); } - user.hashPassword(password, function(err, hash) { - if (err) { - return callback(err); - } - user.setUserField(uid, 'password', hash); - - db.deleteObjectField('reset:uid', code); - db.sortedSetRemove('reset:issueDate', code); - - user.auth.resetLockout(uid, callback); - }); - }); - }); + user.hashPassword(password, next); + }, + function(hash, next) { + async.parallel([ + async.apply(user.setUserField, uid, 'password', hash), + async.apply(db.deleteObjectField, 'reset:uid', code), + async.apply(db.sortedSetRemove, 'reset:issueDate', code), + async.apply(user.auth.resetLockout, uid) + ], next); + } + ], callback); }; UserReset.clean = function(callback) { - // Locate all codes that have expired, and remove them from the set/hash async.waterfall([ - async.apply(db.getSortedSetRangeByScore, 'reset:issueDate', 0, -1, -1, +new Date()-(1000*60*120)), + async.apply(db.getSortedSetRangeByScore, 'reset:issueDate', 0, -1, 0, Date.now() - twoHours), function(tokens, next) { - if (!tokens.length) { return next(); } + if (!tokens.length) { + return next(); + } winston.verbose('[UserReset.clean] Removing ' + tokens.length + ' reset tokens from database'); async.parallel([ - async.apply(db.deleteObjectField, 'reset:uid', tokens), + async.apply(db.deleteObjectFields, 'reset:uid', tokens), async.apply(db.sortedSetRemove, 'reset:issueDate', tokens) ], next); } diff --git a/tests/database/hash.js b/tests/database/hash.js index 5d1d002c3b..5e294f3c4b 100644 --- a/tests/database/hash.js +++ b/tests/database/hash.js @@ -263,7 +263,7 @@ describe('Hash methods', function() { describe('deleteObjectField()', function() { before(function(done) { - db.setObject('testObject10', {foo: 'bar', delete: 'this'}, done); + db.setObject('testObject10', {foo: 'bar', delete: 'this', delete1: 'this', delete2: 'this'}, done); }); it('should delete an objects field', function(done) { @@ -277,6 +277,22 @@ describe('Hash methods', function() { }); }); }); + + it('should delete multiple fields of the object', function(done) { + db.deleteObjectFields('testObject10', ['delete1', 'delete2'], function(err) { + assert.ifError(err); + assert.equal(arguments.length, 1); + async.parallel({ + delete1: async.apply(db.isObjectField, 'testObject10', 'delete1'), + delete2: async.apply(db.isObjectField, 'testObject10', 'delete2') + }, function(err, results) { + assert.ifError(err); + assert.equal(results.delete1, false); + assert.equal(results.delete2, false); + done(); + }); + }); + }); }); describe('incrObjectField()', function() {