From 7382e133714b487333aacb37f7f167f42c66de5c Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Fri, 11 Sep 2026 12:14:24 +0200 Subject: [PATCH] fix(gallery): let an admin preview a draft through its short share URL (stable) (#1407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(gallery): let an admin preview a draft through its short share URL (stable) Stable twin of the main-branch fix. /resolve/:identifier filtered drafts out through ACTIVE_EVENT_FILTER and /:slug/verify-token/:token repeated the filter inline, so with "use short gallery URLs" on the admin's own View Gallery link answered "Gallery Not Found" for an unpublished gallery. With the setting off the link carries the slug, /info serves it, and the preview worked — which is why this looked like a short-URL bug rather than a draft one. The mechanism differs from main by branch: stable identifies an admin preview by a signed admin JWT in ?preview=, so this uses isAdminPreview, the same predicate /info already uses for its draft gate. Both routes now match /info rather than being stricter than the branch they live on. The draft lookup only runs after isAdminPreview accepts the caller, so the published path keeps its single query and an unverified caller never learns the draft exists. GHSA-rh8r is unchanged and pinned by test: a bare slug lookup still never returns share_token. Relates to issue 1386 * fix(gallery): carry the admin preview credential to the API on stable External review found the backend half of the previous commit was unreachable: `preview=` appeared in exactly two places in the whole frontend — building the View Gallery link and reading the token — and nothing forwarded it into the API calls the gallery page then makes. So the new /resolve fallback exited at its guard for every real browser request, and the /info draft escape that has been there all along was equally inert. Draft preview on this branch was broken for both URL forms, not just short ones. The request interceptor now forwards the credential as x-admin-preview, and isAdminPreview accepts it there as well as in ?preview=. A header rather than a query parameter because the credential is the admin's own session JWT, and query strings reach nginx access logs, browser history and Referer headers. ?preview= stays accepted: the gallery PAGE url is what the browser navigates to, and hand-built links rely on it. The tests only exercised ?preview=, which the browser never sends on an API call — so they passed while the feature stayed broken end to end. They now cover the header transport across /resolve, /verify-token and /info. Relates to issue 1386 * fix(gallery): authenticate the draft preview by the admin cookie on stable The header transport in the previous commit could not work. `admin_token` appears exactly once in this frontend — the read inside getPreviewToken() — and nothing ever writes it: AdminAuthContext stores only admin_user and the JWT lives in an HttpOnly cookie. So getPreviewToken() always returned null, the View Gallery link was built as `?preview=` with an empty value, and every transport downstream had nothing to carry. Draft preview on this branch has never worked from the UI, by either URL form. The machinery was already there: verifyGalleryAccess drops the is_draft constraint for a preview in three places. Only delivery was missing. isAdminPreview now also accepts `admin_preview=1` as an intent flag, authenticated by the admin_token cookie the browser already sends. That fixes every caller at once, including the native fetch() in AuthenticatedImage and AuthenticatedVideo, which bypasses the axios interceptor entirely — without the flag on the media URL a preview loaded its metadata and then showed no thumbnails, hero or lightbox media at all. The flag alone authorizes nothing: with no valid admin token the check fails closed. `?preview=` keeps working for hand-built links, but nothing emits it any more, so the admin's own session JWT no longer travels in a query string where nginx access logs, browser history and Referer headers can see it. getPreviewToken() is deleted along with its now-orphaned import. Relates to issue 1386 * fix(gallery): carry the preview flag on every non-axios gallery URL Third review round found the flag still missing on the paths that never touch the axios interceptor: - PhotoLightbox renders VideoPlayer, which assigns the photo URL straight to