fix(federation): resolve code review issues in upload size mismatch

- Fix critical: outbox worker now copies file_rejected payload fields
  (attachmentId, sourceFilename, rejectionReason, rejectionLimit,
  affectedUserIds) so the reverse relay actually delivers them
- Fix: add sourceFilename to file_rejected event for reliable
  multi-attachment matching on the sender side
- Fix: change text-accent-warning to text-accent-amber (valid class)
- Add Array.isArray guard on federationMeta parse
This commit is contained in:
Jannis Braun
2026-03-27 04:51:30 +01:00
parent 589af25b45
commit ceb0c9812d
4 changed files with 20 additions and 9 deletions
+8 -8
View File
@@ -2312,14 +2312,13 @@ function processFileRejectedEvent(
.where(eq(schema.attachments.dmMessageId, localMsg.id)) .where(eq(schema.attachments.dmMessageId, localMsg.id))
.all(); .all();
// The attachmentId from the remote is their local attachment ID, not ours. // Match by filename from the sourceUrl — the remote sends the filename portion
// Match by sourceUrl instead — the remote's sourceUrl points to our upload endpoint. // (e.g., "12345.png") which matches our local attachment's filename.
const ourOrigin = getOurOrigin(); let matchedAttachment = event.sourceFilename
let matchedAttachment = messageAttachments.find(a => ? messageAttachments.find(a => a.filename === event.sourceFilename)
a.sourceUrl && a.sourceUrl.startsWith(ourOrigin), : undefined;
);
// If only one attachment, use it directly // Fallback: if only one attachment, use it directly
if (!matchedAttachment && messageAttachments.length === 1) { if (!matchedAttachment && messageAttachments.length === 1) {
matchedAttachment = messageAttachments[0]; matchedAttachment = messageAttachments[0];
} }
@@ -2357,7 +2356,8 @@ function processFileRejectedEvent(
let existingMeta: Array<{ userId: string; username: string; limit: number }> = []; let existingMeta: Array<{ userId: string; username: string; limit: number }> = [];
if (matchedAttachment.federationMeta) { if (matchedAttachment.federationMeta) {
try { try {
existingMeta = JSON.parse(matchedAttachment.federationMeta); const parsed = JSON.parse(matchedAttachment.federationMeta);
existingMeta = Array.isArray(parsed) ? parsed : [];
} catch { /* ignore parse errors */ } } catch { /* ignore parse errors */ }
} }
@@ -179,6 +179,12 @@ async function processOutboxTick(): Promise<void> {
if (parsed.ownership) evt.ownership = parsed.ownership; if (parsed.ownership) evt.ownership = parsed.ownership;
if (parsed.group) evt.group = parsed.group; if (parsed.group) evt.group = parsed.group;
if (parsed.friendship) evt.friendship = parsed.friendship; if (parsed.friendship) evt.friendship = parsed.friendship;
// file_rejected event fields
if (parsed.attachmentId) evt.attachmentId = parsed.attachmentId;
if (parsed.sourceFilename) evt.sourceFilename = parsed.sourceFilename;
if (parsed.rejectionReason) evt.rejectionReason = parsed.rejectionReason;
if (parsed.rejectionLimit != null) evt.rejectionLimit = parsed.rejectionLimit;
if (parsed.affectedUserIds) evt.affectedUserIds = parsed.affectedUserIds;
return evt; return evt;
}); });
@@ -420,12 +426,16 @@ function handleSizeRejection(
} }
// Queue a file_rejected reverse relay event to the sender's instance // Queue a file_rejected reverse relay event to the sender's instance
// Extract the filename from the sourceUrl (e.g., "https://sender/api/uploads/12345.png" → "12345.png")
const sourceFilename = entry.sourceUrl.split('/').pop() ?? entry.sourceUrl;
const event: FederationRelayEvent = { const event: FederationRelayEvent = {
eventType: 'file_rejected', eventType: 'file_rejected',
messageId: localMsg.sourceMessageId, messageId: localMsg.sourceMessageId,
encryptionVersion: 0, encryptionVersion: 0,
timestamp: now, timestamp: now,
attachmentId: att?.id ?? entry.sourceUrl, attachmentId: att?.id ?? entry.sourceUrl,
sourceFilename,
rejectionReason: 'size_limit_exceeded', rejectionReason: 'size_limit_exceeded',
rejectionLimit: maxUploadSize, rejectionLimit: maxUploadSize,
affectedUserIds, affectedUserIds,
+1
View File
@@ -770,6 +770,7 @@ export interface FederationRelayEvent {
friendship?: FederationFriendshipPayload; friendship?: FederationFriendshipPayload;
// file_rejected event fields // file_rejected event fields
attachmentId?: string; attachmentId?: string;
sourceFilename?: string;
rejectionReason?: string; rejectionReason?: string;
rejectionLimit?: number; rejectionLimit?: number;
affectedUserIds?: string[]; affectedUserIds?: string[];
@@ -70,7 +70,7 @@ export function AttachmentRenderer({ attachment }: AttachmentRendererProps) {
} }
return ( return (
<Tooltip content={tooltipText} position="top"> <Tooltip content={tooltipText} position="top">
<div className="absolute top-2 right-2 flex items-center gap-1 px-1.5 py-0.5 rounded glass-pill text-xs text-accent-warning"> <div className="absolute top-2 right-2 flex items-center gap-1 px-1.5 py-0.5 rounded glass-pill text-xs text-accent-amber">
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="9" x2="12" y2="13" />