mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(api): enforce single-role MFA, fix reindex and NCMEC scans (#2080)
This commit is contained in:
1 parent
8d21c97d08
commit
82941c08c9
4 files changed
+72
-16
No files matched your search
@@ -30,6 +30,7 @@ import {Config} from '../Config';
|
||||
import type {IChannelRepository} from '../channel/IChannelRepository';
|
||||
import type {AttachmentUploadTraceRepository} from '../channel/repositories/message/AttachmentUploadTraceRepository';
|
||||
import {
|
||||
collectMessageAttachments,
|
||||
makeAttachmentCdnKey,
|
||||
makeAttachmentCdnUrl,
|
||||
purgeMessageAttachments,
|
||||
@@ -43,6 +44,9 @@ import type {IStorageService} from '../infrastructure/IStorageService';
|
||||
import type {KVAccountDeletionQueueService} from '../infrastructure/KVAccountDeletionQueueService';
|
||||
import type {UserCacheService} from '../infrastructure/UserCacheService';
|
||||
import {Logger} from '../Logger';
|
||||
import type {Embed} from '../models/Embed';
|
||||
import type {EmbedMedia} from '../models/EmbedMedia';
|
||||
import type {Message} from '../models/Message';
|
||||
import type {User} from '../models/User';
|
||||
import type {IARMessageContext, IARSubmission} from '../report/IReportRepository';
|
||||
import type {ReportRepository} from '../report/ReportRepository';
|
||||
@@ -471,18 +475,34 @@ export class NcmecSubmissionService {
|
||||
|
||||
private async resolveAttachment(input: SubmitAttachmentToNcmecInput): Promise<ResolvedAttachment> {
|
||||
const liveMessage = await this.deps.channelRepository.getMessage(input.channelId, input.messageId);
|
||||
const liveAttachment = liveMessage?.attachments.find(
|
||||
(attachment) => attachment.id === input.attachmentId && attachment.filename === input.filename,
|
||||
);
|
||||
if (liveMessage?.authorId && liveAttachment) {
|
||||
return this.buildAttachmentContext(input, {
|
||||
contentType: liveAttachment.contentType,
|
||||
reportedAt: snowflakeToDate(input.messageId),
|
||||
userId: liveMessage.authorId,
|
||||
sourceReportId: input.sourceReportId ?? null,
|
||||
bucket: Config.s3.buckets.cdn,
|
||||
storageKey: makeAttachmentCdnKey(input.channelId, input.attachmentId, input.filename),
|
||||
});
|
||||
if (liveMessage?.authorId) {
|
||||
const liveAttachment = collectMessageAttachments(liveMessage).find(
|
||||
(attachment) => attachment.id === input.attachmentId && attachment.filename === input.filename,
|
||||
);
|
||||
if (liveAttachment) {
|
||||
return this.buildAttachmentContext(input, {
|
||||
contentType: liveAttachment.contentType,
|
||||
reportedAt: snowflakeToDate(input.messageId),
|
||||
userId: liveMessage.authorId,
|
||||
sourceReportId: input.sourceReportId ?? null,
|
||||
bucket: Config.s3.buckets.cdn,
|
||||
storageKey: makeAttachmentCdnKey(input.channelId, input.attachmentId, input.filename),
|
||||
});
|
||||
}
|
||||
const embedMedia = findEmbedReferencedAttachmentMedia(
|
||||
liveMessage,
|
||||
makeAttachmentCdnUrl(input.channelId, input.attachmentId, input.filename),
|
||||
);
|
||||
if (embedMedia) {
|
||||
return this.buildAttachmentContext(input, {
|
||||
contentType: embedMedia.contentType,
|
||||
reportedAt: snowflakeToDate(input.messageId),
|
||||
userId: liveMessage.authorId,
|
||||
sourceReportId: input.sourceReportId ?? null,
|
||||
bucket: Config.s3.buckets.cdn,
|
||||
storageKey: makeAttachmentCdnKey(input.channelId, input.attachmentId, input.filename),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!input.sourceReportId) {
|
||||
throw new UnknownMessageError();
|
||||
@@ -939,6 +959,30 @@ function buildAuditMetadata(args: {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
function findEmbedReferencedAttachmentMedia(message: Message, targetCdnUrl: string): EmbedMedia | null {
|
||||
const scan = (embeds: Array<Embed>): EmbedMedia | null => {
|
||||
for (const embed of embeds) {
|
||||
for (const media of [embed.image, embed.thumbnail, embed.video, embed.audio]) {
|
||||
if (media?.url === targetCdnUrl) {
|
||||
return media;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const direct = scan(message.embeds);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
for (const snapshot of message.messageSnapshots) {
|
||||
const found = scan(snapshot.embeds);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findAttachmentInReport(
|
||||
report: IARSubmission,
|
||||
channelId: ChannelID,
|
||||
|
||||
@@ -42,7 +42,7 @@ export class GuildMemberRoleService {
|
||||
requestCache: RequestCache;
|
||||
}): Promise<void> {
|
||||
const {userId, targetId, guildId, roleId} = params;
|
||||
const {guildData, canManageRoles} = await this.authService.getGuildAuthenticated({userId, guildId});
|
||||
const {guildData, hasPermission, canManageRoles} = await this.authService.getGuildAuthenticated({userId, guildId});
|
||||
const targetMember = await this.guildRepository.getMember(guildId, targetId);
|
||||
if (!targetMember) throw new UnknownGuildMemberError();
|
||||
await this.validationService.validateRoleAssignment({
|
||||
@@ -51,6 +51,7 @@ export class GuildMemberRoleService {
|
||||
userId,
|
||||
targetId,
|
||||
roleId,
|
||||
hasPermission,
|
||||
canManageRoles,
|
||||
});
|
||||
if (targetMember.roleIds.has(roleId)) return;
|
||||
@@ -75,7 +76,7 @@ export class GuildMemberRoleService {
|
||||
requestCache: RequestCache;
|
||||
}): Promise<void> {
|
||||
const {userId, targetId, guildId, roleId} = params;
|
||||
const {guildData, canManageRoles} = await this.authService.getGuildAuthenticated({userId, guildId});
|
||||
const {guildData, hasPermission, canManageRoles} = await this.authService.getGuildAuthenticated({userId, guildId});
|
||||
const targetMember = await this.guildRepository.getMember(guildId, targetId);
|
||||
if (!targetMember) throw new UnknownGuildMemberError();
|
||||
await this.validationService.validateRoleAssignment({
|
||||
@@ -84,6 +85,7 @@ export class GuildMemberRoleService {
|
||||
userId,
|
||||
targetId,
|
||||
roleId,
|
||||
hasPermission,
|
||||
canManageRoles,
|
||||
});
|
||||
if (!targetMember.roleIds.has(roleId)) return;
|
||||
|
||||
@@ -74,9 +74,10 @@ export class GuildMemberValidationService {
|
||||
userId: UserID;
|
||||
targetId: UserID;
|
||||
roleId: RoleID;
|
||||
hasPermission: (permission: bigint) => Promise<boolean>;
|
||||
canManageRoles: (targetUserId: UserID, targetRoleId: RoleID) => Promise<boolean>;
|
||||
}): Promise<void> {
|
||||
const {guildData, guildId, userId, targetId, roleId, canManageRoles} = params;
|
||||
const {guildData, guildId, userId, targetId, roleId, hasPermission, canManageRoles} = params;
|
||||
ensureNotEveryoneRole(roleId, guildId, 'role_id');
|
||||
if (guildData && guildData.owner_id === userId.toString()) {
|
||||
const role = await this.guildRepository.getRole(roleId, guildId);
|
||||
@@ -84,6 +85,9 @@ export class GuildMemberValidationService {
|
||||
throw new UnknownGuildRoleError();
|
||||
}
|
||||
} else {
|
||||
if (!(await hasPermission(Permissions.MANAGE_ROLES))) {
|
||||
throw new MissingPermissionsError();
|
||||
}
|
||||
if (!(await canManageRoles(targetId, roleId))) {
|
||||
throw new MissingPermissionsError();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '../../SearchFactory';
|
||||
import type {IGuildMemberSearchService} from '../../search/IGuildMemberSearchService';
|
||||
import type {IMessageSearchService} from '../../search/IMessageSearchService';
|
||||
import {deleteChannelMessageSearchDocuments} from '../../search/MessageSearchIndexCleanup';
|
||||
import {getWorkerDependencies} from '../WorkerContext';
|
||||
|
||||
const INDEX_TYPES = [
|
||||
@@ -190,11 +191,16 @@ const refreshChannelMessages: IndexHandler = async (payload, helpers, kvClient,
|
||||
const {channelRepository} = getWorkerDependencies();
|
||||
const guildId = createGuildID(BigInt(payload.guild_id!));
|
||||
const searchService = requireSearchService<IMessageSearchService>(getMessageSearchService());
|
||||
await searchService.deleteGuildMessages(guildId);
|
||||
const channels = await channelRepository.listGuildChannels(guildId);
|
||||
if (channels.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
for (const channel of channels) {
|
||||
await deleteChannelMessageSearchDocuments(channel.id, {
|
||||
searchService,
|
||||
context: {source: 'bulk_reindex', guildId: guildId.toString()},
|
||||
});
|
||||
}
|
||||
const completionKey = `bulk_reindex:${payload.job_id}:remaining`;
|
||||
await kvClient.del(completionKey);
|
||||
for (const channel of channels) {
|
||||
|
||||
Reference in new issue
Block a user