From 577eee2f6a69022a799380bf570feef30856e8a7 Mon Sep 17 00:00:00 2001 From: Shlomo <78599753+ShlomoCode@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:38:45 +0200 Subject: [PATCH 1/5] feat(config): add `acpPluginInstallDisabled` option (#13189) --- public/language/en-GB/error.json | 1 + public/openapi/read/admin/development/info.yaml | 2 ++ src/controllers/admin/info.js | 1 + src/prestart.js | 3 ++- src/socket.io/admin/plugins.js | 4 ++++ test/mocks/databasemock.js | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index caa23964b4..d511ef8300 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -268,6 +268,7 @@ "invalid-plugin-id": "Invalid plugin ID", "plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP", + "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled", "plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.", "theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP", diff --git a/public/openapi/read/admin/development/info.yaml b/public/openapi/read/admin/development/info.yaml index 81b9e3f49e..1eeb77c1b3 100644 --- a/public/openapi/read/admin/development/info.yaml +++ b/public/openapi/read/admin/development/info.yaml @@ -98,6 +98,8 @@ get: type: boolean jobsDisabled: type: boolean + acpPluginInstallDisabled: + type: boolean git: type: object properties: diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index 45ffe078b7..d581780b11 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -88,6 +88,7 @@ async function getNodeInfo() { isPrimary: nconf.get('isPrimary'), runJobs: nconf.get('runJobs'), jobsDisabled: nconf.get('jobsDisabled'), + acpPluginInstallDisabled: nconf.get('acpPluginInstallDisabled') }, }; diff --git a/src/prestart.js b/src/prestart.js index b09ef5d9bc..57b5f0590e 100644 --- a/src/prestart.js +++ b/src/prestart.js @@ -58,6 +58,7 @@ function loadConfig(configFile) { isCluster: false, isPrimary: true, jobsDisabled: false, + acpPluginInstallDisabled: false, fontawesome: { pro: false, styles: '*', @@ -65,7 +66,7 @@ function loadConfig(configFile) { }); // Explicitly cast as Bool, loader.js passes in isCluster as string 'true'/'false' - const castAsBool = ['isCluster', 'isPrimary', 'jobsDisabled']; + const castAsBool = ['isCluster', 'isPrimary', 'jobsDisabled', 'acpPluginInstallDisabled']; nconf.stores.env.readOnly = false; castAsBool.forEach((prop) => { const value = nconf.get(prop); diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js index 2d6f705be9..d926dfa0cf 100644 --- a/src/socket.io/admin/plugins.js +++ b/src/socket.io/admin/plugins.js @@ -22,6 +22,10 @@ Plugins.toggleActive = async function (socket, plugin_id) { }; Plugins.toggleInstall = async function (socket, data) { + const isInstalled = await plugins.isInstalled(data.id); + if (nconf.get('acpPluginInstallDisabled') && !isInstalled) { + throw new Error('[[error:plugin-installation-via-acp-disabled]]'); + } postsCache.reset(); await plugins.checkWhitelist(data.id, data.version); const pluginData = await plugins.toggleInstall(data.id, data.version); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 587dd65dcc..6a568109b7 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -144,6 +144,7 @@ before(async function () { nconf.set('version', packageInfo.version); nconf.set('runJobs', false); nconf.set('jobsDisabled', false); + nconf.set('acpPluginInstallDisabled', false); await db.init(); From ec11b0c2525643827488e842dc84994e1dbfa478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 21 Feb 2025 09:51:24 -0500 Subject: [PATCH 2/5] lint: fix --- src/controllers/admin/info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js index d581780b11..6f63faf8a9 100644 --- a/src/controllers/admin/info.js +++ b/src/controllers/admin/info.js @@ -88,7 +88,7 @@ async function getNodeInfo() { isPrimary: nconf.get('isPrimary'), runJobs: nconf.get('runJobs'), jobsDisabled: nconf.get('jobsDisabled'), - acpPluginInstallDisabled: nconf.get('acpPluginInstallDisabled') + acpPluginInstallDisabled: nconf.get('acpPluginInstallDisabled'), }, }; From c6b8256fffbbe30477697bd768856d13ed6d93c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 21 Feb 2025 11:27:52 -0500 Subject: [PATCH 3/5] fix: add missing await on filter:user.logout if there is an error in logout hooks don't crash send error back instead of building 500 page --- public/src/modules/logout.js | 5 ++++- src/controllers/authentication.js | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/public/src/modules/logout.js b/public/src/modules/logout.js index 400d5c25e1..a44fb56cf0 100644 --- a/public/src/modules/logout.js +++ b/public/src/modules/logout.js @@ -1,6 +1,6 @@ 'use strict'; -define('logout', ['hooks'], function (hooks) { +define('logout', ['hooks', 'alerts'], function (hooks, alerts) { return function logout(redirect) { redirect = redirect === undefined ? true : redirect; hooks.fire('action:app.logout'); @@ -23,6 +23,9 @@ define('logout', ['hooks'], function (hooks) { } } }, + error: function (jqXHR) { + alerts.error(String(jqXHR.responseText || '[[error:logout-error]]')); + }, }); }; }); diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 007d3ba6ac..c946ca8292 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -459,7 +459,7 @@ authenticationController.localLogin = async function (req, username, password, n } }; -authenticationController.logout = async function (req, res, next) { +authenticationController.logout = async function (req, res) { if (!req.loggedIn || !req.sessionID) { res.clearCookie(nconf.get('sessionKey'), meta.configs.cookie.get()); return res.status(200).send('not-logged-in'); @@ -475,21 +475,22 @@ authenticationController.logout = async function (req, res, next) { await user.setUserField(uid, 'lastonline', Date.now() - (meta.config.onlineCutoff * 60000)); await db.sortedSetAdd('users:online', Date.now() - (meta.config.onlineCutoff * 60000), uid); - await plugins.hooks.fire('static:user.loggedOut', { req: req, res: res, uid: uid, sessionID: sessionID }); + await plugins.hooks.fire('static:user.loggedOut', { req, res, uid, sessionID }); // Force session check for all connected socket.io clients with the same session id sockets.in(`sess_${sessionID}`).emit('checkSession', 0); const payload = { next: `${nconf.get('relative_path')}/`, }; - plugins.hooks.fire('filter:user.logout', payload); + await plugins.hooks.fire('filter:user.logout', payload); if (req.body.noscript === 'true') { return res.redirect(payload.next); } res.status(200).send(payload); } catch (err) { - next(err); + winston.error(`${req.method} ${req.originalUrl}\n${err.stack}`); + res.status(500).send(err.message); } }; From 9bfa885392337ad163e174aebbd194d3a526e9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 21 Feb 2025 11:30:50 -0500 Subject: [PATCH 4/5] fix: escape confirm email in acp manage users --- src/controllers/admin/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index 4ea3ebb9e5..14e50bf9eb 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -199,7 +199,7 @@ async function loadUserInfo(callerUid, uids) { const confirmObj = confirmObjs[index]; user['email:expired'] = !confirmObj.expires || Date.now() >= confirmObj.expires; user['email:pending'] = confirmObj.expires && Date.now() < confirmObj.expires; - user.emailToConfirm = confirmObj.email; + user.emailToConfirm = validator.escape(String(confirmObj.email)); } } }); From 9153f8cfaea20fad0deac8308fc7acc382e36bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 21 Feb 2025 12:57:07 -0500 Subject: [PATCH 5/5] feat: merge consecutive share events --- install/package.json | 2 +- public/src/client/topic/threadTools.js | 14 +++++++++++++- src/topics/index.js | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 7ce6758f19..a72f022c1f 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-plugin-spam-be-gone": "2.3.1", "nodebb-plugin-web-push": "0.7.2", "nodebb-rewards-essentials": "1.0.1", - "nodebb-theme-harmony": "2.0.29", + "nodebb-theme-harmony": "2.0.30", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index 38db844cdd..a83a9d86b5 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -85,12 +85,24 @@ define('forum/topic/threadTools', [ topicContainer.on('click', '[component="topic/event/delete"]', function () { const eventId = $(this).attr('data-topic-event-id'); - const eventEl = $(this).parents('[component="topic/event"]'); + const eventEl = $(this).parents('[data-topic-event-id]'); bootbox.confirm('[[topic:delete-event-confirm]]', (ok) => { if (ok) { api.del(`/topics/${tid}/events/${eventId}`, {}) .then(function () { + const itemsParent = eventEl.parents('[component="topic/event/items"]'); eventEl.remove(); + if (itemsParent.length) { + const childrenCount = itemsParent.children().length; + const eventParent = itemsParent.parents('[component="topic/event"]'); + if (!childrenCount) { + eventParent.remove(); + } else { + eventParent + .find('[data-bs-toggle]') + .translateText(`[[topic:announcers-x, ${childrenCount}]]`); + } + } }) .catch(alerts.error); } diff --git a/src/topics/index.js b/src/topics/index.js index 07600e6311..5717f26126 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -198,6 +198,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev ); p.eventStart = undefined; p.eventEnd = undefined; + p.events = mergeConsecutiveShareEvents(p.events); }); topicData.category = category; @@ -230,6 +231,23 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev return result.topic; }; +function mergeConsecutiveShareEvents(arr) { + return arr.reduce((acc, curr) => { + const last = acc[acc.length - 1]; + if (last && last.type === curr.type && last.type === 'share') { + if (!last.items) { + last.items = [{ ...last }]; + ['user', 'text', 'timestamp', 'timestampISO'].forEach(field => delete last[field]); + } + last.items.push(curr); + } else { + acc.push(curr); + } + return acc; + }, []); +} + + async function getDeleter(topicData) { if (!parseInt(topicData.deleterUid, 10)) { return null;