Stop rendering permanent Backblaze URLs in the approval viewer
Some checks failed
build-and-release / build (push) Failing after 56s
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:
parent
d89c8bb3c1
commit
280d99da32
1 changed files with 43 additions and 25 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue