From 0c09b7402d8f2dcc8dc84a34cce1d70e29476ebc Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 8 Feb 2019 10:50:15 -0500 Subject: [PATCH] feat: logging password resets and errors into event log closes #7343, also adds tests for password reset socket calls --- public/language/en-GB/error.json | 1 + src/socket.io/user.js | 21 +++++----- test/socket.io.js | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 5adc3351e2..072fbdcc88 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -39,6 +39,7 @@ "username-too-short": "Username too short", "username-too-long": "Username too long", "password-too-long": "Password too long", + "reset-rate-limited": "Too many password reset requests (rate limited)", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", diff --git a/src/socket.io/user.js b/src/socket.io/user.js index 3db21d34d8..add94f3804 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -1,7 +1,6 @@ 'use strict'; var async = require('async'); -var winston = require('winston'); var user = require('../user'); var topics = require('../topics'); @@ -102,17 +101,17 @@ SocketUser.reset.send = function (socket, email, callback) { } user.reset.send(email, function (err) { - if (err) { - switch (err.message) { - case '[[error:invalid-email]]': - winston.warn('[user/reset] Invalid email attempt: ' + email + ' by IP ' + socket.ip + (socket.uid ? ' (uid: ' + socket.uid + ')' : '')); - err = null; - break; + events.log({ + type: 'password-reset', + text: err ? err.message : '[[success:success]]', + ip: socket.ip, + uid: socket.uid, + email: email, + }); - case '[[error:reset-rate-limited]]': - err = null; - break; - } + const internalErrors = ['[[error:invalid-email]]', '[[error:reset-rate-limited]]']; + if (err && internalErrors.includes(err.message)) { + err = null; } setTimeout(callback.bind(err), 2500); diff --git a/test/socket.io.js b/test/socket.io.js index 72b883e841..4e71fd7a8d 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -17,6 +17,7 @@ var groups = require('../src/groups'); var categories = require('../src/categories'); var helpers = require('./helpers'); var meta = require('../src/meta'); +const events = require('../src/events'); var socketAdmin = require('../src/socket.io/admin'); @@ -634,4 +635,72 @@ describe('socket.io', function () { done(); }); }); + + describe('password reset', function () { + const socketUser = require('../src/socket.io/user'); + + it('should not error on valid email', function (done) { + socketUser.reset.send({ uid: 0 }, 'regular@test.com', function (err) { + assert.ifError(err); + + async.parallel({ + count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()), + event: async.apply(events.getEvents, '', 0, 0), + }, function (err, data) { + assert.ifError(err); + assert.strictEqual(data.count, 1); + + // Event validity + assert.strictEqual(data.event.length, 1); + const event = data.event[0]; + assert.strictEqual(event.type, 'password-reset'); + assert.strictEqual(event.text, '[[success:success]]'); + + done(); + }); + }); + }); + + it('should not generate code if rate limited', function (done) { + socketUser.reset.send({ uid: 0 }, 'regular@test.com', function (err) { + assert.ifError(err); + + async.parallel({ + count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()), + event: async.apply(events.getEvents, '', 0, 0), + }, function (err, data) { + assert.ifError(err); + assert.strictEqual(data.count, 1); // should still equal 1 + + // Event validity + assert.strictEqual(data.event.length, 1); + const event = data.event[0]; + assert.strictEqual(event.type, 'password-reset'); + assert.strictEqual(event.text, '[[error:reset-rate-limited]]'); + + done(); + }); + }); + }); + + it('should not error on invalid email (but not generate reset code)', function (done) { + socketUser.reset.send({ uid: 0 }, 'irregular@test.com', function (err) { + assert.ifError(err); + + db.sortedSetCount('reset:issueDate', 0, Date.now(), function (err, count) { + assert.ifError(err); + assert.strictEqual(count, 1); + done(); + }); + }); + }); + + it('should error on no email', function (done) { + socketUser.reset.send({ uid: 0 }, '', function (err) { + assert(err instanceof Error); + assert.strictEqual(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + }); });