From b29745aa44cb97e7dc06ec38d0b8c138a3fa0d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 23 May 2017 22:09:25 -0400 Subject: [PATCH] more auth tests --- src/controllers/authentication.js | 101 +++++++++++++++-------------- src/user/approval.js | 103 ++++++++++++++++++------------ src/user/auth.js | 38 ++++++----- test/authentication.js | 93 ++++++++++++++++++++++++++- test/user.js | 2 +- 5 files changed, 226 insertions(+), 111 deletions(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index c8de5c60f1..210bbf4bf9 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -52,19 +52,7 @@ authenticationController.register = function (req, res) { user.isPasswordValid(userData.password, next); }, function (next) { - if (registrationType === 'normal' || registrationType === 'invite-only' || registrationType === 'admin-invite-only') { - next(null, false); - } else if (registrationType === 'admin-approval') { - next(null, true); - } else if (registrationType === 'admin-approval-ip') { - db.sortedSetCard('ip:' + req.ip + ':uid', function (err, count) { - if (err) { - next(err); - } else { - next(null, !!count); - } - }); - } + user.shouldQueueUser(req.ip, next); }, function (queue, next) { res.locals.processLogin = true; // set it to false in plugin if you wish to just register only @@ -200,13 +188,15 @@ authenticationController.login = function (req, res, next) { var loginWith = meta.config.allowLoginWith || 'username-email'; if (req.body.username && utils.isEmailValid(req.body.username) && loginWith.indexOf('email') !== -1) { - user.getUsernameByEmail(req.body.username, function (err, username) { - if (err) { - return next(err); - } - req.body.username = username || req.body.username; - continueLogin(req, res, next); - }); + async.waterfall([ + function (next) { + user.getUsernameByEmail(req.body.username, next); + }, + function (username, next) { + req.body.username = username || req.body.username; + continueLogin(req, res, next); + }, + ], next); } else if (loginWith.indexOf('username') !== -1 && !validator.isEmail(req.body.username)) { continueLogin(req, res, next); } else { @@ -375,7 +365,7 @@ authenticationController.localLogin = function (req, username, password, next) { } if (result.banned) { - return banUser(uid, next); + return getBanInfo(uid, next); } user.auth.logAttempt(uid, req.ip, next); @@ -394,48 +384,57 @@ authenticationController.localLogin = function (req, username, password, next) { }; authenticationController.logout = function (req, res, next) { - if (req.user && parseInt(req.user.uid, 10) > 0 && req.sessionID) { - var uid = parseInt(req.user.uid, 10); - var sessionID = req.sessionID; + if (!req.uid || !req.sessionID) { + return res.status(200).send('not-logged-in'); + } - user.auth.revokeSession(sessionID, uid, function (err) { - if (err) { - return next(err); - } + async.waterfall([ + function (next) { + user.auth.revokeSession(req.sessionID, req.uid, next); + }, + function (next) { req.logout(); req.session.destroy(); - user.setUserField(uid, 'lastonline', Date.now() - 300000); - - plugins.fireHook('static:user.loggedOut', { req: req, res: res, uid: uid }, function () { - res.status(200).send(''); - - // Force session check for all connected socket.io clients with the same session id - sockets.in('sess_' + sessionID).emit('checkSession', 0); - }); - }); - } else { - res.status(200).send(''); - } + user.setUserField(req.uid, 'lastonline', Date.now() - 300000, next); + }, + function (next) { + plugins.fireHook('static:user.loggedOut', { req: req, res: res, uid: req.uid }, next); + }, + function () { + // Force session check for all connected socket.io clients with the same session id + sockets.in('sess_' + req.sessionID).emit('checkSession', 0); + res.status(200).send(''); + }, + ], next); }; -function banUser(uid, next) { - user.getLatestBanInfo(uid, function (err, banInfo) { - if (err) { - if (err.message === 'no-ban-info') { - return next(new Error('[[error:user-banned]]')); +function getBanInfo(uid, callback) { + var banInfo; + async.waterfall([ + function (next) { + user.getLatestBanInfo(uid, next); + }, + function (_banInfo, next) { + banInfo = _banInfo; + if (banInfo.reason) { + return next(); } - return next(err); - } - - if (!banInfo.reason) { translator.translate('[[user:info.banned-no-reason]]', function (translated) { banInfo.reason = translated; - next(new Error(banInfo.expiry ? '[[error:user-banned-reason-until, ' + banInfo.expiry_readable + ', ' + banInfo.reason + ']]' : '[[error:user-banned-reason, ' + banInfo.reason + ']]')); + next(); }); - } else { + }, + function (next) { next(new Error(banInfo.expiry ? '[[error:user-banned-reason-until, ' + banInfo.expiry_readable + ', ' + banInfo.reason + ']]' : '[[error:user-banned-reason, ' + banInfo.reason + ']]')); + }, + ], function (err) { + if (err) { + if (err.message === 'no-ban-info') { + err.message = '[[error:user-banned]]'; + } } + callback(err); }); } diff --git a/src/user/approval.js b/src/user/approval.js index 23d24a1e16..d04686a7b0 100644 --- a/src/user/approval.js +++ b/src/user/approval.js @@ -3,6 +3,7 @@ var async = require('async'); var request = require('request'); +var winston = require('winston'); var db = require('../database'); var meta = require('../meta'); @@ -138,6 +139,19 @@ module.exports = function (User) { }); } + User.shouldQueueUser = function (ip, callback) { + var registrationType = meta.config.registrationType || 'normal'; + if (registrationType === 'normal' || registrationType === 'invite-only' || registrationType === 'admin-invite-only') { + setImmediate(callback, null, false); + } else if (registrationType === 'admin-approval') { + setImmediate(callback, null, true); + } else if (registrationType === 'admin-approval-ip') { + db.sortedSetCard('ip:' + ip + ':uid', function (err, count) { + callback(err, !!count); + }); + } + }; + User.getRegistrationQueue = function (start, stop, callback) { var data; async.waterfall([ @@ -152,58 +166,22 @@ module.exports = function (User) { db.getObjects(keys, next); }, function (users, next) { - users = users.map(function (user, index) { - if (user) { - user.timestampISO = utils.toISOString(data[index].score); - delete user.hashedPassword; - } - + users = users.filter(Boolean).map(function (user, index) { + user.timestampISO = utils.toISOString(data[index].score); + delete user.hashedPassword; return user; - }).filter(Boolean); + }); async.map(users, function (user, next) { - if (!user) { - return next(null, user); - } - // temporary: see http://www.stopforumspam.com/forum/viewtopic.php?id=6392 user.ip = user.ip.replace('::ffff:', ''); async.parallel([ function (next) { - User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1, function (err, uids) { - if (err) { - return next(err); - } - - User.getUsersFields(uids, ['uid', 'username', 'picture'], function (err, ipMatch) { - user.ipMatch = ipMatch; - next(err); - }); - }); + getIPMatchedUsers(user.ip, next); }, function (next) { - request({ - method: 'get', - url: 'http://api.stopforumspam.org/api' + - '?ip=' + encodeURIComponent(user.ip) + - '&email=' + encodeURIComponent(user.email) + - '&username=' + encodeURIComponent(user.username) + - '&f=json', - json: true, - }, function (err, response, body) { - if (err) { - return next(); - } - if (response.statusCode === 200 && body) { - user.spamData = body; - user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; - user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; - user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; - } - - next(); - }); + getSpamData(user, next); }, ], function (err) { next(err, user); @@ -218,4 +196,45 @@ module.exports = function (User) { }, ], callback); }; + + function getIPMatchedUsers(ip, callback) { + async.waterfall([ + function (next) { + User.getUidsFromSet('ip:' + ip + ':uid', 0, -1, next); + }, + function (uids, next) { + User.getUsersFields(uids, ['uid', 'username', 'picture'], next); + }, + ], callback); + } + + function getSpamData(user, callback) { + async.waterfall([ + function (next) { + request({ + method: 'get', + url: 'http://api.stopforumspam.org/api' + + '?ip=' + encodeURIComponent(user.ip) + + '&email=' + encodeURIComponent(user.email) + + '&username=' + encodeURIComponent(user.username) + + '&f=json', + json: true, + }, next); + }, + function (response, body, next) { + if (response.statusCode === 200 && body) { + user.spamData = body; + user.usernameSpam = body.username ? (body.username.frequency > 0 || body.username.appears > 0) : true; + user.emailSpam = body.email ? (body.email.frequency > 0 || body.email.appears > 0) : true; + user.ipSpam = body.ip ? (body.ip.frequency > 0 || body.ip.appears > 0) : true; + } + next(); + }, + ], function (err) { + if (err) { + winston.error(err); + } + callback(); + }); + } }; diff --git a/src/user/auth.js b/src/user/auth.js index f1ac386136..2b72ee3c95 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -118,22 +118,28 @@ module.exports = function (User) { User.auth.revokeSession = function (sessionId, uid, callback) { winston.verbose('[user.auth] Revoking session ' + sessionId + ' for user ' + uid); - db.sessionStore.get(sessionId, function (err, sessionObj) { - if (err) { - return callback(err); - } - async.parallel([ - function (next) { - if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) { - db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid, next); - } else { - next(); - } - }, - async.apply(db.sortedSetRemove, 'uid:' + uid + ':sessions', sessionId), - async.apply(db.sessionStore.destroy.bind(db.sessionStore), sessionId), - ], callback); - }); + async.waterfall([ + function (next) { + db.sessionStore.get(sessionId, function (err, sessionObj) { + next(err, sessionObj || null); + }); + }, + function (sessionObj, next) { + async.parallel([ + function (next) { + if (sessionObj && sessionObj.meta && sessionObj.meta.uuid) { + db.deleteObjectField('uid:' + uid + ':sessionUUID:sessionId', sessionObj.meta.uuid, next); + } else { + next(); + } + }, + async.apply(db.sortedSetRemove, 'uid:' + uid + ':sessions', sessionId), + async.apply(db.sessionStore.destroy.bind(db.sessionStore), sessionId), + ], function (err) { + next(err); + }); + }, + ], callback); }; User.auth.revokeAllSessions = function (uid, callback) { diff --git a/test/authentication.js b/test/authentication.js index 1d3651e183..3d32076e4e 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -88,6 +88,7 @@ describe('authentication', function () { email: 'admin@nodebb.org', username: 'admin', password: 'adminpwd', + userLang: 'it', }, json: true, jar: jar, @@ -107,7 +108,11 @@ describe('authentication', function () { assert(body); assert.equal(body.username, 'admin'); assert.equal(body.email, 'admin@nodebb.org'); - done(); + user.getSettings(body.uid, function (err, settings) { + assert.ifError(err); + assert.equal(settings.userLang, 'it'); + done(); + }); }); }); }); @@ -298,6 +303,92 @@ describe('authentication', function () { }); }); + it('should queue user if ip is used before', function (done) { + meta.config.registrationType = 'admin-approval-ip'; + registerUser('another@user.com', 'anotheruser', 'anotherpwd', function (err, response, body) { + meta.config.registrationType = 'normal'; + assert.ifError(err); + assert.equal(response.statusCode, 200); + assert.equal(body.message, '[[register:registration-added-to-queue]]'); + done(); + }); + }); + + + it('should be able to login with email', function (done) { + user.create({ username: 'ginger', password: '123456', email: 'ginger@nodebb.org' }, function (err, uid) { + assert.ifError(err); + loginUser('ginger@nodebb.org', '123456', function (err, response, body) { + assert.ifError(err); + assert.equal(response.statusCode, 200); + done(); + }); + }); + }); + + it('should fail to login if login type is username and an email is sent', function (done) { + meta.config.allowLoginWith = 'username'; + loginUser('ginger@nodebb.org', '123456', function (err, response, body) { + meta.config.allowLoginWith = 'username-email'; + assert.ifError(err); + assert.equal(response.statusCode, 500); + assert.equal(body, '[[error:wrong-login-type-username]]'); + done(); + }); + }); + + it('should send 200 if not logged in', function (done) { + var jar = request.jar(); + request({ + url: nconf.get('url') + '/api/config', + json: true, + jar: jar, + }, function (err, response, body) { + assert.ifError(err); + + request.post(nconf.get('url') + '/logout', { + form: {}, + json: true, + jar: jar, + headers: { + 'x-csrf-token': body.csrf_token, + }, + }, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert.equal(body, 'not-logged-in'); + done(); + }); + }); + }); + + it('should prevent banned user from logging in', function (done) { + user.create({ username: 'banme', password: '123456', email: 'ban@me.com' }, function (err, uid) { + assert.ifError(err); + user.ban(uid, 0, 'spammer', function (err) { + assert.ifError(err); + loginUser('banme', '123456', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 403); + assert.equal(body, '[[error:user-banned-reason, spammer]]'); + user.unban(uid, function (err) { + assert.ifError(err); + var expiry = Date.now() + 10000; + user.ban(uid, expiry, '', function (err) { + assert.ifError(err); + loginUser('banme', '123456', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 403); + assert.equal(body, '[[error:user-banned-reason-until, ' + (new Date(parseInt(expiry, 10)).toString()) + ', No reason given.]]'); + done(); + }); + }); + }); + }); + }); + }); + }); + after(function (done) { db.emptydb(done); }); diff --git a/test/user.js b/test/user.js index fd1d720ab5..284192b8d4 100644 --- a/test/user.js +++ b/test/user.js @@ -1384,7 +1384,7 @@ describe('User', function () { done(); }); - it('should send digetst', function (done) { + it('should send digest', function (done) { db.sortedSetAdd('digest:day:uids', [Date.now(), Date.now()], [1, 2], function (err) { assert.ifError(err); User.digest.execute('day', function (err) {