diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ac7ed189..425a480f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +#### v2.4.5 (2022-08-22) + +##### Chores + +* incrementing version number - v2.4.4 (d5525c87) +* update changelog for v2.4.4 (77e492b8) +* incrementing version number - v2.4.3 (9c647c6c) +* incrementing version number - v2.4.2 (3aa7b855) +* incrementing version number - v2.4.1 (60cbd148) +* incrementing version number - v2.4.0 (4834cde3) +* incrementing version number - v2.3.1 (d2425942) +* incrementing version number - v2.3.0 (046ea120) + +##### Bug Fixes + +* wrap passport.authenticate to pass in keepSessionInfo if not already set (9b96c33d) +* parseInt caller.uid closes #10849 (bc37a5c5) + #### v2.4.4 (2022-08-18) ##### Chores diff --git a/install/package.json b/install/package.json index 9944f2fa3e..2b735cba45 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "2.4.4", + "version": "2.4.5", "homepage": "http://www.nodebb.org", "repository": { "type": "git", diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 0c111bcf7f..5facb841a2 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -177,6 +177,12 @@ define('forum/topic', [ Topic.applyDropup.call(this); } }); + hooks.onPage('action:topic.tools.load', ({ element }) => { + Topic.applyDropup.call(element.get(0).parentNode); + }); + hooks.onPage('action:post.tools.load', ({ element }) => { + Topic.applyDropup.call(element.get(0).parentNode); + }); } function addRepliesHandler() { diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 22576a6a42..c6ef662aba 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -32,7 +32,6 @@ define('forum/topic/postTools', [ function renderMenu() { $('[component="topic"]').on('show.bs.dropdown', '.moderator-tools', function () { - const self = this; const $this = $(this); const dropdownMenu = $this.find('.dropdown-menu'); if (dropdownMenu.html()) { @@ -50,14 +49,14 @@ define('forum/topic/postTools', [ const html = await app.parseAndTranslate('partials/topic/post-menu-list', data); const clipboard = require('clipboard'); - // eslint-disable-next-line import/no-unresolved - const topic = require('forum/topic'); dropdownMenu.html(html); - topic.applyDropup.call(self); +// dropdownMenu.get(0).classList.toggle('hidden', false); new clipboard('[data-clipboard-text]'); - hooks.fire('action:post.tools.load'); + hooks.fire('action:post.tools.load', { + element: dropdownMenu, + }); }); }); } diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index 8b6a6fb0f4..d29bb024c5 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -180,12 +180,15 @@ define('forum/topic/threadTools', [ return; } + dropdownMenu.toggleClass('hidden', true); socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid }, function (err, data) { if (err) { return alerts.error(err); } app.parseAndTranslate('partials/topic/topic-menu-list', data, function (html) { dropdownMenu.html(html); + dropdownMenu.toggleClass('hidden', false); + hooks.fire('action:topic.tools.load', { element: dropdownMenu, }); diff --git a/test/user.js b/test/user.js index 875e5fab1e..9252b25044 100644 --- a/test/user.js +++ b/test/user.js @@ -2548,21 +2548,45 @@ describe('User', () => { describe('hideEmail/hideFullname', () => { const COMMON_PW = '123456'; - let uid; - let jar; - let regularUserUid; + const hidingUser = { + username: 'hiddenemail', + email: 'should@be.hidden', + fullname: 'baris soner usakli', + password: COMMON_PW, + }; + const regularUser = { + username: 'regularUser', + email: 'regular@example.com', + fullname: 'regular user', + password: COMMON_PW, + }; + let hidingUserJar; + let adminUid; + let adminJar; + let globalModJar; + let regularUserJar; before(async () => { - uid = await User.create({ - username: 'hiddenemail', - email: 'should@be.hidden', - fullname: 'baris soner usakli', - }); - regularUserUid = await User.create({ - username: 'regularUser', + adminUid = await User.create({ + username: 'adminhideemail', password: COMMON_PW, }); - ({ jar } = await helpers.loginUser('regularUser', COMMON_PW)); + await groups.join('administrators', adminUid); + ({ jar: adminJar } = await helpers.loginUser('adminhideemail', COMMON_PW)); + + // Edge case: In a grepped test, this user should not be created as the first user to have its email not confirmed + hidingUser.uid = await User.create(hidingUser); + ({ jar: hidingUserJar } = await helpers.loginUser(hidingUser.username, COMMON_PW)); + + const globalModUid = await User.create({ + username: 'globalmodhideemail', + password: COMMON_PW, + }); + await groups.join('Global Moderators', globalModUid); + ({ jar: globalModJar } = await helpers.loginUser('globalmodhideemail', COMMON_PW)); + + regularUser.uid = await User.create(regularUser); + ({ jar: regularUserJar } = await helpers.loginUser(regularUser.username, COMMON_PW)); }); after((done) => { @@ -2571,22 +2595,152 @@ describe('User', () => { done(); }); - it('should hide email and fullname', async () => { + async function assertPrivacy({ expectVisible, jar, v3Api, emailOnly }) { + const path = v3Api ? `v3/users/${hidingUser.uid}` : `user/${hidingUser.username}`; + const response = await requestAsync(`${nconf.get('url')}/api/${path}`, { json: true, jar }); + const { response: userData } = v3Api ? response : { response }; + + assert.strictEqual(userData.email, expectVisible ? hidingUser.email : ''); + if (!emailOnly) { + assert.strictEqual(userData.fullname, expectVisible ? hidingUser.fullname : ''); + } + } + + it('should hide unconfirmed emails on profile pages', async () => { + await assertPrivacy({ v3Api: false, emailOnly: true }); + await assertPrivacy({ v3Api: false, jar: hidingUserJar, emailOnly: true }); + await assertPrivacy({ v3Api: false, jar: adminJar, emailOnly: true }); + await assertPrivacy({ v3Api: false, jar: globalModJar, emailOnly: true }); + await assertPrivacy({ v3Api: false, jar: regularUserJar, emailOnly: true }); + + // Let's confirm for afterwards + await User.email.confirmByUid(hidingUser.uid); + }); + + it('should hide from guests by default', async () => { + await assertPrivacy({ v3Api: false }); + }); + + it('should hide from unprivileged users by default', async () => { + await assertPrivacy({ v3Api: false, jar: regularUserJar }); + await assertPrivacy({ v3Api: true, jar: regularUserJar }); + }); + + it('should be visible to self by default', async () => { + await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true }); + }); + + it('should be visible to privileged users by default', async () => { + await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true }); + }); + + it('should hide from guests (system-wide: hide, by-user: hide)', async () => { + meta.config.hideEmail = 1; + meta.config.hideFullname = 1; + // Explicitly set user's privacy settings to hide its email and fullname + const data = { uid: hidingUser.uid, settings: { showemail: 0, showfullname: 0 } }; + await apiUser.updateSettings({ uid: hidingUser.uid }, data); + + await assertPrivacy({ v3Api: false }); + }); + + it('should hide from unprivileged users (system-wide: hide, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: regularUserJar }); + await assertPrivacy({ v3Api: true, jar: regularUserJar }); + }); + + it('should be visible to self (system-wide: hide, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true }); + }); + + it('should be visible to privileged users (system-wide: hide, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true }); + }); + + it('should hide from guests (system-wide: show, by-user: hide)', async () => { + meta.config.hideEmail = 0; + meta.config.hideFullname = 0; + + await assertPrivacy({ v3Api: false }); + }); + + it('should hide from unprivileged users (system-wide: show, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: regularUserJar }); + await assertPrivacy({ v3Api: true, jar: regularUserJar }); + }); + + it('should be visible to self (system-wide: show, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true }); + }); + + it('should be visible to privileged users (system-wide: show, by-user: hide)', async () => { + await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true }); + }); + + it('should be visible to guests (system-wide: show, by-user: show)', async () => { + meta.config.hideEmail = 0; + meta.config.hideFullname = 0; + + // Set user's individual privacy settings to show its email and fullname + const data = { uid: hidingUser.uid, settings: { showemail: 1, showfullname: 1 } }; + await apiUser.updateSettings({ uid: hidingUser.uid }, data); + + await assertPrivacy({ v3Api: false, expectVisible: true }); + }); + + it('should be visible to unprivileged users (system-wide: show, by-user: show)', async () => { + await assertPrivacy({ v3Api: false, jar: regularUserJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: regularUserJar, expectVisible: true }); + }); + + // System-wide "hide" prioritized over individual users' settings + it('should hide from guests (system-wide: hide, by-user: show)', async () => { meta.config.hideEmail = 1; meta.config.hideFullname = 1; - const userData1 = await requestAsync(`${nconf.get('url')}/api/user/hiddenemail`, { json: true }); - assert.strictEqual(userData1.fullname, ''); - assert.strictEqual(userData1.email, ''); + await assertPrivacy({ v3Api: false }); + }); - const { response } = await requestAsync(`${nconf.get('url')}/api/v3/users/${uid}`, { json: true, jar: jar }); - assert.strictEqual(response.fullname, ''); - assert.strictEqual(response.email, ''); + it('should hide from unprivileged users (system-wide: hide, by-user: show)', async () => { + await assertPrivacy({ v3Api: false, jar: regularUserJar }); + await assertPrivacy({ v3Api: true, jar: regularUserJar }); + }); + + it('should be visible to self (system-wide: hide, by-user: show)', async () => { + await assertPrivacy({ v3Api: false, jar: hidingUserJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: hidingUserJar, expectVisible: true }); + }); + + it('should be visible to privileged users (system-wide: hide, by-user: show)', async () => { + await assertPrivacy({ v3Api: false, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: adminJar, expectVisible: true }); + await assertPrivacy({ v3Api: false, jar: globalModJar, expectVisible: true }); + await assertPrivacy({ v3Api: true, jar: globalModJar, expectVisible: true }); + }); + + it('should handle array of user data (system-wide: hide)', async () => { + const userData = await User.hidePrivateData([hidingUser, regularUser], hidingUser.uid); + assert.strictEqual(userData[0].fullname, hidingUser.fullname); + assert.strictEqual(userData[0].email, hidingUser.email); + assert.strictEqual(userData[1].fullname, ''); + assert.strictEqual(userData[1].email, ''); }); it('should hide fullname in topic list and topic', (done) => { Topics.post({ - uid: uid, + uid: hidingUser.uid, title: 'Topic hidden', content: 'lorem ipsum', cid: testCid,