chore(edit): only send to relative users

to prevent possible data leak
This commit is contained in:
Nya Candy 2024-01-26 13:41:37 +08:00 committed by fly_mc
parent 728c8c6eb8
commit 70a1c308f0

View file

@ -5,6 +5,7 @@
import { Brackets, In } from 'typeorm'; import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common'; import { Injectable, Inject } from '@nestjs/common';
import * as mfm from 'mfm-js';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js'; import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
@ -24,8 +25,10 @@ import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { extractMentions } from '@/misc/extract-mentions.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
@Injectable() @Injectable()
export class NoteUpdateService { export class NoteUpdateService {
@ -47,6 +50,7 @@ export class NoteUpdateService {
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private relayService: RelayService, private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService, private federatedInstanceService: FederatedInstanceService,
private remoteUserResolveService: RemoteUserResolveService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService, private metaService: MetaService,
@ -82,7 +86,7 @@ export class NoteUpdateService {
await this.apRendererService.renderNote(newNote, false), newNote, await this.apRendererService.renderNote(newNote, false), newNote,
), ),
); );
this.deliverToConcerned(user, newNote, content); this.deliverToConcerned(user, newNote, content);
} }
} }
@ -114,38 +118,74 @@ export class NoteUpdateService {
} }
@bindThis @bindThis
private async getMentionedRemoteUsers(note: MiNote) { private async extractMentionedUsers(user: { host: MiUser['host']; }, tokens: mfm.MfmNode[]): Promise<MiUser[]> {
const where = [] as any[]; if (tokens == null) return [];
// mention / reply / dm const mentions = extractMentions(tokens);
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); let mentionedUsers = (await Promise.all(mentions.map(m =>
if (uris.length > 0) { this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
where.push( ))).filter(x => x != null) as MiUser[];
{ uri: In(uris) },
);
}
// renote / quote // Drop duplicate users
if (note.renoteUserId) { mentionedUsers = mentionedUsers.filter((u, i, self) =>
where.push({ i === self.findIndex(u2 => u.id === u2.id),
id: note.renoteUserId, );
});
}
if (where.length === 0) return []; return mentionedUsers;
return await this.usersRepository.find({
where,
}) as MiRemoteUser[];
} }
@bindThis @bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) { private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, noteActivity: any) {
this.apDeliverManagerService.deliverToFollowers(user, content); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
this.relayService.deliverToRelays(user, content);
const remoteUsers = await this.getMentionedRemoteUsers(note); // Parse MFM if needed
for (const remoteUser of remoteUsers) { const tokens = (note.text ? mfm.parse(note.text)! : []);
this.apDeliverManagerService.deliverToUser(user, content, remoteUser); const cwTokens = note.cw ? mfm.parse(note.cw)! : [];
const combinedTokens = tokens.concat(cwTokens);
const mentionedUsers = await this.extractMentionedUsers(user, combinedTokens);
if (note.reply && (user.id !== note.reply.userId) && !mentionedUsers.some(u => u.id === note.reply!.userId)) {
mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: note.reply.userId }));
} }
if (note.visibility === 'specified') {
if (note.visibleUserIds == null) throw new Error('invalid param');
for (const u of note.visibleUserIds) {
if (!mentionedUsers.some(x => x.id === u)) {
mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: u }));
}
}
}
// メンションされたリモートユーザーに配送
for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) {
dm.addDirectRecipe(u as MiRemoteUser);
}
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
if (note.reply && note.reply.userHost !== null) {
const u = await this.usersRepository.findOneBy({ id: note.reply.userId });
if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u);
}
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
if (note.renote && note.renote.userHost !== null) {
const u = await this.usersRepository.findOneBy({ id: note.renote.userId });
if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u);
}
// フォロワーに配送
if (['public', 'home', 'followers'].includes(note.visibility)) {
dm.addFollowersRecipe();
}
if (['public'].includes(note.visibility)) {
this.relayService.deliverToRelays(user, noteActivity);
}
trackPromise(dm.execute());
} }
} }