diff --git a/CHANGELOG.md b/CHANGELOG.md index d36528943f49..0bf9cf714d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ --> +## 202x.x.x-kinel.x (unreleased) + +### General + +### Client + +### Server +- ローカルユーザーがまだ誰もフォローしていないリモートユーザーによる、通知を引き起こす可能性のある投稿を拒否できるように + ## 2024.2.0-kinel.1 ### Client diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index f263c4d06b06..cb92bd877920 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -100,6 +100,7 @@ type Source = { maxWebImageSize?: number; withRepliesInHomeTL?: boolean; withRepliesInUserList?: boolean; + blockMentionsFromUnfamiliarRemoteUsers?: boolean; } }; @@ -185,6 +186,7 @@ export type Config = { maxWebImageSize?: number; withRepliesInHomeTL?: boolean, withRepliesInUserList: boolean, + blockMentionsFromUnfamiliarRemoteUsers: boolean; } }; @@ -230,6 +232,7 @@ export function loadConfig(): Config { // to avoid merge conflict in the future, this is at top nirila: Object.assign({ withRepliesInUserList: true, + blockMentionsFromUnfamiliarRemoteUsers: false, }, config.nirila ?? {}), version, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 4c0ed92cdddd..5297f1e2d3e6 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -59,6 +59,9 @@ import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { isReply } from '@/misc/is-reply.js'; import { trackPromise } from '@/misc/promise-tracker.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -149,6 +152,7 @@ type Option = { @Injectable() export class NoteCreateService implements OnApplicationShutdown { + private logger: Logger; #shutdownController = new AbortController(); public static ContainsProhibitedWordsError = class extends Error {}; @@ -219,7 +223,10 @@ export class NoteCreateService implements OnApplicationShutdown { private instanceChart: InstanceChart, private utilityService: UtilityService, private userBlockingService: UserBlockingService, - ) { } + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('note:create'); + } @bindThis public async create(user: { @@ -359,6 +366,16 @@ export class NoteCreateService implements OnApplicationShutdown { mentionedUsers = data.apMentions ?? await this.extractMentionedUsers(user, combinedTokens); } + const willCauseNotification = mentionedUsers.filter(u => u.host === null).length > 0 || data.reply?.userHost === null || data.renote?.userHost === null; + + if (this.config.nirila.blockMentionsFromUnfamiliarRemoteUsers && user.host !== null && willCauseNotification) { + const userEntity = await this.usersRepository.findOneBy({ id: user.id }); + if ((userEntity?.followersCount ?? 0) === 0) { + this.logger.error('Request rejected because user has no local followers', { user: user.id, note: data }); + throw new IdentifiableError('e11b3a16-f543-4885-8eb1-66cad131dbfd', 'Notes including mentions, replies, or renotes from remote users are not allowed until user has at least one local follower.'); + } + } + tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32); if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {