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 { Injectable, Inject } from '@nestjs/common';
import * as mfm from 'mfm-js';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.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 { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { isPureRenote } from '@/misc/is-pure-renote.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()
export class NoteUpdateService {
@ -47,6 +50,7 @@ export class NoteUpdateService {
private globalEventService: GlobalEventService,
private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService,
private remoteUserResolveService: RemoteUserResolveService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
@ -82,7 +86,7 @@ export class NoteUpdateService {
await this.apRendererService.renderNote(newNote, false), newNote,
),
);
this.deliverToConcerned(user, newNote, content);
}
}
@ -114,38 +118,74 @@ export class NoteUpdateService {
}
@bindThis
private async getMentionedRemoteUsers(note: MiNote) {
const where = [] as any[];
private async extractMentionedUsers(user: { host: MiUser['host']; }, tokens: mfm.MfmNode[]): Promise<MiUser[]> {
if (tokens == null) return [];
// mention / reply / dm
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
if (uris.length > 0) {
where.push(
{ uri: In(uris) },
);
}
const mentions = extractMentions(tokens);
let mentionedUsers = (await Promise.all(mentions.map(m =>
this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
))).filter(x => x != null) as MiUser[];
// renote / quote
if (note.renoteUserId) {
where.push({
id: note.renoteUserId,
});
}
// Drop duplicate users
mentionedUsers = mentionedUsers.filter((u, i, self) =>
i === self.findIndex(u2 => u.id === u2.id),
);
if (where.length === 0) return [];
return await this.usersRepository.find({
where,
}) as MiRemoteUser[];
return mentionedUsers;
}
@bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content);
const remoteUsers = await this.getMentionedRemoteUsers(note);
for (const remoteUser of remoteUsers) {
this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, noteActivity: any) {
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
// Parse MFM if needed
const tokens = (note.text ? mfm.parse(note.text)! : []);
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());
}
}