From 4fe23e5c85c6cbba6905f275b75d61a8f3a95c6f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 20 Feb 2025 18:17:39 +0000 Subject: [PATCH 01/37] chore: incrementing version number - v4.0.5 --- install/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/package.json b/install/package.json index 715b472581..23cdac6288 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.0.4", + "version": "4.0.5", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -200,4 +200,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file From f84b9fc75b1193c6efe4390f45a2b58afcfccb24 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 20 Feb 2025 18:17:39 +0000 Subject: [PATCH 02/37] chore: update changelog for v4.0.5 --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8de9449cc..34402b38c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +#### v4.0.5 (2025-02-20) + +##### Chores + +* bump composer to 10.2.46 for #13132 (7520e4f6) +* up harmony (f82f00e5) +* up widgets (e23a14c1) +* up harmony (c0996a80) +* up dbsearch (d0a9ddea) +* up dbsearch (310fab65) +* add test helper to activitypub file (4bc0031f) +* incrementing version number - v4.0.4 (b1125cce) +* update changelog for v4.0.4 (d3b69a39) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) + +##### New Features + +* add upload button to quickreply (f67a0a12) +* remove activities older than a week (9997189a) + +##### Bug Fixes + +* typo (e63f1234) +* #13136, do not log 404s for AP requests (93f48409) +* #13129, serve category backgroundImage as actor `icon`, not `image` (b8200095) +* escape ip blacklist rules (625f4751) +* closes #13180, don't execute cron jobs if ap disabled (a410587c) +* #13172, Topics.addParentPosts not sending sourceContent in calling parsePosts (bb9687bd) +* #13179, fix context resolution failure bug with frequency (6245e33d) +* add back chronological sorting of asserted notes (de6e63bb) +* #13170, remove mime-type and regex test for "Emoji" attachment, wrap tag name in colons if not provided (92708d2f) +* closes #13176, check if uid is number when creating tokens (80cc1d34) +* notes.assertPrivate sanity checks (5e71d597) +* page index for single page, closes #13173 (b0e8058f) +* remove handle on category purge (4134a075) + +##### Tests + +* dont clear local when testing (669755d1) +* show objects on fail (f2824073) +* wait after post request (64318242) + #### v4.0.4 (2025-02-17) ##### Chores From 2ad48f1714fa5281657f0e86e60255e90d669f24 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 20 Feb 2025 14:07:39 -0500 Subject: [PATCH 03/37] fix: relaxing strict allowedTags configuration for incoming AP content (allowing picture, source, and additional attributes for img) re: #13185 --- src/activitypub/mocks.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 223d1a8cbd..4a2fa1e9b2 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -29,12 +29,14 @@ const Mocks = module.exports; * Done so the output HTML is stripped of all non-essential items; mainly classes from plugins.. */ const sanitizeConfig = { - allowedTags: sanitize.defaults.allowedTags.concat(['img']), + allowedTags: sanitize.defaults.allowedTags.concat(['img', 'picture', 'source']), allowedClasses: { '*': [], }, allowedAttributes: { a: ['href', 'rel'], + source: ['type', 'src', 'srcset', 'sizes', 'media', 'height', 'width'], + img: ['alt', 'height', 'ismap', 'src', 'usemap', 'width', 'srcset'], }, }; From 05bbefd17a4ef662f0943e4cc5151af000766b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 20 Feb 2025 18:47:50 -0500 Subject: [PATCH 04/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 23cdac6288..89dfb9e5d0 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.28", + "nodebb-theme-harmony": "2.0.29", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From 02a8d9b6b41c68065b1fbbddb504b62171d1a53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 20 Feb 2025 18:51:55 -0500 Subject: [PATCH 05/37] chore: up widgets --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 89dfb9e5d0..5032f50ffe 100644 --- a/install/package.json +++ b/install/package.json @@ -112,7 +112,7 @@ "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", - "nodebb-widget-essentials": "7.0.34", + "nodebb-widget-essentials": "7.0.35", "nodemailer": "6.9.16", "nprogress": "0.2.0", "passport": "0.7.0", From 2ab6a368c98d6b1a55c1bda951a0cce7189f31d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 20 Feb 2025 18:57:53 -0500 Subject: [PATCH 06/37] chore: up esbuild --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 5032f50ffe..7ce6758f19 100644 --- a/install/package.json +++ b/install/package.json @@ -68,7 +68,7 @@ "csrf-sync": "4.0.3", "daemon": "1.1.0", "diff": "7.0.0", - "esbuild": "0.24.2", + "esbuild": "0.25.0", "express": "4.21.2", "express-session": "1.18.1", "express-useragent": "1.0.15", 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 07/37] 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 08/37] 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 09/37] 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 10/37] 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 11/37] 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; From c920836a821064460eb4609d40018a0bb63dcc2b 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 18:27:34 -0500 Subject: [PATCH 12/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index a72f022c1f..4e59de2d43 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.30", + "nodebb-theme-harmony": "2.0.31", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From 0a9d28d58f1fcead4db9b3ac1f51fd15764d9894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 09:35:44 -0500 Subject: [PATCH 13/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 4e59de2d43..50c28bbf92 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.31", + "nodebb-theme-harmony": "2.0.32", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From e38d153117aefbe7c047df1e4f9dc2a868a462fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 11:11:47 -0500 Subject: [PATCH 14/37] feat: match events and parent style use displayname in topic events --- install/package.json | 2 +- src/topics/events.js | 2 +- src/views/partials/topic/post-parent.tpl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/install/package.json b/install/package.json index 50c28bbf92..6aab7343ce 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.32", + "nodebb-theme-harmony": "2.0.33", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", diff --git a/src/topics/events.js b/src/topics/events.js index 04dfe9b4d5..10ab9b8936 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -107,7 +107,7 @@ function renderUser(event) { if (!event.user || event.user.system) { return '[[global:system-user]]'; } - return `${helpers.buildAvatar(event.user, '16px', true)} ${event.user.username}`; + return `${helpers.buildAvatar(event.user, '16px', true)} ${event.user.displayname}`; } function renderTimeago(event) { diff --git a/src/views/partials/topic/post-parent.tpl b/src/views/partials/topic/post-parent.tpl index ae597e3b84..cabc6bb36c 100644 --- a/src/views/partials/topic/post-parent.tpl +++ b/src/views/partials/topic/post-parent.tpl @@ -1,8 +1,8 @@
-
+
From 8385d4ae689c800d1a3081281ab701e3d5201fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 11:56:11 -0500 Subject: [PATCH 15/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 6aab7343ce..15588af5c9 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.33", + "nodebb-theme-harmony": "2.0.34", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From fe980688e40d7c1c009c72f3df5ce3db538f9d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 12:07:04 -0500 Subject: [PATCH 16/37] fix: old upgrade script so it doesn't create settings objects if they don't exist --- src/upgrades/3.6.0/category_tracking.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/upgrades/3.6.0/category_tracking.js b/src/upgrades/3.6.0/category_tracking.js index a30be983b6..d57717cd54 100644 --- a/src/upgrades/3.6.0/category_tracking.js +++ b/src/upgrades/3.6.0/category_tracking.js @@ -3,7 +3,6 @@ 'use strict'; const db = require('../../database'); -const user = require('../../user'); const batch = require('../../batch'); module.exports = { @@ -18,7 +17,7 @@ module.exports = { } await batch.processSortedSet(`users:joindate`, async (uids) => { - const userSettings = await user.getMultipleUserSettings(uids); + const userSettings = await db.getObjects(uids.map(uid => `user:${uid}:settings`)); const change = userSettings.filter(s => s && s.categoryWatchState === 'watching'); await db.setObjectBulk( change.map(s => [`user:${s.uid}:settings`, { categoryWatchState: 'tracking' }]) From b8c8ae09988995e8f9f14b3ba5fcee481d395ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 16:56:13 -0500 Subject: [PATCH 17/37] fix: #13194, dont notify about cid=-1 --- src/socket.io/helpers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js index 66e568d1b2..d80638458e 100644 --- a/src/socket.io/helpers.js +++ b/src/socket.io/helpers.js @@ -17,6 +17,10 @@ const batch = require('../batch'); const SocketHelpers = module.exports; SocketHelpers.notifyNew = async function (uid, type, result) { + const post = result.posts[0]; + if (post && post.topic && parseInt(post.topic.cid, 10) === -1) { + return; + } let uids = await user.getUidsFromSet('users:online', 0, -1); uids = uids.filter(toUid => parseInt(toUid, 10) !== uid); await batch.processArray(uids, async (uids) => { From 2c830567784c248a4ce9b6578de1380a14fbd787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 22 Feb 2025 17:12:45 -0500 Subject: [PATCH 18/37] fix: #13193, add lang strings --- install/package.json | 4 ++-- public/language/en-GB/category.json | 3 ++- src/categories/data.js | 4 ++-- src/controllers/activitypub/topics.js | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/package.json b/install/package.json index 15588af5c9..361f5fd644 100644 --- a/install/package.json +++ b/install/package.json @@ -99,7 +99,7 @@ "multiparty": "4.2.3", "nconf": "0.12.1", "nodebb-plugin-2factor": "7.5.9", - "nodebb-plugin-composer-default": "10.2.46", + "nodebb-plugin-composer-default": "10.2.47", "nodebb-plugin-dbsearch": "6.2.12", "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", @@ -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.34", + "nodebb-theme-harmony": "2.0.35", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", diff --git a/public/language/en-GB/category.json b/public/language/en-GB/category.json index 7e3c6630c5..f6cea780cb 100644 --- a/public/language/en-GB/category.json +++ b/public/language/en-GB/category.json @@ -1,7 +1,8 @@ { "category": "Category", "subcategories": "Subcategories", - + "uncategorized": "Uncategorized", + "uncategorized.description": "Topics that do not strictly fit in with any existing categories", "new-topic-button": "New Topic", "guest-login-post": "Log in to post", "no-topics": "There are no topics in this category.
Why don't you try posting one?", diff --git a/src/categories/data.js b/src/categories/data.js index 2b4e029caf..8890abf670 100644 --- a/src/categories/data.js +++ b/src/categories/data.js @@ -15,8 +15,8 @@ const intFields = [ const worldCategory = { cid: -1, - name: 'Uncategorized', - description: 'Topics that do not strictly fit in with any existing categories', + name: '[[category:uncategorized]]', + description: '[[category:uncategorized.description]]', icon: 'fa-globe', imageClass: 'cover', bgColor: '#eee', diff --git a/src/controllers/activitypub/topics.js b/src/controllers/activitypub/topics.js index 7a871a6876..fac130eb97 100644 --- a/src/controllers/activitypub/topics.js +++ b/src/controllers/activitypub/topics.js @@ -66,7 +66,6 @@ controller.list = async function (req, res) { targetUid: targetUid, }; const data = await categories.getCategoryById(cidQuery); - data.name = '[[world:name]]'; delete data.children; const tids = await getTids(cidQuery); From 08014e7ccb3777aa21a164f64274a2bf3f198801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 09:58:36 -0500 Subject: [PATCH 19/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 361f5fd644..724e008112 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.35", + "nodebb-theme-harmony": "2.0.36", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From a9563d753ba06b937270ccfb5f573e9cb4da50ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 09:59:41 -0500 Subject: [PATCH 20/37] chore: up deps --- install/package.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/install/package.json b/install/package.json index 724e008112..027d59cf0c 100644 --- a/install/package.json +++ b/install/package.json @@ -39,24 +39,24 @@ "@textcomplete/contenteditable": "0.1.13", "@textcomplete/core": "0.1.13", "@textcomplete/textarea": "0.1.13", - "ace-builds": "1.37.5", + "ace-builds": "1.38.0", "archiver": "7.0.1", "async": "3.2.6", "autoprefixer": "10.4.20", "bcryptjs": "2.4.3", - "benchpressjs": "2.5.1", + "benchpressjs": "2.5.3", "body-parser": "1.20.3", "bootbox": "6.0.0", "bootstrap": "5.3.3", "bootswatch": "5.3.3", "chalk": "4.1.2", - "chart.js": "4.4.7", + "chart.js": "4.4.8", "cli-graph": "3.2.2", "clipboard": "2.0.11", "colors": "1.4.0", "commander": "12.1.0", "compare-versions": "6.1.1", - "compression": "1.7.5", + "compression": "1.8.0", "connect-flash": "0.1.1", "connect-mongo": "5.1.0", "connect-multiparty": "2.2.0", @@ -93,7 +93,7 @@ "lru-cache": "10.4.3", "mime": "3.0.0", "mkdirp": "3.0.1", - "mongodb": "6.12.0", + "mongodb": "6.13.1", "morgan": "1.10.0", "mousetrap": "1.6.5", "multiparty": "4.2.3", @@ -104,34 +104,34 @@ "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.1.0", - "nodebb-plugin-mentions": "4.6.10", + "nodebb-plugin-mentions": "4.7.0", "nodebb-plugin-spam-be-gone": "2.3.1", - "nodebb-plugin-web-push": "0.7.2", + "nodebb-plugin-web-push": "0.7.3", "nodebb-rewards-essentials": "1.0.1", "nodebb-theme-harmony": "2.0.36", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", "nodebb-widget-essentials": "7.0.35", - "nodemailer": "6.9.16", + "nodemailer": "6.10.0", "nprogress": "0.2.0", "passport": "0.7.0", "passport-http-bearer": "1.0.1", "passport-local": "1.0.0", - "pg": "8.13.1", - "pg-cursor": "2.12.1", - "postcss": "8.5.1", + "pg": "8.13.3", + "pg-cursor": "2.12.3", + "postcss": "8.5.3", "postcss-clean": "1.2.0", "progress-webpack-plugin": "1.0.16", "prompt": "1.3.0", - "ioredis": "5.4.2", + "ioredis": "5.5.0", "rimraf": "5.0.10", "rss": "1.2.2", "rtlcss": "4.3.0", "sanitize-html": "2.14.0", - "sass": "1.83.4", + "sass": "1.85.0", "satori": "0.12.1", - "semver": "7.6.3", + "semver": "7.7.1", "serve-favicon": "2.5.0", "sharp": "0.32.6", "sitemap": "8.0.0", @@ -146,9 +146,9 @@ "timeago": "1.6.7", "tinycon": "0.6.8", "toobusy-js": "0.5.1", - "tough-cookie": "5.1.0", + "tough-cookie": "5.1.1", "validator": "13.12.0", - "webpack": "5.97.1", + "webpack": "5.98.0", "webpack-merge": "6.0.1", "winston": "3.17.0", "workerpool": "9.2.0", From ad92e93129e3430b737a02579b4e081f9af5e27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 10:21:47 -0500 Subject: [PATCH 21/37] fix: add sourceContent to getPostSummaryByPids ap posts wasnt showing any content in mobile navigator --- src/posts/summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts/summary.js b/src/posts/summary.js index 89e6087036..18e5bd81fd 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -22,7 +22,7 @@ module.exports = function (Posts) { options.escape = options.hasOwnProperty('escape') ? options.escape : false; options.extraFields = options.hasOwnProperty('extraFields') ? options.extraFields : []; - const fields = ['pid', 'tid', 'toPid', 'url', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes', 'replies', 'handle'].concat(options.extraFields); + const fields = ['pid', 'tid', 'toPid', 'url', 'content', 'sourceContent', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes', 'replies', 'handle'].concat(options.extraFields); let posts = await Posts.getPostsFields(pids, fields); posts = posts.filter(Boolean); From e9d4c7b97ecea624237b249e7b3183531e424c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 10:25:53 -0500 Subject: [PATCH 22/37] fix: summary post parse --- src/posts/summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/posts/summary.js b/src/posts/summary.js index 18e5bd81fd..5995514eb6 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -74,7 +74,7 @@ module.exports = function (Posts) { async function parsePosts(posts, options) { return await Promise.all(posts.map(async (post) => { - if (!post.content) { + if (!post.content && !post.sourceContent) { return post; } if (options.parse) { From f94cf5ce606ee4df8c106be9b064244690731792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 12:02:28 -0500 Subject: [PATCH 23/37] chore: up harmony --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 027d59cf0c..835d5fdfa9 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.3", "nodebb-rewards-essentials": "1.0.1", - "nodebb-theme-harmony": "2.0.36", + "nodebb-theme-harmony": "2.0.37", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.39", "nodebb-theme-persona": "14.0.15", From b2cdd5fdb78e6630e5297cb58fccf3430a143270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 23 Feb 2025 12:06:32 -0500 Subject: [PATCH 24/37] chore: add sourceContent to schema --- public/openapi/components/schemas/PostObject.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml index 00f840127a..502ea8044d 100644 --- a/public/openapi/components/schemas/PostObject.yaml +++ b/public/openapi/components/schemas/PostObject.yaml @@ -18,6 +18,9 @@ PostObject: For posts received via ActivityPub, it is the url of the original piece of content. content: type: string + sourceContent: + type: string + nullable: true uid: type: number description: A user identifier From b4dfc48b0430092d3033d703f01eb5634b11f638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 24 Feb 2025 10:52:41 -0500 Subject: [PATCH 25/37] fix: #13198, use email from confirmObj if includeUnverifiedEmails setting is turned on. userData.email is set to the unconfirmed email --- src/emailer.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/emailer.js b/src/emailer.js index 1b545b1a35..48b463759c 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -223,6 +223,13 @@ Emailer.send = async (template, uid, params) => { // 'welcome' and 'verify-email' explicitly used passed-in email address if (['welcome', 'verify-email'].includes(template)) { userData.email = params.email; + } else if (meta.config.includeUnverifiedEmails && !userData.email) { + // get unconfirmed email to use + const code = await db.get(`confirm:byUid:${uid}`); + const confirmObj = code ? await db.getObject(`confirm:${code}`) : null; + if (confirmObj && confirmObj.email) { + userData.email = String(confirmObj.email); + } } ({ template, userData, params } = await Plugins.hooks.fire('filter:email.prepare', { template, uid, userData, params })); From 52b23313a3abc139a049f0fdf43d4fbb9929d9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 24 Feb 2025 11:04:19 -0500 Subject: [PATCH 26/37] fix: missing db --- src/emailer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emailer.js b/src/emailer.js index 48b463759c..6c57d6b44a 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -12,6 +12,7 @@ const fs = require('fs'); const _ = require('lodash'); const jwt = require('jsonwebtoken'); +const db = require('./database'); const User = require('./user'); const Plugins = require('./plugins'); const meta = require('./meta'); From 07957e82436e9af6ae9be5163a308239dbd12665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 24 Feb 2025 12:40:25 -0500 Subject: [PATCH 27/37] fix: don't send validation email for pending emails --- src/user/profile.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/user/profile.js b/src/user/profile.js index c150675d6a..3009d0a3d5 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -282,6 +282,9 @@ module.exports = function (User) { if (oldEmail === newEmail) { return; } + if (await User.email.isValidationPending(uid, newEmail)) { + return; + } // 👉 Looking for email change logic? src/user/email.js (UserEmail.confirmByUid) if (newEmail) { From 53a2be9defea7cd144694473910bd81d20ed4538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 25 Feb 2025 10:27:38 -0500 Subject: [PATCH 28/37] refactor: don't make db request if there is no code --- src/user/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/email.js b/src/user/email.js index aec9379f41..161a3b1b0f 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -53,7 +53,7 @@ UserEmail.getEmailForValidation = async (uid) => { let email = ''; // check email from confirmObj const code = await db.get(`confirm:byUid:${uid}`); - const confirmObj = await db.getObject(`confirm:${code}`); + const confirmObj = code ? await db.getObject(`confirm:${code}`) : null; if (confirmObj && confirmObj.email && parseInt(uid, 10) === parseInt(confirmObj.uid, 10)) { email = confirmObj.email; } From 349084d8f90f56b4ac1873b99673d8cfe102fc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 25 Feb 2025 10:33:06 -0500 Subject: [PATCH 29/37] refactor: use sortedSetRemoveBulk --- src/user/email.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/user/email.js b/src/user/email.js index 161a3b1b0f..c14c9c93fc 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -36,8 +36,10 @@ UserEmail.remove = async function (uid, sessionId) { email: '', 'email:confirmed': 0, }), - db.sortedSetRemove('email:uid', email.toLowerCase()), - db.sortedSetRemove('email:sorted', `${email.toLowerCase()}:${uid}`), + db.sortedSetRemoveBulk([ + ['email:uid', email.toLowerCase()], + ['email:sorted', `${email.toLowerCase()}:${uid}`], + ]), user.email.expireValidation(uid), sessionId ? user.auth.revokeAllSessions(uid, sessionId) : Promise.resolve(), events.log({ From d91b80d293de38b7aac31abc498e1602f1203962 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 25 Feb 2025 13:32:40 -0500 Subject: [PATCH 30/37] fix: handle multiple types in remote actor payload --- src/activitypub/actors.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index bda513661c..0fa91f49c2 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -100,8 +100,16 @@ Actors.assert = async (ids, options = {}) => { try { activitypub.helpers.log(`[activitypub/actors] Processing ${id}`); const actor = (typeof id === 'object' && id.hasOwnProperty('id')) ? id : await activitypub.get('uid', 0, id, { cache: process.env.CI === 'true' }); + + let typeOk = false; + if (Array.isArray(actor.type)) { + typeOk = actor.type.some(type => activitypub._constants.acceptableActorTypes.has(type)); + } else { + typeOk = activitypub._constants.acceptableActorTypes.has(actor.type); + } + if ( - !activitypub._constants.acceptableActorTypes.has(actor.type) || + !typeOk || !activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop)) ) { return null; From 64267f7de01d0b7ec53dceab24bcd7bab67a9d58 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 25 Feb 2025 14:24:56 -0500 Subject: [PATCH 31/37] test: remove extra .only, add basic tests for public note assertion --- test/activitypub.js | 2 +- test/activitypub/notes.js | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/test/activitypub.js b/test/activitypub.js index d2c2334140..d05f50e4e6 100644 --- a/test/activitypub.js +++ b/test/activitypub.js @@ -350,7 +350,7 @@ describe('ActivityPub integration', () => { }); }); - describe.only('Category Actor endpoint', () => { + describe('Category Actor endpoint', () => { let cid; let slug; let description; diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index 482b706d14..803c345c4b 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -3,6 +3,8 @@ const assert = require('assert'); const db = require('../../src/database'); +const meta = require('../../src/meta'); +const install = require('../../src/install'); const user = require('../../src/user'); const categories = require('../../src/categories'); const topics = require('../../src/topics'); @@ -10,6 +12,62 @@ const activitypub = require('../../src/activitypub'); const utils = require('../../src/utils'); describe('Notes', () => { + describe('Assertion', () => { + const baseUrl = 'https://example.org'; + + before(async () => { + meta.config.activitypubEnabled = 1; + await install.giveWorldPrivileges(); + }); + + it('should pull a remote root-level object by its id and create a new topic', async () => { + const uuid = utils.generateUUID(); + const id = `${baseUrl}/resource/${uuid}`; + activitypub._cache.set(`0;${id}`, { + '@context': 'https://www.w3.org/ns/activitystreams', + id, + url: id, + type: 'Note', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: ['https://example.org/user/foobar/followers'], + inReplyTo: null, + attributedTo: 'https://example.org/user/foobar', + name: 'Foo Bar', + content: 'Baz quux', + published: new Date().toISOString(), + }); + + const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); + assert.strictEqual(count, 1); + + const exists = await topics.exists(tid); + assert(exists); + }); + + it('should assert if the cc property is missing', async () => { + const uuid = utils.generateUUID(); + const id = `${baseUrl}/resource/${uuid}`; + activitypub._cache.set(`0;${id}`, { + '@context': 'https://www.w3.org/ns/activitystreams', + id, + url: id, + type: 'Note', + to: ['https://www.w3.org/ns/activitystreams#Public'], + inReplyTo: null, + attributedTo: 'https://example.org/user/foobar', + name: 'Foo Bar', + content: 'Baz quux', + published: new Date().toISOString(), + }); + + const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); + assert.strictEqual(count, 1); + + const exists = await topics.exists(tid); + assert(exists); + }); + }); + describe('Inbox Synchronization', () => { let cid; let uid; From 13a13e1d1365ce553fda5466dd443e0371993700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 26 Feb 2025 11:39:59 -0500 Subject: [PATCH 32/37] fix: closes #13207, add localComments --- src/routes/well-known.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/well-known.js b/src/routes/well-known.js index becd1370f8..399668b679 100644 --- a/src/routes/well-known.js +++ b/src/routes/well-known.js @@ -39,8 +39,8 @@ module.exports = function (app, middleware, controllers) { const oneMonthAgo = addMonths(new Date(), -1); const sixMonthsAgo = addMonths(new Date(), -6); - const [{ postCount, userCount }, activeMonth, activeHalfyear] = await Promise.all([ - db.getObjectFields('global', ['postCount', 'userCount']), + const [{ postCount, topicCount, userCount }, activeMonth, activeHalfyear] = await Promise.all([ + db.getObjectFields('global', ['postCount', 'topicCount', 'userCount']), db.sortedSetCount('users:online', oneMonthAgo.getTime(), '+inf'), db.sortedSetCount('users:online', sixMonthsAgo.getTime(), '+inf'), ]); @@ -64,7 +64,8 @@ module.exports = function (app, middleware, controllers) { activeMonth: activeMonth, activeHalfyear: activeHalfyear, }, - localPosts: postCount, + localPosts: topicCount, + localComments: postCount - topicCount, }, openRegistrations: meta.config.registrationType === 'normal', metadata: { From 51872d5435b2a6349874fc74c90a03f5c430749d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 26 Feb 2025 11:51:35 -0500 Subject: [PATCH 33/37] fix: closes #13206, truncate long usernames --- src/views/partials/chats/parent.tpl | 2 +- src/views/partials/topic/post-parent.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/partials/chats/parent.tpl b/src/views/partials/chats/parent.tpl index 2d2e66bf3b..68f91414c5 100644 --- a/src/views/partials/chats/parent.tpl +++ b/src/views/partials/chats/parent.tpl @@ -4,7 +4,7 @@
diff --git a/src/views/partials/topic/post-parent.tpl b/src/views/partials/topic/post-parent.tpl index cabc6bb36c..38f22da616 100644 --- a/src/views/partials/topic/post-parent.tpl +++ b/src/views/partials/topic/post-parent.tpl @@ -3,7 +3,7 @@
From efb27ce0ac48623cf91340734b2de434c31b887e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 26 Feb 2025 12:29:36 -0500 Subject: [PATCH 34/37] fix: tests for public and private note assertion, failing test for private note assertion with missing cc prop --- test/activitypub/notes.js | 86 +++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index 803c345c4b..6a27c5904b 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -1,70 +1,78 @@ 'use strict'; const assert = require('assert'); +const nconf = require('nconf'); const db = require('../../src/database'); const meta = require('../../src/meta'); const install = require('../../src/install'); const user = require('../../src/user'); const categories = require('../../src/categories'); +const posts = require('../../src/posts'); const topics = require('../../src/topics'); const activitypub = require('../../src/activitypub'); const utils = require('../../src/utils'); +const helpers = require('./helpers'); + describe('Notes', () => { describe('Assertion', () => { - const baseUrl = 'https://example.org'; - before(async () => { meta.config.activitypubEnabled = 1; await install.giveWorldPrivileges(); }); - it('should pull a remote root-level object by its id and create a new topic', async () => { - const uuid = utils.generateUUID(); - const id = `${baseUrl}/resource/${uuid}`; - activitypub._cache.set(`0;${id}`, { - '@context': 'https://www.w3.org/ns/activitystreams', - id, - url: id, - type: 'Note', - to: ['https://www.w3.org/ns/activitystreams#Public'], - cc: ['https://example.org/user/foobar/followers'], - inReplyTo: null, - attributedTo: 'https://example.org/user/foobar', - name: 'Foo Bar', - content: 'Baz quux', - published: new Date().toISOString(), + describe('Public objects', () => { + it('should pull a remote root-level object by its id and create a new topic', async () => { + const { id } = helpers.mocks.note(); + const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); + assert.strictEqual(count, 1); + + const exists = await topics.exists(tid); + assert(exists); }); - const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); - assert.strictEqual(count, 1); + it('should assert if the cc property is missing', async () => { + const { id } = helpers.mocks.note({ cc: 'remove' }); + const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); + assert.strictEqual(count, 1); - const exists = await topics.exists(tid); - assert(exists); + const exists = await topics.exists(tid); + assert(exists); + }); }); - it('should assert if the cc property is missing', async () => { - const uuid = utils.generateUUID(); - const id = `${baseUrl}/resource/${uuid}`; - activitypub._cache.set(`0;${id}`, { - '@context': 'https://www.w3.org/ns/activitystreams', - id, - url: id, - type: 'Note', - to: ['https://www.w3.org/ns/activitystreams#Public'], - inReplyTo: null, - attributedTo: 'https://example.org/user/foobar', - name: 'Foo Bar', - content: 'Baz quux', - published: new Date().toISOString(), + describe('Private objects', () => { + let recipientUid; + + before(async () => { + recipientUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); }); - const { tid, count } = await activitypub.notes.assert(0, id, { skipChecks: true }); - assert.strictEqual(count, 1); + it('should NOT create a new topic or post when asserting a private note', async () => { + const { id, note } = helpers.mocks.note({ + to: [`${nconf.get('url')}/uid/${recipientUid}`], + cc: [], + }); + const { activity } = helpers.mocks.create(note); + const { roomId } = await activitypub.inbox.create({ body: activity }); + assert(roomId); + assert(utils.isNumber(roomId)); - const exists = await topics.exists(tid); - assert(exists); + const exists = await posts.exists(id); + assert(!exists); + }); + + it('should still assert if the cc property is missing', async () => { + const { id, note } = helpers.mocks.note({ + to: [`${nconf.get('url')}/uid/${recipientUid}`], + cc: 'remove', + }); + const { activity } = helpers.mocks.create(note); + const { roomId } = await activitypub.inbox.create({ body: activity }); + assert(roomId); + assert(utils.isNumber(roomId)); + }); }); }); From c65e1ebb8f5e3ca53de62085afa3e416118ee230 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 26 Feb 2025 12:29:52 -0500 Subject: [PATCH 35/37] fix: #13202, private note assertion failure when cc property is missing --- src/activitypub/inbox.js | 2 +- src/activitypub/notes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 2887caac04..980709fb58 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -64,7 +64,7 @@ inbox.create = async (req) => { const { object } = req.body; // Alternative logic for non-public objects - const isPublic = [...object.to, ...object.cc].includes(activitypub._constants.publicAddress); + const isPublic = [...(object.to || []), ...(object.cc || [])].includes(activitypub._constants.publicAddress); if (!isPublic) { return await activitypub.notes.assertPrivate(object); } diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 2b6a0fceaa..ed0b1f6791 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -234,7 +234,7 @@ Notes.assertPrivate = async (object) => { } const localUids = []; - const recipients = new Set([...object.to, ...object.cc]); + const recipients = new Set([...(object.to || []), ...(object.cc || [])]); await Promise.all(Array.from(recipients).map(async (value) => { const { type, id } = await activitypub.helpers.resolveLocalId(value); if (type === 'user') { From 04d5edbbe9481d55e33fb6f803829913c0c606d0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 26 Feb 2025 18:25:51 -0500 Subject: [PATCH 36/37] fix: add missing file --- test/activitypub/helpers.js | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/activitypub/helpers.js diff --git a/test/activitypub/helpers.js b/test/activitypub/helpers.js new file mode 100644 index 0000000000..23fc29be14 --- /dev/null +++ b/test/activitypub/helpers.js @@ -0,0 +1,60 @@ +'use strict'; + +const utils = require('../../src/utils'); +const activitypub = require('../../src/activitypub'); + +const Helpers = module.exports; + +Helpers.mocks = {}; + +Helpers.mocks.note = (override = {}) => { + const baseUrl = 'https://example.org'; + const uuid = utils.generateUUID(); + const id = `${baseUrl}/object/${uuid}`; + const note = { + '@context': 'https://www.w3.org/ns/activitystreams', + id, + url: id, + type: 'Note', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: ['https://example.org/user/foobar/followers'], + inReplyTo: null, + attributedTo: 'https://example.org/user/foobar', + name: utils.generateUUID(), + content: `

${utils.generateUUID()}

`, + published: new Date().toISOString(), + ...override, + }; + + // If any values contain the hardcoded string "remove", remove that prop + Object.entries(note).forEach(([key, value]) => { + if (value === 'remove') { + delete note[key]; + } + }); + activitypub._cache.set(`0;${id}`, note); + + return { id, note }; +}; + +Helpers.mocks.create = (object) => { + // object is optional, will generate a public note if undefined + const baseUrl = 'https://example.org'; + const uuid = utils.generateUUID(); + const id = `${baseUrl}/activity/${uuid}`; + + object = object || Helpers.mocks.note().note; + const activity = { + '@context': 'https://www.w3.org/ns/activitystreams', + id, + type: 'Create', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: ['https://example.org/user/foobar/followers'], + actor: 'https://example.org/user/foobar', + object, + }; + + activitypub._cache.set(`0;${id}`, activity); + + return { id, activity }; +}; From 9e1a0a13e14d0293e80ff0b9674fb46159a3b67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 26 Feb 2025 20:36:10 -0500 Subject: [PATCH 37/37] chore: up dbsearch --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 835d5fdfa9..74801adb49 100644 --- a/install/package.json +++ b/install/package.json @@ -100,7 +100,7 @@ "nconf": "0.12.1", "nodebb-plugin-2factor": "7.5.9", "nodebb-plugin-composer-default": "10.2.47", - "nodebb-plugin-dbsearch": "6.2.12", + "nodebb-plugin-dbsearch": "6.2.13", "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.1.0",