From c6fc167d80898832504000c24860be8c4099fb93 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Thu, 4 Jun 2026 17:45:43 +0300 Subject: [PATCH] fix(security): use signed media tokens for ticket attachments Backend now requires a signed, expiring token to download media (a raw file_id 404s). Carry the per-attachment `token` (and the legacy `media_token`) from the ticket response through MessageMediaGrid and append it in ticketsApi.getMediaUrl, so attachments keep rendering while leaked URLs expire. Must deploy together with the backend change. --- src/api/tickets.ts | 8 +++++--- src/components/tickets/MessageMediaGrid.tsx | 13 ++++++++++--- src/types/index.ts | 4 ++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/api/tickets.ts b/src/api/tickets.ts index 2794127..86a3d50 100644 --- a/src/api/tickets.ts +++ b/src/api/tickets.ts @@ -79,9 +79,11 @@ export const ticketsApi = { return response.data; }, - // Get media URL for display - getMediaUrl: (fileId: string): string => { + // Get media URL for display. The signed `token` comes from the ticket + // response and is required by the backend (a raw file_id alone 404s). + getMediaUrl: (fileId: string, token?: string | null): string => { const baseUrl = import.meta.env.VITE_API_URL || ''; - return `${baseUrl}/cabinet/media/${fileId}`; + const suffix = token ? `?token=${encodeURIComponent(token)}` : ''; + return `${baseUrl}/cabinet/media/${fileId}${suffix}`; }, }; diff --git a/src/components/tickets/MessageMediaGrid.tsx b/src/components/tickets/MessageMediaGrid.tsx index df3496f..e76fb47 100644 --- a/src/components/tickets/MessageMediaGrid.tsx +++ b/src/components/tickets/MessageMediaGrid.tsx @@ -9,12 +9,15 @@ export interface MediaItem { type: string; file_id: string; caption?: string | null; + /** Signed, expiring download token from the ticket response. */ + token?: string | null; } interface MessageLike { has_media?: boolean; media_type?: string | null; media_file_id?: string | null; + media_token?: string | null; media_caption?: string | null; media_items?: MediaItem[] | null; } @@ -33,6 +36,7 @@ function getItems(message: MessageLike): MediaItem[] { type: message.media_type, file_id: message.media_file_id, caption: message.media_caption, + token: message.media_token, }, ]; } @@ -113,7 +117,7 @@ export function MessageMediaGrid({ onClick={() => openFullscreen(originalIdx)} > {item.caption { - const mediaUrl = ticketsApi.getMediaUrl(item.file_id); + const mediaUrl = ticketsApi.getMediaUrl(item.file_id, item.token); if (item.type === 'video') { return (
@@ -203,7 +207,10 @@ export function MessageMediaGrid({ onClick={closeFullscreen} >