diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 8a37128..aa7da8c 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -365,6 +365,49 @@ export function getDocumentContentUrl(docId) { return `/api/documents/${docId}/content` } +/** + * Fetch document content bytes with authentication, returning the raw Response. + * + * Unlike request(), this function does NOT call res.json() — it returns the raw + * Response so callers can call .blob() to build an object URL for iframe preview + * or window.open() without an unauthenticated src= attribute. + * + * On 401: attempts one token refresh via authStore.refresh() then retries. + * On refresh failure: clears auth state and throws 'Session expired'. + * + * Security: closes the unauthenticated content-access gap where an iframe src= + * or window.open() with a raw /content URL would bypass the Bearer auth check + * in cases where the browser does not send the cookie (cross-origin, incognito). + * See plan 05-09 trust boundary: frontend→/api/documents/{id}/content. + */ +export async function fetchDocumentContent(docId, options = {}) { + const { useAuthStore } = await import('../stores/auth.js') + const authStore = useAuthStore() + + const headers = {} + if (authStore.accessToken) { + headers['Authorization'] = `Bearer ${authStore.accessToken}` + } + + const res = await fetch(`/api/documents/${docId}/content`, { + headers, + credentials: 'include', + }) + + if (res.status === 401 && !options._retry) { + try { + await authStore.refresh() + return fetchDocumentContent(docId, { _retry: true }) + } catch { + authStore.accessToken = null + authStore.user = null + throw new Error('Session expired') + } + } + + return res +} + // ── Cloud Storage ───────────────────────────────────────────────────────────── export function listCloudConnections() { diff --git a/frontend/src/components/documents/DocumentPreviewModal.vue b/frontend/src/components/documents/DocumentPreviewModal.vue index 50128aa..85c7dc9 100644 --- a/frontend/src/components/documents/DocumentPreviewModal.vue +++ b/frontend/src/components/documents/DocumentPreviewModal.vue @@ -23,10 +23,37 @@ -
Preview failed
+{{ loadError }}
+