Stop rendering permanent Backblaze URLs in the approval viewer
Some checks failed
build-and-release / build (push) Failing after 56s

Every image/PDF/document field in a submission was rendered directly
from the stored profile_data value, which is a permanent, unsigned B2
URL. Resolve a short-lived signed URL via the new admin presign
endpoint before ever using it as an img src, iframe src, or link href.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-27 18:07:56 +05:30
parent d89c8bb3c1
commit 280d99da32

View file

@ -3,6 +3,24 @@ import { createMemo, createResource, createSignal, For, Show, createEffect } fro
const API = '';
// Documents submitted for verification are stored as permanent Backblaze URLs.
// Never render one of those directly (img src / a href) — always exchange it for
// a short-lived signed URL through this endpoint first, on demand.
async function presignDocUrl(url: string): Promise<string> {
try {
const res = await fetch(`${API}/api/admin/verifications/presign`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url }),
});
if (!res.ok) return url;
const data = await res.json();
return data?.url ?? url;
} catch {
return url;
}
}
// ── Types ──────────────────────────────────────────────────────────
type RoleType =
@ -475,23 +493,11 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<Show when={kind === 'image'}>
<div
style="background:#f1f5f9;cursor:pointer;position:relative;height:140px;display:flex;align-items:center;justify-content:center;overflow:hidden"
onClick={() => setLightbox({ src: field.value, label })}
onClick={async () => setLightbox({ src: await presignDocUrl(field.value), label })}
>
<img
src={field.value}
alt={label}
style="max-width:100%;max-height:140px;object-fit:contain"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
((e.target as HTMLImageElement).nextElementSibling as HTMLElement)!.style.display = 'flex';
}}
/>
<div style="display:none;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:4px;color:#94a3b8">
<div style="display:flex;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:4px;color:#94a3b8">
<span style="font-size:32px">🖼</span>
<span style="font-size:11px">Preview unavailable</span>
</div>
<div style="position:absolute;top:6px;right:6px;background:rgba(0,0,0,.5);color:#fff;border-radius:4px;font-size:10px;padding:2px 6px">
🔍 Click to enlarge
<span style="font-size:11px">Click to view</span>
</div>
</div>
</Show>
@ -499,7 +505,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<Show when={kind === 'pdf'}>
<div
style="background:#fef2f2;cursor:pointer;height:140px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px"
onClick={() => setPdfViewer({ src: field.value, label })}
onClick={async () => setPdfViewer({ src: await presignDocUrl(field.value), label })}
>
<span style="font-size:40px">📄</span>
<span style="font-size:12px;color:#b91c1c;font-weight:600">PDF Document</span>
@ -511,11 +517,17 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<div style="background:#eff6ff;height:140px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px">
<span style="font-size:40px">📎</span>
<span style="font-size:12px;color:#1d4ed8;font-weight:600">Document</span>
<a href={field.value} target="_blank" rel="noreferrer"
style="font-size:11px;color:#2563eb;text-decoration:underline"
onClick={(e) => e.stopPropagation()}>
<button
type="button"
onClick={async (e) => {
e.stopPropagation();
const signed = await presignDocUrl(field.value);
window.open(signed, '_blank', 'noopener,noreferrer');
}}
style="font-size:11px;color:#2563eb;background:none;border:none;padding:0;cursor:pointer;text-decoration:underline"
>
Download
</a>
</button>
</div>
</Show>
@ -529,7 +541,7 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<button
type="button"
style="font-size:11px;color:#2563eb;background:none;border:none;padding:0;cursor:pointer;text-decoration:underline"
onClick={() => setLightbox({ src: field.value, label })}
onClick={async () => setLightbox({ src: await presignDocUrl(field.value), label })}
>
🔍 View Full
</button>
@ -538,15 +550,21 @@ function SubmissionViewer(props: { rows: Array<{ key: string; value: string }> }
<button
type="button"
style="font-size:11px;color:#b91c1c;background:none;border:none;padding:0;cursor:pointer;text-decoration:underline"
onClick={() => setPdfViewer({ src: field.value, label })}
onClick={async () => setPdfViewer({ src: await presignDocUrl(field.value), label })}
>
📄 Open PDF
</button>
</Show>
<a href={field.value} target="_blank" rel="noreferrer"
style="font-size:11px;color:#64748b;text-decoration:underline">
<button
type="button"
onClick={async () => {
const signed = await presignDocUrl(field.value);
window.open(signed, '_blank', 'noopener,noreferrer');
}}
style="font-size:11px;color:#64748b;background:none;border:none;padding:0;cursor:pointer;text-decoration:underline"
>
Download
</a>
</button>
</div>
</div>
</div>