From bf2091365dc9afbf37f1c7a73be1c8fb2f6132ef Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Sun, 29 Mar 2026 11:02:24 -0500 Subject: [PATCH] Add signed URLs for file access, fix unauthorized file viewing New /files/signed-url/:id endpoint generates a 15-minute JWT-signed URL for any file. New /files/s/* endpoint serves files using the token from the query string without requiring auth headers. This allows files to open in new browser tabs without authentication issues. Photos and documents in repair tickets now use signed URLs when clicked. --- .../src/components/repairs/ticket-photos.tsx | 28 ++++++---- packages/backend/src/routes/v1/files.ts | 55 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/packages/admin/src/components/repairs/ticket-photos.tsx b/packages/admin/src/components/repairs/ticket-photos.tsx index b529a79..91f78f4 100644 --- a/packages/admin/src/components/repairs/ticket-photos.tsx +++ b/packages/admin/src/components/repairs/ticket-photos.tsx @@ -7,6 +7,15 @@ import { Button } from '@/components/ui/button' import { FileText, ImageIcon, Plus, Trash2 } from 'lucide-react' import { toast } from 'sonner' +async function openSignedFile(fileId: string) { + try { + const res = await api.get<{ url: string }>(`/v1/files/signed-url/${fileId}`) + window.open(res.url, '_blank') + } catch { + toast.error('Failed to open file') + } +} + interface FileRecord { id: string path: string @@ -145,27 +154,22 @@ function PhotoSection({ return (
{isPdf ? ( - openSignedFile(photo.id)} + className="h-20 w-20 rounded-md border flex flex-col items-center justify-center bg-muted hover:bg-muted/80 transition-colors cursor-pointer" > {photo.filename} - + ) : ( - + )}