diff --git a/CHANGELOG.md b/CHANGELOG.md index db0d025621..8b793bc928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,119 @@ +#### v2.8.5 (2023-01-27) + +##### Chores + +* incrementing version number - v2.8.4 (a46b2bbc) +* update changelog for v2.8.4 (c13f0e21) +* incrementing version number - v2.8.3 (c20b20a7) +* incrementing version number - v2.8.2 (050e43f8) +* incrementing version number - v2.8.1 (727f879e) +* incrementing version number - v2.8.0 (8e77673d) +* incrementing version number - v2.7.0 (96cc0617) +* incrementing version number - v2.6.1 (7e52a7a5) +* incrementing version number - v2.6.0 (e7fcf482) +* incrementing version number - v2.5.8 (dec0e7de) +* incrementing version number - v2.5.7 (5836bf4a) +* incrementing version number - v2.5.6 (c7bd7dbf) +* incrementing version number - v2.5.5 (3509ed94) +* incrementing version number - v2.5.4 (e83260ca) +* incrementing version number - v2.5.3 (7e922936) +* incrementing version number - v2.5.2 (babcd17e) +* incrementing version number - v2.5.1 (ce3aa950) +* incrementing version number - v2.5.0 (01d276cb) +* incrementing version number - v2.4.5 (dd3e1a28) +* incrementing version number - v2.4.4 (d5525c87) +* 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 + +* import resolution within plugin modules (#11219) (f6c96948) + +#### v2.8.4 (2023-01-26) + +##### Chores + +* incrementing version number - v2.8.3 (c20b20a7) +* update changelog for v2.8.3 (eb2841ee) +* incrementing version number - v2.8.2 (050e43f8) +* incrementing version number - v2.8.1 (727f879e) +* incrementing version number - v2.8.0 (8e77673d) +* incrementing version number - v2.7.0 (96cc0617) +* incrementing version number - v2.6.1 (7e52a7a5) +* incrementing version number - v2.6.0 (e7fcf482) +* incrementing version number - v2.5.8 (dec0e7de) +* incrementing version number - v2.5.7 (5836bf4a) +* incrementing version number - v2.5.6 (c7bd7dbf) +* incrementing version number - v2.5.5 (3509ed94) +* incrementing version number - v2.5.4 (e83260ca) +* incrementing version number - v2.5.3 (7e922936) +* incrementing version number - v2.5.2 (babcd17e) +* incrementing version number - v2.5.1 (ce3aa950) +* incrementing version number - v2.5.0 (01d276cb) +* incrementing version number - v2.4.5 (dd3e1a28) +* incrementing version number - v2.4.4 (d5525c87) +* 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) + +#### v2.8.3 (2023-01-25) + +##### Chores + +* remove extraneous lines from changelog (48c9f447) +* incrementing version number - v2.8.2 (050e43f8) +* update changelog for v2.8.2 (66aa3169) +* incrementing version number - v2.8.1 (727f879e) +* incrementing version number - v2.8.0 (8e77673d) +* incrementing version number - v2.7.0 (96cc0617) +* incrementing version number - v2.6.1 (7e52a7a5) +* incrementing version number - v2.6.0 (e7fcf482) +* incrementing version number - v2.5.8 (dec0e7de) +* incrementing version number - v2.5.7 (5836bf4a) +* incrementing version number - v2.5.6 (c7bd7dbf) +* incrementing version number - v2.5.5 (3509ed94) +* incrementing version number - v2.5.4 (e83260ca) +* incrementing version number - v2.5.3 (7e922936) +* incrementing version number - v2.5.2 (babcd17e) +* incrementing version number - v2.5.1 (ce3aa950) +* incrementing version number - v2.5.0 (01d276cb) +* incrementing version number - v2.4.5 (dd3e1a28) +* incrementing version number - v2.4.4 (d5525c87) +* 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 + +* import resolution within plugin modules (#11200) (89e059a0) +* #11195, allow users with admin:users privilege to delete users in acp (0bffd3d9) +* #11194, allow access to sub dashboard pages (7d04e952) +* #11136, tests, and returning the proper number of arrays (459bc523) +* #11136, only show mods of active categories when getModeratorUids is called (39e009c0) +* closes #11173, clear require cache if wrong dependency is installed (747cb1f0) +* **deps:** + * downgrade swagger-parser to v9 (00e48803) + * pinning sub dependency json-schema-ref-parser to 9.0.9 (9c250b78) + +##### Reverts + +* a788bd1344825ad4759e39d6e98d8bf3695bd639 (fecd84d1) +* 9c250b78b05ca2abf31a79971ed0c60ca07664ec, fix: comment out broken test for now (a788bd13) + +##### Tests + +* fix broken test (f295174e) + #### v2.8.2 (2023-01-13) ##### Chores diff --git a/install/package.json b/install/package.json index b3195d2f6f..049f31c949 100644 --- a/install/package.json +++ b/install/package.json @@ -31,7 +31,7 @@ "@adactive/bootstrap-tagsinput": "0.8.2", "@isaacs/ttlcache": "1.2.1", "@popperjs/core": "2.11.6", - "ace-builds": "1.14.0", + "ace-builds": "1.15.0", "archiver": "5.3.1", "async": "3.2.4", "autoprefixer": "10.4.13", @@ -60,7 +60,7 @@ "csurf": "1.11.0", "daemon": "1.1.0", "diff": "5.1.0", - "esbuild": "0.16.16", + "esbuild": "0.17.5", "express": "4.18.2", "express-session": "1.17.3", "express-useragent": "1.0.15", @@ -84,7 +84,7 @@ "lru-cache": "7.14.1", "material-design-lite": "1.3.0", "mime": "3.0.0", - "mkdirp": "1.0.4", + "mkdirp": "2.1.3", "mongodb": "4.13.0", "morgan": "1.10.0", "mousetrap": "1.6.5", @@ -99,24 +99,24 @@ "nodebb-plugin-mentions": "4.0.3", "nodebb-plugin-ntfy": "1.0.15", "nodebb-plugin-spam-be-gone": "2.0.5", - "nodebb-rewards-essentials": "0.2.1", + "nodebb-rewards-essentials": "0.2.2", "nodebb-theme-harmony": "1.0.0-beta.56", "nodebb-theme-lavender": "7.0.8", "nodebb-theme-peace": "2.0.18", "nodebb-theme-persona": "13.0.49", "nodebb-widget-essentials": "7.0.6", - "nodemailer": "6.8.0", + "nodemailer": "6.9.1", "nprogress": "0.2.0", "passport": "0.6.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.8.0", - "pg-cursor": "2.7.4", + "pg": "8.9.0", + "pg-cursor": "2.8.0", "postcss": "8.4.21", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "ioredis": "5.2.4", + "ioredis": "5.3.0", "request": "2.88.2", "request-promise-native": "1.0.9", "rimraf": "3.0.2", @@ -151,17 +151,17 @@ "zxcvbn": "4.4.2" }, "devDependencies": { - "@apidevtools/swagger-parser": "9.0.0", - "@commitlint/cli": "17.4.1", - "@commitlint/config-angular": "17.4.0", + "@apidevtools/swagger-parser": "9.0.1", + "@commitlint/cli": "17.4.2", + "@commitlint/config-angular": "17.4.2", "coveralls": "3.1.1", - "eslint": "8.31.0", + "eslint": "8.32.0", "eslint-config-nodebb": "0.2.1", - "eslint-plugin-import": "2.26.0", + "eslint-plugin-import": "2.27.5", "grunt": "1.5.3", "grunt-contrib-watch": "1.1.0", "husky": "8.0.3", - "jsdom": "21.0.0", + "jsdom": "21.1.0", "lint-staged": "13.1.0", "mocha": "10.2.0", "mocha-lcov-reporter": "1.3.0", diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 14d080b2af..4daad12c70 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -70,6 +70,7 @@ "no-user": "User does not exist", "no-teaser": "Teaser does not exist", "no-flag": "Flag does not exist", + "no-chat-room": "Chat room does not exist", "no-privileges": "You do not have enough privileges for this action.", "category-disabled": "Category disabled", @@ -182,6 +183,9 @@ "chat-deleted-already": "This chat message has already been deleted.", "chat-restored-already": "This chat message has already been restored.", "chat-room-does-not-exist": "Chat room does not exist.", + "cant-add-users-to-chat-room": "Can't add users to chat room.", + "cant-remove-users-from-chat-room": "Can't remove users from chat room.", + "chat-room-name-too-long": "Chat room name too long.", "already-voting-for-this-post": "You have already voted for this post.", "reputation-system-disabled": "Reputation system is disabled.", diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 2816cd21b3..13ad3f3525 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -88,7 +88,7 @@ "category-not-selected": "Није одабрана категорија", "too-many-posts": "Можете објављивати поруке само једном у %1 секунди - сачекајте пре него што покушате поново", "too-many-posts-newbie": "Као нови корисник, можете објављивати поруке само једном у %1 секунди док не достигнете %2 углед - сачекајте пре него што покушате поново", - "already-posting": "You are already posting", + "already-posting": "Већ објављујете", "tag-too-short": "Унесите дужу ознаку. Ознаке морају садржати најмање %1 знак(ов)а.", "tag-too-long": "Унесите краћу ознаку. Ознаке не смеју бити дуже од %1 знак(ов)а.", "not-enough-tags": "Нема довољно ознака. Теме морају имати најмање %1 ознаке/а.", diff --git a/src/categories/index.js b/src/categories/index.js index 619f756642..9fc7479a69 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -5,7 +5,6 @@ const _ = require('lodash'); const db = require('../database'); const user = require('../user'); -const groups = require('../groups'); const plugins = require('../plugins'); const privileges = require('../privileges'); const cache = require('../cache'); @@ -99,39 +98,7 @@ Categories.getModerators = async function (cid) { }; Categories.getModeratorUids = async function (cids) { - // Only check active categories - const disabled = (await Categories.getCategoriesFields(cids, ['disabled'])).map(obj => obj.disabled); - // cids = cids.filter((_, idx) => !disabled[idx]); - - const groupNames = cids.reduce((memo, cid) => { - memo.push(`cid:${cid}:privileges:moderate`); - memo.push(`cid:${cid}:privileges:groups:moderate`); - return memo; - }, []); - - const memberSets = await groups.getMembersOfGroups(groupNames); - // Every other set is actually a list of user groups, not uids, so convert those to members - const sets = memberSets.reduce((memo, set, idx) => { - if (idx % 2) { - memo.groupNames.push(set); - } else { - memo.uids.push(set); - } - - return memo; - }, { groupNames: [], uids: [] }); - - const uniqGroups = _.uniq(_.flatten(sets.groupNames)); - const groupUids = await groups.getMembersOfGroups(uniqGroups); - const map = _.zipObject(uniqGroups, groupUids); - const moderatorUids = cids.map((cid, index) => { - if (disabled[index]) { - return []; - } - - return _.uniq(sets.uids[index].concat(_.flatten(sets.groupNames[index].map(g => map[g])))); - }); - return moderatorUids; + return await privileges.categories.getUidsWithPrivilege(cids, 'moderate'); }; Categories.getCategories = async function (cids, uid) { diff --git a/src/controllers/accounts/notifications.js b/src/controllers/accounts/notifications.js index 79db01c715..c059aa4283 100644 --- a/src/controllers/accounts/notifications.js +++ b/src/controllers/accounts/notifications.js @@ -45,10 +45,11 @@ notificationsController.get = async function (req, res, next) { { separator: true }, ]).concat(filters.moderatorFilters); } - const selectedFilter = allFilters.find((filterData) => { + + allFilters.forEach((filterData) => { filterData.selected = filterData.filter === filter; - return filterData.selected; }); + const selectedFilter = allFilters.find(filterData => filterData.selected); if (!selectedFilter) { return next(); } diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index ebc91b2235..47a06ffe42 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -335,7 +335,7 @@ authenticationController.doLogin = async function (req, uid) { return; } const loginAsync = util.promisify(req.login).bind(req); - await loginAsync({ uid: uid }, { keepSessionInfo: req.res.locals !== false }); + await loginAsync({ uid: uid }, { keepSessionInfo: req.res.locals.reroll !== false }); await authenticationController.onSuccessfulLogin(req, uid); }; diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 48a3bd8ff3..3656146652 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -1,6 +1,9 @@ 'use strict'; +const _ = require('lodash'); + const user = require('../user'); +const groups = require('../groups'); const posts = require('../posts'); const flags = require('../flags'); const analytics = require('../analytics'); @@ -110,7 +113,6 @@ modsController.flags.detail = async function (req, res, next) { isAdminOrGlobalMod: user.isAdminOrGlobalMod(req.uid), moderatedCids: user.getModeratedCids(req.uid), flagData: flags.get(req.params.flagId), - assignees: user.getAdminsandGlobalModsandModerators(), privileges: Promise.all(['global', 'admin'].map(async type => privileges[type].get(req.uid))), }); results.privileges = { ...results.privileges[0], ...results.privileges[1] }; @@ -119,6 +121,28 @@ modsController.flags.detail = async function (req, res, next) { return next(); // 404 } + async function getAssignees(flagData) { + let uids = []; + const [admins, globalMods] = await Promise.all([ + groups.getMembers('administrators', 0, -1), + groups.getMembers('Global Moderators', 0, -1), + ]); + if (flagData.type === 'user') { + uids = await privileges.admin.getUidsWithPrivilege('admin:users'); + uids = _.uniq(admins.concat(uids)); + } else if (flagData.type === 'post') { + const cid = await posts.getCidByPid(flagData.targetId); + if (!cid) { + return []; + } + uids = (await privileges.categories.getUidsWithPrivilege([cid], 'moderate'))[0]; + uids = _.uniq(admins.concat(globalMods).concat(uids)); + } + const userData = await user.getUsersData(uids); + return userData.filter(u => u && u.userslug); + } + + const assignees = await getAssignees(results.flagData); results.flagData.history = results.isAdminOrGlobalMod ? (await flags.getHistory(req.params.flagId)) : null; if (results.flagData.type === 'user') { @@ -128,7 +152,7 @@ modsController.flags.detail = async function (req, res, next) { } res.render('flags/detail', Object.assign(results.flagData, { - assignees: results.assignees, + assignees: assignees, type_bool: ['post', 'user', 'empty'].reduce((memo, cur) => { if (cur !== 'empty') { memo[cur] = results.flagData.type === cur && ( diff --git a/src/meta/js.js b/src/meta/js.js index aa3f996cd4..df3fe41bae 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -40,14 +40,24 @@ async function linkModules() { await Promise.all(Object.keys(modules).map(async (relPath) => { const srcPath = path.join(__dirname, '../../', modules[relPath]); const destPath = path.join(__dirname, '../../build/public/src/modules', relPath); + const destDir = path.dirname(destPath); + const [stats] = await Promise.all([ fs.promises.stat(srcPath), - mkdirp(path.dirname(destPath)), + mkdirp(destDir), ]); + if (stats.isDirectory()) { await file.linkDirs(srcPath, destPath, true); } else { - await fs.promises.copyFile(srcPath, destPath); + // Get the relative path to the destination directory + const relPath = path.relative(destDir, srcPath) + // and convert to a posix path + .split(path.sep).join(path.posix.sep); + + // Instead of copying file, create a new file re-exporting it + // This way, imports in modules are resolved correctly + await fs.promises.writeFile(destPath, `module.exports = require('${relPath}');`); } })); } diff --git a/src/middleware/user.js b/src/middleware/user.js index 0ed315e729..fb3e5ed84f 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -38,7 +38,7 @@ module.exports = function (middleware) { const loginAsync = util.promisify(req.login).bind(req); await loginAsync(user, { keepSessionInfo: true }); await controllers.authentication.onSuccessfulLogin(req, user.uid); - req.uid = user.uid; + req.uid = parseInt(user.uid, 10); req.loggedIn = req.uid > 0; return true; } diff --git a/src/privileges/admin.js b/src/privileges/admin.js index 166236ac76..e77d2e9982 100644 --- a/src/privileges/admin.js +++ b/src/privileges/admin.js @@ -211,3 +211,8 @@ privsAdmin.groupPrivileges = async function (groupName) { const groupPrivilegeList = await privsAdmin.getGroupPrivilegeList(); return await helpers.userOrGroupPrivileges(0, groupName, groupPrivilegeList); }; + +privsAdmin.getUidsWithPrivilege = async function (privilege) { + const uidsByCid = await helpers.getUidsWithPrivilege([0], privilege); + return uidsByCid[0]; +}; diff --git a/src/privileges/categories.js b/src/privileges/categories.js index c95f52acc8..ae0d285766 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -224,3 +224,7 @@ privsCategories.groupPrivileges = async function (cid, groupName) { const groupPrivilegeList = await privsCategories.getGroupPrivilegeList(); return await helpers.userOrGroupPrivileges(cid, groupName, groupPrivilegeList); }; + +privsCategories.getUidsWithPrivilege = async function (cids, privilege) { + return await helpers.getUidsWithPrivilege(cids, privilege); +}; diff --git a/src/privileges/global.js b/src/privileges/global.js index 472448d4c4..3cfe50e522 100644 --- a/src/privileges/global.js +++ b/src/privileges/global.js @@ -134,3 +134,8 @@ privsGlobal.groupPrivileges = async function (groupName) { const groupPrivilegeList = await privsGlobal.getGroupPrivilegeList(); return await helpers.userOrGroupPrivileges(0, groupName, groupPrivilegeList); }; + +privsGlobal.getUidsWithPrivilege = async function (privilege) { + const uidsByCid = await helpers.getUidsWithPrivilege([0], privilege); + return uidsByCid[0]; +}; diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index e6a0266f67..b8c45dfdb3 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -6,6 +6,7 @@ const validator = require('validator'); const groups = require('../groups'); const user = require('../user'); +const categories = require('../categories'); const plugins = require('../plugins'); const translator = require('../translator'); @@ -189,4 +190,38 @@ helpers.userOrGroupPrivileges = async function (cid, uidOrGroup, privilegeList) return _.zipObject(privilegeList, isMembers); }; +helpers.getUidsWithPrivilege = async (cids, privilege) => { + const disabled = (await categories.getCategoriesFields(cids, ['disabled'])).map(obj => obj.disabled); + + const groupNames = cids.reduce((memo, cid) => { + memo.push(`cid:${cid}:privileges:${privilege}`); + memo.push(`cid:${cid}:privileges:groups:${privilege}`); + return memo; + }, []); + + const memberSets = await groups.getMembersOfGroups(groupNames); + // Every other set is actually a list of user groups, not uids, so convert those to members + const sets = memberSets.reduce((memo, set, idx) => { + if (idx % 2) { + memo.groupNames.push(set); + } else { + memo.uids.push(set); + } + + return memo; + }, { groupNames: [], uids: [] }); + + const uniqGroups = _.uniq(_.flatten(sets.groupNames)); + const groupUids = await groups.getMembersOfGroups(uniqGroups); + const map = _.zipObject(uniqGroups, groupUids); + const uidsByCid = cids.map((cid, index) => { + if (disabled[index]) { + return []; + } + + return _.uniq(sets.uids[index].concat(_.flatten(sets.groupNames[index].map(g => map[g])))); + }); + return uidsByCid; +}; + require('../promisify')(helpers); diff --git a/src/topics/scheduled.js b/src/topics/scheduled.js index 56c363db39..3544e54945 100644 --- a/src/topics/scheduled.js +++ b/src/topics/scheduled.js @@ -87,14 +87,15 @@ function unpin(tid, topicData) { } async function sendNotifications(uids, topicsData) { - const usernames = await Promise.all(uids.map(uid => user.getUserField(uid, 'username'))); - const uidToUsername = Object.fromEntries(uids.map((uid, idx) => [uid, usernames[idx]])); + const userData = await user.getUsersData(uids); + const uidToUserData = Object.fromEntries(uids.map((uid, idx) => [uid, userData[idx]])); - const postsData = await posts.getPostsData(topicsData.map(({ mainPid }) => mainPid)); + const postsData = await posts.getPostsData(topicsData.map(t => t && t.mainPid)); postsData.forEach((postData, idx) => { - postData.user = {}; - postData.user.username = uidToUsername[postData.uid]; - postData.topic = topicsData[idx]; + if (postData) { + postData.user = uidToUserData[topicsData[idx].uid]; + postData.topic = topicsData[idx]; + } }); return Promise.all(topicsData.map( diff --git a/src/views/admin/dashboard/topics.tpl b/src/views/admin/dashboard/topics.tpl index 43ff763b21..aee31ff56f 100644 --- a/src/views/admin/dashboard/topics.tpl +++ b/src/views/admin/dashboard/topics.tpl @@ -17,7 +17,7 @@ {{{ end }}} {{{ each topics }}} - {../title} + {../title} [[topic:posted_by, {../user.username}]]