From 71b3b5a60c487a8a24886364694929b33dda9846 Mon Sep 17 00:00:00 2001 From: Norm Date: Thu, 17 Nov 2022 21:24:38 +0000 Subject: [PATCH] backend: implement not forwarding block activities (#212) Fixes https://akkoma.dev/FoundKeyGang/FoundKey/issues/211 Commits pulled from https://github.com/misskey-dev/misskey/pull/7799 Changelog: Added Co-authored-by: FloatingGhost Co-authored-by: Johann150 Co-authored-by: Francis Dinh Reviewed-on: https://akkoma.dev/FoundKeyGang/FoundKey/pulls/212 --- locales/en-US.yml | 2 + locales/ja-JP.yml | 2 + .../1631880003000-user-block-federation.js | 12 ++ packages/backend/package.json | 2 + packages/backend/src/models/entities/user.ts | 5 + .../backend/src/models/repositories/user.ts | 1 + .../src/server/api/endpoints/i/update.ts | 2 + .../backend/src/services/blocking/create.ts | 16 +-- packages/backend/test/services/blocking.ts | 58 ++++++++ .../client/src/pages/settings/privacy.vue | 8 +- yarn.lock | 124 ++++++++++++++++-- 11 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 packages/backend/migration/1631880003000-user-block-federation.js create mode 100644 packages/backend/test/services/blocking.ts diff --git a/locales/en-US.yml b/locales/en-US.yml index 973b9ab72..c8cca2f4f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -799,6 +799,8 @@ onlineStatus: "Online status" hideOnlineStatus: "Hide online status" hideOnlineStatusDescription: "Hiding your online status reduces the convenience of\ \ some features such as the search." +federateBlocks: "Federate blocks" +federateBlocksDescription: "If disabled, block activities won't be sent." online: "Online" active: "Active" offline: "Offline" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6e821d051..22d0eebbd 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -740,6 +740,8 @@ unknown: "不明" onlineStatus: "オンライン状態" hideOnlineStatus: "オンライン状態を隠す" hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。" +federateBlocks: "ブロックを連合に送信" +federateBlocksDescription: "オフにするとBlockのActivityは連合に送信しません" online: "オンライン" active: "アクティブ" offline: "オフライン" diff --git a/packages/backend/migration/1631880003000-user-block-federation.js b/packages/backend/migration/1631880003000-user-block-federation.js new file mode 100644 index 000000000..1a90d5ae4 --- /dev/null +++ b/packages/backend/migration/1631880003000-user-block-federation.js @@ -0,0 +1,12 @@ +export class userBlockFederation1631880003000 { + name = 'userBlockFederation1631880003000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "federateBlocks" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "federateBlocks"`); + } + +} diff --git a/packages/backend/package.json b/packages/backend/package.json index b79c6a2cf..863aa4ac9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -158,6 +158,7 @@ "@types/sanitize-html": "2.6.2", "@types/semver": "7.3.12", "@types/sharp": "0.30.5", + "@types/sinon": "^10.0.13", "@types/sinonjs__fake-timers": "8.1.2", "@types/speakeasy": "2.0.7", "@types/tinycolor2": "1.4.3", @@ -173,6 +174,7 @@ "eslint-plugin-import": "^2.26.0", "execa": "6.1.0", "form-data": "^4.0.0", + "sinon": "^14.0.2", "typescript": "^4.8.3" } } diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index df92fb825..dd2598322 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -218,6 +218,11 @@ export class User { }) public token: string | null; + @Column('boolean', { + default: true, + }) + public federateBlocks: boolean; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index ee842a4b5..a36a575fe 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -393,6 +393,7 @@ export const UserRepository = db.getRepository(User).extend({ mutingNotificationTypes: profile!.mutingNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes, showTimelineReplies: user.showTimelineReplies || falsy, + federateBlocks: user!.federateBlocks, } : {}), ...(opts.includeSecrets ? { diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index ce613b190..57c349803 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -81,6 +81,7 @@ export const paramDef = { emailNotificationTypes: { type: 'array', items: { type: 'string', } }, + federateBlocks: { type: 'boolean' }, }, } as const; @@ -129,6 +130,7 @@ export default define(meta, paramDef, async (ps, _user, token) => { if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; + if (typeof ps.federateBlocks === 'boolean') updates.federateBlocks = ps.federateBlocks; if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 8665f5d6f..1550e3022 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -12,7 +12,7 @@ import { perUserFollowingChart } from '@/services/chart/index.js'; import { genId } from '@/misc/gen-id.js'; import { getActiveWebhooks } from '@/misc/webhook-cache.js'; -export default async function(blocker: User, blockee: User) { +export default async function(blocker: User, blockee: User): Promise { await Promise.all([ cancelRequest(blocker, blockee), cancelRequest(blockee, blocker), @@ -32,13 +32,13 @@ export default async function(blocker: User, blockee: User) { await Blockings.insert(blocking); - if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { + if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee) && blocker.federateBlocks) { const content = renderActivity(renderBlock(blocking)); deliver(blocker, content, blockee.inbox); } } -async function cancelRequest(follower: User, followee: User) { +async function cancelRequest(follower: User, followee: User): Promise { const request = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, @@ -75,20 +75,20 @@ async function cancelRequest(follower: User, followee: User) { }); } - // リモートにフォローリクエストをしていたらUndoFollow送信 + // Send Undo Follow if followee is remote if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); deliver(follower, content, followee.inbox); } - // リモートからフォローリクエストを受けていたらReject送信 + // Send Reject if follower is remote if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId!), followee)); deliver(followee, content, follower.inbox); } } -async function unFollow(follower: User, followee: User) { +async function unFollow(follower: User, followee: User): Promise { const following = await Followings.findOneBy({ followerId: follower.id, followeeId: followee.id, @@ -122,14 +122,14 @@ async function unFollow(follower: User, followee: User) { }); } - // リモートにフォローをしていたらUndoFollow送信 + // Send Undo Follow if follower is remote if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { const content = renderActivity(renderUndo(renderFollow(follower, followee), follower)); deliver(follower, content, followee.inbox); } } -async function removeFromList(listOwner: User, user: User) { +async function removeFromList(listOwner: User, user: User): Promise { const userLists = await UserLists.findBy({ userId: listOwner.id, }); diff --git a/packages/backend/test/services/blocking.ts b/packages/backend/test/services/blocking.ts new file mode 100644 index 000000000..41e5ef5be --- /dev/null +++ b/packages/backend/test/services/blocking.ts @@ -0,0 +1,58 @@ +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import * as sinon from 'sinon'; +import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.js'; + +describe('Creating a block activity', () => { + let p: childProcess.ChildProcess; + + // alice blocks bob + let alice: any; + let bob: any; + let carol: any; + + before(async () => { + await initTestDb(); + p = await startServer(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + bob.host = 'http://remote'; + carol.host = 'http://remote'; + }); + + beforeEach(() => { + sinon.restore(); + }); + + after(async () => { + await shutdownServer(p); + }); + + it('Should federate blocks normally', async(async () => { + const createBlock = (await import('../../src/services/blocking/create')).default; + const deleteBlock = (await import('../../src/services/blocking/delete')).default; + + const queues = await import('../../src/queue/index'); + const spy = sinon.spy(queues, 'deliver'); + await createBlock(alice, bob); + assert(spy.calledOnce); + await deleteBlock(alice, bob); + assert(spy.calledTwice); + })); + + it('Should not federate blocks if federateBlocks is false', async () => { + const createBlock = (await import('../../src/services/blocking/create')).default; + const deleteBlock = (await import('../../src/services/blocking/delete')).default; + + alice.federateBlocks = true; + + const queues = await import('../../src/queue/index'); + const spy = sinon.spy(queues, 'deliver'); + await createBlock(alice, carol); + await deleteBlock(alice, carol); + assert(spy.notCalled); + }); +}); diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index d933fcd5e..f31a135b9 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -28,6 +28,10 @@ {{ i18n.ts.makeExplorable }} + + {{ $ts.federateBlocks }} + + @@ -69,12 +73,13 @@ let isExplorable = $ref($i.isExplorable); let hideOnlineStatus = $ref($i.hideOnlineStatus); let publicReactions = $ref($i.publicReactions); let ffVisibility = $ref($i.ffVisibility); +let federateBlocks = $ref($i.federateBlocks); let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility')); let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly')); let keepCw = $computed(defaultStore.makeGetterSetter('keepCw')); -function save() { +function save(): void { os.api('i/update', { isLocked: !!isLocked, autoAcceptFollowed: !!autoAcceptFollowed, @@ -83,6 +88,7 @@ function save() { hideOnlineStatus: !!hideOnlineStatus, publicReactions: !!publicReactions, ffVisibility, + federateBlocks, }); } diff --git a/yarn.lock b/yarn.lock index 0ef2ac3ac..735cf8da7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1449,7 +1449,16 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:9.1.2": +"@sinonjs/commons@npm:^2.0.0": + version: 2.0.0 + resolution: "@sinonjs/commons@npm:2.0.0" + dependencies: + type-detect: 4.0.8 + checksum: 5023ba17edf2b85ed58262313b8e9b59e23c6860681a9af0200f239fe939e2b79736d04a260e8270ddd57196851dde3ba754d7230be5c5234e777ae2ca8af137 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:9.1.2, @sinonjs/fake-timers@npm:^9.1.2": version: 9.1.2 resolution: "@sinonjs/fake-timers@npm:9.1.2" dependencies: @@ -1458,6 +1467,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^7.0.4": + version: 7.1.2 + resolution: "@sinonjs/fake-timers@npm:7.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: c84773d7973edad5511a31d2cc75023447b5cf714a84de9bb50eda45dda88a0d3bd2c30bf6e6e936da50a048d5352e2151c694e13e59b97d187ba1f329e9a00c + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:^8.0.1": version: 8.1.0 resolution: "@sinonjs/fake-timers@npm:8.1.0" @@ -1467,6 +1485,24 @@ __metadata: languageName: node linkType: hard +"@sinonjs/samsam@npm:^7.0.1": + version: 7.0.1 + resolution: "@sinonjs/samsam@npm:7.0.1" + dependencies: + "@sinonjs/commons": ^2.0.0 + lodash.get: ^4.4.2 + type-detect: ^4.0.8 + checksum: 291efb158d54c67dee23ddabcb28873d22063449b692aaa3b2a4f1826d2f79d38695574063c92e9c17573cc805cd6acbf0ab0c66c9f3aed7afd0f12a2b905615 + languageName: node + linkType: hard + +"@sinonjs/text-encoding@npm:^0.7.1": + version: 0.7.2 + resolution: "@sinonjs/text-encoding@npm:0.7.2" + checksum: fe690002a32ba06906cf87e2e8fe84d1590294586f2a7fd180a65355b53660c155c3273d8011a5f2b77209b819aa7306678ae6e4aea0df014bd7ffd4bbbcf1ab + languageName: node + linkType: hard + "@sqltools/formatter@npm:^1.2.2": version: 1.2.3 resolution: "@sqltools/formatter@npm:1.2.3" @@ -2369,6 +2405,22 @@ __metadata: languageName: node linkType: hard +"@types/sinon@npm:^10.0.13": + version: 10.0.13 + resolution: "@types/sinon@npm:10.0.13" + dependencies: + "@types/sinonjs__fake-timers": "*" + checksum: 46a14c888db50f0098ec53d451877e0111d878ec4a653b9e9ed7f8e54de386d6beb0e528ddc3e95cd3361a8ab9ad54e4cca33cd88d45b9227b83e9fc8fb6688a + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:*, @types/sinonjs__fake-timers@npm:8.1.2": + version: 8.1.2 + resolution: "@types/sinonjs__fake-timers@npm:8.1.2" + checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd + languageName: node + linkType: hard + "@types/sinonjs__fake-timers@npm:8.1.1": version: 8.1.1 resolution: "@types/sinonjs__fake-timers@npm:8.1.1" @@ -2376,13 +2428,6 @@ __metadata: languageName: node linkType: hard -"@types/sinonjs__fake-timers@npm:8.1.2": - version: 8.1.2 - resolution: "@types/sinonjs__fake-timers@npm:8.1.2" - checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd - languageName: node - linkType: hard - "@types/sizzle@npm:^2.3.2": version: 2.3.3 resolution: "@types/sizzle@npm:2.3.3" @@ -3657,6 +3702,7 @@ __metadata: "@types/sanitize-html": 2.6.2 "@types/semver": 7.3.12 "@types/sharp": 0.30.5 + "@types/sinon": ^10.0.13 "@types/sinonjs__fake-timers": 8.1.2 "@types/speakeasy": 2.0.7 "@types/tinycolor2": 1.4.3 @@ -3744,6 +3790,7 @@ __metadata: sanitize-html: 2.7.0 semver: 7.3.7 sharp: 0.31.2 + sinon: ^14.0.2 speakeasy: 2.0.0 strict-event-emitter-types: 2.0.0 stringz: 2.1.0 @@ -5917,6 +5964,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.0.0": + version: 5.1.0 + resolution: "diff@npm:5.1.0" + checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90 + languageName: node + linkType: hard + "dijkstrajs@npm:^1.0.1": version: 1.0.2 resolution: "dijkstrajs@npm:1.0.2" @@ -9764,6 +9818,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 + languageName: node + linkType: hard + "isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -10808,6 +10869,13 @@ __metadata: languageName: node linkType: hard +"just-extend@npm:^4.0.2": + version: 4.2.1 + resolution: "just-extend@npm:4.2.1" + checksum: ff9fdede240fad313efeeeb68a660b942e5586d99c0058064c78884894a2690dc09bba44c994ad4e077e45d913fef01a9240c14a72c657b53687ac58de53b39c + languageName: node + linkType: hard + "jwa@npm:^2.0.0": version: 2.0.0 resolution: "jwa@npm:2.0.0" @@ -12262,6 +12330,19 @@ __metadata: languageName: node linkType: hard +"nise@npm:^5.1.2": + version: 5.1.2 + resolution: "nise@npm:5.1.2" + dependencies: + "@sinonjs/commons": ^2.0.0 + "@sinonjs/fake-timers": ^7.0.4 + "@sinonjs/text-encoding": ^0.7.1 + just-extend: ^4.0.2 + path-to-regexp: ^1.7.0 + checksum: 688c557333dcbc5b41f4f1f1b0ea32fb0f8b424541a8958140bc61074980362c954b2aeb027c282de26b9ddcb4b230656f68ac4206777499e405dd7e716ec1f8 + languageName: node + linkType: hard + "node-abi@npm:^3.3.0": version: 3.24.0 resolution: "node-abi@npm:3.24.0" @@ -13133,6 +13214,15 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^1.7.0": + version: 1.8.0 + resolution: "path-to-regexp@npm:1.8.0" + dependencies: + isarray: 0.0.1 + checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd + languageName: node + linkType: hard + "path-to-regexp@npm:^6.1.0": version: 6.2.1 resolution: "path-to-regexp@npm:6.2.1" @@ -15353,6 +15443,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^14.0.2": + version: 14.0.2 + resolution: "sinon@npm:14.0.2" + dependencies: + "@sinonjs/commons": ^2.0.0 + "@sinonjs/fake-timers": ^9.1.2 + "@sinonjs/samsam": ^7.0.1 + diff: ^5.0.0 + nise: ^5.1.2 + supports-color: ^7.2.0 + checksum: de7730cd7785a457e42f9a93e955780c870296036a13816e3c0c5648360afae82fdc748e36c854cf26fb8abd117855a7211aee49265c334fa61439aae17a1b72 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -16054,7 +16158,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -16764,7 +16868,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15