diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 1b0b005805..9cbed30fdd 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -46,6 +46,7 @@ "username-too-long": "Username too long", "password-too-long": "Password too long", "reset-rate-limited": "Too many password reset requests (rate limited)", + "reset-same-password": "Please use a password that is different from your current one", "user-banned": "User banned", "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", diff --git a/public/src/app.js b/public/src/app.js index 1d47e7bd01..72dd0faf87 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -180,8 +180,9 @@ app.cacheBuster = null; message = message.message || message; if (message === '[[error:invalid-session]]') { + app.handleInvalidSession(); app.logout(false); - return app.handleInvalidSession(); + return; } app.alert({ diff --git a/public/src/sockets.js b/public/src/sockets.js index 97c95affdf..b83e6956e4 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -69,6 +69,9 @@ socket = window.socket; }); socket.on('event:banned', onEventBanned); + socket.on('event:logout', function () { + app.logout(); + }); socket.on('event:alert', function (params) { app.alert(params); }); diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index bf0325e484..65201d50d3 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -120,6 +120,7 @@ User.forcePasswordReset = async function (socket, uids) { await db.setObjectField(uids.map(uid => 'user:' + uid), 'passwordExpiry', Date.now()); await user.auth.revokeAllSessions(uids); + uids.forEach(uid => sockets.in('uid_' + uid).emit('event:logout')); }; User.deleteUsers = async function (socket, uids) { diff --git a/src/user/reset.js b/src/user/reset.js index 1e152e6909..eabf826c5a 100644 --- a/src/user/reset.js +++ b/src/user/reset.js @@ -11,6 +11,7 @@ const batch = require('../batch'); const db = require('../database'); const meta = require('../meta'); const emailer = require('../emailer'); +const Password = require('../password'); const UserReset = module.exports; @@ -67,12 +68,28 @@ UserReset.commit = async function (code, password) { if (!uid) { throw new Error('[[error:reset-code-not-valid]]'); } - + const userData = await db.getObjectFields( + 'user:' + uid, + ['password', 'passwordExpiry', 'password:shaWrapped'] + ); + const ok = await Password.compare(password, userData.password, !!parseInt(userData['password:shaWrapped'], 10)); + if (ok) { + throw new Error('[[error:reset-same-password]]'); + } const hash = await user.hashPassword(password); - - await user.setUserFields(uid, { password: hash, 'email:confirmed': 1, 'password:shaWrapped': 1 }); - await groups.join('verified-users', uid); - await groups.leave('unverified-users', uid); + const data = { + password: hash, + 'password:shaWrapped': 1, + }; + + // don't verify email if password reset is due to expiry + const isPasswordExpired = userData.passwordExpiry && userData.passwordExpiry < Date.now(); + if (!isPasswordExpired) { + data['email:confirmed'] = 1; + await groups.join('verified-users', uid); + await groups.leave('unverified-users', uid); + } + await user.setUserFields(uid, data); await db.deleteObjectField('reset:uid', code); await db.sortedSetRemoveBulk([ ['reset:issueDate', code], diff --git a/test/user.js b/test/user.js index f87fe2bfe5..5b8c7a911f 100644 --- a/test/user.js +++ b/test/user.js @@ -628,6 +628,35 @@ describe('User', function () { }, ], done); }); + + it('.should error if same password is used for reset', async function () { + const uid = await User.create({ username: 'badmemory', email: 'bad@memory.com', password: '123456' }); + const code = await User.reset.generate(uid); + let err; + try { + await User.reset.commit(code, '123456'); + } catch (_err) { + err = _err; + } + assert.strictEqual(err.message, '[[error:reset-same-password]]'); + }); + + it('should not validate email if password reset is due to expiry', async function () { + const uid = await User.create({ username: 'resetexpiry', email: 'reset@expiry.com', password: '123456' }); + let confirmed = await User.getUserField(uid, 'email:confirmed'); + let [verified, unverified] = await groups.isMemberOfGroups(uid, ['verified-users', 'unverified-users']); + assert.strictEqual(confirmed, 0); + assert.strictEqual(verified, false); + assert.strictEqual(unverified, true); + await User.setUserField(uid, 'passwordExpiry', Date.now()); + const code = await User.reset.generate(uid); + await User.reset.commit(code, '654321'); + confirmed = await User.getUserField(uid, 'email:confirmed'); + [verified, unverified] = await groups.isMemberOfGroups(uid, ['verified-users', 'unverified-users']); + assert.strictEqual(confirmed, 0); + assert.strictEqual(verified, false); + assert.strictEqual(unverified, true); + }); }); describe('hash methods', function () {