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
@@ -762,6 +762,32 @@ func TestBulkTagAutoRule(t *testing.T) {
assert.ElementsMatch(t, []string{"outdoor", "nature"}, names)
}
// TestMediaQueryTokenAuth verifies the ?access_token= fallback: it authenticates
// a GET (so media can be opened via a plain link/new tab) but is rejected for a
// non-GET, and a missing token is still 401.
func TestMediaQueryTokenAuth(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
h := setupSuite(t)
token := h.login("admin", "admin")
file := h.uploadJPEG(token, "q.jpg")
fileID := file["id"].(string)
// GET with token in the query, no Authorization header → 200.
resp := h.do("GET", "/files/"+fileID+"/content?access_token="+token, nil, "", "")
require.Equal(t, http.StatusOK, resp.StatusCode, resp.String())
// No token anywhere → 401.
resp = h.do("GET", "/files/"+fileID+"/content", nil, "", "")
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
// Query token must NOT authorize a state-changing (non-GET) request → 401.
resp = h.do("DELETE", "/files/"+fileID+"?access_token="+token, nil, "", "")
require.Equal(t, http.StatusUnauthorized, resp.StatusCode, resp.String())
}
// ---------------------------------------------------------------------------
// Security regression tests
// ---------------------------------------------------------------------------