diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 2f1310b8ef..ca57857eb1 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -8,6 +8,8 @@ import { ModuleRef } from '@nestjs/core'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; import type { MiBlocking } from '@/models/Blocking.js'; +import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -20,6 +22,7 @@ import { UserWebhookService } from '@/core/UserWebhookService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { RoleService } from '@/core/RoleService.js'; @Injectable() export class UserBlockingService implements OnModuleInit { @@ -29,6 +32,9 @@ export class UserBlockingService implements OnModuleInit { constructor( private moduleRef: ModuleRef, + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.followRequestsRepository) private followRequestsRepository: FollowRequestsRepository, @@ -41,6 +47,7 @@ export class UserBlockingService implements OnModuleInit { @Inject(DI.userListMembershipsRepository) private userListMembershipsRepository: UserListMembershipsRepository, + private roleService: RoleService, private cacheService: CacheService, private userEntityService: UserEntityService, private idService: IdService, @@ -59,6 +66,15 @@ export class UserBlockingService implements OnModuleInit { @bindThis public async block(blocker: MiUser, blockee: MiUser, silent = false) { + + // フォロー解除できない(=ブロックもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && + !await this.roleService.isModerator(blocker) + ) { + throw new IdentifiableError('e2f04d25-0d94-4ac3-a4d8-ba401062741b', 'You cannot block that user due to server policy.'); + } + await Promise.all([ this.cancelRequest(blocker, blockee, silent), this.cancelRequest(blockee, blocker, silent), diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 8963003057..5e980f27af 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -27,6 +27,7 @@ import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { RoleService } from '@/core/RoleService.js'; import type { ThinUser } from '@/queue/types.js'; import Logger from '../logger.js'; @@ -73,6 +74,7 @@ export class UserFollowingService implements OnModuleInit { @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, + private roleService: RoleService, private cacheService: CacheService, private utilityService: UtilityService, private userEntityService: UserEntityService, @@ -365,13 +367,22 @@ export class UserFollowingService implements OnModuleInit { @bindThis public async unfollow( follower: { - id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; + id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; isRoot: MiUser['isRoot']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, followee: { id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']; }, silent = false, ): Promise { + + // フォロー解除できないユーザーの場合 + if ( + this.meta.forciblyFollowedUsers.includes(followee.id) && + !await this.roleService.isModerator(follower) + ) { + throw new IdentifiableError('19f25f61-0141-4683-99dc-217a88d633cb', 'You cannot unfollow that user due to server policy.'); + } + const following = await this.followingsRepository.findOne({ relations: { follower: true, diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 06643be5fb..504b7e0e1b 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -5,19 +5,26 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MutingsRepository, MiMuting } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; +import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; @Injectable() export class UserMutingService { constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, + private roleService: RoleService, private idService: IdService, private cacheService: CacheService, ) { @@ -25,6 +32,15 @@ export class UserMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { + + // フォロー解除できない(=ミュートもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator(user) + ) { + throw new IdentifiableError('15273a89-374d-49fa-8df6-8bb3feeea455', 'You cannot mute that user due to server policy.'); + } + await this.mutingsRepository.insert({ id: this.idService.gen(), expiresAt: expiresAt ?? null, diff --git a/packages/backend/src/core/UserRenoteMutingService.ts b/packages/backend/src/core/UserRenoteMutingService.ts index bdc5e23f4b..1f5c311e3b 100644 --- a/packages/backend/src/core/UserRenoteMutingService.ts +++ b/packages/backend/src/core/UserRenoteMutingService.ts @@ -10,16 +10,23 @@ import type { MiRenoteMuting } from '@/models/RenoteMuting.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser } from '@/models/User.js'; +import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class UserRenoteMutingService { constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, + private roleService: RoleService, private idService: IdService, private cacheService: CacheService, ) { @@ -27,6 +34,15 @@ export class UserRenoteMutingService { @bindThis public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise { + + // フォロー解除できない(=リノートミュートもできない)ユーザーの場合 + if ( + this.serverSettings.forciblyFollowedUsers.includes(target.id) && + !await this.roleService.isModerator(user) + ) { + throw new IdentifiableError('15273a89-374d-49fa-8df6-8bb3feeea455', 'You cannot mute that user due to server policy.'); + } + await this.renoteMutingsRepository.insert({ id: this.idService.gen(), muterId: user.id, diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts index ec9d2b6c4c..0ae1e6ae15 100644 --- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts @@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class ImportMutingProcessorService { @@ -90,7 +91,13 @@ export class ImportMutingProcessorService { this.logger.info(`Mute[${linenum}] ${target.id} ...`); - await this.userMutingService.mute(user, target); + await this.userMutingService.mute(user, target).catch((err) => { + if (err instanceof IdentifiableError && err.id === '15273a89-374d-49fa-8df6-8bb3feeea455') { + // フォロー解除できない(=ミュートもできない)ユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); } catch (e) { this.logger.warn(`Error in line:${linenum} ${e}`); } diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index 408b02fb38..16ee99a2e0 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -16,6 +16,7 @@ import { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { RelationshipJobData } from '../types.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; @Injectable() export class RelationshipProcessorService { @@ -50,7 +51,13 @@ export class RelationshipProcessorService { this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), ]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser]; - await this.userFollowingService.unfollow(follower, followee, job.data.silent); + await this.userFollowingService.unfollow(follower, followee, job.data.silent).catch((err) => { + if (err instanceof IdentifiableError && err.id === '19f25f61-0141-4683-99dc-217a88d633cb') { + // フォロー解除できないユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); return 'ok'; } @@ -61,7 +68,13 @@ export class RelationshipProcessorService { this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), ]); - await this.userBlockingService.block(blockee, blocker, job.data.silent); + await this.userBlockingService.block(blockee, blocker, job.data.silent).catch((err) => { + if (err instanceof IdentifiableError && err.id === 'e2f04d25-0d94-4ac3-a4d8-ba401062741b') { + // フォロー解除できない(=ブロックもできない)ユーザー。動作は正常のため、エラーを無視する + return; + } + throw err; + }); return 'ok'; } diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 087140e4fa..30a5652cc3 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -7,13 +7,12 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, BlockingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -72,16 +71,12 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, - private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userBlockingService: UserBlockingService, @@ -96,7 +91,9 @@ export default class extends Endpoint { // eslint- // Get blockee const blockee = await this.getterService.getUser(ps.userId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + if (err instanceof IdentifiableError && err.id === '15348ddd-432d-49c2-8a5a-8069753becff') { + throw new ApiError(meta.errors.noSuchUser); + } throw err; }); @@ -112,14 +109,11 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyBlocking); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(blockee.id) && - !await this.roleService.isModerator(blocker) - ) { - throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); - } - - await this.userBlockingService.block(blocker, blockee); + await this.userBlockingService.block(blocker, blockee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotBlockDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotBlockDueToServerPolicy); + } + }); return await this.userEntityService.pack(blockee.id, blocker, { schema: 'UserDetailedNotMe', diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 1709fb8e4c..ac1422c5ff 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -7,10 +7,9 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { FollowingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; @@ -72,13 +71,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - private roleService: RoleService, private userEntityService: UserEntityService, private getterService: GetterService, private userFollowingService: UserFollowingService, @@ -109,14 +104,11 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.notFollowing); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(followee.id) && - !await this.roleService.isModerator(follower) - ) { - throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); - } - - await this.userFollowingService.unfollow(follower, followee); + await this.userFollowingService.unfollow(follower, followee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotUnfollowDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotUnfollowDueToServerPolicy); + } + }); return await this.userEntityService.pack(followee.id, me); }); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 69762610fa..c86d2b8de6 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -7,12 +7,11 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MutingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -71,13 +70,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, - private roleService: RoleService, private getterService: GetterService, private userMutingService: UserMutingService, ) { @@ -107,18 +102,15 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && - !await this.roleService.isModerator(muter) - ) { - throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); - } - if (ps.expiresAt && ps.expiresAt <= Date.now()) { return; } - await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null); + await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotMuteDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index 709f1b170f..40eea71cad 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -9,10 +9,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApiError } from '../../error.js'; -import { RoleService } from '@/core/RoleService.js'; import { UserRenoteMutingService } from '@/core/UserRenoteMutingService.js'; import type { RenoteMutingsRepository } from '@/models/_.js'; -import type { MiMeta } from '@/models/Meta.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['account'], @@ -66,13 +65,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.renoteMutingsRepository) private renoteMutingsRepository: RenoteMutingsRepository, - private roleService: RoleService, private getterService: GetterService, private userRenoteMutingService: UserRenoteMutingService, ) { @@ -102,15 +97,12 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } - if ( - this.serverSettings.forciblyFollowedUsers.includes(mutee.id) && - !await this.roleService.isModerator(muter) - ) { - throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); - } - // Create mute - await this.userRenoteMutingService.mute(muter, mutee); + await this.userRenoteMutingService.mute(muter, mutee).catch((err) => { + if (err instanceof IdentifiableError && err.id === meta.errors.cannotMuteDueToServerPolicy.id) { + throw new ApiError(meta.errors.cannotMuteDueToServerPolicy); + } + }); }); } }