From 4a4b223ef7f4fdd4f4effeb97005b247e212a39f Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 27 Feb 2025 18:59:18 +0000 Subject: [PATCH 01/26] chore: incrementing version number - v4.1.0 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index fdbea1d17c..ab1ada6165 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.6", + "version": "4.1.0", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From 3292a85820ed42379ee5f29fccad1d803a0ec001 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Thu, 27 Feb 2025 18:59:19 +0000 Subject: [PATCH 02/26] chore: update changelog for v4.1.0 --- CHANGELOG.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 273e8de4c5..33cb6e494a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,133 @@ +#### v4.1.0 (2025-02-27) + +##### Chores + +* incrementing version number - v4.0.6 (4a52fb2e) +* update changelog for v4.0.6 (78bbea30) +* comment out testing helper call (bad0a4c2) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* up harmony (ea110a0e) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* bump emoji for #13077 as well (ff0de097) +* fix ap dev helper (b14494b0) +* add helper method to ease in dev (d7d64a14) +* add helper method to ease in dev (7d5482b2) +* incrementing version number - v4.0.0 (c1eaee45) +* **deps:** + * update dependency sass-embedded to v1.85.1 (#13208) (3907e6c8) + * update postgres docker tag to v17.4 (#13196) (cba2bc5e) + * update postgres docker tag to v17.3 (#13162) (47e28a0e) + * update dependency sass-embedded to v1.85.0 (#13161) (2258e145) + * update commitlint monorepo to v19.7.1 (#13123) (ca6734b3) + * update coverallsapp/github-action action to v2.3.6 (#13089) (84b28fae) + * update dependency lint-staged to v15.4.3 (#13079) (1d846134) + * update dependency mocha to v11.1.0 (#13069) (8e99c97a) + * update dependency lint-staged to v15.4.1 (#13060) (153e65bc) + * update dependency lint-staged to v15.4.1 (#13060) (37b2b83d) +* **i18n:** + * fallback strings for new resources: nodebb.category (00253821) + * fallback strings for new resources: nodebb.error (589be143) + * fallback strings for new resources: nodebb.themes-harmony (25049714) + * fallback strings for new resources: nodebb.admin-settings-advanced (ad6b6132) + * fallback strings for new resources: nodebb.themes-harmony (fc063bb0) + * fallback strings for new resources: nodebb.admin-settings-general (d41109a0) + +##### New Features + +* support remote "Video" type objects in note assertion, #13120 (95f2c4ed) +* 1b12 compatibility (7dc1e8ab) +* remove activities older than a week (d9e86c7b) +* federate out Announce of a tid's mainPid if the tid is moved out of cid -1 (b7f9983a) +* syncUserInboxes to take into account remote topic tags, closes #13074 (637addc4) +* allow search bar to load remote 7888 Conversations, aka nodebb topics (7687da00) +* introduce new 'markdown' post parsing type, closes #13077 (b386e4a6) +* #13066, report canonical URL in user agent for outgoing requests (c3e9cb68) +* changes to how a topic is presented via ActivityPub; conformance with upcoming changes to 7888 (4fd7a9dc) +* changes to how a topic is presented via ActivityPub; conformance with upcoming changes to 7888 (adeaff4b) + +##### Bug Fixes + +* scheduled topics and posts should return 404 on AP request (428300de) +* tag handling when remote objects contain tags without leading # symbol (5c3f1cfe) +* handle multiple types in remote actor payload (65895651) +* missing db (058befb3) +* remove handle on category purge (adb430f2) +* restrict feps methods to real cids (8b717d54) +* restore old behaviour of 1b12 federating both object and activity (f0ee43dc) +* send `actor` with 1b12 announce, fixes #13072 again (86b0e591) +* isArray check (5f3ed76d) +* delete from payload instead of setting null (489c5ce2) +* send `actor` with 1b12 announce, fixes #13072 (3cd87f1b) +* #13139, payload.version can be null (be1598d1) +* tidChanged (bfd4e68b) +* bad logic that invisibly broke outgoing user follows completely (334be721) +* #13076, allow pulling in of topics by their topic URL fix: reapply fixes that were part of since-reverted 4fd7a9dc59b65e8654d704c493f2254793e8d6a9 (c6e6ab43) +* call relativeToAbsolute helper when generating markdown source content in mocks.notes.public/private (02fb99eb) +* extend remoteAnchorToLocalProfile ap helper to handle markdown content (db1f8959) +* incorrect `posts` url in topic posts collection (812ec73e) +* incorrect `posts` url in topic posts collection (b2530e61) +* **deps:** + * update dependency sass to v1.85.1 (#13209) (386ab89f) + * update dependency bcryptjs to v3 (#13160) (6ea65678) + * update dependency cron to v4 (#13184) (41eec8d7) + * update dependency xregexp to v5.1.2 (#13195) (23621eca) + * update dependency ace-builds to v1.39.0 (#13197) (a3f5721a) + * update dependency chart.js to v4.4.8 (#13182) (474d267e) + * update dependency postcss to v8.5.3 (#13183) (5fc4c806) + * update dependency mongodb to v6.13.1 (#13187) (77b0160c) + * update dependency nodebb-plugin-web-push to v0.7.3 (#13178) (000ceee4) + * update dependency sass to v1.85.0 (#13163) (75a7188a) + * update dependency pg to v8.13.3 (#13157) (f3c156e9) + * update dependency pg-cursor to v2.12.3 (#13158) (6b8e4b39) + * update dependency webpack to v5.98.0 (#13159) (db74c1e8) + * update dependency nodebb-widget-essentials to v7.0.33 (#13156) (af7f4242) + * update dependency pg-cursor to v2.12.2 (#13150) (b5ce9e14) + * update dependency compression to v1.8.0 (#13152) (1e52cf34) + * update dependency ace-builds to v1.38.0 (#13151) (db0b816c) + * update dependency pg to v8.13.2 (#13149) (bea1367d) + * update dependency postcss to v8.5.2 (#13144) (3449e76d) + * update dependency benchpressjs to v2.5.3 (#13098) (6688edde) + * update dependency esbuild to v0.25.0 (#13141) (d7fdd80c) + * update dependency tough-cookie to v5.1.1 (#13140) (33ce7239) + * update dependency ioredis to v5.5.0 (#13138) (b337e999) + * update dependency sass to v1.84.0 (#13128) (f872a768) + * update dependency semver to v7.7.1 (#13122) (5f3c5a55) + * update dependency mongodb to v6.13.0 (#13106) (31ff6c2e) + * update dependency semver to v7.7.0 (#13099) (a348e808) + * update dependency nodemailer to v6.10.0 (#13073) (8ab71e4f) + * update dependency nodebb-theme-persona to v14.0.2 (#13064) (8ec3ceae) + * update dependency nodebb-theme-harmony to v2.0.3 (#13063) (b98d047a) + +##### Other Changes + +* remove unused db (06b3d9ad) +* remove tab (54bc54e1) +* fix tab (397d28e3) + +##### Performance Improvements + +* closes #13145, reduce calls in actors.prune (676acb7e) + +##### Refactors + +* remove cid:-1:tids (and variants) from intersection in /world, fixes #13125 (d0561a60) +* single remove (0784e11b) +* move 1b12 announce logic out of inbox and into separate feps module (9fd6ac6b) +* acceptable types in context.js to index.js, allow searching for remote topis by topic url (d644c0f4) +* Posts.relativeToAbsolute so that the regexes passed to it no longer need a pre-defined length, it is now calculated from the match result, added new regex for markdown image/anchors (f64e6f0f) + +##### Tests + +* moved AP actor tests to separate actors.js file, added failing test for scheduled topics (01be4d79) +* update test to assert the note assertion itself (c6ba56a5) +* update bcrypt hash for 3.x (bfffbfbe) +* update pwd test for bcrypt3.x (ca0fa1d3) +* add sourceContent to spec (d1d55461) +* adjust webfinger test for updated 404 status code (59afd193) + #### v4.0.6 (2025-02-27) ##### Chores From 30068245d3a854a3a5554d81b25b12738d201c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 28 Feb 2025 11:10:31 -0500 Subject: [PATCH 03/26] fix: don't crash if there are exceptions in action hooks since some action hooks are called without an await --- src/plugins/hooks.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index 8a5d1a885d..553e90db45 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -275,8 +275,12 @@ async function fireActionHook(hook, hookList, params) { winston.warn(`[plugins] Expected method for hook '${hook}' in plugin '${hookObj.id}' not found, skipping.`); } } else { - // eslint-disable-next-line - await hookObj.method(params); + try { + // eslint-disable-next-line + await hookObj.method(params); + } catch (err) { + winston.error(`[plugins] Error in hook ${hookObj.id}@${hookObj.hook} \n${err.stack}`); + } } } } From 1d4be4752c413aab0d37e4bcaa21a15c82ff1311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 28 Feb 2025 14:35:02 -0500 Subject: [PATCH 04/26] chore: up markdown --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index ab1ada6165..cf70babd8d 100644 --- a/install/package.json +++ b/install/package.json @@ -103,7 +103,7 @@ "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", + "nodebb-plugin-markdown": "13.1.1", "nodebb-plugin-mentions": "4.7.0", "nodebb-plugin-spam-be-gone": "2.3.1", "nodebb-plugin-web-push": "0.7.3", From 8dbd50d452649f82525398b21cb6a98070073a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 28 Feb 2025 14:37:03 -0500 Subject: [PATCH 05/26] fix: closes #13219, only delete local user folder --- src/user/delete.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/user/delete.js b/src/user/delete.js index 9329e5150a..a4aac56c9f 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -234,7 +234,9 @@ module.exports = function (User) { } async function deleteImages(uid) { - const folder = path.join(nconf.get('upload_path'), 'profile', `uid-${uid}`); - await rimraf(folder); + if (utils.isNumber(uid)) { + const folder = path.join(nconf.get('upload_path'), 'profile', `uid-${uid}`); + await rimraf(folder); + } } }; From 39ff5dde3fb9949d4951f6defd0f523d93f041f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 28 Feb 2025 20:52:03 -0500 Subject: [PATCH 06/26] chore: up mentions --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index cf70babd8d..01ef385a9a 100644 --- a/install/package.json +++ b/install/package.json @@ -104,7 +104,7 @@ "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.1.1", - "nodebb-plugin-mentions": "4.7.0", + "nodebb-plugin-mentions": "4.7.1", "nodebb-plugin-spam-be-gone": "2.3.1", "nodebb-plugin-web-push": "0.7.3", "nodebb-rewards-essentials": "1.0.1", From 40a5314e642e5e8ba59638e5ff256631b81d9b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 3 Mar 2025 09:15:43 -0500 Subject: [PATCH 07/26] fix: add starterPlan check --- src/socket.io/admin/plugins.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js index d926dfa0cf..c673d448b3 100644 --- a/src/socket.io/admin/plugins.js +++ b/src/socket.io/admin/plugins.js @@ -23,7 +23,8 @@ 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) { + const isStarterPlan = nconf.get('saas_plan') === 'starter'; + if ((isStarterPlan || nconf.get('acpPluginInstallDisabled') && !isInstalled) { throw new Error('[[error:plugin-installation-via-acp-disabled]]'); } postsCache.reset(); From 9549f1fa93206cb940ba20f719eb96232cedf764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 3 Mar 2025 09:24:11 -0500 Subject: [PATCH 08/26] lint: fix ) --- src/socket.io/admin/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js index c673d448b3..4489be42bd 100644 --- a/src/socket.io/admin/plugins.js +++ b/src/socket.io/admin/plugins.js @@ -24,7 +24,7 @@ Plugins.toggleActive = async function (socket, plugin_id) { Plugins.toggleInstall = async function (socket, data) { const isInstalled = await plugins.isInstalled(data.id); const isStarterPlan = nconf.get('saas_plan') === 'starter'; - if ((isStarterPlan || nconf.get('acpPluginInstallDisabled') && !isInstalled) { + if ((isStarterPlan || nconf.get('acpPluginInstallDisabled')) && !isInstalled) { throw new Error('[[error:plugin-installation-via-acp-disabled]]'); } postsCache.reset(); From 34414f168a10d0bb9aceb4a0358c70d44d09ccd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 3 Mar 2025 17:55:44 -0500 Subject: [PATCH 09/26] chore: up themes --- install/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/package.json b/install/package.json index 01ef385a9a..375b88a07e 100644 --- a/install/package.json +++ b/install/package.json @@ -108,10 +108,10 @@ "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.37", - "nodebb-theme-lavender": "7.1.17", + "nodebb-theme-harmony": "2.0.39", + "nodebb-theme-lavender": "7.1.18", "nodebb-theme-peace": "2.2.39", - "nodebb-theme-persona": "14.0.15", + "nodebb-theme-persona": "14.0.16", "nodebb-widget-essentials": "7.0.35", "nodemailer": "6.10.0", "nprogress": "0.2.0", From 48f0f47a2e412d67e4de3ecb57778ea962539561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 5 Mar 2025 11:30:46 -0500 Subject: [PATCH 10/26] fix: #13228, use timestamp from mainpost/lastpost when forking a topic --- src/topics/fork.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/topics/fork.js b/src/topics/fork.js index d86322a9df..faff873061 100644 --- a/src/topics/fork.js +++ b/src/topics/fork.js @@ -38,25 +38,29 @@ module.exports = function (Topics) { cid = await posts.getCidByPid(mainPid); } - const [postData, isAdminOrMod] = await Promise.all([ + const [mainPost, isAdminOrMod] = await Promise.all([ posts.getPostData(mainPid), privileges.categories.isAdminOrMod(cid, uid), ]); + let lastPost = mainPost; + if (pids.length > 1) { + lastPost = await posts.getPostData(pids[pids.length - 1]); + } if (!isAdminOrMod) { throw new Error('[[error:no-privileges]]'); } - - const scheduled = postData.timestamp > Date.now(); + const now = Date.now(); + const scheduled = mainPost.timestamp > now; const params = { - uid: postData.uid, + uid: mainPost.uid, title: title, cid: cid, - timestamp: scheduled && postData.timestamp, + timestamp: mainPost.timestamp, }; const result = await plugins.hooks.fire('filter:topic.fork', { params: params, - tid: postData.tid, + tid: mainPost.tid, }); const tid = await Topics.create(result.params); @@ -71,21 +75,21 @@ module.exports = function (Topics) { await Topics.movePostToTopic(uid, pid, tid, scheduled); } - await Topics.updateLastPostTime(tid, scheduled ? (postData.timestamp + 1) : Date.now()); + await Topics.updateLastPostTime(tid, scheduled ? (mainPost.timestamp + 1) : lastPost.timestamp); await Promise.all([ Topics.setTopicFields(tid, { - upvotes: postData.upvotes, - downvotes: postData.downvotes, + upvotes: mainPost.upvotes, + downvotes: mainPost.downvotes, forkedFromTid: fromTid, forkerUid: uid, - forkTimestamp: Date.now(), + forkTimestamp: now, }), - db.sortedSetsAdd(['topics:votes', `cid:${cid}:tids:votes`], postData.votes, tid), + db.sortedSetsAdd(['topics:votes', `cid:${cid}:tids:votes`], mainPost.votes, tid), Topics.events.log(fromTid, { type: 'fork', uid, href: `/topic/${tid}` }), ]); - plugins.hooks.fire('action:topic.fork', { tid: tid, fromTid: fromTid, uid: uid }); + plugins.hooks.fire('action:topic.fork', { tid, fromTid, uid }); return await Topics.getTopicData(tid); }; From bb13ea301375ce4b06497df23afce942c9a25415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 5 Mar 2025 17:19:47 -0500 Subject: [PATCH 11/26] fix: closes #13231, add some text-truncate, match width --- .../en-GB/admin/manage/categories.json | 1 + .../admin/manage/category-federation.tpl | 146 +++++++++--------- 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json index a5c0253dae..9965edf634 100644 --- a/public/language/en-GB/admin/manage/categories.json +++ b/public/language/en-GB/admin/manage/categories.json @@ -94,6 +94,7 @@ "federation.followers-handle": "Handle", "federation.followers-id": "ID", "federation.followers-none": "No followers.", + "federation.followers-autofill": "Autofill", "alert.created": "Created", "alert.create-success": "Category successfully created!", diff --git a/src/views/admin/manage/category-federation.tpl b/src/views/admin/manage/category-federation.tpl index 9d8f1d632f..1f2652b68b 100644 --- a/src/views/admin/manage/category-federation.tpl +++ b/src/views/admin/manage/category-federation.tpl @@ -14,85 +14,85 @@ [[admin/manage/categories:federation.disabled-cta]] {{{ else }}} -
-
-
-
-
-
[[admin/manage/categories:federation.syncing-header]]
-

