diff --git a/CHANGELOG.md b/CHANGELOG.md index beaab4c40c..c35feb4d24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,75 @@ +#### v4.5.2 (2025-09-29) + +##### Chores + +* remove obsolete deprecation (52fec493) +* up persona (405d2172) +* incrementing version number - v4.5.1 (69f4b61f) +* update changelog for v4.5.1 (a9fffd7c) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* 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 a term param to recent controller so it can be controller without req.query.term (9c18c6fe) +* add a new hook to override generateUrl in navigator.js (68a8db85) +* add topic templates per category, closes #13649 (0311b98e) + +##### Bug Fixes + +* skip header checking during note assertion if test runner is active (7abdfd86) +* update note assertion topic members check to simpler posts.exists check (d0c05826) +* re-jig handling of ap tag values so that only hashtags are considered (not Piefed community tags, etc.) (4d68e3fe) +* missing actor assertion on 1b12 announced upboat (f9edb13f) +* use parameterized query for key lookup (6cca55e3) +* add pre-processing step to title generation logic so sbd doesn't fall over so badly (f7c47429) +* switch to action (f7bbec7c) +* handle cases where incoming ap object tag can be a non-array (b66c30a2) +* local pids not always converted to absolute URLs on topic actor controller (f67942ca) +* #13657, fix remote category data inconsistency in `sendNotificationToPostOwner` (225bf85e) +* don't show votes on unread if rep system disabled (dfe19a98) +* if reputation is disabled hide votes on /recent (8a786c71) +* favicon path (e2dc592c) +* check brand:touchIcon for correct path (56fad0be) +* remove .auth call (f9ddbeba) +* port the try/catch for notes.assert from develop (f9688b36) +* perform Link header check on note assertion only when skipChecks is falsy (953c051c) +* make auto-categorization logic case-insensitive (527f27af) +* closes #13641, log test email sending errors server side (b3ffa007) +* pass object to.auth (290a9395) +* **deps:** bump 2factor to 7.6.0 (d1f5060f) + +##### Other Changes + +* remove unused (a6674f67) +* fix (a37521b0) + +##### Performance Improvements + +* update upgrade script to use bulk methods (0a2fa45d) +* update old upgrade scripts to use bulkSet/Add (32d0ee48) + #### v4.5.1 (2025-09-04) ##### Chores diff --git a/install/package.json b/install/package.json index a609ed462d..e57a59d011 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.5.1", + "version": "4.5.2", "homepage": "https://www.nodebb.org", "repository": { "type": "git", @@ -202,4 +202,4 @@ "url": "https://github.com/barisusakli" } ] -} +} \ No newline at end of file diff --git a/public/openapi/read/ap.yaml b/public/openapi/read/ap.yaml index 0704cc6dab..89bee103c5 100644 --- a/public/openapi/read/ap.yaml +++ b/public/openapi/read/ap.yaml @@ -14,8 +14,8 @@ get: name: resource schema: type: string - description: A URL to query for potential ActivityPub resource - example: 'https://example.org/ap' + description: A URL-encoded address to query for potential ActivityPub resource + example: 'https://try.nodebb.org/uid/1' responses: "200": description: Sent if the `/api` prefix is used. The `X-Redirect` header is sent with the redirection target. @@ -24,7 +24,7 @@ get: schema: type: string "307": - description: Redirect the user to the local representation or original URL. + description: Redirect the user to the local representation or /outgoing interstitial page for original URL. headers: Location: schema: diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 459c86c8fe..5971c69326 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -591,42 +591,58 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) { ActivityPub.checkHeader = async (url, timeout) => { timeout = timeout || meta.config.activitypubProbeTimeout || 2000; - const { response } = await request.head(url, { - timeout, - }); - const { headers } = response; - if (headers && headers.link) { - // Multiple link headers could be combined - const links = headers.link.split(','); - let apLink = false; - links.forEach((link) => { - let parts = link.split(';'); - const url = parts.shift().match(/<(.+)>/)[1]; - if (!url || apLink) { - return; - } - - parts = parts - .map(p => p.trim()) - .reduce((memo, cur) => { - cur = cur.split('='); - if (cur.length < 2) { - cur.push(''); - } - memo[cur[0]] = cur[1].slice(1, -1); - return memo; - }, {}); - - if (parts.rel === 'alternate' && parts.type === 'application/activity+json') { - apLink = url; - } + try { + const { hostname } = new URL(url); + const { response } = await request.head(url, { + timeout, }); + const { headers } = response; - return apLink; + // headers.link = + if (headers && headers.link) { + // Multiple link headers could be combined + const links = headers.link.split(','); + let apLink = false; + + links.forEach((link) => { + let parts = link.split(';'); + const url = parts.shift().match(/<(.+)>/)[1]; + if (!url || apLink) { + return; + } + + parts = parts + .map(p => p.trim()) + .reduce((memo, cur) => { + cur = cur.split('='); + if (cur.length < 2) { + cur.push(''); + } + memo[cur[0]] = cur[1].slice(1, -1); + return memo; + }, {}); + + if (parts.rel === 'alternate' && parts.type === 'application/activity+json') { + apLink = url; + } + }); + + if (apLink) { + const { hostname: compare } = new URL(apLink); + if (hostname !== compare) { + apLink = false; + } + } + + return apLink; + } + + return false; + } catch (e) { + ActivityPub.helpers.log(`[activitypub/checkHeader] Failed on ${url}: ${e.message}`); + return false; } - - return false; }; ActivityPub.probe = async ({ uid, url }) => { diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 121c8536aa..c64ef9ef57 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -48,6 +48,11 @@ Controller.fetch = async (req, res, next) => { } } + // Force outgoing links page on direct access + if (!res.locals.isAPI) { + url = new URL(`outgoing?url=${encodeURIComponent(url.href)}`, nconf.get('url')); + } + helpers.redirect(res, url.href, false); } catch (e) { if (!url || !url.href) { diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index fef6f088b6..d5a0965d7b 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -420,6 +420,10 @@ authenticationController.localLogin = async function (req, username, password, n } const userslug = slugify(username); + if (!utils.isUserNameValid(username) || !userslug) { + return next(new Error('[[error:invalid-username]]')); + } + const uid = await user.getUidByUserslug(userslug); try { const [userData, isAdminOrGlobalMod, canLoginIfBanned] = await Promise.all([