fix(webapp): a page's picture is as private as the page (#894) #927
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/894-private-page-pictures"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #894, and a second door to the same kind of file found on the way.
The bug. A wiki page's picture was written to the public media store and served by nginx's
/media/withpublic, max-age=31536000, immutable, whatever the page's audience. A GM-only page's picture was anyone's who had or was forwarded the URL, and it kept working after the page narrowed.The fix follows handout attachments (W7):
media/private/and served only byGET /api/campaigns/{cid}/lore/{id}/image. That route checks the reader against the page on every request (a handout reveal opens it too, as for the page) and answers withX-Accel-Redirect.image_urlis that route, versioned by content hash./media/campaigns/(only page pictures ever lived there), so every old public address stops working at once. A page read presents its old URL as the route, and the route moves the file into the private store the first time it serves it. No migration: the migrate container can't see the media volume.Also fixed: the backend's own
/mediaStaticFiles mount (main.py, for the dev stack or anywhere the backend's port is reachable) served the whole volume,private/included: every handout, private map and tile, by URL with no check. It refusesprivate/andcampaigns/now, as nginx does.Checked
tests/wiki/test_page_picture_privacy.py: storage path; GM 200 / player 404 / non-member refused; cache headers; legacy move on first read, with the page presenting the route; delete removes legacy copies; typed URLs untouched; the backend mount refusesprivate/,campaigns/and a../walk but serves public files. The nginx test pins the new block. The import test expects the route. 159 related tests pass; ruff clean.image/pngprivate, no-store; Priya 404; table page 200private, max-age=3600;/media/campaigns/…,/media/private/…and/internal-files/…all 404; the picture displays on Priya's article page.Deploy note: after this lands, dev's existing pictures move to private storage one at a time as they're viewed; nothing needs running.
🤖 Generated with Claude Code
A page's picture was written to the public media store and served by nginx's /media/ with public, cache-forever headers, whatever the page's audience: a GM-only page's picture was anyone's who had the URL. - Pictures are stored under media/private/ and served by GET /lore/{id}/image, which checks the reader against the page (a handout reveal opens it too) and answers with X-Accel-Redirect, like attachments (W7). image_url is that route, versioned by content. - A picture stored before this moves into the private store the first time the route serves it; the page already names the route when read. nginx refuses /media/campaigns/, where they lived, so every old address stops working at once rather than picture by picture. - The backend's own /media mount (the dev stack, or anywhere its port is reachable) served the whole volume, private/ included: every handout, private map and tile, with no check. It refuses private/ and campaigns/ now, as nginx does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>