feat: open file original in a new tab via authenticated direct link

The file viewer's preview is now a real link (target=_blank) to the original,
instead of fetching it into a blob. A navigation can't send the auth header, so
the access token rides in the query — the auth middleware accepts ?access_token=
as a fallback, but only for GET, so a crafted link can't drive a mutation.

GetContent gains an ?inline=1 toggle (Content-Disposition: inline) so the tab
views the original instead of downloading it; download stays the default.

Documented in openapi.yaml; TestMediaQueryTokenAuth covers GET-with-query-token
(200), missing token (401) and query-token rejected on a non-GET (401).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 15:40:50 +03:00
parent 03936243e4
commit d357ae3156
5 changed files with 98 additions and 5 deletions
@@ -101,6 +101,16 @@
}
}
// Direct link to the full-resolution original, opened in a new tab. A
// navigation can't send the auth header, so the token rides in the query —
// the server accepts ?access_token= for GET media. Reactive on the token so a
// silent refresh keeps the link valid.
let originalUrl = $derived(
fileId
? `/api/v1/files/${fileId}/content?inline=1&access_token=${encodeURIComponent($authStore.accessToken ?? '')}`
: '#'
);
// ---- Tags (lazy) ----
// Fetch the current file's tags the first time the Tags section is visible.
// Re-runs when fileId changes while the section is still on-screen.
@@ -222,7 +232,15 @@
<!-- Preview -->
<div class="preview-wrap">
{#if previewSrc}
<img src={previewSrc} alt={file?.original_name ?? ''} class="preview-img" />
<a
class="preview-link"
href={originalUrl}
target="_blank"
rel="noopener"
title="Open original in a new tab"
>
<img src={previewSrc} alt={file?.original_name ?? ''} class="preview-img" />
</a>
{:else if loading}
<div class="preview-placeholder shimmer"></div>
{:else}
@@ -414,6 +432,17 @@
overflow: hidden;
}
/* Whole preview area is a link: click opens the original in a new tab. */
.preview-link {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: zoom-in;
text-decoration: none;
}
.preview-img {
max-width: 100%;
max-height: 100%;