From 68f8f57a34d3a47e9670c4dbf852b27a35958611 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 1 Dec 2016 22:19:50 -0500 Subject: [PATCH 01/10] upped composer version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3978707596..062c8dc63f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.3.0", + "nodebb-plugin-composer-default": "4.3.1", "nodebb-plugin-dbsearch": "1.0.4", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From 4b6e4f085d43c5482fc91e9ff53026e80ab1ffef Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 14:05:54 +0300 Subject: [PATCH 02/10] more tests group cover upload tests registration approval queue tests --- src/controllers/groups.js | 20 ++++-- src/controllers/helpers.js | 6 +- src/emailer.js | 7 +- src/groups/cover.js | 30 +++----- src/socket.io/admin/user.js | 48 +++++++------ src/socket.io/groups.js | 34 +++++---- src/user/approval.js | 12 ++-- test/groups.js | 139 +++++++++++++++++++++++++++++++++++- test/helpers/index.js | 54 ++++++++++++++ test/mocks/databasemock.js | 2 +- test/user.js | 74 +++++++++++++++++++ 11 files changed, 356 insertions(+), 70 deletions(-) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index b960f1446c..2cbbcc7c76 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -162,14 +162,24 @@ groupsController.members = function (req, res, callback) { groupsController.uploadCover = function (req, res, next) { var params = JSON.parse(req.body.params); - groups.updateCover(req.uid, { - file: req.files.files[0].path, - groupName: params.groupName - }, function (err, image) { + async.waterfall([ + function (next) { + groups.ownership.isOwner(req.uid, params.groupName, next); + }, + function (isOwner, next) { + if (!isOwner) { + return next(new Error('[[error:no-privileges]]')); + } + + groups.updateCover(req.uid, { + file: req.files.files[0].path, + groupName: params.groupName + }, next); + } + ], function (err, image) { if (err) { return next(err); } - res.json([{url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]); }); }; diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 1faf4625d0..488ac6507c 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -26,13 +26,15 @@ helpers.notAllowed = function (req, res, error) { if (res.locals.isAPI) { res.status(403).json({ path: req.path.replace(/^\/api/, ''), - loggedIn: !!req.uid, error: error, + loggedIn: !!req.uid, + error: error, title: '[[global:403.title]]' }); } else { res.status(403).render('403', { path: req.path, - loggedIn: !!req.uid, error: error, + loggedIn: !!req.uid, + error: error, title: '[[global:403.title]]' }); } diff --git a/src/emailer.js b/src/emailer.js index 1e66ce2c1a..ce90839f12 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -132,7 +132,12 @@ var fallbackTransport; delete data.from_name; winston.verbose('[emailer] Sending email to uid ' + data.uid); - fallbackTransport.sendMail(data, callback); + fallbackTransport.sendMail(data, function (err) { + if (err) { + winston.error(err); + } + callback(); + }); }; function render(tpl, params, next) { diff --git a/src/groups/cover.js b/src/groups/cover.js index d7dfc8890c..b3a380d80c 100644 --- a/src/groups/cover.js +++ b/src/groups/cover.js @@ -10,7 +10,6 @@ var mime = require('mime'); var winston = require('winston'); var db = require('../database'); -var file = require('../file'); var uploadsController = require('../controllers/uploads'); module.exports = function (Groups) { @@ -25,7 +24,7 @@ module.exports = function (Groups) { Groups.updateCover = function (uid, data, callback) { // Position only? That's fine - if (!data.imageData && data.position) { + if (!data.imageData && !data.file && data.position) { return Groups.updateCoverPosition(data.groupName, data.position, callback); } @@ -66,26 +65,19 @@ module.exports = function (Groups) { Groups.setGroupField(data.groupName, 'cover:thumb:url', uploadData.url, next); }, function (next) { - fs.unlink(tempPath, next); // Delete temporary file + if (data.position) { + Groups.updateCoverPosition(data.groupName, data.position, next); + } else { + next(null); + } } ], function (err) { - if (err) { - return fs.unlink(tempPath, function (unlinkErr) { - if (unlinkErr) { - winston.error(unlinkErr); - } - - callback(err); // send back original error - }); - } - - if (data.position) { - Groups.updateCoverPosition(data.groupName, data.position, function (err) { - callback(err, {url: url}); - }); - } else { + fs.unlink(tempPath, function (unlinkErr) { + if (unlinkErr) { + winston.error(unlinkErr); + } callback(err, {url: url}); - } + }); }); }; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index fb6d6e31ec..964001a3f1 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -220,33 +220,37 @@ User.deleteInvitation = function (socket, data, callback) { }; User.acceptRegistration = function (socket, data, callback) { - user.acceptRegistration(data.username, function (err, uid) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + user.acceptRegistration(data.username, next); + }, + function (uid, next) { + events.log({ + type: 'registration-approved', + uid: socket.uid, + ip: socket.ip, + targetUid: uid + }); + next(null, uid); } - events.log({ - type: 'registration-approved', - uid: socket.uid, - ip: socket.ip, - targetUid: uid, - }); - callback(); - }); + ], callback); }; User.rejectRegistration = function (socket, data, callback) { - user.rejectRegistration(data.username, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + user.rejectRegistration(data.username, next); + }, + function (next) { + events.log({ + type: 'registration-rejected', + uid: socket.uid, + ip: socket.ip, + username: data.username, + }); + next(); } - events.log({ - type: 'registration-rejected', - uid: socket.uid, - ip: socket.ip, - username: data.username, - }); - callback(); - }); + ], callback); }; User.restartJobs = function (socket, data, callback) { diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 858d9bdeff..5bff7ba944 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -277,13 +277,18 @@ SocketGroups.cover.update = function (socket, data, callback) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { - if (err || !isOwner) { - return callback(err || new Error('[[error:no-privileges]]')); - } + async.waterfall([ + function (next) { + groups.ownership.isOwner(socket.uid, data.groupName, next); + }, + function (isOwner, next) { + if (!isOwner) { + return next(new Error('[[error:no-privileges]]')); + } - groups.updateCover(socket.uid, data, callback); - }); + groups.updateCover(socket.uid, data, next); + } + ], callback); }; SocketGroups.cover.remove = function (socket, data, callback) { @@ -291,13 +296,18 @@ SocketGroups.cover.remove = function (socket, data, callback) { return callback(new Error('[[error:no-privileges]]')); } - groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) { - if (err || !isOwner) { - return callback(err || new Error('[[error:no-privileges]]')); - } + async.waterfall([ + function (next) { + groups.ownership.isOwner(socket.uid, data.groupName, next); + }, + function (isOwner, next) { + if (!isOwner) { + return next(new Error('[[error:no-privileges]]')); + } - groups.removeCover(data, callback); - }); + groups.removeCover(data, next); + } + ], callback); }; module.exports = SocketGroups; diff --git a/src/user/approval.js b/src/user/approval.js index e09e6431ff..c8b78eb75f 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -78,6 +78,12 @@ module.exports = function (User) { uid = _uid; User.setUserField(uid, 'password', userData.hashedPassword, next); }, + function (next) { + removeFromQueue(username, next); + }, + function (next) { + markNotificationRead(username, next); + }, function (next) { var title = meta.config.title || meta.config.browserTitle || 'NodeBB'; translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function (subject) { @@ -92,12 +98,6 @@ module.exports = function (User) { emailer.send('registration_accepted', uid, data, next); }); }, - function (next) { - removeFromQueue(username, next); - }, - function (next) { - markNotificationRead(username, next); - }, function (next) { next(null, uid); } diff --git a/test/groups.js b/test/groups.js index c2b30b9d94..182910bc32 100644 --- a/test/groups.js +++ b/test/groups.js @@ -1,10 +1,12 @@ 'use strict'; -/*global require, before, after*/ var assert = require('assert'); var async = require('async'); +var path = require('path'); +var nconf = require('nconf'); var db = require('./mocks/databasemock'); +var helpers = require('./helpers'); var Groups = require('../src/groups'); var User = require('../src/user'); @@ -47,7 +49,8 @@ describe('Groups', function () { function (next) { User.create({ username: 'admin', - email: 'admin@admin.com' + email: 'admin@admin.com', + password: '123456' }, next); }, function (next) { @@ -694,6 +697,138 @@ describe('Groups', function () { }); }); + describe('groups cover', function () { + var socketGroups = require('../src/socket.io/groups'); + var regularUid; + var logoPath = path.join(__dirname, '../public/logo.png'); + var imagePath = path.join(__dirname, '../public/groupcover.png'); + before(function (done) { + User.create({username: 'regularuser', password: '123456'}, function (err, uid) { + assert.ifError(err); + regularUid = uid; + async.series([ + function (next) { + Groups.join('Test', adminUid, next); + }, + function (next) { + Groups.join('Test', regularUid, next); + }, + function (next) { + helpers.copyFile(logoPath, imagePath, next); + } + ], done); + }); + }); + + it('should fail if user is not logged in or not owner', function (done) { + socketGroups.cover.update({uid: 0}, {}, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + socketGroups.cover.update({uid: regularUid}, {}, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + }); + + it('should upload group cover image from file', function (done) { + var data = { + groupName: 'Test', + file: imagePath + }; + socketGroups.cover.update({uid: adminUid}, data, function (err, data) { + assert.ifError(err); + Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) { + assert.ifError(err); + assert.equal(data.url, groupData['cover:url']); + done(); + }); + }); + }); + + + it('should upload group cover image from data', function (done) { + var data = { + groupName: 'Test', + imageData: 'data:image/png;base64,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' + }; + socketGroups.cover.update({uid: adminUid}, data, function (err, data) { + assert.ifError(err); + Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) { + assert.ifError(err); + assert.equal(data.url, groupData['cover:url']); + done(); + }); + }); + }); + + it('should update group cover position', function (done) { + var data = { + groupName: 'Test', + position: '50% 50%' + }; + socketGroups.cover.update({uid: adminUid}, data, function (err) { + assert.ifError(err); + Groups.getGroupFields('Test', ['cover:position'], function (err, groupData) { + assert.ifError(err); + assert.equal('50% 50%', groupData['cover:position']); + done(); + }); + }); + }); + + it('should fail to update cover position if group name is missing', function (done) { + Groups.updateCoverPosition('', '50% 50%', function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error if user is not owner of group', function (done) { + helpers.loginUser('regularuser', '123456', function (err, jar, io, csrf_token) { + assert.ifError(err); + helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, {params: JSON.stringify({groupName: 'Test'})}, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 500); + assert.equal(body.error, '[[error:no-privileges]]'); + done(); + }); + }); + }); + + it('should upload group cover with api route', function (done) { + helpers.loginUser('admin', '123456', function (err, jar, io, csrf_token) { + assert.ifError(err); + helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, {params: JSON.stringify({groupName: 'Test'})}, jar, csrf_token, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) { + assert.ifError(err); + assert.equal(body[0].url, groupData['cover:url']); + done(); + }); + }); + }); + }); + + it('should fail to remove cover if not owner', function (done) { + socketGroups.cover.remove({uid: regularUid}, {groupName: 'Test'}, function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should remove cover', function (done) { + socketGroups.cover.remove({uid: adminUid}, {groupName: 'Test'}, function (err) { + assert.ifError(err); + Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) { + assert.ifError(err); + assert(!groupData['cover:url']); + done(); + }); + }); + }); + }); + after(function (done) { db.emptydb(done); }); diff --git a/test/helpers/index.js b/test/helpers/index.js index a04a6a855c..edae035b66 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -112,4 +112,58 @@ helpers.uploadFile = function (uploadEndPoint, filePath, body, jar, csrf_token, } callback(err, res, body); }); +}; + +helpers.registerUser = function (data, callback) { + var jar = request.jar(); + request({ + url: nconf.get('url') + '/api/config', + json: true, + jar: jar + }, function (err, response, body) { + if (err) { + return callback(err); + } + + request.post(nconf.get('url') + '/register', { + form: data, + json: true, + jar: jar, + headers: { + 'x-csrf-token': body.csrf_token + } + }, function (err, res, body) { + if (err) { + return callback(err); + } + + callback(null, jar); + }); + }); +}; + +//http://stackoverflow.com/a/14387791/583363 +helpers.copyFile = function (source, target, callback) { + + var cbCalled = false; + + var rd = fs.createReadStream(source); + rd.on("error", function (err) { + done(err); + }); + var wr = fs.createWriteStream(target); + wr.on("error", function (err) { + done(err); + }); + wr.on("close", function () { + done(); + }); + rd.pipe(wr); + + function done(err) { + if (!cbCalled) { + callback(err); + cbCalled = true; + } + } }; \ No newline at end of file diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index f1fcb89c5a..44de8c7122 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -138,7 +138,7 @@ nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates')); nconf.set('theme_templates_path', meta.config['theme:templates'] ? path.join(nconf.get('themes_path'), meta.config['theme:id'], meta.config['theme:templates']) : nconf.get('base_templates_path')); nconf.set('theme_config', path.join(nconf.get('themes_path'), 'nodebb-theme-persona', 'theme.json')); - nconf.set('bcrypt_rounds', 4); + nconf.set('bcrypt_rounds', 1); require('../../build').buildTargets(['js', 'clientCSS', 'acpCSS', 'tpl'], next); }, diff --git a/test/user.js b/test/user.js index c2f67ef709..3567906601 100644 --- a/test/user.js +++ b/test/user.js @@ -661,6 +661,80 @@ describe('User', function () { }); }); + describe('approval queue', function () { + var socketAdmin = require('../src/socket.io/admin'); + + var oldRegistrationType; + var adminUid; + before(function (done) { + oldRegistrationType = Meta.config.registrationType; + Meta.config.registrationType = 'admin-approval'; + User.create({username: 'admin', password: '123456'}, function (err, uid) { + assert.ifError(err); + adminUid = uid; + groups.join('administrators', uid, done); + }); + }); + + after(function (done) { + Meta.config.registrationType = oldRegistrationType; + done(); + }); + + it('should add user to approval queue', function (done) { + helpers.registerUser({ + username: 'rejectme', + password: '123456', + email: 'reject@me.com' + }, function (err) { + assert.ifError(err); + helpers.loginUser('admin', '123456', function (err, jar) { + assert.ifError(err); + request(nconf.get('url') + '/api/admin/manage/registration', {jar: jar, json: true}, function (err, res, body) { + assert.ifError(err); + assert.equal(body.users[0].username, 'rejectme'); + assert.equal(body.users[0].email, 'reject@me.com'); + done(); + }); + }); + }); + }); + + it('should reject user registration', function (done) { + socketAdmin.user.rejectRegistration({uid: adminUid}, {username: 'rejectme'}, function (err) { + assert.ifError(err); + User.getRegistrationQueue(0, -1, function (err, users) { + assert.ifError(err); + assert.equal(users.length, 0); + done(); + }); + }); + }); + + it('should accept user registration', function (done) { + helpers.registerUser({ + username: 'acceptme', + password: '123456', + email: 'accept@me.com' + }, function (err) { + assert.ifError(err); + socketAdmin.user.acceptRegistration({uid: adminUid}, {username: 'acceptme'}, function (err, uid) { + assert.ifError(err); + User.exists(uid, function (err, exists) { + assert.ifError(err); + assert(exists); + User.getRegistrationQueue(0, -1, function (err, users) { + assert.ifError(err); + assert.equal(users.length, 0); + done(); + }); + }); + }); + }); + }); + + }); + after(function (done) { db.emptydb(done); From 091f459f5ebe6fa70ee2427857a5ee11d50fb665 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 14:58:13 +0300 Subject: [PATCH 03/10] search socket test --- src/user/approval.js | 31 +++++++++++++++---------------- test/user.js | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/user/approval.js b/src/user/approval.js index c8b78eb75f..2f9d9f847a 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -46,18 +46,19 @@ module.exports = function (User) { }; function sendNotificationToAdmins(username, callback) { - notifications.create({ - bodyShort: '[[notifications:new_register, ' + username + ']]', - nid: 'new_register:' + username, - path: '/admin/manage/registration', - mergeId: 'new_register' - }, function (err, notification) { - if (err || !notification) { - return callback(err); + async.waterfall([ + function (next) { + notifications.create({ + bodyShort: '[[notifications:new_register, ' + username + ']]', + nid: 'new_register:' + username, + path: '/admin/manage/registration', + mergeId: 'new_register' + }, next); + }, + function (notification, next) { + notifications.pushGroup(notification, 'administrators', next); } - - notifications.pushGroup(notification, 'administrators', callback); - }); + ], callback); } User.acceptRegistration = function (username, callback) { @@ -153,13 +154,11 @@ module.exports = function (User) { }, function (users, next) { users = users.map(function (user, index) { - if (!user) { - return null; + if (user) { + user.timestampISO = utils.toISOString(data[index].score); + delete user.hashedPassword; } - user.timestampISO = utils.toISOString(data[index].score); - delete user.hashedPassword; - return user; }).filter(Boolean); diff --git a/test/user.js b/test/user.js index 3567906601..c5769579e1 100644 --- a/test/user.js +++ b/test/user.js @@ -170,6 +170,7 @@ describe('User', function () { }); describe('.search()', function () { + var socketUser = require('../src/socket.io/user'); it('should return an object containing an array of matching users', function (done) { User.search({query: 'john'}, function (err, searchData) { assert.ifError(err); @@ -178,6 +179,30 @@ describe('User', function () { done(); }); }); + + it('should search user', function (done) { + socketUser.search({uid: testUid}, {query: 'john'}, function (err, searchData) { + assert.ifError(err); + assert.equal(searchData.users[0].username, 'John Smith'); + done(); + }); + }); + + it('should error for guest', function (done) { + Meta.config.allowGuestUserSearching = 0; + socketUser.search({uid: 0}, {query: 'john'}, function (err) { + assert.equal(err.message, '[[error:not-logged-in]]'); + Meta.config.allowGuestUserSearching = 1; + done(); + }); + }); + + it('should error with invalid data', function (done) { + socketUser.search({uid: testUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); }); describe('.delete()', function () { From 1440139903850c2721a52aa28353823c9936fec5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 16:10:07 +0300 Subject: [PATCH 04/10] more tests --- src/posts.js | 52 ++++++++++++++++++------------------- src/socket.io/admin/user.js | 25 +++++++++--------- test/categories.js | 20 ++++++++++++++ test/controllers.js | 19 ++++++++++++++ test/socket.io.js | 42 ++++++++++++++++++++++++++---- test/user.js | 23 ++++++++++++++++ 6 files changed, 137 insertions(+), 44 deletions(-) diff --git a/src/posts.js b/src/posts.js index 047917cb5f..3a7d2d7e19 100644 --- a/src/posts.js +++ b/src/posts.js @@ -188,39 +188,37 @@ var plugins = require('./plugins'); return callback(null, []); } - user.getSettings(uid, function (err, settings) { - if (err) { - return callback(err); - } - - var byVotes = settings.topicPostSort === 'most_votes'; - var sets = posts.map(function (post) { - return byVotes ? 'tid:' + post.tid + ':posts:votes' : 'tid:' + post.tid + ':posts'; - }); - - var uniqueSets = _.uniq(sets); - var method = 'sortedSetsRanks'; - if (uniqueSets.length === 1) { - method = 'sortedSetRanks'; - sets = uniqueSets[0]; - } - - var pids = posts.map(function (post) { - return post.pid; - }); - - db[method](sets, pids, function (err, indices) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + user.getSettings(uid, next); + }, + function (settings, next) { + var byVotes = settings.topicPostSort === 'most_votes'; + var sets = posts.map(function (post) { + return byVotes ? 'tid:' + post.tid + ':posts:votes' : 'tid:' + post.tid + ':posts'; + }); + + var uniqueSets = _.uniq(sets); + var method = 'sortedSetsRanks'; + if (uniqueSets.length === 1) { + method = 'sortedSetRanks'; + sets = uniqueSets[0]; } + var pids = posts.map(function (post) { + return post.pid; + }); + + db[method](sets, pids, next); + }, + function (indices, next) { for (var i = 0; i < indices.length; ++i) { indices[i] = utils.isNumber(indices[i]) ? parseInt(indices[i], 10) + 1 : 0; } - callback(null, indices); - }); - }); + next(null, indices); + } + ], callback); }; Posts.updatePostVoteCount = function (postData, callback) { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 964001a3f1..4a77c224a7 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -104,19 +104,20 @@ User.sendValidationEmail = function (socket, uids, callback) { return callback(new Error('[[error:email-confirmations-are-disabled]]')); } - user.getUsersFields(uids, ['uid', 'email'], function (err, usersData) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + user.getUsersFields(uids, ['uid', 'email'], next); + }, + function (usersData, next) { + async.eachLimit(usersData, 50, function (userData, next) { + if (userData.email && userData.uid) { + user.email.sendValidationEmail(userData.uid, userData.email, next); + } else { + next(); + } + }, next); } - - async.eachLimit(usersData, 50, function (userData, next) { - if (userData.email && userData.uid) { - user.email.sendValidationEmail(userData.uid, userData.email, next); - } else { - next(); - } - }, callback); - }); + ], callback); }; User.sendPasswordResetEmail = function (socket, uids, callback) { diff --git a/test/categories.js b/test/categories.js index abcaf118c8..d968cacc5d 100644 --- a/test/categories.js +++ b/test/categories.js @@ -390,7 +390,27 @@ describe('Categories', function () { }); }); + it('should get active users', function (done) { + Categories.create({ + name: 'test' + }, function (err, category) { + assert.ifError(err); + Topics.post({ + uid: posterUid, + cid: category.cid, + title: 'Test Topic Title', + content: 'The content of test topic' + }, function (err) { + assert.ifError(err); + Categories.getActiveUsers(category.cid, function (err, uids) { + assert.ifError(err); + assert.equal(uids[0], posterUid); + done(); + }); + }); + }); + }); after(function (done) { diff --git a/test/controllers.js b/test/controllers.js index cd08039557..89fce43246 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -829,6 +829,25 @@ describe('Controllers', function () { }); }); + describe('post redirect', function () { + it('should 404 for invalid pid', function (done) { + request(nconf.get('url') + '/post/fail', function (err, res) { + assert.ifError(err); + assert.equal(res.statusCode, 404); + done(); + }); + }); + + it('should return correct post path', function (done) { + request(nconf.get('url') + '/api/post/' + pid, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 308); + assert.equal(body, '"/topic/1/test-topic-title/1"'); + done(); + }); + }); + }); + after(function (done) { var analytics = require('../src/analytics'); analytics.writeData(function (err) { diff --git a/test/socket.io.js b/test/socket.io.js index b03753f362..ddb136d3b0 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -253,13 +253,45 @@ describe('socket.io', function () { }); }); - it('should validate emails', function (done) { + + + describe('validation emails', function () { var socketAdmin = require('../src/socket.io/admin'); - socketAdmin.user.validateEmail({uid: adminUid}, [regularUid], function (err) { - assert.ifError(err); - user.getUserField(regularUid, 'email:confirmed', function (err, emailConfirmed) { + var meta = require('../src/meta'); + + it('should validate emails', function (done) { + socketAdmin.user.validateEmail({uid: adminUid}, [regularUid], function (err) { + assert.ifError(err); + user.getUserField(regularUid, 'email:confirmed', function (err, emailConfirmed) { + assert.ifError(err); + assert.equal(parseInt(emailConfirmed, 10), 1); + done(); + }); + }); + }); + + it('should error with invalid uids', function (done) { + var socketAdmin = require('../src/socket.io/admin'); + socketAdmin.user.sendValidationEmail({uid: adminUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error if email validation is not required', function (done) { + var socketAdmin = require('../src/socket.io/admin'); + socketAdmin.user.sendValidationEmail({uid: adminUid}, [regularUid], function (err) { + assert.equal(err.message, '[[error:email-confirmations-are-disabled]]'); + done(); + }); + }); + + it('should send validation email', function (done) { + var socketAdmin = require('../src/socket.io/admin'); + meta.config.requireEmailConfirmation = 1; + socketAdmin.user.sendValidationEmail({uid: adminUid}, [regularUid], function (err) { assert.ifError(err); - assert.equal(parseInt(emailConfirmed, 10), 1); + meta.config.requireEmailConfirmation = 0; done(); }); }); diff --git a/test/user.js b/test/user.js index c5769579e1..4854fbf110 100644 --- a/test/user.js +++ b/test/user.js @@ -684,6 +684,29 @@ describe('User', function () { }); }); }); + + it('should fail if data is invalid', function (done) { + socketUser.emailExists({uid: testUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should return true if email exists', function (done) { + socketUser.emailExists({uid: testUid}, {email: 'john@example.com'}, function (err, exists) { + assert.ifError(err); + assert(exists); + done(); + }); + }); + + it('should return false if email does not exist', function (done) { + socketUser.emailExists({uid: testUid}, {email: 'does@not.exist'}, function (err, exists) { + assert.ifError(err); + assert(!exists); + done(); + }); + }); }); describe('approval queue', function () { From 69b766bbc80c0bb90ee3ad4907d0d186b5edb7ad Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 17:05:46 +0300 Subject: [PATCH 05/10] more tests --- src/socket.io/user.js | 74 ++++++++++++++++----------------- src/user/settings.js | 3 ++ test/controllers.js | 9 +++- test/user.js | 96 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 39 deletions(-) diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 69b229b4b1..c10a510e71 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -72,15 +72,21 @@ SocketUser.emailConfirm = function (socket, data, callback) { } if (parseInt(meta.config.requireEmailConfirmation, 10) !== 1) { - callback(); + return callback(new Error('[[error:email-confirmations-are-disabled]]')); } - user.getUserField(socket.uid, 'email', function (err, email) { - if (err || !email) { - return callback(err); - } - user.email.sendValidationEmail(socket.uid, email, callback); - }); + async.waterfall([ + function (next) { + user.getUserField(socket.uid, 'email', next); + }, + function (email, next) { + if (!email) { + return callback(); + } + + user.email.sendValidationEmail(socket.uid, email, next); + } + ], callback); }; @@ -109,39 +115,37 @@ SocketUser.reset.commit = function (socket, data, callback) { if (!data || !data.code || !data.password) { return callback(new Error('[[error:invalid-data]]')); } + var uid; + async.waterfall([ + function (next) { + async.parallel({ + uid: async.apply(db.getObjectField, 'reset:uid', data.code), + reset: async.apply(user.reset.commit, data.code, data.password) + }, next); + }, + function (results, next) { + uid = results.uid; + events.log({ + type: 'password-reset', + uid: uid, + ip: socket.ip + }); - async.parallel({ - uid: async.apply(db.getObjectField, 'reset:uid', data.code), - reset: async.apply(user.reset.commit, data.code, data.password) - }, function (err, results) { - if (err) { - return callback(err); - } - - var uid = results.uid; - var now = new Date(); - var parsedDate = now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate(); - - user.getUserField(uid, 'username', function (err, username) { - if (err) { - return callback(err); - } - + user.getUserField(uid, 'username', next); + }, + function (username, next) { + var now = new Date(); + var parsedDate = now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate(); 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: uid, - ip: socket.ip - }); - callback(); - }); + next(); + } + ], callback); }; SocketUser.isFollowing = function (socket, data, callback) { @@ -224,16 +228,10 @@ SocketUser.saveSettings = function (socket, data, callback) { }; SocketUser.setTopicSort = function (socket, sort, callback) { - if (!socket.uid) { - return callback(); - } user.setSetting(socket.uid, 'topicPostSort', sort, callback); }; SocketUser.setCategorySort = function (socket, sort, callback) { - if (!socket.uid) { - return callback(); - } user.setSetting(socket.uid, 'categoryTopicSort', sort, callback); }; diff --git a/src/user/settings.js b/src/user/settings.js index 3a5b53b9c3..3d93a9724a 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -160,6 +160,9 @@ module.exports = function (User) { }; User.setSetting = function (uid, key, value, callback) { + if (!parseInt(uid, 10)) { + return callback(); + } db.setObjectField('user:' + uid + ':settings', key, value, callback); }; }; diff --git a/test/controllers.js b/test/controllers.js index 89fce43246..1f31e0a037 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -794,7 +794,14 @@ describe('Controllers', function () { user.create({username: 'follower'}, function (err, _uid) { assert.ifError(err); uid = _uid; - socketUser.follow({uid: uid}, {uid: fooUid}, done); + socketUser.follow({uid: uid}, {uid: fooUid}, function (err) { + assert.ifError(err); + socketUser.isFollowing({uid: uid}, {uid: fooUid}, function (err, isFollowing) { + assert.ifError(err); + assert(isFollowing); + done(); + }); + }); }); }); diff --git a/test/user.js b/test/user.js index 4854fbf110..8621575fe2 100644 --- a/test/user.js +++ b/test/user.js @@ -707,6 +707,102 @@ describe('User', function () { done(); }); }); + + it('should error if requireEmailConfirmation is disabled', function (done) { + socketUser.emailConfirm({uid: testUid}, {}, function (err) { + assert.equal(err.message, '[[error:email-confirmations-are-disabled]]'); + done(); + }); + }); + + it('should send email confirm', function (done) { + Meta.config.requireEmailConfirmation = 1; + socketUser.emailConfirm({uid: testUid}, {}, function (err) { + assert.ifError(err); + Meta.config.requireEmailConfirmation = 0; + done(); + }); + }); + + it('should send reset email', function (done) { + socketUser.reset.send({uid: 0}, 'john@example.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should return invalid-data error', function (done) { + socketUser.reset.send({uid: 0}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should not error', function (done) { + socketUser.reset.send({uid: 0}, 'doestnot@exist.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should commit reset', function (done) { + db.getObject('reset:uid', function (err, data) { + assert.ifError(err); + var code = Object.keys(data)[0]; + socketUser.reset.commit({uid: 0}, {code: code, password: 'swordfish'}, function (err) { + assert.ifError(err); + done(); + }); + }); + }); + + it('should save user settings', function (done) { + var data = { + uid: 1, + settings: { + bootswatchSkin: 'default', + homePageRoute: 'none', + homePageCustom: '', + openOutgoingLinksInNewTab: 0, + scrollToMyPost: 1, + delayImageLoading: 1, + userLang: 'en-GB', + usePagination: 1, + topicsPerPage: '10', + postsPerPage: '5', + showemail: 1, + showfullname: 1, + restrictChat: 0, + followTopicsOnCreate: 1, + followTopicsOnReply: 1, + notificationSound: '', + incomingChatSound: '', + outgoingChatSound: '' + } + }; + socketUser.saveSettings({uid: testUid}, data, function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should set moderation note', function (done) { + User.create({username: 'noteadmin'}, function (err, adminUid) { + assert.ifError(err); + groups.join('administrators', adminUid, function (err) { + assert.ifError(err); + socketUser.setModerationNote({uid: adminUid}, {uid: testUid, note: 'this is a test user'}, function (err) { + assert.ifError(err); + User.getUserField(testUid, 'moderationNote', function (err, note) { + assert.ifError(err); + assert.equal(note, 'this is a test user'); + done(); + }); + }); + }); + }); + + }); }); describe('approval queue', function () { From a1b49a98e7cc08b1033a01e9bdc8f11d0eb16590 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 2 Dec 2016 09:49:52 -0500 Subject: [PATCH 06/10] locking down session deletion route to admins and global mods only --- src/middleware/index.js | 14 ++++++++++++++ src/routes/accounts.js | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 7f55388804..ae2622beea 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -49,6 +49,20 @@ middleware.authenticate = function (req, res, next) { controllers.helpers.notAllowed(req, res); }; +middleware.ensureGlobalPrivilege = function (req, res, next) { + if (req.user) { + user.isAdminOrGlobalMod(req.uid, function (err, ok) { + if (ok) { + return next(); + } else { + controllers.helpers.notAllowed(req, res); + } + }); + } else { + controllers.helpers.notAllowed(req, res); + } +}; + middleware.pageView = function (req, res, next) { analytics.pageView({ ip: req.ip, diff --git a/src/routes/accounts.js b/src/routes/accounts.js index 14382bd568..118a613112 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -28,7 +28,7 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/info', middleware, accountMiddlewares, controllers.accounts.info.get); setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get); - app.delete('/api/user/:userslug/session/:uuid', [middleware.requireUser], controllers.accounts.session.revoke); + app.delete('/api/user/:userslug/session/:uuid', [middleware.ensureGlobalPrivilege], controllers.accounts.session.revoke); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get); setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, middlewares, controllers.accounts.chats.get); From 23cdeeb3444f9d575d3f45c2fe5ba399c3949f12 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 2 Dec 2016 10:07:33 -0500 Subject: [PATCH 07/10] linting :shipit: --- src/middleware/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index ae2622beea..87fcb0a6a6 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -52,7 +52,9 @@ middleware.authenticate = function (req, res, next) { middleware.ensureGlobalPrivilege = function (req, res, next) { if (req.user) { user.isAdminOrGlobalMod(req.uid, function (err, ok) { - if (ok) { + if (err) { + return next(err); + } else if (ok) { return next(); } else { controllers.helpers.notAllowed(req, res); From 55416911f75c3e7827b0fda835a8a546be9ad2a6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 18:08:08 +0300 Subject: [PATCH 08/10] category tests --- test/categories.js | 134 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/test/categories.js b/test/categories.js index d968cacc5d..1a95846264 100644 --- a/test/categories.js +++ b/test/categories.js @@ -12,6 +12,7 @@ var Categories = require('../src/categories'); var Topics = require('../src/topics'); var User = require('../src/user'); var groups = require('../src/groups'); +var privileges = require('../src/privileges'); describe('Categories', function () { var categoryObj; @@ -312,7 +313,7 @@ describe('Categories', function () { var socketCategories = require('../src/socket.io/admin/categories'); var cid; before(function (done) { - Categories.create({ + socketCategories.create({uid: adminUid}, { name: 'update name', description: 'update description', parentCid: categoryObj.cid, @@ -388,6 +389,137 @@ describe('Categories', function () { }); }); + + it('should get all categories', function (done) { + socketCategories.getAll({uid: adminUid}, {}, function (err, data) { + assert.ifError(err); + done(); + }); + }); + + it('should get all category names', function (done) { + socketCategories.getNames({uid: adminUid}, {}, function (err, data) { + assert.ifError(err); + assert(Array.isArray(data)); + done(); + }); + }); + + it('should give privilege', function (done) { + socketCategories.setPrivilege({uid: adminUid}, {cid: categoryObj.cid, privilege: ['groups:topics:delete'], set: true, member: 'registered-users'}, function (err) { + assert.ifError(err); + privileges.categories.can('topics:delete', categoryObj.cid, posterUid, function (err, canDeleteTopcis) { + assert.ifError(err); + assert(canDeleteTopcis); + done(); + }); + }); + }); + + it('should remove privilege', function (done) { + socketCategories.setPrivilege({uid: adminUid}, {cid: categoryObj.cid, privilege: 'groups:topics:delete', set: false, member: 'registered-users'}, function (err) { + assert.ifError(err); + privileges.categories.can('topics:delete', categoryObj.cid, posterUid, function (err, canDeleteTopcis) { + assert.ifError(err); + assert(!canDeleteTopcis); + done(); + }); + }); + }); + + it('should get privilege settings', function (done) { + socketCategories.getPrivilegeSettings({uid: adminUid}, categoryObj.cid, function (err, data) { + assert.ifError(err); + assert(data); + done(); + }); + }); + + it('should copy privileges to children', function (done) { + var parentCid; + var child1Cid; + var child2Cid; + async.waterfall([ + function (next) { + Categories.create({name: 'parent'}, next); + }, + function (category, next) { + parentCid = category.cid; + Categories.create({name: 'child1', parentCid: parentCid}, next); + }, + function (category, next) { + child1Cid = category.cid; + Categories.create({name: 'child2', parentCid: child1Cid}, next); + }, + function (category, next) { + child2Cid = category.cid; + socketCategories.setPrivilege({uid: adminUid}, {cid: parentCid, privilege: 'groups:topics:delete', set: true, member: 'registered-users'}, next); + }, + function (next) { + socketCategories.copyPrivilegesToChildren({uid: adminUid}, parentCid, next); + }, + function (next) { + privileges.categories.can('topics:delete', child2Cid, posterUid, next); + }, + function (canDelete, next) { + assert(canDelete); + next(); + } + ], done); + }); + + it('should copy settings from', function (done) { + var child1Cid; + var parentCid; + async.waterfall([ + function (next) { + Categories.create({name: 'parent', description: 'copy me'}, next); + }, + function (category, next) { + parentCid = category.cid; + Categories.create({name: 'child1'}, next); + }, + function (category, next) { + child1Cid = category.cid; + socketCategories.copySettingsFrom({uid: adminUid}, {fromCid: parentCid, toCid: child1Cid}, next); + }, + function (canDelete, next) { + Categories.getCategoryField(child1Cid, 'description', next); + }, + function (description, next) { + assert.equal(description, 'copy me'); + next(); + } + ], done); + }); + + it('should copy privileges from', function (done) { + var child1Cid; + var parentCid; + async.waterfall([ + function (next) { + Categories.create({name: 'parent', description: 'copy me'}, next); + }, + function (category, next) { + parentCid = category.cid; + Categories.create({name: 'child1'}, next); + }, + function (category, next) { + child1Cid = category.cid; + socketCategories.setPrivilege({uid: adminUid}, {cid: parentCid, privilege: 'groups:topics:delete', set: true, member: 'registered-users'}, next); + }, + function (next) { + socketCategories.copyPrivilegesFrom({uid: adminUid}, {fromCid: parentCid, toCid: child1Cid}, next); + }, + function (next) { + privileges.categories.can('topics:delete', child1Cid, posterUid, next); + }, + function (canDelete, next) { + assert(canDelete); + next(); + } + ], done); + }); }); it('should get active users', function (done) { From aad9a39f0223ebe642e8fb3c381d91ee8eebcd34 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 2 Dec 2016 18:22:30 +0300 Subject: [PATCH 09/10] move post test --- test/posts.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/posts.js b/test/posts.js index 207406fd5d..eaf2b0b373 100644 --- a/test/posts.js +++ b/test/posts.js @@ -343,6 +343,55 @@ describe('Post\'s', function () { }); }); + describe('move', function () { + var replyPid; + var tid; + var moveTid; + var socketPosts = require('../src/socket.io/posts'); + + before(function (done) { + async.waterfall([ + function (next) { + topics.post({ + uid: voterUid, + cid: cid, + title: 'topic 1', + content: 'some content' + }, next); + }, + function (data, next) { + tid = data.topicData.tid; + topics.post({ + uid: voterUid, + cid: cid, + title: 'topic 2', + content: 'some content' + }, next); + }, + function (data, next) { + moveTid = data.topicData.tid; + topics.reply({ + uid: voterUid, + tid: tid, + timestamp: Date.now(), + content: 'A reply to move' + }, function (err, data) { + assert.ifError(err); + replyPid = data.pid; + socketPosts.movePost({uid: globalModUid}, {pid: replyPid, tid: moveTid}, next); + }); + }, + function (next) { + posts.getPostField(replyPid, 'tid', next); + }, + function (tid, next) { + assert(tid, moveTid); + next(); + } + ], done); + }); + }); + describe('flagging a post', function () { var meta = require('../src/meta'); var socketPosts = require('../src/socket.io/posts'); From 33ff5e09bb714ffd7bdd2c1706b8b4025e2827cb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 2 Dec 2016 10:50:42 -0500 Subject: [PATCH 10/10] updated revoke session middleware to allow self or admin or global mod invocation, tweaked tests a bit --- src/controllers/accounts/helpers.js | 1 + src/controllers/accounts/session.js | 6 +----- src/middleware/index.js | 10 +++++++++- src/routes/accounts.js | 2 +- test/controllers.js | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 7a90930053..f3efd67285 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -107,6 +107,7 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { userData.isModerator = isModerator; userData.isAdminOrGlobalModerator = isAdmin || isGlobalModerator; userData.isAdminOrGlobalModeratorOrModerator = isAdmin || isGlobalModerator || isModerator; + userData.isSelfOrAdminOrGlobalModerator = isSelf || isAdmin || isGlobalModerator; userData.canEdit = isAdmin || (isGlobalModerator && !results.isTargetAdmin); userData.canBan = isAdmin || (isGlobalModerator && !results.isTargetAdmin); userData.canChangePassword = isAdmin || (isSelf && parseInt(meta.config['password:disableEdit'], 10) !== 1); diff --git a/src/controllers/accounts/session.js b/src/controllers/accounts/session.js index 7e31906f26..e8123820ee 100644 --- a/src/controllers/accounts/session.js +++ b/src/controllers/accounts/session.js @@ -13,13 +13,9 @@ sessionController.revoke = function (req, res, next) { } var _id; - var uid; + var uid = res.locals.uid; async.waterfall([ function (next) { - user.getUidByUserslug(req.params.userslug, next); - }, - function (_uid, next) { - uid = _uid; if (!uid) { return next(new Error('[[error:no-session-found]]')); } diff --git a/src/middleware/index.js b/src/middleware/index.js index 87fcb0a6a6..9bf02c1449 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -49,8 +49,16 @@ middleware.authenticate = function (req, res, next) { controllers.helpers.notAllowed(req, res); }; -middleware.ensureGlobalPrivilege = function (req, res, next) { +middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) { + /* + The "self" part of this middleware hinges on you having used + middleware.exposeUid prior to invoking this middleware. + */ if (req.user) { + if (req.user.uid === res.locals.uid) { + return next(); + } + user.isAdminOrGlobalMod(req.uid, function (err, ok) { if (err) { return next(err); diff --git a/src/routes/accounts.js b/src/routes/accounts.js index 118a613112..ae80b8aa4a 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -28,7 +28,7 @@ module.exports = function (app, middleware, controllers) { setupPageRoute(app, '/user/:userslug/info', middleware, accountMiddlewares, controllers.accounts.info.get); setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.settings.get); - app.delete('/api/user/:userslug/session/:uuid', [middleware.ensureGlobalPrivilege], controllers.accounts.session.revoke); + app.delete('/api/user/:userslug/session/:uuid', [middleware.exposeUid, middleware.ensureSelfOrGlobalPrivilege], controllers.accounts.session.revoke); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get); setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, middlewares, controllers.accounts.chats.get); diff --git a/test/controllers.js b/test/controllers.js index 1f31e0a037..1d1d12f39b 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -533,8 +533,8 @@ describe('Controllers', function () { } }, function (err, res, body) { assert.ifError(err); - assert.equal(res.statusCode, 500); - assert.equal(body, '[[error:no-session-found]]'); + assert.equal(res.statusCode, 403); + assert.equal(body, '{"path":"/user/doesnotexist/session/1112233","loggedIn":true,"title":"[[global:403.title]]"}'); done(); }); });