mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-18 21:42:55 +01:00
Merge commit '524a1e8bfe403fa240e804f076943871264caf2f' into v4.x
This commit is contained in:
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,3 +1,47 @@
|
||||
#### v4.4.1 (2025-05-16)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up themes (61a63851)
|
||||
* incrementing version number - v4.4.0 (0a75eee3)
|
||||
* update changelog for v4.4.0 (09cc91d5)
|
||||
* 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
|
||||
|
||||
* save width and height values into post attachment (3674fa57)
|
||||
* use local date string for digest subject (3d96afb2)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* openapi schema to handle additional `attachments` field in postsobject (ce5ef1ab)
|
||||
* group edit url (0a574d72)
|
||||
* add attachments to getpostsummaries call in search, #13324 (8f9f3771)
|
||||
* bring back auto-categorization if group and object are same-origin, handle Peertube putting channel names in `attributedTo` (a460a550)
|
||||
* #13419, handle remote content with mediaType text/markdown (45a11d45)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* create date once per digest.send (6c3e2a8e)
|
||||
|
||||
##### Tests
|
||||
|
||||
* fix tests to account for a460a55064e1280f36a0021e0510c7c557251030 (948bfe46)
|
||||
|
||||
#### v4.4.0 (2025-05-14)
|
||||
|
||||
##### Breaking Changes
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"nconf": "0.13.0",
|
||||
"nodebb-plugin-2factor": "7.5.10",
|
||||
"nodebb-plugin-composer-default": "10.2.50",
|
||||
"nodebb-plugin-dbsearch": "6.2.16",
|
||||
"nodebb-plugin-dbsearch": "6.2.19",
|
||||
"nodebb-plugin-emoji": "6.0.2",
|
||||
"nodebb-plugin-emoji-android": "4.1.1",
|
||||
"nodebb-plugin-markdown": "13.2.1",
|
||||
@@ -107,7 +107,7 @@
|
||||
"nodebb-plugin-spam-be-gone": "2.3.2",
|
||||
"nodebb-plugin-web-push": "0.7.4",
|
||||
"nodebb-rewards-essentials": "1.0.2",
|
||||
"nodebb-theme-harmony": "2.1.13",
|
||||
"nodebb-theme-harmony": "2.1.15",
|
||||
"nodebb-theme-lavender": "7.1.19",
|
||||
"nodebb-theme-peace": "2.2.43",
|
||||
"nodebb-theme-persona": "14.1.12",
|
||||
@@ -162,8 +162,8 @@
|
||||
"@commitlint/config-angular": "19.8.1",
|
||||
"coveralls": "3.1.1",
|
||||
"@eslint/js": "9.26.0",
|
||||
"@stylistic/eslint-plugin-js": "4.2.0",
|
||||
"eslint-config-nodebb": "1.1.4",
|
||||
"@stylistic/eslint-plugin-js": "4.4.0",
|
||||
"eslint-config-nodebb": "1.1.5",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"grunt": "1.6.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
|
||||
@@ -271,6 +271,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",
|
||||
"cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
|
||||
"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",
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.dropdown-left {
|
||||
.dropdown-menu { --bs-position: start; }
|
||||
html[data-dir="ltr"] {
|
||||
.dropdown-left .dropdown-menu { --bs-position: start; }
|
||||
.dropdown-right .dropdown-menu { --bs-position: end; }
|
||||
}
|
||||
.dropdown-right {
|
||||
.dropdown-menu { --bs-position: end; }
|
||||
html[data-dir="rtl"] {
|
||||
.dropdown-left .dropdown-menu { --bs-position: end; }
|
||||
.dropdown-right .dropdown-menu { --bs-position: start; }
|
||||
}
|
||||
|
||||
.category-dropdown-menu {
|
||||
|
||||
@@ -225,7 +225,7 @@ define('forum/topic/threadTools', [
|
||||
return;
|
||||
}
|
||||
dropdownMenu.html(helpers.generatePlaceholderWave([8, 8, 8]));
|
||||
const data = await socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid });
|
||||
const data = await socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid }).catch(alerts.error);
|
||||
const html = await app.parseAndTranslate('partials/topic/topic-menu-list', data);
|
||||
$(dropdownMenu).attr('data-loaded', 'true').html(html);
|
||||
hooks.fire('action:topic.tools.load', {
|
||||
|
||||
@@ -95,6 +95,12 @@ inbox.update = async (req) => {
|
||||
try {
|
||||
switch (true) {
|
||||
case isNote: {
|
||||
const cid = await posts.getCidByPid(object.id);
|
||||
const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid);
|
||||
if (!allowed) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
const postData = await activitypub.mocks.post(object);
|
||||
postData.tags = await activitypub.notes._normalizeTags(postData._activitypub.tag, postData.cid);
|
||||
await posts.edit(postData);
|
||||
@@ -200,7 +206,7 @@ inbox.delete = async (req) => {
|
||||
|
||||
const objectHostname = new URL(pid).hostname;
|
||||
if (actorHostname !== objectHostname) {
|
||||
throw new Error('[[error:activitypub.origin-mismatch]]');
|
||||
return reject('Delete', object, actor);
|
||||
}
|
||||
|
||||
const [isNote/* , isActor */] = await Promise.all([
|
||||
@@ -210,6 +216,12 @@ inbox.delete = async (req) => {
|
||||
|
||||
switch (true) {
|
||||
case isNote: {
|
||||
const cid = await posts.getCidByPid(pid);
|
||||
const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid);
|
||||
if (!allowed) {
|
||||
return reject('Delete', object, actor);
|
||||
}
|
||||
|
||||
const uid = await posts.getPostField(pid, 'uid');
|
||||
await activitypub.feps.announce(pid, req.body);
|
||||
await api.posts[method]({ uid }, { pid });
|
||||
@@ -282,9 +294,13 @@ inbox.announce = async (req) => {
|
||||
const { id: localId } = await activitypub.helpers.resolveLocalId(id);
|
||||
const exists = await posts.exists(localId || id);
|
||||
if (exists) {
|
||||
const result = await posts.upvote(localId || id, object.actor);
|
||||
if (localId) {
|
||||
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
|
||||
try {
|
||||
const result = await posts.upvote(localId || id, object.actor);
|
||||
if (localId) {
|
||||
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
|
||||
}
|
||||
} catch (e) {
|
||||
// vote denied due to local limitations (frequency, privilege, etc.); noop.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +546,8 @@ inbox.undo = async (req) => {
|
||||
case 'Like': {
|
||||
const exists = await posts.exists(id);
|
||||
if (localType !== 'post' || !exists) {
|
||||
throw new Error('[[error:invalid-pid]]');
|
||||
reject('Like', object, actor);
|
||||
break;
|
||||
}
|
||||
|
||||
const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid);
|
||||
|
||||
@@ -27,6 +27,9 @@ const probeCache = ttl({
|
||||
max: 500,
|
||||
ttl: 1000 * 60 * 60, // 1 hour
|
||||
});
|
||||
const probeRateLimit = ttl({
|
||||
ttl: 1000 * 3, // 3 seconds
|
||||
});
|
||||
|
||||
const ActivityPub = module.exports;
|
||||
|
||||
@@ -506,6 +509,13 @@ ActivityPub.probe = async ({ uid, url }) => {
|
||||
* - Returns a relative path if already available, true if not, and false otherwise.
|
||||
*/
|
||||
|
||||
// Disable on config setting; restrict lookups to HTTPS-enabled URLs only
|
||||
const { activitypubProbe } = meta.config;
|
||||
const { protocol } = new URL(url);
|
||||
if (!activitypubProbe || protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Known resources
|
||||
const [isNote, isMessage, isActor, isActorUrl] = await Promise.all([
|
||||
posts.exists(url),
|
||||
@@ -541,6 +551,17 @@ ActivityPub.probe = async ({ uid, url }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Guests not allowed to use expensive logic path
|
||||
if (!uid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// One request allowed every 3 seconds (configured at top)
|
||||
const limited = probeRateLimit.get(uid);
|
||||
if (limited) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cached result
|
||||
if (probeCache.has(url)) {
|
||||
return probeCache.get(url);
|
||||
@@ -572,6 +593,7 @@ ActivityPub.probe = async ({ uid, url }) => {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
probeRateLimit.set(uid, true);
|
||||
return await checkHeader(meta.config.activitypubProbeTimeout || 2000);
|
||||
} catch (e) {
|
||||
if (e.name === 'TimeoutError') {
|
||||
|
||||
@@ -196,8 +196,8 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
const { to, cc, attachment } = mainPost._activitypub;
|
||||
const tags = await Notes._normalizeTags(mainPost._activitypub.tag || []);
|
||||
|
||||
await Promise.all([
|
||||
topics.post({
|
||||
try {
|
||||
await topics.post({
|
||||
tid,
|
||||
uid: authorId,
|
||||
cid: options.cid || cid,
|
||||
@@ -208,13 +208,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
content: mainPost.content,
|
||||
sourceContent: mainPost.sourceContent,
|
||||
_activitypub: mainPost._activitypub,
|
||||
}),
|
||||
Notes.updateLocalRecipients(mainPid, { to, cc }),
|
||||
]);
|
||||
unprocessed.shift();
|
||||
});
|
||||
unprocessed.shift();
|
||||
} catch (e) {
|
||||
activitypub.helpers.log(`[activitypub/notes.assert] Could not post topic (${mainPost.pid}): ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// These must come after topic is posted
|
||||
await Promise.all([
|
||||
Notes.updateLocalRecipients(mainPid, { to, cc }),
|
||||
mainPost._activitypub.image ? topics.thumbs.associate({
|
||||
id: tid,
|
||||
path: mainPost._activitypub.image,
|
||||
|
||||
@@ -119,6 +119,7 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => {
|
||||
await activitypub.send(type, id, [actor], {
|
||||
id: `${nconf.get('url')}/${type}/${id}#activity/undo:follow/${encodeURIComponent(actor)}/${timestamp}`,
|
||||
type: 'Undo',
|
||||
actor: object.actor,
|
||||
object,
|
||||
});
|
||||
|
||||
@@ -126,6 +127,7 @@ activitypubApi.unfollow = enabledCheck(async (caller, { type, id, actor }) => {
|
||||
await Promise.all([
|
||||
db.sortedSetRemove(`followingRemote:${id}`, actor),
|
||||
db.sortedSetRemove(`followRequests:uid.${id}`, actor),
|
||||
db.sortedSetRemove(`followersRemote:${actor}`, id),
|
||||
db.decrObjectField(`user:${id}`, 'followingRemoteCount'),
|
||||
]);
|
||||
} else if (type === 'cid') {
|
||||
|
||||
@@ -31,7 +31,7 @@ Controller.fetch = async (req, res, next) => {
|
||||
if (typeof result === 'string') {
|
||||
return helpers.redirect(res, result);
|
||||
} else if (result) {
|
||||
const { id, type } = await activitypub.get('uid', req.uid || 0, url.href);
|
||||
const { id, type } = await activitypub.get('uid', req.uid, url.href);
|
||||
switch (true) {
|
||||
case activitypub._constants.acceptedPostTypes.includes(type): {
|
||||
return helpers.redirect(res, `/post/${encodeURIComponent(id)}`);
|
||||
@@ -145,7 +145,7 @@ Controller.postInbox = async (req, res) => {
|
||||
const method = String(req.body.type).toLowerCase();
|
||||
if (!activitypub.inbox.hasOwnProperty(method)) {
|
||||
winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
|
||||
return res.sendStatus(501);
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -150,6 +150,7 @@ module.exports = function (middleware) {
|
||||
async function loadClientHeaderFooterData(req, res, options) {
|
||||
const registrationType = meta.config.registrationType || 'normal';
|
||||
res.locals.config = res.locals.config || {};
|
||||
const userLang = res.locals.config.userLang || meta.config.userLang || 'en-GB';
|
||||
const templateValues = {
|
||||
title: meta.config.title || '',
|
||||
'title:url': meta.config['title:url'] || '',
|
||||
@@ -180,9 +181,9 @@ module.exports = function (middleware) {
|
||||
blocks: user.blocks.list(req.uid),
|
||||
user: user.getUserData(req.uid),
|
||||
isEmailConfirmSent: req.uid <= 0 ? false : await user.email.isValidationPending(req.uid),
|
||||
languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang),
|
||||
timeagoCode: languages.userTimeagoCode(res.locals.config.userLang),
|
||||
browserTitle: translator.translate(controllersHelpers.buildTitle(title)),
|
||||
languageDirection: translator.translate('[[language:dir]]', userLang),
|
||||
timeagoCode: languages.userTimeagoCode(userLang),
|
||||
browserTitle: translator.translate(controllersHelpers.buildTitle(title), userLang),
|
||||
navigation: navigation.get(req.uid),
|
||||
roomIds: req.uid > 0 ? db.getSortedSetRevRange(`uid:${req.uid}:chat:rooms`, 0, 0) : [],
|
||||
});
|
||||
|
||||
@@ -156,6 +156,16 @@ module.exports = function (Plugins) {
|
||||
}
|
||||
};
|
||||
|
||||
Plugins.isSystemPlugin = async function (id) {
|
||||
const pluginDir = path.join(paths.nodeModules, id, 'plugin.json');
|
||||
try {
|
||||
const pluginData = JSON.parse(await fs.readFile(pluginDir, 'utf8'));
|
||||
return pluginData && pluginData.system === true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Plugins.isActive = async function (id) {
|
||||
if (nconf.get('plugins:active')) {
|
||||
return nconf.get('plugins:active').includes(id);
|
||||
|
||||
@@ -188,13 +188,16 @@ module.exports = function (Posts) {
|
||||
data: data,
|
||||
};
|
||||
payload = await plugins.hooks.fire('filter:post-queue.save', payload);
|
||||
payload.data = JSON.stringify(data);
|
||||
|
||||
await db.sortedSetAdd('post:queue', now, id);
|
||||
await db.setObject(`post:queue:${id}`, payload);
|
||||
await db.setObject(`post:queue:${id}`, {
|
||||
...payload,
|
||||
data: JSON.stringify(payload.data),
|
||||
});
|
||||
await user.setUserField(data.uid, 'lastqueuetime', now);
|
||||
cache.del('post-queue');
|
||||
|
||||
await plugins.hooks.fire('action:post-queue.save', payload);
|
||||
const cid = await getCid(type, data);
|
||||
const uids = await getNotificationUids(cid);
|
||||
const bodyLong = await parseBodyLong(cid, type, data);
|
||||
|
||||
@@ -11,6 +11,9 @@ const { pluginNamePattern } = require('../../constants');
|
||||
const Plugins = module.exports;
|
||||
|
||||
Plugins.toggleActive = async function (socket, plugin_id) {
|
||||
if (await plugins.isSystemPlugin(plugin_id)) {
|
||||
throw new Error('[[error:cannot-toggle-system-plugin]]');
|
||||
}
|
||||
postsCache.reset();
|
||||
const data = await plugins.toggleActive(plugin_id);
|
||||
await events.log({
|
||||
@@ -22,6 +25,9 @@ Plugins.toggleActive = async function (socket, plugin_id) {
|
||||
};
|
||||
|
||||
Plugins.toggleInstall = async function (socket, data) {
|
||||
if (await plugins.isSystemPlugin(data.id)) {
|
||||
throw new Error('[[error:cannot-toggle-system-plugin]]');
|
||||
}
|
||||
const isInstalled = await plugins.isInstalled(data.id);
|
||||
const isStarterPlan = nconf.get('saas_plan') === 'starter';
|
||||
if ((isStarterPlan || nconf.get('acpPluginInstallDisabled')) && !isInstalled) {
|
||||
|
||||
@@ -6,9 +6,6 @@ const plugins = require('../../plugins');
|
||||
|
||||
module.exports = function (SocketTopics) {
|
||||
SocketTopics.loadTopicTools = async function (socket, data) {
|
||||
if (!socket.uid) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
if (!data) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
@@ -21,7 +18,7 @@ module.exports = function (SocketTopics) {
|
||||
if (!topicData) {
|
||||
throw new Error('[[error:no-topic]]');
|
||||
}
|
||||
if (!userPrivileges['topics:read']) {
|
||||
if (!userPrivileges['topics:read'] || !userPrivileges.view_thread_tools) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
topicData.privileges = userPrivileges;
|
||||
|
||||
@@ -202,3 +202,26 @@ Helpers.mocks.update = (override = {}) => {
|
||||
return { activity };
|
||||
};
|
||||
|
||||
Helpers.mocks.delete = (override = {}) => {
|
||||
let actor = override.actor;
|
||||
let object = override.object;
|
||||
if (!actor) {
|
||||
({ id: actor } = Helpers.mocks.person());
|
||||
}
|
||||
if (!object) {
|
||||
({ id: object } = Helpers.mocks.note());
|
||||
}
|
||||
|
||||
const activity = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: `${Helpers.mocks._baseUrl}/delete/${encodeURIComponent(object.id || object)}`,
|
||||
type: 'Delete',
|
||||
to: [activitypub._constants.publicAddress],
|
||||
cc: [`${actor}/followers`],
|
||||
actor,
|
||||
object,
|
||||
};
|
||||
|
||||
return { activity };
|
||||
};
|
||||
|
||||
|
||||
206
test/activitypub/privileges.js
Normal file
206
test/activitypub/privileges.js
Normal file
@@ -0,0 +1,206 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const nconf = require('nconf');
|
||||
|
||||
const db = require('../mocks/databasemock');
|
||||
const request = require('../../src/request');
|
||||
const user = require('../../src/user');
|
||||
const topics = require('../../src/topics');
|
||||
const posts = require('../../src/posts');
|
||||
const categories = require('../../src/categories');
|
||||
const privileges = require('../../src/privileges');
|
||||
const meta = require('../../src/meta');
|
||||
const install = require('../../src/install');
|
||||
const utils = require('../../src/utils');
|
||||
const activitypub = require('../../src/activitypub');
|
||||
|
||||
const helpers = require('./helpers');
|
||||
|
||||
describe('Privilege logic for remote users/content (ActivityPub)', () => {
|
||||
before(async () => {
|
||||
meta.config.activitypubEnabled = 1;
|
||||
// await install.giveWorldPrivileges();
|
||||
});
|
||||
|
||||
describe('"fediverse" pseudo-user', () => {
|
||||
describe('no privileges given', () => {
|
||||
let uid;
|
||||
let cid;
|
||||
let topicData;
|
||||
let postData;
|
||||
let mainPid;
|
||||
let handle;
|
||||
|
||||
before(async () => {
|
||||
uid = await user.create({ username: utils.generateUUID() });
|
||||
({ cid } = await categories.create({ name: utils.generateUUID() }));
|
||||
({ topicData, postData } = await topics.post({
|
||||
cid,
|
||||
uid,
|
||||
title: utils.generateUUID(),
|
||||
content: utils.generateUUID(),
|
||||
}));
|
||||
handle = await categories.getCategoryField(cid, 'handle');
|
||||
const privsToRemove = await privileges.categories.getGroupPrivilegeList();
|
||||
await privileges.categories.rescind(privsToRemove, cid, ['fediverse']);
|
||||
});
|
||||
|
||||
describe('incoming requests', () => {
|
||||
it('should not respond to a webfinger request to a category\'s handle', async () => {
|
||||
const response = await activitypub.helpers.query(`${handle}@${nconf.get('url_parsed').hostname}`);
|
||||
assert.strictEqual(response, false);
|
||||
});
|
||||
|
||||
it('should not respond to a request for the category actor', async () => {
|
||||
await assert.rejects(
|
||||
activitypub.get('uid', uid, `${nconf.get('url')}/category/${cid}`),
|
||||
{ message: '[[error:activitypub.get-failed]]' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should not respond to a request for a topic collection', async () => {
|
||||
await assert.rejects(
|
||||
activitypub.get('uid', uid, `${nconf.get('url')}/topic/${topicData.tid}`),
|
||||
{ message: '[[error:activitypub.get-failed]]' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should not respond to a request for a post', async () => {
|
||||
await assert.rejects(
|
||||
activitypub.get('uid', uid, `${nconf.get('url')}/post/${topicData.mainPid}`),
|
||||
{ message: '[[error:activitypub.get-failed]]' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incoming activities', () => {
|
||||
describe('Create(Note)', () => {
|
||||
let note;
|
||||
let activity;
|
||||
|
||||
before(async () => {
|
||||
({ note } = helpers.mocks.note({
|
||||
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||
}));
|
||||
({ activity } = helpers.mocks.create(note));
|
||||
await activitypub.inbox.create({ body: activity });
|
||||
});
|
||||
|
||||
it('should not assert the note', async () => {
|
||||
const exists = await posts.exists(note.id);
|
||||
assert.strictEqual(exists, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update(Note)', () => {
|
||||
let note;
|
||||
let activity;
|
||||
|
||||
before(async () => {
|
||||
({ note } = helpers.mocks.note({
|
||||
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||
}));
|
||||
({ activity } = helpers.mocks.create(note));
|
||||
await privileges.categories.give(['groups:topics:create'], cid, ['fediverse']);
|
||||
await activitypub.inbox.create({ body: activity });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await privileges.categories.rescind(['groups:topics:create'], cid, ['fediverse']);
|
||||
});
|
||||
|
||||
it('should assert the note', async () => {
|
||||
const exists = await posts.exists(note.id);
|
||||
assert.strictEqual(exists, true);
|
||||
});
|
||||
|
||||
it('should not allow edits to the note', async () => {
|
||||
const oldContent = note.content;
|
||||
note.content = 'new content';
|
||||
({ activity } = helpers.mocks.update({
|
||||
object: note,
|
||||
}));
|
||||
|
||||
await activitypub.inbox.update({ body: activity });
|
||||
|
||||
const postData = await posts.getPostData(note.id);
|
||||
assert.strictEqual(postData.content, oldContent);
|
||||
assert.strictEqual(postData.edited, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete(Note)', () => {
|
||||
let note;
|
||||
let activity;
|
||||
|
||||
before(async () => {
|
||||
({ note } = helpers.mocks.note({
|
||||
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||
}));
|
||||
({ activity } = helpers.mocks.create(note));
|
||||
await privileges.categories.give(['groups:topics:create'], cid, ['fediverse']);
|
||||
await activitypub.inbox.create({ body: activity });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await privileges.categories.rescind(['groups:topics:create'], cid, ['fediverse']);
|
||||
});
|
||||
|
||||
it('should assert the note', async () => {
|
||||
const exists = await posts.exists(note.id);
|
||||
assert.strictEqual(exists, true);
|
||||
});
|
||||
|
||||
it('should ignore remote deletion of said note', async () => {
|
||||
({ activity } = helpers.mocks.delete({ object: note }));
|
||||
await activitypub.inbox.delete({ body: activity });
|
||||
|
||||
const exists = await posts.exists(note.id);
|
||||
assert.strictEqual(exists, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('outgoing requests', () => {
|
||||
it('should not federate out a new post', async () => {
|
||||
|
||||
});
|
||||
|
||||
it('should not federate out a post edit', async () => {
|
||||
|
||||
});
|
||||
|
||||
it('should not federate out a post deletion', async () => {
|
||||
|
||||
});
|
||||
|
||||
it('should not federate out a post announce', async () => {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('regular privilege set', () => {
|
||||
let cid;
|
||||
let handle;
|
||||
|
||||
before(async () => {
|
||||
({ cid } = await categories.create({ name: utils.generateUUID() }));
|
||||
handle = await categories.getCategoryField(cid, 'handle');
|
||||
const privsToRemove = await privileges.categories.getGroupPrivilegeList();
|
||||
});
|
||||
|
||||
describe('groups:find', () => {
|
||||
it('should return webfinger response to a category\'s handle', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct:${handle}@${nconf.get('url_parsed').host}`);
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.links && body.links.length);
|
||||
assert.strictEqual(body.subject, `acct:${handle}@${nconf.get('url_parsed').host}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user