From 03ae7db56154a17a447df1d896969eb9922ef224 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 17 Jan 2015 16:22:04 -0500 Subject: [PATCH 01/30] on user delete remove from ip sorted set, fix search by ip --- src/user/delete.js | 22 +++++++++++++++++++++- src/user/search.js | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/user/delete.js b/src/user/delete.js index c115c727f1..b13fa1aa97 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -79,11 +79,14 @@ module.exports = function(User) { 'uid:' + uid + ':favourites', 'uid:' + uid + ':followed_tids', 'user:' + uid + ':settings', 'uid:' + uid + ':topics', 'uid:' + uid + ':posts', 'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread', - 'uid:' + uid + ':ip', 'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote', + 'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote', 'uid:' + uid + ':ignored:cids' ]; db.deleteAll(keys, next); }, + function(next) { + deleteUserIps(uids, next); + }, function(next) { deleteUserFromFollowers(uid, next); }, @@ -110,6 +113,23 @@ module.exports = function(User) { }); }; + function deleteUserIps(uid, callback) { + db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, function(err, ips) { + if (err) { + return callback(err); + } + + async.each(ips, function(ip, next) { + db.sortedSetRemove('ip:' + ip + ':uid', uid, next); + }, function(err) { + if (err) { + return callback(err); + } + db.delete('uid:' + uid + ':ip', callback); + }); + }) + } + function deleteUserFromFollowers(uid, callback) { db.getSetMembers('followers:' + uid, function(err, uids) { if (err) { diff --git a/src/user/search.js b/src/user/search.js index 1ea83a863f..a95b64bfaa 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -18,7 +18,7 @@ module.exports = function(User) { return callback(null, {timing: 0, users: [], matchCount: 0, pages: []}); } - if (searchBy === 'ip') { + if (searchBy.indexOf('ip') !== -1) { return searchByIP(query, callback); } From e46cda835a1d0b83954d55dfcc841ae71736090f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Jan 2015 14:31:37 -0500 Subject: [PATCH 02/30] closes #2624 --- src/categories/update.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/categories/update.js b/src/categories/update.js index e103d3e5dc..f68a7aed20 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -11,12 +11,18 @@ module.exports = function(Categories) { Categories.update = function(modified, callback) { function updateCategory(cid, next) { - var category = modified[cid]; - var fields = Object.keys(category); - - async.each(fields, function(key, next) { - updateCategoryField(cid, key, category[key], next); - }, next); + Categories.exists(cid, function(err, exists) { + if (err || !exists) { + return next(err); + } + + var category = modified[cid]; + var fields = Object.keys(category); + + async.each(fields, function(key, next) { + updateCategoryField(cid, key, category[key], next); + }, next); + }); } var cids = Object.keys(modified); From f5b448c300e10f865d6c5db55c21d3043ff08667 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Jan 2015 14:32:47 -0500 Subject: [PATCH 03/30] closes #2623 --- src/categories/delete.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/categories/delete.js b/src/categories/delete.js index d01dd13317..0ebac949d7 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -31,6 +31,7 @@ module.exports = function(Categories) { 'cid:' + cid + ':tids', 'cid:' + cid + ':tids:posts', 'cid:' + cid + ':pids', + 'cid:' + cid + ':read_by_uid', 'category:' + cid ], next); } From fe45fc967d3aae8f845bd7435dd2e1d3ca3ec838 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 18 Jan 2015 16:21:26 -0500 Subject: [PATCH 04/30] updating maintenance mode middleware so js doesn't crash until login client-side js is ready --- src/middleware/middleware.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 334e6d0b5a..f71c2b638f 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -450,7 +450,9 @@ middleware.maintenanceMode = function(req, res, next) { '/login', '/stylesheet.css', '/nodebb.min.js', - '/vendor/fontawesome/fonts/fontawesome-webfont.woff' + '/vendor/fontawesome/fonts/fontawesome-webfont.woff', + '/src/modules/[\\w]+\.js', + '/api/get_templates_listing' ], render = function() { res.status(503); From 491e0060d410c6f04dd3b1da884bdca6840fb614 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 18 Jan 2015 16:52:50 -0500 Subject: [PATCH 05/30] more files that should be loaded for maintenanceMode to work --- src/middleware/middleware.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index f71c2b638f..2d5faace76 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -452,7 +452,10 @@ middleware.maintenanceMode = function(req, res, next) { '/nodebb.min.js', '/vendor/fontawesome/fonts/fontawesome-webfont.woff', '/src/modules/[\\w]+\.js', - '/api/get_templates_listing' + '/api/get_templates_listing', + '/api/login', + '/api/?', + '/language/.+' ], render = function() { res.status(503); From 4317b3c8721c1a5f8ad49b6f7302df6ab04f1b45 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 18 Jan 2015 17:03:08 -0500 Subject: [PATCH 06/30] fixing more exposed XSS outlets in groups frontend --- public/src/client/groups/details.js | 4 ++-- public/src/client/groups/list.js | 2 +- public/src/utils.js | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 0ef47b3fd8..7ad5702a7d 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -134,7 +134,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', }; Details.deleteGroup = function() { - bootbox.confirm('Are you sure you want to delete the group: ' + ajaxify.variables.get('group_name'), function(confirm) { + bootbox.confirm('Are you sure you want to delete the group: ' + utils.escapeHTML(ajaxify.variables.get('group_name')), function(confirm) { if (confirm) { bootbox.prompt('Please enter the name of this group in order to delete it:', function(response) { if (response === ajaxify.variables.get('group_name')) { @@ -142,7 +142,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', groupName: ajaxify.variables.get('group_name') }, function(err) { if (!err) { - app.alertSuccess('[[groups:event.deleted, ' + ajaxify.variables.get('group_name') + ']]'); + app.alertSuccess('[[groups:event.deleted, ' + utils.escapeHTML(ajaxify.variables.get('group_name')) + ']]'); ajaxify.go('groups'); } else { app.alertError(err.message); diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index d4a92ac9aa..7022a64725 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -21,7 +21,7 @@ define('forum/groups/list', function() { name: name }, function(err) { if (!err) { - ajaxify.go('groups/' + name); + ajaxify.go('groups/' + encodeURIComponent(name)); } else { app.alertError(err.message); } diff --git a/public/src/utils.js b/public/src/utils.js index a3f3be9648..27a4f2e8e5 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -248,6 +248,10 @@ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); }, + escapeHTML: function(raw) { + return raw.replace(/&/gm,"&").replace(//gm,">"); + }, + isAndroidBrowser: function() { // http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser var nua = navigator.userAgent; From 6f140384e0995fdb61930c0206e2f1bbab09250f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Jan 2015 17:07:21 -0500 Subject: [PATCH 07/30] send groupData on action:group.create --- src/groups.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/groups.js b/src/groups.js index 8f64ea57bd..7615666903 100644 --- a/src/groups.js +++ b/src/groups.js @@ -434,9 +434,7 @@ var async = require('async'), async.parallel(tasks, function(err) { if (!err) { - plugins.fireHook('action:group.create', { - name: data.name - }); + plugins.fireHook('action:group.create', groupData); } callback(err); From c026a64e8e05764b26bad2849d561d50511a6461 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Sun, 18 Jan 2015 17:18:53 -0500 Subject: [PATCH 08/30] adding concept of group slugs, #2588. ping @barisusakli for downstream plugin changes --- public/src/client/groups/list.js | 6 +++--- src/controllers/groups.js | 2 +- src/groups.js | 27 ++++++++++++++++++++++++--- src/routes/index.js | 2 +- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index 7022a64725..d11eb7c8e6 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -1,5 +1,5 @@ "use strict"; -/* globals app, define, ajaxify, socket, bootbox */ +/* globals app, define, ajaxify, socket, bootbox, utils */ define('forum/groups/list', function() { var Groups = {}; @@ -10,7 +10,7 @@ define('forum/groups/list', function() { groupsEl.on('click', '.list-cover', function() { var groupName = $(this).parents('[data-group]').attr('data-group'); - ajaxify.go('groups/' + encodeURIComponent(groupName)); + ajaxify.go('groups/' + utils.slugify(groupName)); }); // Group creation @@ -21,7 +21,7 @@ define('forum/groups/list', function() { name: name }, function(err) { if (!err) { - ajaxify.go('groups/' + encodeURIComponent(name)); + ajaxify.go('groups/' + utils.slugify(name)); } else { app.alertError(err.message); } diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 2d2ce8f050..d6307b8daf 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -26,7 +26,7 @@ groupsController.details = function(req, res, next) { async.parallel({ group: function(next) { - groups.get(req.params.name, { + groups.getByGroupslug(req.params.slug, { expand: true, uid: uid }, next); diff --git a/src/groups.js b/src/groups.js index 7615666903..6d2b5a3e7b 100644 --- a/src/groups.js +++ b/src/groups.js @@ -193,8 +193,10 @@ var async = require('async'), }); } }, function (err, results) { - if (err || !results.base) { + if (err) { return callback(err); + } else if (!results.base) { + return callback(new Error('[[error:no-group]]')); } // Default image @@ -224,6 +226,18 @@ var async = require('async'), }); }; + Groups.getByGroupslug = function(slug, options, callback) { + db.getObjectField('groupslug:groupname', slug, function(err, groupName) { + if (err) { + return callback(err); + } else if (!groupName) { + return callback(new Error('[[error:no-group]]')); + } + + Groups.get.call(Groups, groupName, options, callback); + }); + }; + Groups.getGroupFields = function(groupName, fields, callback) { db.getObjectFields('group:' + groupName, fields, callback); }; @@ -413,8 +427,10 @@ var async = require('async'), return callback(new Error('[[error:group-already-exists]]')); } - var groupData = { + var slug = utils.slugify(data.name), + groupData = { name: data.name, + slug: slug, userTitle: data.name, description: data.description || '', deleted: '0', @@ -432,6 +448,10 @@ var async = require('async'), tasks.push(async.apply(db.setAdd, 'group:' + data.name + ':members', data.ownerUid)); } + if (!data.hidden) { + tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', slug, data.name)); + } + async.parallel(tasks, function(err) { if (!err) { plugins.fireHook('action:group.create', groupData); @@ -567,6 +587,7 @@ var async = require('async'), async.apply(db.delete, 'group:' + groupName + ':members'), async.apply(db.delete, 'group:' + groupName + ':pending'), async.apply(db.delete, 'group:' + groupName + ':owners'), + async.apply(db.deleteObjectField, 'groupslug:groupname', utils.slugify(groupName)), function(next) { db.getSetMembers('groups', function(err, groups) { if (err) { @@ -578,7 +599,7 @@ var async = require('async'), }); } ], callback); - }) + }); }; Groups.join = function(groupName, uid, callback) { diff --git a/src/routes/index.js b/src/routes/index.js index c189404e5f..b4427f90f1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -93,7 +93,7 @@ function groupRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; setupPageRoute(app, '/groups', middleware, middlewares, controllers.groups.list); - setupPageRoute(app, '/groups/:name', middleware, middlewares, controllers.groups.details); + setupPageRoute(app, '/groups/:slug', middleware, middlewares, controllers.groups.details); } function setupPageRoute(router, name, middleware, middlewares, controller) { From 94fa212eabe473e5ef96820de8f9a8830edfa6e3 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Jan 2015 17:35:11 -0500 Subject: [PATCH 09/30] added filter:groups.get --- src/groups.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 6d2b5a3e7b..62f5d5c7dd 100644 --- a/src/groups.js +++ b/src/groups.js @@ -222,7 +222,9 @@ var async = require('async'), results.base.isPending = results.isPending; results.base.isOwner = results.isOwner; - callback(err, results.base); + plugins.fireHook('filter:groups.get', {group: results.base}, function(err, data) { + callback(err, data ? data.group : null); + }); }); }; From 9f8b274e24ced1641cc367e40cc2e34ecee68cdd Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 18 Jan 2015 17:35:58 -0500 Subject: [PATCH 10/30] filter:group.get --- src/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 62f5d5c7dd..100550416c 100644 --- a/src/groups.js +++ b/src/groups.js @@ -222,7 +222,7 @@ var async = require('async'), results.base.isPending = results.isPending; results.base.isOwner = results.isOwner; - plugins.fireHook('filter:groups.get', {group: results.base}, function(err, data) { + plugins.fireHook('filter:group.get', {group: results.base}, function(err, data) { callback(err, data ? data.group : null); }); }); From e5a33539d2f37b76de40be6f9c193920037b2fe4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 10:26:36 -0500 Subject: [PATCH 11/30] updated globals for jslint --- public/src/client/groups/details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 7ad5702a7d..316b3083ec 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -1,5 +1,5 @@ "use strict"; -/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH */ +/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect) { var Details = { From 1e57c5d00173fb258ae44b60c0ab4199d65cd3f2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 10:39:44 -0500 Subject: [PATCH 12/30] linting --- public/src/utils.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/public/src/utils.js b/public/src/utils.js index 27a4f2e8e5..bdb8bf1a81 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -119,7 +119,7 @@ str = XRegExp.replace(str, utils.invalidUnicodeChars, '-'); } str = str.toLocaleLowerCase(); - str = str.replace(utils.collapseWhitespace, '-') + str = str.replace(utils.collapseWhitespace, '-'); str = str.replace(utils.collapseDash, '-'); str = str.replace(utils.trimTrailingDash, ''); str = str.replace(utils.trimLeadingDash, ''); @@ -193,7 +193,7 @@ return function (path) { var extension = utils.fileExtension(path); return map[extension] || '*'; - } + }; })(), isRelativeUrl: function(url) { @@ -293,8 +293,9 @@ key = decodeURI(val[0]), value = options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1])); - if (key) + if (key) { hash[key] = value; + } }); return hash; }, @@ -335,12 +336,15 @@ return str; } else { var nb = parseFloat(str); - if (!isNaN(nb) && isFinite(str)) + if (!isNaN(nb) && isFinite(str)) { return nb; - if (str === 'false') + } + if (str === 'false') { return false; - if (str === 'true') + } + if (str === 'true') { return true; + } try { str = JSON.parse(str); @@ -356,21 +360,25 @@ // get example: utils.props(A, 'a.b.c.foo.bar') // returns undefined without throwing a TypeError // credits to github.com/gkindel props: function(obj, props, value) { - if(obj === undefined) + if(obj === undefined) { obj = window; - if(props == null) + } + if(props == null) { return undefined; + } var i = props.indexOf('.'); if( i == -1 ) { - if(value !== undefined) + if(value !== undefined) { obj[props] = value; + } return obj[props]; } var prop = props.slice(0, i), newProps = props.slice(i + 1); - if(props !== undefined && !(obj[prop] instanceof Object) ) + if(props !== undefined && !(obj[prop] instanceof Object) ) { obj[prop] = {}; + } return utils.props(obj[prop], newProps, value); } @@ -378,10 +386,12 @@ if (typeof String.prototype.startsWith != 'function') { String.prototype.startsWith = function (prefix){ - if (this.length < prefix.length) + if (this.length < prefix.length) { return false; - for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) + } + for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) { continue; + } return i < 0; }; } From eb5b8ccec37f00075f2dbe394db489cddf29169e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 10:46:14 -0500 Subject: [PATCH 13/30] better check for group exist, groups with different cases can no longer be created, #2588 --- src/database/redis/hash.js | 18 ++++++++++++++++++ src/groups.js | 26 +++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index ddc21b893c..322440a656 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -88,6 +88,24 @@ module.exports = function(redisClient, module) { }); }; + module.isObjectFields = function(key, fields, callback) { + var multi = redisClient.multi(); + for (var i=0; i Date: Mon, 19 Jan 2015 12:59:32 -0500 Subject: [PATCH 14/30] send values to plugins when updating groups --- src/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 69e33772c0..11539576e5 100644 --- a/src/groups.js +++ b/src/groups.js @@ -512,7 +512,7 @@ var async = require('async'), plugins.fireHook('action:group.update', { name: groupName, - values: payload + values: values }); renameGroup(groupName, values.name, callback); }); From a926a11e701d5c023bf4540a397b6b441f3fb15b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 12:59:49 -0500 Subject: [PATCH 15/30] fix redirection when saving groups --- public/src/client/groups/details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 316b3083ec..b667176ae9 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -122,7 +122,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', if (settings.name) { var pathname = window.location.pathname; pathname = pathname.substr(1, pathname.lastIndexOf('/')); - ajaxify.go(pathname + encodeURIComponent(settings.name)); + ajaxify.go(pathname + utils.slugify(settings.name)); } else { ajaxify.refresh(); } From 4c62590bc8db5a08e6f91cd44023b21cdb677345 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 14:13:29 -0500 Subject: [PATCH 16/30] closes #2628 --- src/database/mongo/hash.js | 25 +++++++++++++++++++++++++ tests/database/hash.js | 23 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 6db3b5a4a9..f40a756600 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -174,6 +174,31 @@ module.exports = function(db, module) { }); }; + module.isObjectFields = function(key, fields, callback) { + if (!key) { + return callback(); + } + + var data = {}; + fields.forEach(function(field) { + field = helpers.fieldToString(field); + data[field] = ''; + }); + + db.collection('objects').findOne({_key: key}, {fields: data}, function(err, item) { + if (err) { + return callback(err); + } + var results = []; + + fields.forEach(function(field, index) { + results[index] = !!item && item[field] !== undefined && item[field] !== null; + }); + + callback(null, results); + }); + }; + module.deleteObjectField = function(key, field, callback) { callback = callback || helpers.noop; if (!key || !field) { diff --git a/tests/database/hash.js b/tests/database/hash.js index c67b57fd64..5d1d002c3b 100644 --- a/tests/database/hash.js +++ b/tests/database/hash.js @@ -222,7 +222,7 @@ describe('Hash methods', function() { }); it('should return false if field does not exist', function(done) { - db.isObjectField('testObject1', 'field1', function(err, value) { + db.isObjectField('hashTestObject', 'field1', function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); assert.equal(value, false); @@ -240,6 +240,27 @@ describe('Hash methods', function() { }); }); + + describe('isObjectFields()', function() { + it('should return an array of false if object does not exist', function(done) { + db.isObjectFields('doesnotexist', ['field1', 'field2'], function(err, values) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(values, [false, false]); + done(); + }); + }); + + it('should return false if field does not exist', function(done) { + db.isObjectFields('hashTestObject', ['name', 'age', 'field1'], function(err, values) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(values, [true, true, false]); + done(); + }); + }); + }); + describe('deleteObjectField()', function() { before(function(done) { db.setObject('testObject10', {foo: 'bar', delete: 'this'}, done); From c0622743d22741b13fd5e75ab163c49258c8907a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 15:03:28 -0500 Subject: [PATCH 17/30] closes #2629 --- tests/groups.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/groups.js b/tests/groups.js index 3d24c6f5ae..22300193dc 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -223,9 +223,8 @@ describe('Groups', function() { Groups.destroy('foo', function(err) { if (err) return done(err); - Groups.get('foo', {}, function(err, groupObj) { - if (err) return done(err); - assert.strictEqual(undefined, groupObj); + Groups.get('foo', {}, function(err) { + assert(err, 'Group still exists!'); done(); }); From 5160ab1ed11adf731e405b4a5de70b663022c389 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 15:09:36 -0500 Subject: [PATCH 18/30] closed #2625 --- src/groups.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/groups.js b/src/groups.js index 11539576e5..42f1e6528f 100644 --- a/src/groups.js +++ b/src/groups.js @@ -542,6 +542,15 @@ var async = require('async'), function(next) { db.setObjectField('group:' + oldName, 'name', newName, next); }, + function(next) { + db.setObjectField('group:' + oldName, 'slug', utils.slugify(newName), next); + }, + function(next) { + db.deleteObjectField('groupslug:groupname', group.slug, next); + }, + function(next) { + db.setObjectField('groupslug:groupname', utils.slugify(newName), newName, next); + }, function(next) { db.getSetMembers('groups', function(err, groups) { if (err) { From d96a1f03658aee3bbd0c16ca32318b5279055f6d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 15:12:05 -0500 Subject: [PATCH 19/30] closes #2630 --- src/meta/title.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/meta/title.js b/src/meta/title.js index a476cb61a3..11af43ae9b 100644 --- a/src/meta/title.js +++ b/src/meta/title.js @@ -26,7 +26,6 @@ module.exports = function(Meta) { } Meta.title.parseFragment(uri, language, locals, function(err, title) { - if (err) { title = fallbackTitle; } else { @@ -41,7 +40,6 @@ module.exports = function(Meta) { }; Meta.title.parseFragment = function (urlFragment, language, locals, callback) { - urlFragment = validator.escape(urlFragment); var translated = ['', 'recent', 'unread', 'users', 'notifications']; if (translated.indexOf(urlFragment) !== -1) { if (!urlFragment.length) { @@ -75,7 +73,7 @@ module.exports = function(Meta) { return callback(err); } - if (locals.notFound) { + if (!username) { username = '[[error:no-user]]'; } From 81c929fa77395a20eb16e47a56d567676bfd8d16 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 15:14:34 -0500 Subject: [PATCH 20/30] added a new test for group renaming, #2629 --- tests/groups.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/groups.js b/tests/groups.js index 22300193dc..47da1b8bbb 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -212,18 +212,35 @@ describe('Groups', function() { }); }); }); + + it('should rename a group if the name was updated', function(done) { + Groups.update('foo', { + name: 'foobar?' + }, function(err) { + if (err) return done(err); + + Groups.get('foobar?', {}, function(err, groupObj) { + if (err) return done(err); + + assert.strictEqual('foobar?', groupObj.name); + assert.strictEqual('foobar', groupObj.slug); + + done(); + }); + }); + }); }); describe('.destroy()', function() { before(function(done) { - Groups.join('foo', 1, done); + Groups.join('foobar?', 1, done); }); it('should destroy a group', function(done) { - Groups.destroy('foo', function(err) { + Groups.destroy('foobar?', function(err) { if (err) return done(err); - Groups.get('foo', {}, function(err) { + Groups.get('foobar?', {}, function(err) { assert(err, 'Group still exists!'); done(); From d2938ea509ed89f66919a1b06b30f7f3829c4eaa Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 15:26:50 -0500 Subject: [PATCH 21/30] closed #2626 --- src/upgrade.js | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/upgrade.js b/src/upgrade.js index 35f3acaa87..b28158d826 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, 15); + latestSchema = Date.UTC(2015, 0, 19); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -72,6 +72,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 9, 31); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/10/31] Applying newbiePostDelay values'); async.series([ @@ -93,6 +94,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 6, 18, 30); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/6] Updating topic authorship sorted set'); async.waterfall([ @@ -138,6 +140,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 7); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/7] Renaming sorted set names'); async.waterfall([ @@ -199,6 +202,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 11); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/11] Upgrading permissions'); async.waterfall([ @@ -272,6 +276,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 17, 13); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/17] Updating user email digest settings'); async.waterfall([ @@ -306,6 +311,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 29, 22); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/29] Updating config.json to new format'); var configPath = path.join(__dirname, '../config.json'); @@ -358,6 +364,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 2); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/2] Removing register user fields'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -393,6 +400,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 12); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/12] Updating teasers'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -419,6 +427,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 20); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/20] Updating digest settings'); async.waterfall([ @@ -455,6 +464,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 8); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/08] Updating category topics sorted sets'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -494,6 +504,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 9); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/09] Creating fullname:uid hash'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -529,6 +540,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 13); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/13] Creating uid:followed_tids sorted set'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -570,6 +582,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 14); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/14] Upgrading follow sets to sorted sets'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -634,11 +647,12 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 15); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/15] Creating topiccount for users'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { if (err) { - winston.error('[2014/01/15] Error encountered while Creating topiccount for users'); + winston.error('[2015/01/15] Error encountered while Creating topiccount for users'); return next(err); } @@ -668,6 +682,34 @@ Upgrade.upgrade = function(callback) { next(); } }, + function(next) { + thisSchemaDate = Date.UTC(2015, 0, 19); + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2015/01/19] Generating group slugs'); + + Groups.list({}, function(err, groups) { + var tasks = []; + groups.forEach(function(groupObj) { + tasks.push(async.apply(db.setObjectField, 'group:' + groupObj.name, 'slug', Utils.slugify(groupObj.name))); + tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', Utils.slugify(groupObj.name), groupObj.name)); + }); + + async.parallel(tasks, function(err) { + if (err) { + winston.error('[2015/01/19] Error encountered while Generating group slugs'); + return next(err); + } + + winston.info('[2015/01/19] Generating group slugs done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2015/01/19] Generating group slugs skipped'); + next(); + } + } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!! From 99dc37bd9c5c8ad3d01cf99f03353529e96e811c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 19 Jan 2015 15:43:22 -0500 Subject: [PATCH 22/30] removed relativeTime util method, utils.js better be a C now. --- public/src/modules/notifications.js | 8 +++++- public/src/translator.js | 28 +++++++++++++++++++++ public/src/utils.js | 38 ----------------------------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 46e6833806..319d88041f 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -25,11 +25,14 @@ define('notifications', ['sounds'], function(sound) { image = ''; } - return '
  • ' + image + '' + utils.relativeTime(notification.datetime, true) + '' + notification.bodyShort + '
  • '; + return '
  • ' + image + '' + $.timeago(new Date(parseInt(notification.datetime, 10))) + '' + notification.bodyShort + '
  • '; } var x, html = ''; + // Switch to shorthand + translator.toggleTimeagoShorthand(); + if (!err && (data.read.length + data.unread.length) > 0) { var image = ''; for (x = 0; x < data.unread.length; x++) { @@ -43,6 +46,9 @@ define('notifications', ['sounds'], function(sound) { html += '
  • [[notifications:no_notifs]]
  • '; } + // Switch back to original timeago strings + translator.toggleTimeagoShorthand(); + html += ''; notifList.translateHtml(html); diff --git a/public/src/translator.js b/public/src/translator.js index 07794f360c..5b052af216 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -85,6 +85,34 @@ } }; + translator.toggleTimeagoShorthand = function() { + if (!translator.timeagoStrings) { + translator.timeagoStrings = $.extend({}, jQuery.timeago.settings.strings); + jQuery.timeago.settings.strings = { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "", + suffixFromNow: "", + seconds: "1m", + minute: "1m", + minutes: "%dm", + hour: "1h", + hours: "%dh", + day: "1d", + days: "%dd", + month: "1mo", + months: "%dmo", + year: "1yr", + years: "%dyr", + wordSeparator: " ", + numbers: [] + }; + } else { + jQuery.timeago.settings.strings = $.extend({}, translator.timeagoStrings); + delete translator.timeagoStrings; + } + }; + translator.translate = function (text, language, callback) { if (typeof language === 'function') { callback = language; diff --git a/public/src/utils.js b/public/src/utils.js index bdb8bf1a81..b9658593cc 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -63,44 +63,6 @@ }); }, - relativeTime: function(timestamp, min) { - var now = +new Date(), - difference = now - Math.floor(parseFloat(timestamp)); - - if(difference < 0) { - difference = 0; - } - - difference = Math.floor(difference / 1000); - - if (difference < 60) { - return difference + (min ? 's' : ' second') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 60); - if (difference < 60) { - return difference + (min ? 'm' : ' minute') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 60); - if (difference < 24) { - return difference + (min ? 'h' : ' hour') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 24); - if (difference < 30) { - return difference + (min ? 'd' : ' day') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 30); - if (difference < 12) { - return difference + (min ? 'mon' : ' month') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 12); - return difference + (min ? 'y' : ' year') + (difference !== 1 && !min ? 's' : ''); - }, - invalidUnicodeChars: XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'), invalidLatinChars: /[^\w\s\d\-_]/g, trimRegex: /^\s+|\s+$/g, From cb5ee2a14153452ff4aaad4be46acf6fd056c66f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 16:38:45 -0500 Subject: [PATCH 23/30] closes #2632 --- public/src/app.js | 4 ++-- public/src/client/account/settings.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index ea567e239e..d7097f91e7 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -364,7 +364,7 @@ app.uid = null; } }; - function exposeConfigToTemplates() { + app.exposeConfigToTemplates = function() { $(document).ready(function() { templates.setGlobal('loggedIn', config.loggedIn); templates.setGlobal('relative_path', RELATIVE_PATH); @@ -599,7 +599,7 @@ app.uid = null; showWelcomeMessage = window.location.href.indexOf('loggedin') !== -1; - exposeConfigToTemplates(); + app.exposeConfigToTemplates(); socketIOConnect(); diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 3dde90dd98..0b4bbbd00f 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -42,6 +42,7 @@ define('forum/account/settings', ['forum/account/header'], function(header) { config[key] = newSettings[key]; } } + app.exposeConfigToTemplates(); if (parseInt(app.uid, 10) === parseInt(ajaxify.variables.get('theirid'), 10)) { ajaxify.refresh(); From 2f955c3af4ffb3a4593e82c1343c4c17b864d5d4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 17:06:43 -0500 Subject: [PATCH 24/30] getUpvoters works with array --- public/src/client/topic/postTools.js | 10 +++++----- src/socket.io/posts.js | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 2f5a617572..5e6df47e8b 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -39,15 +39,15 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com function addVoteHandler() { $('#post-container').on('mouseenter', '.post-row .votes', function() { - loadDataAndCreateTooltip($(this), 'posts.getUpvoters'); + loadDataAndCreateTooltip($(this)); }); } - function loadDataAndCreateTooltip(el, method) { + function loadDataAndCreateTooltip(el) { var pid = el.parents('.post-row').attr('data-pid'); - socket.emit(method, pid, function(err, data) { - if (!err) { - createTooltip(el, data); + socket.emit('posts.getUpvoters', [pid], function(err, data) { + if (!err && data.length) { + createTooltip(el, data[0]); } }); } diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 97b8e7ea74..4a8e5f19bd 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -339,8 +339,11 @@ SocketPosts.getPrivileges = function(socket, pids, callback) { }); }; -SocketPosts.getUpvoters = function(socket, pid, callback) { - favourites.getUpvotedUidsByPids([pid], function(err, data) { +SocketPosts.getUpvoters = function(socket, pids, callback) { + if (!Array.isArray(pids)) { + return callback(new Error('[[error:invalid-data]]')); + } + favourites.getUpvotedUidsByPids(pids, function(err, data) { if (err || !Array.isArray(data) || !data.length) { return callback(err, []); } From 96c27d29f46baf359b2741928678d181f82ede01 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 19 Jan 2015 20:49:24 -0500 Subject: [PATCH 25/30] dont crash if callback isn't supplied --- src/database/redis/hash.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 322440a656..ee60ba98a2 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -107,6 +107,7 @@ module.exports = function(redisClient, module) { }; module.deleteObjectField = function(key, field, callback) { + callback = callback || function() {}; redisClient.hdel(key, field, function(err, res) { callback(err); }); From 21522e59c424755d8a24081015776b9cc87a746d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Jan 2015 01:30:42 -0500 Subject: [PATCH 26/30] added slug to user group return #2635 --- src/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 42f1e6528f..939d8fa5c6 100644 --- a/src/groups.js +++ b/src/groups.js @@ -775,7 +775,7 @@ var async = require('async'), return 'group:' + groupName; }); - db.getObjectsFields(groupKeys, ['name', 'hidden', 'userTitle', 'icon', 'labelColor'], function(err, groupData) { + db.getObjectsFields(groupKeys, ['name', 'slug', 'hidden', 'userTitle', 'icon', 'labelColor'], function(err, groupData) { if (err) { return callback(err); } From c4bc51b834a911f789068c92289c1d22ae2b2b5d Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 20 Jan 2015 09:41:27 -0500 Subject: [PATCH 27/30] fixing installation script that broke with recent updates to groups --- src/install.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/install.js b/src/install.js index 91c55293e0..54fb075ced 100644 --- a/src/install.js +++ b/src/install.js @@ -268,11 +268,7 @@ function enableDefaultTheme(next) { function createAdministrator(next) { var Groups = require('./groups'); Groups.get('administrators', {}, function (err, groupObj) { - if (err) { - return next(err); - } - - if (groupObj && groupObj.memberCount > 0) { + if (!err && groupObj && groupObj.memberCount > 0) { winston.info('Administrator found, skipping Admin setup'); next(); } else { From aab2bc0b3923630c4d18ec8edc5ee7437e9e9917 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Jan 2015 17:04:05 -0500 Subject: [PATCH 28/30] keep track of the last 20 events --- src/socket.io/index.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 08dab140e6..171a80d38f 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -124,11 +124,6 @@ function onMessage(socket, payload) { return winston.warn('[socket.io] Empty method name'); } - if (ratelimit.isFlooding(socket)) { - winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Message : ' + eventName); - return socket.disconnect(); - } - var parts = eventName.toString().split('.'), namespace = parts[0], methodToCall = parts.reduce(function(prev, cur) { @@ -146,6 +141,17 @@ function onMessage(socket, payload) { return; } + socket.previousEvents = socket.previousEvents || []; + socket.previousEvents.push(eventName); + if (socket.previousEvents.length > 20) { + socket.previousEvents.shift(); + } + + if (ratelimit.isFlooding(socket)) { + winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); + return socket.disconnect(); + } + if (Namespaces[namespace].before) { Namespaces[namespace].before(socket, eventName, function() { callMethod(methodToCall, socket, params, callback); From f1a3815ddc69219d186fe4a4385bdae41bf01a6a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Jan 2015 17:42:05 -0500 Subject: [PATCH 29/30] action:user.updateProfile --- src/user/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/profile.js b/src/user/profile.js index b901bdb066..13f29591c0 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -96,7 +96,7 @@ module.exports = function(User) { if (err) { return callback(err); } - + plugins.fireHook('action:user.updateProfile', {data: data, uid: uid}); User.getUserFields(uid, ['email', 'userslug', 'picture', 'gravatarpicture'], callback); }); }); From 7efc0619cc1af5846b81eabbea02e492d3e417af Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 20 Jan 2015 18:13:48 -0500 Subject: [PATCH 30/30] dont crash if data.username is undefined, dont set username to empty value --- src/user/profile.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/user/profile.js b/src/user/profile.js index 13f29591c0..8c44b86a6f 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -57,6 +57,9 @@ module.exports = function(User) { } function isUsernameAvailable(next) { + if (!data.username) { + return next(); + } User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { var userslug = utils.slugify(data.username); @@ -177,6 +180,9 @@ module.exports = function(User) { } function updateUsername(uid, newUsername, callback) { + if (!newUsername) { + return callback(); + } User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { function update(field, object, value, callback) { async.parallel([