fix(documents): read attachments with service client so colleague uploads open (#1207)

The documents bucket SELECT policy only covers the uploader's own folder
(documents/{uid}/...), but document_attachments rows are company-scoped.
Every surface that touched storage with the user-bound client therefore
failed for attachments uploaded by another member of the same company
(colleague uploads, email-inbox ingest attributed to the company creator):

- GET /api/documents/:id 500ed with "Failed to create download URL", so
  viewing a bilaga on a verifikat or supplier invoice was broken for
  every member except the uploader (support case: Odin Aero, where all
  40 documents live in the owner's folder and the second member could
  open none of them).
- GET /api/documents/:id/integrity 500ed the same way.
- POST /api/documents/:id/verify failed the storage download.
- invoice-inbox retry-extraction could not download the attachment.
- cloud-backup user-triggered syncs silently dropped colleague-uploaded
  documents from the Drive archive (manifest rows flipped to 'error').

Fix: authorize on the user client (RLS + explicit company filter, plus
the membership check where present), then do the storage read with the
service-role client. This is the pattern the inline proxy route and the
v1 download route already use; these five call sites were left behind.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-26 12:34:11 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 6d9846b1e7
commit 968161b42b
11 changed files with 152 additions and 53 deletions
+6 -2
View File
@@ -488,8 +488,12 @@ export async function verifyIntegrity(
throw new Error('Document not found')
}
// Download file from storage
const { data: fileData, error: downloadError } = await supabase.storage
// Download via the service-role client: the storage SELECT policy only
// covers the uploader's own folder (documents/{uid}/...), so a caller-bound
// client cannot read colleague-uploaded files. The company-filtered row
// fetch above (RLS on document_attachments) is the authorization.
const serviceClient = createServiceClientNoCookies()
const { data: fileData, error: downloadError } = await serviceClient.storage
.from('documents')
.download(doc.storage_path)