diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c7456e2ae..3ee1f3f3f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +#### v3.4.1 (2023-09-06) + +##### Chores + +* up dbsearch (8357bb2e) +* incrementing version number - v3.4.0 (fd9247c5) +* update changelog for v3.4.0 (5c023025) +* incrementing version number - v3.3.9 (5805e770) +* incrementing version number - v3.3.8 (a5603565) +* incrementing version number - v3.3.7 (b26f1744) +* incrementing version number - v3.3.6 (7fb38792) +* incrementing version number - v3.3.4 (a67f84ea) +* incrementing version number - v3.3.3 (f94d239b) +* incrementing version number - v3.3.2 (ec9dac97) +* incrementing version number - v3.3.1 (151cc68f) +* incrementing version number - v3.3.0 (fc1ad70f) +* incrementing version number - v3.2.3 (b06d3e63) +* incrementing version number - v3.2.2 (758ecfcd) +* incrementing version number - v3.2.1 (20145074) +* incrementing version number - v3.2.0 (9ecac38e) +* incrementing version number - v3.1.7 (0b4e81ab) +* incrementing version number - v3.1.6 (b3a3b130) +* incrementing version number - v3.1.5 (ec19343a) +* incrementing version number - v3.1.4 (2452783c) +* incrementing version number - v3.1.3 (3b4e9d3f) +* incrementing version number - v3.1.2 (40fa3489) +* incrementing version number - v3.1.1 (40250733) +* incrementing version number - v3.1.0 (0cb386bd) +* incrementing version number - v3.0.1 (26f6ea49) +* incrementing version number - v3.0.0 (224e08cd) + +##### Bug Fixes + +* #11981, post immediately when canceling scheduling (19b7cdb2) + #### v3.4.0 (2023-09-06) ##### Chores diff --git a/install/package.json b/install/package.json index 83dc54d8dc..67e579e57f 100644 --- a/install/package.json +++ b/install/package.json @@ -92,8 +92,8 @@ "mousetrap": "1.6.5", "multiparty": "4.2.3", "nconf": "0.12.0", - "nodebb-plugin-2factor": "7.2.1", - "nodebb-plugin-composer-default": "10.2.18", + "nodebb-plugin-2factor": "7.2.2", + "nodebb-plugin-composer-default": "10.2.20", "nodebb-plugin-dbsearch": "6.2.2", "nodebb-plugin-emoji": "5.1.5", "nodebb-plugin-emoji-android": "4.0.0", @@ -102,10 +102,10 @@ "nodebb-plugin-ntfy": "1.6.1", "nodebb-plugin-spam-be-gone": "2.1.1", "nodebb-rewards-essentials": "0.2.3", - "nodebb-theme-harmony": "1.1.52", + "nodebb-theme-harmony": "1.1.60", "nodebb-theme-lavender": "7.1.3", "nodebb-theme-peace": "2.1.18", - "nodebb-theme-persona": "13.2.27", + "nodebb-theme-persona": "13.2.29", "nodebb-widget-essentials": "7.0.13", "nodemailer": "6.9.5", "nprogress": "0.2.0", @@ -194,4 +194,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} diff --git a/public/src/client/chats.js b/public/src/client/chats.js index cc8e3d7133..53a2835e9a 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -331,9 +331,22 @@ define('forum/chats', [ textarea.on('focus', () => textarea.val() && emitTyping(true)); textarea.on('blur', () => emitTyping(false)); - textarea.on('input', utils.throttle(function () { - emitTyping(!!textarea.val()); - }, 2500, true)); + let timeoutid = 0; + let hasText = !!textarea.val(); + textarea.on('input', function () { + const _hasText = !!textarea.val(); + if (_hasText !== hasText) { + clearTimeout(timeoutid); + timeoutid = 0; + hasText = _hasText; + emitTyping(hasText); + } else if (!timeoutid) { + timeoutid = setTimeout(() => { + emitTyping(!!textarea.val()); + timeoutid = 0; + }, 5000); + } + }); }; Chats.addActionHandlers = function (element, roomId) { @@ -389,7 +402,7 @@ define('forum/chats', [ return; } const lastMid = message.attr('data-mid'); - messages.prepEdit(inputEl, lastMid, ajaxify.data.roomId); + messages.prepEdit(message, lastMid, ajaxify.data.roomId); } }); }; diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index 88e03bf4bc..17c85abb37 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -198,6 +198,7 @@ define('forum/chats/messages', [ const messageControls = msgEl.find(`[component="chat/message/controls"]`); const chatContent = messageBody.parents('[component="chat/message/content"]'); + const isAtBottom = messages.isAtBottom(chatContent); messageBody.addClass('hidden'); messageControls.addClass('hidden'); editEl.insertAfter(messageBody); @@ -207,7 +208,7 @@ define('forum/chats/messages', [ textarea.focus().putCursorAtEnd(); autoresizeTextArea(textarea); - if (chatContent.length && messages.isAtBottom(chatContent)) { + if (chatContent.length && isAtBottom) { messages.scrollToBottom(chatContent); } diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 08515f1124..82eec9f566 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -379,15 +379,12 @@ authenticationController.onSuccessfulLogin = async function (req, uid) { new Promise((resolve) => { req.session.save(resolve); }), - user.auth.addSession(uid, req.sessionID), + user.auth.addSession(uid, req.sessionID, uuid), user.updateLastOnlineTime(uid), user.onUserOnline(uid, Date.now()), analytics.increment('logins'), db.incrObjectFieldBy('global', 'loginCount', 1), ]); - if (uid > 0) { - await db.setObjectField(`uid:${uid}:sessionUUID:sessionId`, uuid, req.sessionID); - } // Force session check for all connected socket.io clients with the same session id sockets.in(`sess_${req.sessionID}`).emit('checkSession', uid); diff --git a/src/messaging/create.js b/src/messaging/create.js index 81da98d0d6..f40002b801 100644 --- a/src/messaging/create.js +++ b/src/messaging/create.js @@ -42,8 +42,13 @@ module.exports = function (Messaging) { if (!roomData) { throw new Error('[[error:no-room]]'); } - if (data.toMid && !utils.isNumber(data.toMid)) { - throw new Error('[[error:invalid-mid]]'); + if (data.toMid) { + if (!utils.isNumber(data.toMid)) { + throw new Error('[[error:invalid-mid]]'); + } + if (!await Messaging.canViewMessage(data.toMid, roomId, uid)) { + throw new Error('[[error:no-privileges]]'); + } } const mid = await db.incrObjectField('global', 'nextMid'); const timestamp = data.timestamp || Date.now(); diff --git a/src/messaging/data.js b/src/messaging/data.js index e6466f579c..20568cc3f7 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -132,6 +132,9 @@ module.exports = function (Messaging) { return; } parentMids = _.uniq(parentMids); + const canView = await Messaging.canViewMessage(parentMids, roomId, uid); + parentMids = parentMids.filter((mid, idx) => canView[idx]); + const parentMessages = await Messaging.getMessagesFields(parentMids, [ 'fromuid', 'content', 'timestamp', 'deleted', ]); diff --git a/src/user/auth.js b/src/user/auth.js index d8113547e6..5330903a15 100644 --- a/src/user/auth.js +++ b/src/user/auth.js @@ -106,12 +106,15 @@ module.exports = function (User) { await db.sortedSetRemove(`uid:${uid}:sessions`, expiredSids); } - User.auth.addSession = async function (uid, sessionId) { + User.auth.addSession = async function (uid, sessionId, uuid) { if (!(parseInt(uid, 10) > 0)) { return; } await cleanExpiredSessions(uid); - await db.sortedSetAdd(`uid:${uid}:sessions`, Date.now(), sessionId); + await Promise.all([ + db.sortedSetAdd(`uid:${uid}:sessions`, Date.now(), sessionId), + db.setObjectField(`uid:${uid}:sessionUUID:sessionId`, uuid, sessionId), + ]); await revokeSessionsAboveThreshold(uid, meta.config.maxUserSessions); }; diff --git a/test/messaging.js b/test/messaging.js index 395d0f8764..df88236044 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -1,7 +1,6 @@ 'use strict'; const assert = require('assert'); -const async = require('async'); const request = require('request-promise-native'); const nconf = require('nconf'); const util = require('util'); @@ -369,7 +368,6 @@ describe('Messaging Library', () => { }); it('should fail to send second message due to rate limit', async () => { - const socketMock = { uid: mocks.users.foo.uid }; const oldValue = meta.config.chatMessageDelay; meta.config.chatMessageDelay = 1000; @@ -572,6 +570,55 @@ describe('Messaging Library', () => { }); }); + describe('toMid', () => { + let roomId; + let firstMid; + before(async () => { + // create room + const { body } = await callv3API('post', `/chats`, { + uids: [mocks.users.bar.uid], + }, 'foo'); + roomId = body.response.roomId; + // send message + const result = await callv3API('post', `/chats/${roomId}`, { + roomId: roomId, + message: 'first chat message', + }, 'foo'); + + firstMid = result.body.response.mid; + }); + + it('should fail if toMid is not a number', async () => { + const result = await callv3API('post', `/chats/${roomId}`, { + roomId: roomId, + message: 'invalid', + toMid: 'osmaosd', + }, 'foo'); + assert.strictEqual(result.body.status.message, 'Invalid Chat Message ID'); + }); + + it('should reply to firstMid using toMid', async () => { + const { body } = await callv3API('post', `/chats/${roomId}`, { + roomId: roomId, + message: 'invalid', + toMid: firstMid, + }, 'bar'); + assert(body.response.mid); + }); + + it('should fail if user can not view toMid', async () => { + // add new user + await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.herp.uid] }, 'foo'); + // try to reply to firstMid that this user cant see + const { body } = await callv3API('post', `/chats/${roomId}`, { + roomId: roomId, + message: 'invalid', + toMid: firstMid, + }, 'herp'); + assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.'); + }); + }); + describe('edit/delete', () => { const socketModules = require('../src/socket.io/modules'); let mid; @@ -766,7 +813,7 @@ describe('Messaging Library', () => { assert.equal(response.statusCode, 200); assert(Array.isArray(body.rooms)); - assert.equal(body.rooms.length, 2); + assert.equal(body.rooms.length, 3); assert.equal(body.title, '[[pages:chats]]'); }); diff --git a/test/user/reset.js b/test/user/reset.js index 36f80b2dda..a2c1d631cd 100644 --- a/test/user/reset.js +++ b/test/user/reset.js @@ -112,7 +112,7 @@ describe('Password reset (library methods)', () => { }); }); -describe.only('locks', () => { +describe('locks', () => { let uid; let email; beforeEach(async () => {