From 0a0a7da9ba3c76cdcd22e6a2b4b0635e8da3eef2 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 29 Dec 2025 14:20:25 -0500 Subject: [PATCH] fix: bug where privileges users could not uncrosspost others' crossposts. Tests --- src/topics/crossposts.js | 5 +- test/topics/crossposts.js | 120 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/topics/crossposts.js b/src/topics/crossposts.js index 20ed086d71..ce2bcda93a 100644 --- a/src/topics/crossposts.js +++ b/src/topics/crossposts.js @@ -2,6 +2,7 @@ const db = require('../database'); const topics = require('.'); +const user = require('../user'); const categories = require('../categories'); const posts = require('../posts'); const activitypub = require('../activitypub'); @@ -87,8 +88,10 @@ Crossposts.add = async function (tid, cid, uid) { Crossposts.remove = async function (tid, cid, uid) { let crossposts = await Crossposts.get(tid); + const isPrivileged = await user.isAdminOrGlobalMod(uid); + const isMod = await user.isModerator(uid, cid); const crosspostId = crossposts.reduce((id, { id: _id, cid: _cid, uid: _uid }) => { - if (String(cid) === String(_cid) && String(uid) === String(_uid)) { + if (String(cid) === String(_cid) && (isPrivileged || isMod || String(uid) === String(_uid))) { id = _id; } diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 878dc864d2..a018973a50 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -8,9 +8,11 @@ const db = require('../mocks/databasemock'); const meta = require('../../src/meta'); const install = require('../../src/install'); const user = require('../../src/user'); +const groups = require('../../src/groups'); const categories = require('../../src/categories'); const topics = require('../../src/topics'); const posts = require('../../src/posts'); +const privileges = require('../../src/privileges'); const activitypub = require('../../src/activitypub'); const utils = require('../../src/utils'); @@ -142,6 +144,14 @@ describe('Crossposting (& related logic)', () => { await topics.crossposts.add(tid, cid2, uid); }); + it('should not let another user uncrosspost', async () => { + const uid2 = await user.create({ username: utils.generateUUID().slice(0, 8) }); + assert.rejects( + topics.crossposts.remove(tid, cid2, uid2), + '[[error:invalid-data]]', + ); + }); + it('should successfully uncrosspost from a cid', async () => { const crossposts = await topics.crossposts.remove(tid, cid2, uid); @@ -168,6 +178,116 @@ describe('Crossposting (& related logic)', () => { }); }); + describe('uncrosspost (as administrator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + await groups.join('administrators', privUid); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should successfully uncrosspost from a cid', async () => { + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + + describe('uncrosspost (as global moderator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + await groups.join('Global Moderators', privUid); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should successfully uncrosspost from a cid', async () => { + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + + describe('uncrosspost (as category moderator)', () => { + let tid; + let cid1; + let cid2; + let uid; + let privUid; + + before(async () => { + ({ cid: cid1 } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + const crosspostCategory = await categories.create({ name: utils.generateUUID().slice(0, 8) }); + cid2 = crosspostCategory.cid; + uid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + privUid = await user.create({ username: utils.generateUUID().slice(0, 8) }); + + const { topicData } = await topics.post({ + uid, + cid: cid1, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + tid = topicData.tid; + + await topics.crossposts.add(tid, cid2, uid); + }); + + it('should fail to uncrosspost if not mod of passed-in category', async () => { + await privileges.categories.give(['moderate'], cid1, [privUid]); + assert.rejects( + topics.crossposts.remove(tid, cid2, privUid), + '[[error:invalid-data]]', + ); + }); + + it('should successfully uncrosspost from a cid if proper mod', async () => { + await privileges.categories.give(['moderate'], cid2, [privUid]); + const crossposts = await topics.crossposts.remove(tid, cid2, privUid); + + assert(Array.isArray(crossposts)); + assert.strictEqual(crossposts.length, 0); + }); + }); + describe('ActivityPub effects (or lack thereof)', () => { describe('local canonical category', () => { let tid;