diff --git a/src/socket.io/user.js b/src/socket.io/user.js index c10a510e71..060c9e7b2e 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -272,35 +272,34 @@ SocketUser.invite = function (socket, email, callback) { return callback(new Error('[[error:forum-not-invite-only]]')); } - var max = meta.config.maximumInvites; + async.waterfall([ + function (next) { + user.isAdministrator(socket.uid, next); + }, + function (isAdmin, next) { + if (registrationType === 'admin-invite-only' && !isAdmin) { + return next(new Error('[[error:no-privileges]]')); + } + + var max = parseInt(meta.config.maximumInvites, 10); + if (!max) { + return user.sendInvitationEmail(socket.uid, email, callback); + } - user.isAdministrator(socket.uid, function (err, admin) { - if (err) { - return callback(err); - } - if (registrationType === 'admin-invite-only' && !admin) { - return callback(new Error('[[error:no-privileges]]')); - } - if (max) { async.waterfall([ function (next) { user.getInvitesNumber(socket.uid, next); }, function (invites, next) { - if (!admin && invites > max) { + if (!isAdmin && invites >= max) { return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]')); } - next(); - }, - function (next) { + user.sendInvitationEmail(socket.uid, email, next); } - ], callback); - } else { - user.sendInvitationEmail(socket.uid, email, callback); + ], next); } - }); - + ], callback); }; SocketUser.getUserByUID = function (socket, uid, callback) { diff --git a/src/user/invite.js b/src/user/invite.js index 1066a662ac..043bf62a7b 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -61,9 +61,7 @@ module.exports = function (User) { if (exists) { return next(new Error('[[error:email-taken]]')); } - next(); - }, - function (next) { + async.parallel([ function (next) { db.setAdd('invitation:uid:' + uid, email, next); @@ -131,11 +129,11 @@ module.exports = function (User) { return next(new Error('[[error:invalid-username]]')); } async.parallel([ - function deleteFromReferenceList(next) { - db.setRemove('invitation:uid:' + invitedByUid, email, next); + function (next) { + deleteFromReferenceList(invitedByUid, email, next); }, - function deleteInviteKey(next) { - db.delete('invitation:email:' + email, callback); + function (next) { + db.delete('invitation:email:' + email, next); } ], function (err) { next(err); @@ -146,7 +144,37 @@ module.exports = function (User) { User.deleteInvitationKey = function (email, callback) { callback = callback || function () {}; - db.delete('invitation:email:' + email, callback); + + async.waterfall([ + function (next) { + User.getInvitingUsers(next); + }, + function (uids, next) { + async.each(uids, function (uid, next) { + deleteFromReferenceList(uid, email, next); + }, next); + }, + function (next) { + db.delete('invitation:email:' + email, next); + } + ], callback); }; + function deleteFromReferenceList(uid, email, callback) { + async.waterfall([ + function (next) { + db.setRemove('invitation:uid:' + uid, email, next); + }, + function (next) { + db.setCount('invitation:uid:' + uid, next); + }, + function (count, next) { + if (count === 0) { + return db.setRemove('invitation:uids', uid, next); + } + setImmediate(next); + } + ], callback); + } + }; diff --git a/test/user.js b/test/user.js index a1cc142afb..a666d2c115 100644 --- a/test/user.js +++ b/test/user.js @@ -513,8 +513,7 @@ describe('User', function () { it('should upload profile picture', function (done) { helpers.copyFile( path.join(nconf.get('base_dir'), 'test/files/test.png'), - path.join(nconf.get('base_dir'), 'test/files/test_copy.png') - , function (err) { + path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), function (err) { assert.ifError(err); var picture = { path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), @@ -572,7 +571,7 @@ describe('User', function () { }); it('should return error if no plugins listening for filter:uploadImage when uploading from url', function (done) { - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; User.uploadFromUrl(uid, url, function (err) { assert.equal(err.message, '[[error:no-plugin]]'); done(); @@ -595,7 +594,7 @@ describe('User', function () { }); it('should return error if the file is too big when uploading from url', function (done) { - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; meta.config.maximumProfileImageSize = 1; function filterMethod(data, callback) { @@ -613,7 +612,7 @@ describe('User', function () { it('should error with invalid data', function (done) { var socketUser = require('../src/socket.io/user'); - socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: ''}, function (err, uploadedPicture) { + socketUser.uploadProfileImageFromUrl({uid: uid}, {uid: uid, url: ''}, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); done(); }); @@ -621,7 +620,7 @@ describe('User', function () { it('should upload picture when uploading from url', function (done) { var socketUser = require('../src/socket.io/user'); - var url = nconf.get('url') + '/logo.png'; + var url = nconf.get('url') + '/assets/logo.png'; meta.config.maximumProfileImageSize = ''; function filterMethod(data, callback) { @@ -1006,6 +1005,149 @@ describe('User', function () { }); }); }); + }); + + describe('invites', function () { + var socketUser = require('../src/socket.io/user'); + var inviterUid; + + before(function (done) { + User.create({ + username: 'inviter', + email: 'inviter@nodebb.org' + }, function (err, uid) { + assert.ifError(err); + inviterUid = uid; + done(); + }); + }); + + it('should error with invalid data', function (done) { + socketUser.invite({uid: inviterUid}, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should eror if forum is not invite only', function (done) { + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.equal(err.message, '[[error:forum-not-invite-only]]'); + done(); + }); + }); + + it('should error if user is not admin and type is admin-invite-only', function (done) { + meta.config.registrationType = 'admin-invite-only'; + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + done(); + }); + }); + + it('should send invitation email', function (done) { + meta.config.registrationType = 'invite-only'; + socketUser.invite({uid: inviterUid}, 'invite1@test.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should error if ouf of invitations', function (done) { + meta.config.maximumInvites = 1; + socketUser.invite({uid: inviterUid}, 'invite2@test.com', function (err) { + assert.equal(err.message, '[[error:invite-maximum-met, ' + 1 + ', ' + 1 + ']]'); + meta.config.maximumInvites = 5; + done(); + }); + }); + + it('should error if email exists', function (done) { + socketUser.invite({uid: inviterUid}, 'inviter@nodebb.org', function (err) { + assert.equal(err.message, '[[error:email-taken]]'); + done(); + }); + }); + + it('should send invitation email', function (done) { + socketUser.invite({uid: inviterUid}, 'invite2@test.com', function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should get user\'s invites', function (done) { + User.getInvites(inviterUid, function (err, data) { + assert.ifError(err); + assert.deepEqual(data, ['invite1@test.com', 'invite2@test.com']); + done(); + }); + }); + + it('should get all invites', function (done) { + User.getAllInvites(function (err, data) { + assert.ifError(err); + assert.deepEqual(data, [ { uid: inviterUid, invitations: [ 'invite1@test.com', 'invite2@test.com' ] } ]); + done(); + }); + }); + + it('should fail to verify invitation with invalid data', function (done) { + User.verifyInvitation({token: '', email: ''}, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should fail to verify invitation with invalid email', function (done) { + User.verifyInvitation({token: 'test', email: 'doesnotexist@test.com'}, function (err) { + assert.equal(err.message, '[[error:invalid-token]]'); + done(); + }); + }); + + it('should verify installation with no errors', function (done) { + db.get('invitation:email:' + 'invite1@test.com', function (err, token) { + assert.ifError(err); + User.verifyInvitation({token: token, email: 'invite1@test.com'}, function (err) { + assert.ifError(err); + done(); + }); + }); + }); + + it('should error with invalid username', function (done) { + User.deleteInvitation('doesnotexist', 'test@test.com', function (err) { + assert.equal(err.message, '[[error:invalid-username]]'); + done(); + }); + }); + + it('should delete invitation', function (done) { + var socketAdmin = require('../src/socket.io/admin'); + socketAdmin.user.deleteInvitation({uid: inviterUid}, {invitedBy: 'inviter', email: 'invite1@test.com'}, function (err) { + assert.ifError(err); + db.isSetMember('invitation:uid:' + inviterUid, 'invite1@test.com', function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + done(); + }); + }); + }); + + it('should delete invitation key', function (done) { + User.deleteInvitationKey('invite2@test.com', function (err) { + assert.ifError(err); + db.isSetMember('invitation:uid:' + inviterUid, 'invite2@test.com', function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + db.isSetMember('invitation:uids', inviterUid, function (err, isMember) { + assert.ifError(err); + assert.equal(isMember, false); + done(); + }); + }); + }); + }); });