From 130b93eca91f305cc2c7b95ec818ae637f76a4dc Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 7 May 2025 15:38:18 +0000 Subject: [PATCH 01/15] chore: incrementing version number - v4.3.1 --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index 0cabe00a97..9b6429f93e 100644 --- a/install/package.json +++ b/install/package.json @@ -2,7 +2,7 @@ "name": "nodebb", "license": "GPL-3.0", "description": "NodeBB Forum", - "version": "4.3.0", + "version": "4.3.1", "homepage": "https://www.nodebb.org", "repository": { "type": "git", From 2310a7b835438db75c288282c6651dec7f75c0e7 Mon Sep 17 00:00:00 2001 From: Misty Release Bot Date: Wed, 7 May 2025 15:38:18 +0000 Subject: [PATCH 02/15] chore: update changelog for v4.3.1 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91b6e9874b..cc3228268f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +#### v4.3.1 (2025-05-07) + +##### Chores + +* node 18 eol (800426d6) +* up widgets (ee2f91ad) +* up themes (18867fb1) +* update bundled plugins to use eslint9 (343f13e1) +* incrementing version number - v4.3.0 (bff291db) +* update changelog for v4.3.0 (76c03019) +* 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) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13367 (d35aad31) + +##### Tests + +* fix android test (31af05c7) +* fix android test (25979294) +* fix a test (7ef79981) + #### v4.3.0 (2025-05-01) ##### Chores From 10077d0f89b4e3779f2e9217d5061d7d55f84240 Mon Sep 17 00:00:00 2001 From: Opliko Date: Thu, 8 May 2025 00:57:40 +0200 Subject: [PATCH 03/15] fix: correct stage name in dev dockerfile (#13393) Co-authored-by: ThisIsMissEm --- dev.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev.Dockerfile b/dev.Dockerfile index 4946d18725..b02384558a 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -58,8 +58,8 @@ RUN corepack enable \ && mkdir -p /usr/src/app/logs/ /opt/config/ \ && chown -R ${USER}:${USER} /usr/src/app/ /opt/config/ -COPY --from=build --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/ -COPY --from=build --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/ +COPY --from=git --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/ +COPY --from=git --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/ COPY --from=node_modules_touch --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/ COPY --from=git --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/ From a9a5ab5e4bfef9acde8d45d04322081f44a3da47 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 8 May 2025 13:41:43 -0400 Subject: [PATCH 04/15] fix: #13397, update getCidByHandle to work with remote categories, fix sync with handles causing issues with null entries --- src/api/activitypub.js | 10 +++++++++- src/categories/index.js | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/api/activitypub.js b/src/api/activitypub.js index be6600d3a2..ed9e6f2f64 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -13,6 +13,7 @@ const winston = require('winston'); const db = require('../database'); const user = require('../user'); +const categories = require('../categories'); const meta = require('../meta'); const privileges = require('../privileges'); const activitypub = require('../activitypub'); @@ -43,7 +44,14 @@ activitypubApi.follow = enabledCheck(async (caller, { type, id, actor } = {}) => throw new Error('[[error:activitypub.invalid-id]]'); } - actor = actor.includes('@') ? await user.getUidByUserslug(actor) : actor; + if (actor.includes('@')) { + const [uid, cid] = await Promise.all([ + user.getUidByUserslug(actor), + categories.getCidByHandle(actor), + ]); + + actor = uid || cid; + } const [handle, isFollowing] = await Promise.all([ user.getUserField(actor, 'username'), db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor), diff --git a/src/categories/index.js b/src/categories/index.js index 308516cd97..de7ff6d769 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -85,7 +85,13 @@ Categories.getCategoryById = async function (data) { }; Categories.getCidByHandle = async function (handle) { - return await db.sortedSetScore('categoryhandle:cid', handle); + let cid = await db.sortedSetScore('categoryhandle:cid', handle); + if (!cid) { + // remote cids + cid = await db.getObjectField('handle:cid', handle); + } + + return cid; }; Categories.getAllCidsFromSet = async function (key) { From 401ff797c91217c4887b86f013f038b7d4e09b13 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 8 May 2025 13:55:17 -0400 Subject: [PATCH 05/15] fix: #13392, regression from c6f2c87, unable to unfollow from pending follows --- src/api/activitypub.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/api/activitypub.js b/src/api/activitypub.js index ed9e6f2f64..2752a50bc6 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -84,13 +84,22 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => { throw new Error('[[error:activitypub.invalid-id]]'); } - actor = actor.includes('@') ? await user.getUidByUserslug(actor) : actor; - const [handle, isFollowing] = await Promise.all([ + if (actor.includes('@')) { + const [uid, cid] = await Promise.all([ + user.getUidByUserslug(actor), + categories.getCidByHandle(actor), + ]); + + actor = uid || cid; + } + + const [handle, isFollowing, isPending] = await Promise.all([ user.getUserField(actor, 'username'), db.isSortedSetMember(type === 'uid' ? `followingRemote:${id}` : `cid:${id}:following`, actor), + db.isSortedSetMember(`followRequests:${type === 'uid' ? 'uid' : 'cid'}.${id}`, actor), ]); - if (!isFollowing) { // already not following + if (!isFollowing && !isPending) { // already not following/pending return; } From 26e6a22278c426802e8f80e70cefcffc73653a25 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 8 May 2025 13:55:42 -0400 Subject: [PATCH 06/15] fix: #13397, null values in category sync list --- src/upgrades/4.3.2/fix_category_sync_null_values.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/upgrades/4.3.2/fix_category_sync_null_values.js diff --git a/src/upgrades/4.3.2/fix_category_sync_null_values.js b/src/upgrades/4.3.2/fix_category_sync_null_values.js new file mode 100644 index 0000000000..e81590cf27 --- /dev/null +++ b/src/upgrades/4.3.2/fix_category_sync_null_values.js @@ -0,0 +1,12 @@ +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'Fix null values in category synchronization list', + timestamp: Date.UTC(2025, 4, 8), + method: async () => { + const cids = await db.getSortedSetMembers('categories:cid'); + await db.sortedSetsRemove(cids.map(cid => `followRequests:cid.${cid}`), 'null'); + }, +}; From 64fdf91b6b02c291edd29f2a7de0dbe85591fd79 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 9 May 2025 10:16:33 -0400 Subject: [PATCH 07/15] fix: wrap generateCollection calls in try..catch to send 404 if thrown --- src/controllers/activitypub/actors.js | 45 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/controllers/activitypub/actors.js b/src/controllers/activitypub/actors.js index f9212ba09d..85e2fda01c 100644 --- a/src/controllers/activitypub/actors.js +++ b/src/controllers/activitypub/actors.js @@ -84,7 +84,7 @@ Actors.note = async function (req, res, next) { res.status(200).json(payload); }; -Actors.replies = async function (req, res) { +Actors.replies = async function (req, res, next) { const allowed = utils.isNumber(req.params.pid) && await privileges.posts.can('topics:read', req.params.pid, activitypub._constants.uid); const exists = await posts.exists(req.params.pid); if (!allowed || !exists) { @@ -92,12 +92,17 @@ Actors.replies = async function (req, res) { } const page = parseInt(req.query.page, 10); - const replies = await activitypub.helpers.generateCollection({ - set: `pid:${req.params.pid}:replies`, - page, - perPage: meta.config.postsPerPage, - url: `${nconf.get('url')}/post/${req.params.pid}/replies`, - }); + let replies; + try { + replies = await activitypub.helpers.generateCollection({ + set: `pid:${req.params.pid}:replies`, + page, + perPage: meta.config.postsPerPage, + url: `${nconf.get('url')}/post/${req.params.pid}/replies`, + }); + } catch (e) { + return next(); // invalid page; 404 + } // Convert pids to urls replies.orderedItems = replies.orderedItems.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); @@ -126,16 +131,22 @@ Actors.topic = async function (req, res, next) { return next(); } - let [collection, pids] = await Promise.all([ - activitypub.helpers.generateCollection({ - set: `tid:${req.params.tid}:posts`, - method: posts.getPidsFromSet, - page, - perPage, - url: `${nconf.get('url')}/topic/${req.params.tid}/posts`, - }), - db.getSortedSetMembers(`tid:${req.params.tid}:posts`), - ]); + let collection; + let pids; + try { + ([collection, pids] = await Promise.all([ + activitypub.helpers.generateCollection({ + set: `tid:${req.params.tid}:posts`, + method: posts.getPidsFromSet, + page, + perPage, + url: `${nconf.get('url')}/topic/${req.params.tid}/posts`, + }), + db.getSortedSetMembers(`tid:${req.params.tid}:posts`), + ])); + } catch (e) { + return next(); // invalid page; 404 + } pids.push(mainPid); pids = pids.map(pid => (utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid)); collection.totalItems += 1; // account for mainPid From 20ab90694c47ec3dde3afccde22b3ac2f89ca85b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 9 May 2025 10:34:19 -0400 Subject: [PATCH 08/15] fix: send proper accept header for outgoing webfinger requests --- src/activitypub/helpers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js index 4a8b2b08fa..071c70e395 100644 --- a/src/activitypub/helpers.js +++ b/src/activitypub/helpers.js @@ -108,7 +108,11 @@ Helpers.query = async (id) => { let response; let body; try { - ({ response, body } = await request.get(`https://${hostname}/.well-known/webfinger?${query}`)); + ({ response, body } = await request.get(`https://${hostname}/.well-known/webfinger?${query}`, { + headers: { + accept: 'application/jrd+json', + }, + })); } catch (e) { return false; } From 8174578c5bfc45057be71b99425a9b93decaec98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sun, 11 May 2025 22:31:00 -0400 Subject: [PATCH 09/15] fix: closes #13405, catch errors in ap.verify --- src/activitypub/index.js | 82 ++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index e055522d81..c87a0654f3 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -232,49 +232,49 @@ ActivityPub.verify = async (req) => { return false; } - // Break the signature apart - let { keyId, headers, signature, algorithm, created, expires } = req.headers.signature.split(',').reduce((memo, cur) => { - const split = cur.split('="'); - const key = split.shift(); - const value = split.join('="'); - memo[key] = value.slice(0, -1); - return memo; - }, {}); - - const acceptableHashes = getHashes(); - if (algorithm === 'hs2019' || !acceptableHashes.includes(algorithm)) { - algorithm = 'sha256'; - } - - // Re-construct signature string - const signed_string = headers.split(' ').reduce((memo, cur) => { - switch (cur) { - case '(request-target)': { - memo.push(`${cur}: ${String(req.method).toLowerCase()} ${req.baseUrl}${req.path}`); - break; - } - - case '(created)': { - memo.push(`${cur}: ${created}`); - break; - } - - case '(expires)': { - memo.push(`${cur}: ${expires}`); - break; - } - - default: { - memo.push(`${cur}: ${req.headers[cur]}`); - break; - } - } - - return memo; - }, []).join('\n'); - // Verify the signature string via public key try { + // Break the signature apart + let { keyId, headers, signature, algorithm, created, expires } = req.headers.signature.split(',').reduce((memo, cur) => { + const split = cur.split('="'); + const key = split.shift(); + const value = split.join('="'); + memo[key] = value.slice(0, -1); + return memo; + }, {}); + + const acceptableHashes = getHashes(); + if (algorithm === 'hs2019' || !acceptableHashes.includes(algorithm)) { + algorithm = 'sha256'; + } + + // Re-construct signature string + const signed_string = headers.split(' ').reduce((memo, cur) => { + switch (cur) { + case '(request-target)': { + memo.push(`${cur}: ${String(req.method).toLowerCase()} ${req.baseUrl}${req.path}`); + break; + } + + case '(created)': { + memo.push(`${cur}: ${created}`); + break; + } + + case '(expires)': { + memo.push(`${cur}: ${expires}`); + break; + } + + default: { + memo.push(`${cur}: ${req.headers[cur]}`); + break; + } + } + + return memo; + }, []).join('\n'); + // Retrieve public key from remote instance ActivityPub.helpers.log(`[activitypub/verify] Retrieving pubkey for ${keyId}`); const { publicKeyPem } = await ActivityPub.fetchPublicKey(keyId); From fcf9e8b796d81db1c7e50561f25b78e5ebfe3bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 09:01:32 -0400 Subject: [PATCH 10/15] 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 9b6429f93e..eca4d69ed8 100644 --- a/install/package.json +++ b/install/package.json @@ -103,7 +103,7 @@ "nodebb-plugin-emoji": "6.0.2", "nodebb-plugin-emoji-android": "4.1.1", "nodebb-plugin-markdown": "13.1.2", - "nodebb-plugin-mentions": "4.7.4", + "nodebb-plugin-mentions": "4.7.5", "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.4", "nodebb-rewards-essentials": "1.0.2", From 31be083e86060b5903d9f3ba6cba65f6c86a9c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 09:12:51 -0400 Subject: [PATCH 11/15] fix: #13407, don't restart user jobs if jobsDisabled=true on that process --- src/socket.io/admin/user.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 9367ff9c53..698f854597 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -2,6 +2,8 @@ const async = require('async'); const winston = require('winston'); +const nconf = require('nconf'); +const pubsub = require('../../pubsub'); const db = require('../../database'); const groups = require('../../groups'); @@ -129,8 +131,15 @@ User.forcePasswordReset = async function (socket, uids) { uids.forEach(uid => sockets.in(`uid_${uid}`).emit('event:logout')); }; +pubsub.on('admin.user.restartJobs', () => { + if (nconf.get('runJobs')) { + winston.verbose('[user/jobs] Restarting jobs...'); + user.startJobs(); + } +}); + User.restartJobs = async function () { - user.startJobs(); + pubsub.publish('admin.user.restartJobs', {}); }; User.loadGroups = async function (socket, uids) { From 285d438cb3f98a729765a1bde6b12efa535a3940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 09:30:33 -0400 Subject: [PATCH 12/15] fix: escape flag filters --- src/controllers/mods.js | 5 +++-- test/flags.js | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/controllers/mods.js b/src/controllers/mods.js index 4976dd9e82..c0abc18fe8 100644 --- a/src/controllers/mods.js +++ b/src/controllers/mods.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const validator = require('validator'); const user = require('../user'); const groups = require('../groups'); @@ -43,9 +44,9 @@ modsController.flags.list = async function (req, res) { filters = filters.reduce((memo, cur) => { if (req.query.hasOwnProperty(cur)) { if (typeof req.query[cur] === 'string' && req.query[cur].trim() !== '') { - memo[cur] = req.query[cur].trim(); + memo[cur] = validator.escape(String(req.query[cur].trim())); } else if (Array.isArray(req.query[cur]) && req.query[cur].length) { - memo[cur] = req.query[cur]; + memo[cur] = req.query[cur].map(item => validator.escape(String(item).trim())); } } diff --git a/test/flags.js b/test/flags.js index ee150a10c4..aad4802e7a 100644 --- a/test/flags.js +++ b/test/flags.js @@ -928,6 +928,11 @@ describe('Flags', () => { assert.strictEqual(flagData.reports[0].value, '"<script>alert('ok');</script>'); }); + it('should escape filters', async () => { + const { body } = await request.get(`${nconf.get('url')}/api/flags?quick="`, { jar }); + assert.strictEqual(body.filters.quick, '"<script>alert('foo');</script>'); + }); + it('should not allow flagging post in private category', async () => { const category = await Categories.create({ name: 'private category' }); @@ -1185,5 +1190,7 @@ describe('Flags', () => { } }); }); + + }); }); From 16504bad8100aa25327dd8b8b26483df9e087b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 10:02:59 -0400 Subject: [PATCH 13/15] fix: sql injection in sortedSetScan --- src/database/postgres/sorted.js | 4 ++-- test/database/sorted.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/database/postgres/sorted.js b/src/database/postgres/sorted.js index 916425e0c1..9f59224497 100644 --- a/src/database/postgres/sorted.js +++ b/src/database/postgres/sorted.js @@ -707,9 +707,9 @@ SELECT z."value", ON o."_key" = z."_key" AND o."type" = z."type" WHERE o."_key" = $1::TEXT - AND z."value" LIKE '${match}' + AND z."value" LIKE $3 LIMIT $2::INTEGER`, - values: [params.key, params.limit], + values: [params.key, params.limit, match], }); if (!params.withScores) { return res.rows.map(r => r.value); diff --git a/test/database/sorted.js b/test/database/sorted.js index 33d3e4c4b5..b98d969730 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -78,6 +78,21 @@ describe('Sorted Set methods', () => { assert(data.includes('ddb')); assert(data.includes('adb')); }); + + it('should not error with invalid input', async () => { + const query = `-3217' +OR 1251=CAST((CHR(113)||CHR(98)||CHR(118)||CHR(98)||CHR(113))||(SELECT +(CASE WHEN (1251=1251) THEN 1 ELSE 0 +END))::text||(CHR(113)||CHR(113)||CHR(118)||CHR(98)||CHR(113)) AS +NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:read&states[]=watching&states[]=tracking&states[]=notwatching&showLinks=`; + const match = `*${query.toLowerCase()}*`; + const data = await db.getSortedSetScan({ + key: 'categories:name', + match: match, + limit: 500, + }); + assert.strictEqual(data.length, 0); + }); }); describe('sortedSetAdd()', () => { From dfa213298b7dce55845c18eee657ad4d32145ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 10:28:26 -0400 Subject: [PATCH 14/15] refactor: call verify if request is POST --- src/middleware/activitypub.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index 490715dbcb..b38caa552a 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -33,10 +33,12 @@ middleware.verify = async function (req, res, next) { return next(); } - const verified = await activitypub.verify(req); - if (!verified && req.method === 'POST') { - activitypub.helpers.log('[middleware/activitypub] HTTP signature verification failed.'); - return res.sendStatus(400); + if (req.method === 'POST') { + const verified = await activitypub.verify(req); + if (!verified) { + activitypub.helpers.log('[middleware/activitypub] HTTP signature verification failed.'); + return res.sendStatus(400); + } } // Set calling user From 00668bdc342f22b1ef263ca2d6003b12bbb194af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 12 May 2025 10:29:32 -0400 Subject: [PATCH 15/15] refactor: wrap ap routes in try/catch --- src/routes/activitypub.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js index b6a516b481..760287e102 100644 --- a/src/routes/activitypub.js +++ b/src/routes/activitypub.js @@ -25,28 +25,28 @@ module.exports = function (app, middleware, controllers) { middleware.activitypub.normalize, ]; - app.get('/actor', middlewares, controllers.activitypub.actors.application); - app.post('/inbox', [...middlewares, ...inboxMiddlewares], controllers.activitypub.postInbox); + app.get('/actor', middlewares, helpers.tryRoute(controllers.activitypub.actors.application)); + app.post('/inbox', [...middlewares, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub.postInbox)); - app.get('/uid/:uid', [...middlewares, middleware.assert.user], controllers.activitypub.actors.user); - app.get('/user/:userslug', [...middlewares, middleware.exposeUid, middleware.assert.user], controllers.activitypub.actors.userBySlug); - app.get('/uid/:uid/inbox', [...middlewares, middleware.assert.user], controllers.activitypub.getInbox); - app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, ...inboxMiddlewares], controllers.activitypub.postInbox); - app.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.getOutbox); - app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], controllers.activitypub.postOutbox); - app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowing); - app.get('/uid/:uid/followers', [...middlewares, middleware.assert.user], controllers.activitypub.getFollowers); + app.get('/uid/:uid', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.actors.user)); + app.get('/user/:userslug', [...middlewares, middleware.exposeUid, middleware.assert.user], helpers.tryRoute(controllers.activitypub.actors.userBySlug)); + app.get('/uid/:uid/inbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getInbox)); + app.post('/uid/:uid/inbox', [...middlewares, middleware.assert.user, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub.postInbox)); + app.get('/uid/:uid/outbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getOutbox)); + app.post('/uid/:uid/outbox', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.postOutbox)); + app.get('/uid/:uid/following', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getFollowing)); + app.get('/uid/:uid/followers', [...middlewares, middleware.assert.user], helpers.tryRoute(controllers.activitypub.getFollowers)); - app.get('/post/:pid', [...middlewares, middleware.assert.post], controllers.activitypub.actors.note); - app.get('/post/:pid/replies', [...middlewares, middleware.assert.post], controllers.activitypub.actors.replies); + app.get('/post/:pid', [...middlewares, middleware.assert.post], helpers.tryRoute(controllers.activitypub.actors.note)); + app.get('/post/:pid/replies', [...middlewares, middleware.assert.post], helpers.tryRoute(controllers.activitypub.actors.replies)); - app.get('/topic/:tid/:slug?', [...middlewares, middleware.assert.topic], controllers.activitypub.actors.topic); + app.get('/topic/:tid/:slug?', [...middlewares, middleware.assert.topic], helpers.tryRoute(controllers.activitypub.actors.topic)); - app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], controllers.activitypub.getInbox); - app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], controllers.activitypub.postInbox); - app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.getCategoryOutbox); - app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], controllers.activitypub.postOutbox); - app.get('/category/:cid/:slug?', [...middlewares, middleware.assert.category], controllers.activitypub.actors.category); + app.get('/category/:cid/inbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.getInbox)); + app.post('/category/:cid/inbox', [...inboxMiddlewares, middleware.assert.category, ...inboxMiddlewares], helpers.tryRoute(controllers.activitypub).postInbox); + app.get('/category/:cid/outbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.getCategoryOutbox)); + app.post('/category/:cid/outbox', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.postOutbox)); + app.get('/category/:cid/:slug?', [...middlewares, middleware.assert.category], helpers.tryRoute(controllers.activitypub.actors.category)); - app.get('/message/:mid', [...middlewares, middleware.assert.message], controllers.activitypub.actors.message); + app.get('/message/:mid', [...middlewares, middleware.assert.message], helpers.tryRoute(controllers.activitypub.actors.message)); };