mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-13 00:21:51 +02:00
Merge branch 'master' into develop
This commit is contained in:
45
CHANGELOG.md
45
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
|
||||
|
||||
@@ -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",
|
||||
@@ -99,7 +99,7 @@
|
||||
"multiparty": "4.2.3",
|
||||
"nconf": "0.12.1",
|
||||
"nodebb-plugin-2factor": "7.5.9",
|
||||
"nodebb-plugin-composer-default": "10.2.45",
|
||||
"nodebb-plugin-composer-default": "10.2.46",
|
||||
"nodebb-plugin-dbsearch": "6.2.12",
|
||||
"nodebb-plugin-emoji": "6.0.2",
|
||||
"nodebb-plugin-emoji-android": "4.1.1",
|
||||
|
||||
@@ -64,6 +64,11 @@ Helpers.isUri = (value) => {
|
||||
});
|
||||
};
|
||||
|
||||
Helpers.assertAccept = accept => (accept && accept.split(',').some((value) => {
|
||||
const parts = value.split(';').map(v => v.trim());
|
||||
return activitypub._constants.acceptableTypes.includes(value || parts[0]);
|
||||
}));
|
||||
|
||||
Helpers.isWebfinger = (value) => {
|
||||
// N.B. returns normalized handle, so truthy check!
|
||||
if (webfingerRegex.test(value) && !Helpers.isUri(value)) {
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -342,23 +344,24 @@ Mocks.actors.category = async (cid) => {
|
||||
} = await categories.getCategoryData(cid);
|
||||
const publicKey = await activitypub.getPublicKey('cid', cid);
|
||||
|
||||
let image;
|
||||
let icon;
|
||||
if (backgroundImage) {
|
||||
const filename = path.basename(utils.decodeHTMLEntities(backgroundImage));
|
||||
image = {
|
||||
icon = {
|
||||
type: 'Image',
|
||||
mediaType: mime.getType(filename),
|
||||
url: `${nconf.get('url')}${utils.decodeHTMLEntities(backgroundImage)}`,
|
||||
};
|
||||
} else {
|
||||
icon = await categories.icons.get(cid);
|
||||
icon = icon.get('png');
|
||||
icon = {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}${icon}`,
|
||||
};
|
||||
}
|
||||
|
||||
let icon = await categories.icons.get(cid);
|
||||
icon = icon.get('png');
|
||||
icon = {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}${icon}`,
|
||||
};
|
||||
|
||||
return {
|
||||
'@context': [
|
||||
@@ -376,7 +379,7 @@ Mocks.actors.category = async (cid) => {
|
||||
name,
|
||||
preferredUsername,
|
||||
summary,
|
||||
image,
|
||||
// image, // todo once categories have cover photos
|
||||
icon,
|
||||
|
||||
publicKey: {
|
||||
|
||||
@@ -6,6 +6,7 @@ const validator = require('validator');
|
||||
|
||||
const meta = require('../meta');
|
||||
const plugins = require('../plugins');
|
||||
const activitypub = require('../activitypub');
|
||||
const middleware = require('../middleware');
|
||||
const helpers = require('../middleware/helpers');
|
||||
const { secureRandom } = require('../utils');
|
||||
@@ -24,6 +25,12 @@ exports.handle404 = helpers.try(async (req, res) => {
|
||||
|
||||
if (isClientScript.test(req.url)) {
|
||||
res.type('text/javascript').status(404).send('Not Found');
|
||||
} else if (
|
||||
activitypub.helpers.assertAccept(req.headers.accept) ||
|
||||
(req.headers['Content-Type'] && activitypub._constants.acceptableTypes.includes(req.headers['Content-Type']))
|
||||
) {
|
||||
// todo: separate logging of AP 404s
|
||||
res.sendStatus(404);
|
||||
} else if (
|
||||
!res.locals.isAPI && (
|
||||
req.path.startsWith(`${relativePath}/assets/uploads`) ||
|
||||
|
||||
@@ -16,10 +16,8 @@ middleware.assertS2S = async function (req, res, next) {
|
||||
return next('route');
|
||||
}
|
||||
|
||||
const pass = (accept && accept.split(',').some((value) => {
|
||||
const parts = value.split(';').map(v => v.trim());
|
||||
return activitypub._constants.acceptableTypes.includes(value || parts[0]);
|
||||
})) || (contentType && activitypub._constants.acceptableTypes.includes(contentType));
|
||||
const pass = activitypub.helpers.assertAccept(accept) ||
|
||||
(contentType && activitypub._constants.acceptableTypes.includes(contentType));
|
||||
|
||||
if (!pass) {
|
||||
return next('route');
|
||||
|
||||
@@ -350,6 +350,79 @@ describe('ActivityPub integration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('Category Actor endpoint', () => {
|
||||
let cid;
|
||||
let slug;
|
||||
let description;
|
||||
|
||||
beforeEach(async () => {
|
||||
slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
description = utils.generateUUID();
|
||||
({ cid } = await categories.create({
|
||||
name: slug,
|
||||
description,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', /* 'followers', 'following', */ 'inbox', 'outbox'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, `${nconf.get('url')}/category/${cid}`);
|
||||
assert.strictEqual(body.type, 'Group');
|
||||
assert.strictEqual(body.summary, description);
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/category/category-${cid}-icon.png`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
|
||||
it('should serve the the backgroundImage in `icon` if set', async () => {
|
||||
const payload = {};
|
||||
payload[cid] = {
|
||||
backgroundImage: `/assets/uploads/files/test.png`,
|
||||
};
|
||||
await categories.update(payload);
|
||||
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/files/test.png`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Instance Actor endpoint', () => {
|
||||
let response;
|
||||
let body;
|
||||
|
||||
Reference in New Issue
Block a user