From 3fa7e8b8662c066561e71f8431caf4e65e0a01e5 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 1 Jun 2026 14:26:05 +0200 Subject: [PATCH] fix(06.2): CR-04 WR-05 audit export functions use 401-refresh-retry and safe URL.revokeObjectURL --- frontend/src/api/client.js | 54 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 6368be9..9bc6339 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -395,7 +395,7 @@ export function adminListAuditLog({ start, end, user_handle, event_type, page = * the endpoint can authenticate the request (D-13, T-06.2-04-03). * Must NOT call res.json() — CSV is text/csv (Pitfall 5). */ -export async function adminExportAuditLogCsv(params = {}) { +export async function adminExportAuditLogCsv(params = {}, _retry = false) { const { useAuthStore } = await import('../stores/auth.js') const authStore = useAuthStore() @@ -414,6 +414,18 @@ export async function adminExportAuditLogCsv(params = {}) { headers, credentials: 'include', }) + + if (res.status === 401 && !_retry) { + try { + await authStore.refresh() + return adminExportAuditLogCsv(params, true) + } catch { + authStore.accessToken = null + authStore.user = null + throw new Error('Session expired') + } + } + if (!res.ok) throw new Error(`Export failed: ${res.status}`) const text = await res.text() @@ -422,8 +434,10 @@ export async function adminExportAuditLogCsv(params = {}) { const a = document.createElement('a') a.href = url a.download = 'audit-export.csv' + document.body.appendChild(a) a.click() - URL.revokeObjectURL(url) + document.body.removeChild(a) + setTimeout(() => URL.revokeObjectURL(url), 1000) } /** @@ -431,22 +445,10 @@ export async function adminExportAuditLogCsv(params = {}) { * * Returns: { items: [{ date: "YYYY-MM-DD", key: "audit-logs/YYYY-MM-DD.csv" }] } * Items are sorted descending by date. + * Routes through request() which has built-in 401-refresh-retry logic. */ -export async function adminListDailyExports() { - 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/admin/audit-log/daily-exports', { - headers, - credentials: 'include', - }) - if (!res.ok) throw new Error(`Failed to list daily exports: ${res.status}`) - return res.json() +export function adminListDailyExports() { + return request('/api/admin/audit-log/daily-exports') } /** @@ -457,7 +459,7 @@ export async function adminListDailyExports() { * * @param {string} date — YYYY-MM-DD format date string */ -export async function adminDownloadDailyExport(date) { +export async function adminDownloadDailyExport(date, _retry = false) { const { useAuthStore } = await import('../stores/auth.js') const authStore = useAuthStore() @@ -470,6 +472,18 @@ export async function adminDownloadDailyExport(date) { headers, credentials: 'include', }) + + if (res.status === 401 && !_retry) { + try { + await authStore.refresh() + return adminDownloadDailyExport(date, true) + } catch { + authStore.accessToken = null + authStore.user = null + throw new Error('Session expired') + } + } + if (!res.ok) throw new Error(`Download failed: ${res.status}`) const text = await res.text() @@ -478,8 +492,10 @@ export async function adminDownloadDailyExport(date) { const a = document.createElement('a') a.href = url a.download = `audit-${date}.csv` + document.body.appendChild(a) a.click() - URL.revokeObjectURL(url) + document.body.removeChild(a) + setTimeout(() => URL.revokeObjectURL(url), 1000) } // ── Document content proxy URL ────────────────────────────────────────────────