From 1990b90022927b7c3238963780ae68de255d1eb5 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:37:27 +0100 Subject: [PATCH] feat(federation): send file_rejected reverse relay on size limit exceeded --- packages/server/src/utils/federationWorker.ts | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/federationWorker.ts b/packages/server/src/utils/federationWorker.ts index 54f6bec1..40e60d02 100644 --- a/packages/server/src/utils/federationWorker.ts +++ b/packages/server/src/utils/federationWorker.ts @@ -2,7 +2,7 @@ import { getDb } from '../db/index.js'; import * as schema from '../db/schema.js'; import { eq, and, lte, asc, inArray } from 'drizzle-orm'; import { config } from '../config.js'; -import { isFederationRelayEnabled } from './federationOutbox.js'; +import { isFederationRelayEnabled, queueOutboxEvent } from './federationOutbox.js'; import { runFederationJanitor } from './storageJanitor.js'; import { buildFederationHeaders, getOurOrigin } from './federationAuth.js'; import { generateSnowflake } from './snowflake.js'; @@ -355,6 +355,100 @@ async function processFileQueueTick(): Promise { } } +function handleSizeRejection( + db: ReturnType, + entry: typeof schema.federationFileQueue.$inferSelect, + maxUploadSize: number, + now: number, +): void { + // Look up the local DM message to find the sender and channel info + const localMsg = db.select() + .from(schema.dmMessages) + .where(eq(schema.dmMessages.id, entry.dmMessageId)) + .get(); + + if (!localMsg || !localMsg.sourceInstance || !localMsg.sourceMessageId) return; + + // Find the attachment row to get its ID + const att = db.select() + .from(schema.attachments) + .where( + and( + eq(schema.attachments.dmMessageId, entry.dmMessageId), + eq(schema.attachments.sourceUrl, entry.sourceUrl), + ), + ) + .get(); + + // Resolve the sender's username from their local replicated user stub + const senderUser = db.select() + .from(schema.users) + .where(eq(schema.users.id, localMsg.userId)) + .get(); + const sourceUsername = senderUser?.displayName || senderUser?.username || 'unknown'; + + // Update attachment federation status + if (att) { + db.update(schema.attachments) + .set({ + federationStatus: 'remote', + federationMeta: JSON.stringify({ + sourceInstance: localMsg.sourceInstance, + sourceUserId: localMsg.userId, + sourceUsername, + }), + }) + .where(eq(schema.attachments.id, att.id)) + .run(); + } + + // Find local DM members native to THIS instance + const ourOrigin = getOurOrigin(); + const dmMembers = db.select() + .from(schema.dmMembers) + .where(eq(schema.dmMembers.dmChannelId, localMsg.dmChannelId)) + .all(); + const affectedUserIds: string[] = []; + for (const member of dmMembers) { + const user = db.select() + .from(schema.users) + .where(eq(schema.users.id, member.userId)) + .get(); + if (user && (!user.homeInstance || user.homeInstance === ourOrigin)) { + affectedUserIds.push(user.homeUserId || user.id); + } + } + + // Queue a file_rejected reverse relay event to the sender's instance + const event: FederationRelayEvent = { + eventType: 'file_rejected', + messageId: localMsg.sourceMessageId, + encryptionVersion: 0, + timestamp: now, + attachmentId: att?.id ?? entry.sourceUrl, + rejectionReason: 'size_limit_exceeded', + rejectionLimit: maxUploadSize, + affectedUserIds, + }; + + queueOutboxEvent( + localMsg.sourceMessageId, + localMsg.dmChannelId, + 'file_rejected', + JSON.stringify(event), + [localMsg.sourceInstance], + ); + + // Broadcast updated message to local clients so they see the 'remote' badge + const updatedMsg = getDmMessageWithUser(entry.dmMessageId); + if (updatedMsg) { + connectionManager.sendToDmMembers(updatedMsg.dmChannelId, { + type: 'dm_message_updated', + message: updatedMsg, + }); + } +} + async function processFileQueueEntry( db: ReturnType, entry: typeof schema.federationFileQueue.$inferSelect, @@ -398,6 +492,7 @@ async function processFileQueueEntry( }) .where(eq(schema.federationFileQueue.id, entry.id)) .run(); + handleSizeRejection(db, entry, maxUploadSize, now); return; } @@ -450,6 +545,7 @@ async function processFileQueueEntry( }) .where(eq(schema.federationFileQueue.id, entry.id)) .run(); + handleSizeRejection(db, entry, maxUploadSize, now); return; }