[[admin/manage/categories:federation.syncing-intro]]

-

[[admin/manage/categories:federation.syncing-caveat]]

+
+
+
+ +
[[admin/manage/categories:federation.syncing-header]]
+

[[admin/manage/categories:federation.syncing-intro]]

+

[[admin/manage/categories:federation.syncing-caveat]]

- {{{ if !following.length }}} -
[[admin/manage/categories:federation.syncing-none]]
- {{{ else }}} - - - - - - - - - {{{ each following }}} - - - - - {{{ end }}} - -
[[admin/manage/categories:federation.syncing-actorUri]]
-
{./id}
- {{{ if !./approved }}} - Pending - {{{ end }}} -
- -
- {{{ end }}} + {{{ if !following.length }}} +
[[admin/manage/categories:federation.syncing-none]]
+ {{{ else }}} + + + + + + + + + {{{ each following }}} + + + + + {{{ end }}} + +
[[admin/manage/categories:federation.syncing-actorUri]]
+
{./id}
+ {{{ if !./approved }}} + Pending + {{{ end }}} +
+ +
+ {{{ end }}} -
- -
- - -
+
+ +
+ +
+
-
+
-
-

[[admin/manage/categories:federation.followers]]

- - - - - - {{{ if !followers.length}}} - - - - {{{ end }}} - {{{ each followers }}} - - - + + {{{ end }}} +
[[admin/manage/categories:federation.followers-handle]][[admin/manage/categories:federation.followers-id]]
- [[admin/manage/categories:federation.followers-none]] -
- {buildAvatar(followers, "24px", true)} - {./userslug} - - {./uid} -
+
+
From 84d3fe796961ba8058c4844f3a29c525e7f85035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 7 Mar 2025 10:21:59 -0500 Subject: [PATCH 12/26] refactor: show warning if there is no email for validation --- src/user/email.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/user/email.js b/src/user/email.js index c14c9c93fc..9535d87415 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -134,6 +134,7 @@ UserEmail.sendValidationEmail = async function (uid, options) { options.email = await user.getUserField(uid, 'email'); } if (!options.email) { + winston.warn(`[user/email] No email found for uid ${uid}`); return; } From c83f91bd122d292ff7ea2f2d0437c58fd00ece78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 7 Mar 2025 10:23:55 -0500 Subject: [PATCH 13/26] refactor: dont generate UUID if no email --- src/user/email.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/user/email.js b/src/user/email.js index 9535d87415..b485081713 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -124,11 +124,6 @@ UserEmail.sendValidationEmail = async function (uid, options) { }; } - const confirm_code = utils.generateUUID(); - const confirm_link = `${nconf.get('url')}/confirm/${confirm_code}`; - - const { emailConfirmInterval, emailConfirmExpiry } = meta.config; - // If no email passed in (default), retrieve email from uid if (!options.email || !options.email.length) { options.email = await user.getUserField(uid, 'email'); @@ -138,10 +133,13 @@ UserEmail.sendValidationEmail = async function (uid, options) { return; } + const { emailConfirmInterval, emailConfirmExpiry } = meta.config; if (!options.force && !await UserEmail.canSendValidation(uid, options.email)) { throw new Error(`[[error:confirm-email-already-sent, ${emailConfirmInterval}]]`); } + const confirm_code = utils.generateUUID(); + const confirm_link = `${nconf.get('url')}/confirm/${confirm_code}`; const username = await user.getUserField(uid, 'username'); const data = await plugins.hooks.fire('filter:user.verify', { uid, From bef17920860fe4e11ef6a0b588e830492349b245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 8 Mar 2025 00:09:41 -0500 Subject: [PATCH 14/26] fix: closes #13078, toggle /world navItem when AP is toggled --- src/meta/configs.js | 8 ++++++++ src/navigation/admin.js | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/meta/configs.js b/src/meta/configs.js index 80159490c4..f5adf5b000 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -132,6 +132,7 @@ Configs.setMultiple = async function (data) { await processConfig(data); data = serialize(data); await db.setObject('config', data); + await updateNavItems(data); updateConfig(deserialize(data)); }; @@ -228,6 +229,13 @@ async function getLogoSize(data) { data['brand:emailLogo:width'] = size.width; } +async function updateNavItems(data) { + if (data.hasOwnProperty('activitypubEnabled')) { + const navAdmin = require('../navigation/admin'); + await navAdmin.update('/world', { enabled: data.activitypubEnabled ? 'on' : '' }); + } +} + function updateConfig(config) { updateLocalConfig(config); pubsub.publish('config:update', config); diff --git a/src/navigation/admin.js b/src/navigation/admin.js index df8241c8ba..f944918371 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -85,6 +85,19 @@ admin.get = async function () { return cache.map(item => ({ ...item })); }; +admin.update = async function (route, data) { + const ids = await db.getSortedSetRange('navigation:enabled', 0, -1); + const navItems = await db.getObjects(ids.map(id => `navigation:enabled:${id}`)); + const matchedRoutes = navItems.filter(item => item && item.route === route); + if (matchedRoutes.length) { + await db.setObjectBulk( + matchedRoutes.map(item => [`navigation:enabled:${item.order}`, data]) + ); + cache = null; + pubsub.publish('admin:navigation:save'); + } +}; + async function getAvailable() { const core = require('../../install/data/navigation.json').map((item) => { item.core = true; From 042797187947aac01dc24640cdbf3923c64b0b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 8 Mar 2025 00:39:05 -0500 Subject: [PATCH 15/26] test: #13078, add nav test --- test/navigation.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/navigation.js diff --git a/test/navigation.js b/test/navigation.js new file mode 100644 index 0000000000..765e649d20 --- /dev/null +++ b/test/navigation.js @@ -0,0 +1,25 @@ +'use strict'; + +const assert = require('assert'); + +const db = require('./mocks/databasemock'); +const meta = require('../src/meta'); +const navAdmin = require('../src/navigation/admin'); + +describe('Navigation', () => { + before(async () => { + const navigation = require('../src/navigation/admin'); + const data = require('../install/data/navigation.json'); + await navigation.save(data); + }); + + it('should toggle /world route when ap is toggled', async () => { + let nav = await navAdmin.get(); + let world = nav.find(item => item.route === '/world'); + assert.strictEqual(!!world.enabled, true); + await meta.configs.setMultiple({ activitypubEnabled: 0 }); + nav = await navAdmin.get(); + world = nav.find(item => item.route === '/world'); + assert.strictEqual(!!world.enabled, false); + }); +}); From b517f05e90c3211b58553a22cb2df9ca346efdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 8 Mar 2025 00:39:46 -0500 Subject: [PATCH 16/26] refactor: use navAdmin --- test/navigation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/navigation.js b/test/navigation.js index 765e649d20..78e2b4447c 100644 --- a/test/navigation.js +++ b/test/navigation.js @@ -8,9 +8,8 @@ const navAdmin = require('../src/navigation/admin'); describe('Navigation', () => { before(async () => { - const navigation = require('../src/navigation/admin'); const data = require('../install/data/navigation.json'); - await navigation.save(data); + await navAdmin.save(data); }); it('should toggle /world route when ap is toggled', async () => { From 6b9f166cb8391f64c7764a22c1bb03a7c53d0523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 9 Mar 2025 11:14:16 -0400 Subject: [PATCH 17/26] fix: don't update topic lastposttime by announce this was causing topics to show up as unread eventhough there are no new posts and out of order on /recent --- src/activitypub/inbox.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index e9b17d92c8..4da0800670 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -297,7 +297,6 @@ inbox.announce = async (req) => { } ({ tid } = assertion); - await topics.updateLastPostTime(tid, timestamp); await activitypub.notes.updateLocalRecipients(pid, { to, cc }); await activitypub.notes.syncUserInboxes(tid); } From 1e6c6f4e4408f4fd438661b65475cf00cd49f98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 9 Mar 2025 12:03:09 -0400 Subject: [PATCH 18/26] fix: #13094, update unread chats on reconnect unread topics and notifications were updated on reconnections, added chats as well convert function to async added awaits --- src/messaging/unread.js | 3 --- src/socket.io/meta.js | 17 ++++++++++------- test/socket.io.js | 7 ++----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/messaging/unread.js b/src/messaging/unread.js index 1a63d81139..8ca893a638 100644 --- a/src/messaging/unread.js +++ b/src/messaging/unread.js @@ -18,9 +18,6 @@ module.exports = function (Messaging) { uids = [uids]; } uids = uids.filter(uid => parseInt(uid, 10) > 0); - if (!uids.length) { - return; - } uids.forEach((uid) => { io.in(`uid_${uid}`).emit('event:unread.updateChatCount', data); }); diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 0e59e93849..f2181344e8 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -6,20 +6,23 @@ const user = require('../user'); const meta = require('../meta'); const topics = require('../topics'); const privileges = require('../privileges'); +const messaging = require('../messaging'); const SocketMeta = module.exports; SocketMeta.rooms = {}; -SocketMeta.reconnected = function (socket, data, callback) { - callback = callback || function () {}; - if (socket.uid) { - topics.pushUnreadCount(socket.uid); - user.notifications.pushCount(socket.uid); +SocketMeta.reconnected = async function (socket) { + if (socket.uid > 0) { + await Promise.all([ + topics.pushUnreadCount(socket.uid), + user.notifications.pushCount(socket.uid), + messaging.pushUnreadCount(socket.uid), + ]); } - callback(null, { + return { 'cache-buster': meta.config['cache-buster'], hostname: os.hostname(), - }); + }; }; /* Rooms */ diff --git a/test/socket.io.js b/test/socket.io.js index 8fd2895809..0e725ad3be 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -269,12 +269,9 @@ describe('socket.io', () => { }); }); - it('should push unread notifications on reconnect', (done) => { + it('should push unread notifications/chats on reconnect', async () => { const socketMeta = require('../src/socket.io/meta'); - socketMeta.reconnected({ uid: 1 }, {}, (err) => { - assert.ifError(err); - done(); - }); + await socketMeta.reconnected({ uid: 1 }, {}); }); From 810e8dbbbfd2bdf5c4e9ae2c4346af0b723896cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 15:51:43 -0400 Subject: [PATCH 19/26] fix: sanitize category svg image files --- src/controllers/admin/uploads.js | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index fc6ee9c1f1..e1ad938902 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -3,6 +3,7 @@ const path = require('path'); const nconf = require('nconf'); const fs = require('fs'); +const sanitizeHtml = require('sanitize-html'); const meta = require('../../meta'); const posts = require('../../posts'); @@ -121,11 +122,50 @@ uploadsController.uploadCategoryPicture = async function (req, res, next) { return next(new Error('[[error:invalid-json]]')); } + if (uploadedFile.path.endsWith('.svg')) { + await sanitizeSvg(uploadedFile.path); + } + await validateUpload(uploadedFile, allowedImageTypes); const filename = `category-${params.cid}${path.extname(uploadedFile.name)}`; await uploadImage(filename, 'category', uploadedFile, req, res, next); }; +async function sanitizeSvg(filePath) { + const dirty = await fs.promises.readFile(filePath, 'utf8'); + const clean = sanitizeHtml(dirty, { + allowedTags: [ + 'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop', + 'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect', + 'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern', + 'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode', + ], + allowedAttributes: { + '*': [ + // Geometry + 'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', + 'width', 'height', 'd', 'points', 'viewBox', 'transform', + + // Presentation + 'fill', 'stroke', 'stroke-width', 'opacity', + 'stop-color', 'stop-opacity', 'offset', 'style', 'class', + + // Text + 'text-anchor', 'font-size', 'font-family', + + // Misc + 'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform', + 'xmlns', 'preserveAspectRatio', + ], + }, + parser: { + lowerCaseTags: false, + lowerCaseAttributeNames: false, + }, + }); + await fs.promises.writeFile(filePath, clean); +} + uploadsController.uploadFavicon = async function (req, res, next) { const uploadedFile = req.files.files[0]; const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon']; From 6d74ee2f59dfaaf3a3eebf4f67bc36d88e361d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 16:20:51 -0400 Subject: [PATCH 20/26] refactor: show simple error if path doesn't exist --- src/controllers/admin/uploads.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index e1ad938902..ccdf1ed0f9 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -3,6 +3,7 @@ const path = require('path'); const nconf = require('nconf'); const fs = require('fs'); +const winston = require('winston'); const sanitizeHtml = require('sanitize-html'); const meta = require('../../meta'); @@ -23,8 +24,14 @@ uploadsController.get = async function (req, res, next) { } const itemsPerPage = 20; const page = parseInt(req.query.page, 10) || 1; + let files = []; + try { + files = await fs.promises.readdir(currentFolder); + } catch (err) { + winston.error(err.stack); + return next(new Error('[[error:invalid-path]]')); + } try { - let files = await fs.promises.readdir(currentFolder); files = files.filter(filename => filename !== '.gitignore'); const itemCount = files.length; const start = Math.max(0, (page - 1) * itemsPerPage); From 76896859faba63eee24f1f51bb68035a4d215471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 16:49:40 -0400 Subject: [PATCH 21/26] fix: check if folder exists when uploading files in acp --- src/controllers/admin/uploads.js | 3 +++ test/uploads.js | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index ccdf1ed0f9..bb777c2f31 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -244,6 +244,9 @@ uploadsController.uploadFile = async function (req, res, next) { return next(new Error('[[error:invalid-json]]')); } + if (!await file.exists(path.join(nconf.get('upload_path'), params.folder))) { + return next(new Error('[[error:invalid-path]]')); + } try { const data = await file.saveFileToLocal(uploadedFile.name, params.folder, uploadedFile.path); res.json([{ url: data.url }]); diff --git a/test/uploads.js b/test/uploads.js index a8e48afac5..76148d25d2 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -400,6 +400,17 @@ describe('Upload Controllers', () => { assert.strictEqual(body.error, '[[error:invalid-path]]'); }); + it('should fail to upload regular file if directory does not exist', async () => { + const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), { + params: JSON.stringify({ + folder: 'does-not-exist', + }), + }, jar, csrf_token); + + assert.equal(response.statusCode, 500); + assert.strictEqual(body.error, '[[error:invalid-path]]'); + }); + describe('ACP uploads screen', () => { it('should create a folder', async () => { const { response } = await helpers.createFolder('', 'myfolder', jar, csrf_token); From e775564fc1e728504c008c314aa98ee28e91caae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 17:59:31 -0400 Subject: [PATCH 22/26] refactor: prevent following symlinks --- src/controllers/admin/uploads.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index bb777c2f31..ced7385983 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -26,13 +26,13 @@ uploadsController.get = async function (req, res, next) { const page = parseInt(req.query.page, 10) || 1; let files = []; try { - files = await fs.promises.readdir(currentFolder); + await checkSymLinks(req.query.dir) + files = await getFilesInFolder(currentFolder); } catch (err) { winston.error(err.stack); return next(new Error('[[error:invalid-path]]')); } try { - files = files.filter(filename => filename !== '.gitignore'); const itemCount = files.length; const start = Math.max(0, (page - 1) * itemsPerPage); const stop = start + itemsPerPage; @@ -72,6 +72,30 @@ uploadsController.get = async function (req, res, next) { } }; +async function checkSymLinks(folder) { + let dir = path.normalize(folder || ''); + while (dir.length && dir !== '.') { + const nextPath = path.join(nconf.get('upload_path'), dir); + // eslint-disable-next-line no-await-in-loop + const stat = await fs.promises.lstat(nextPath); + if (stat.isSymbolicLink()) { + throw new Error('[[invalid-path]]'); + } + dir = path.dirname(dir); + } +} + +async function getFilesInFolder(folder) { + const dirents = await fs.promises.readdir(folder, { withFileTypes: true }); + const files = []; + for await (const dirent of dirents) { + if (!dirent.isSymbolicLink() && dirent.name !== '.gitignore') { + files.push(dirent.name); + } + } + return files; +} + function buildBreadcrumbs(currentFolder) { const crumbs = []; const parts = currentFolder.replace(nconf.get('upload_path'), '').split(path.sep); @@ -102,14 +126,14 @@ async function getFileData(currentDir, file) { const stat = await fs.promises.stat(pathToFile); let filesInDir = []; if (stat.isDirectory()) { - filesInDir = await fs.promises.readdir(pathToFile); + filesInDir = await getFilesInFolder(pathToFile); } const url = `${nconf.get('upload_url') + currentDir.replace(nconf.get('upload_path'), '')}/${file}`; return { name: file, path: pathToFile.replace(path.join(nconf.get('upload_path'), '/'), ''), url: url, - fileCount: Math.max(0, filesInDir.length - 1), // ignore .gitignore + fileCount: filesInDir.length, size: stat.size, sizeHumanReadable: `${(stat.size / 1024).toFixed(1)}KiB`, isDirectory: stat.isDirectory(), From c4e31395997e3cc411fea541093342e8f862548c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 18:13:16 -0400 Subject: [PATCH 23/26] lint: missing semi --- src/controllers/admin/uploads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index ced7385983..0d91484002 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -26,7 +26,7 @@ uploadsController.get = async function (req, res, next) { const page = parseInt(req.query.page, 10) || 1; let files = []; try { - await checkSymLinks(req.query.dir) + await checkSymLinks(req.query.dir); files = await getFilesInFolder(currentFolder); } catch (err) { winston.error(err.stack); From 1ca7b7eccea219ecefc9c6a1bd28ed45b6996a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 10 Mar 2025 18:25:53 -0400 Subject: [PATCH 24/26] test: fix inf loop if dirname results in same dir, ie \ --- src/controllers/admin/uploads.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index 0d91484002..56d64674cf 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -81,7 +81,11 @@ async function checkSymLinks(folder) { if (stat.isSymbolicLink()) { throw new Error('[[invalid-path]]'); } - dir = path.dirname(dir); + const newDir = path.dirname(dir); + if (newDir === dir) { + break; + } + dir = newDir; } } From de502cd2eec9d928ae97b82b2429a59af4cee2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 11 Mar 2025 11:49:40 -0400 Subject: [PATCH 25/26] feat: allow self-signed certs, closes #13238 --- install/data/defaults.json | 1 + public/language/en-GB/admin/settings/email.json | 2 ++ src/emailer.js | 6 +++++- src/views/admin/settings/email.tpl | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/install/data/defaults.json b/install/data/defaults.json index a86fa023e4..43dd55bcc6 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -147,6 +147,7 @@ "username:disableEdit": 0, "email:disableEdit": 0, "email:smtpTransport:pool": 0, + "email:smtpTransport:allow-self-signed": 0, "hideFullname": 0, "hideEmail": 0, "showFullnameAsDisplayName": 0, diff --git a/public/language/en-GB/admin/settings/email.json b/public/language/en-GB/admin/settings/email.json index a3f49a0416..31d5a4f0fb 100644 --- a/public/language/en-GB/admin/settings/email.json +++ b/public/language/en-GB/admin/settings/email.json @@ -28,6 +28,8 @@ "smtp-transport.password": "Password", "smtp-transport.pool": "Enable pooled connections", "smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.", + "smtp-transport.allow-self-signed": "Allow self-signed certificates", + "smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-self or invalid TLS certificates.", "template": "Edit Email Template", "template.select": "Select Email Template", diff --git a/src/emailer.js b/src/emailer.js index 6c57d6b44a..f280c21399 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -153,7 +153,11 @@ Emailer.setupFallbackTransport = (config) => { } else { smtpOptions.service = String(config['email:smtpTransport:service']); } - + if (config['email:smtpTransport:allow-self-signed']) { + smtpOptions.tls = { + rejectUnauthorized: false, + }; + } Emailer.transports.smtp = nodemailer.createTransport(smtpOptions); Emailer.fallbackTransport = Emailer.transports.smtp; } else { diff --git a/src/views/admin/settings/email.tpl b/src/views/admin/settings/email.tpl index a89fb850a8..d39d3b8f72 100644 --- a/src/views/admin/settings/email.tpl +++ b/src/views/admin/settings/email.tpl @@ -116,6 +116,11 @@

[[admin/settings/email:smtp-transport.pool-help]]

+
+ + +

[[admin/settings/email:smtp-transport.allow-self-signed-help]]

+