Require login to export files; allow downloading the original too
CI / check (push) Successful in 43s
CI / deploy (push) Successful in 16s

?download=... on GET /api/gpx/file now requires login, same as
upload/move/delete/create-folder -- plain viewing (no ?download)
stays public. Added ?download=original alongside the existing
GeoJSON export, serving the untouched, as-uploaded file directly via
send_file -- the file name link still downloads GeoJSON, a new
button next to delete downloads the original.

Since both are real <a href> links (so right-click/middle-click still
work), a logged-out click can't just be left as a plain navigation --
it would land on a raw JSON error page instead of being sent to log
in. _download (gpx.js) fetches instead, reuses the existing
redirectIfUnauthorized 401 check, and only then turns the response
into a blob and clicks a throwaway <a download> to trigger the save.

Verified in a real browser: an anonymous click on either download
correctly redirects through /login to the real OAuth login page
instead of showing raw JSON.
This commit is contained in:
randy committed 2026-09-07 23:18:59 +02:00
1 parent ceaaa0bb92
commit eca8519152
4 files changed
+123 -30

No files matched your search

+27 -13
View File
@@ -343,18 +343,29 @@ name instead — `"ride.gpx"` reads as `"ride.gpx.geojson"`, matching
the real cache file sitting next to it on disk — since the GeoJSON,
not the original, is what's actually rendered on the map from that
point on. The original is never deleted or re-parsed once cached, just
not re-shown as itself. Two ways to get that converted form out of the
app as an actual file (`GET /api/gpx/file/<path>?download=1`) — the
cache is otherwise an implementation detail with no name of its own to
expose: the file name itself is a real link (an `<a>`, not a `<span>`,
specifically so clicking it downloads instead of also toggling the
row's checkbox — a native `<a>`/`<button>` nested inside a `<label>`
claims its own click instead of the label forwarding it to the
associated control, so the two behaviors don't fight over the same
click), and a dedicated download button sits next to delete for anyone
who wouldn't guess the name itself is clickable. A directly-uploaded
`.geojson` keeps its own name (nothing to convert) and downloads
unchanged either way. `Gpx` parses the GPX with REXML: `<wpt>`
not re-shown as itself, so both forms need a way out of the app as an
actual file: the file name itself (`GET /api/gpx/file/<path>?download=geojson`)
is a real link, not a `<span>` — clicking it downloads the GeoJSON
rather than also toggling the row's checkbox, since a native
`<a>`/`<button>` nested inside a `<label>` claims its own click
instead of the label forwarding it to the associated control — and a
second button next to delete (`?download=original`) downloads the
untouched, as-uploaded file, since the name alone only covers half of
what's now downloadable and isn't an obvious click target regardless.
A directly-uploaded `.geojson` keeps its own name and both buttons
download the same (identical) content — redundant there, not wrong.
Both kinds of download require login, same as upload/move/delete/
create-folder — `GET /api/gpx/file` itself stays open to anyone
(viewing a file on the map isn't a write action), just not
`?download=...` on it. Since both links are real `<a href>`s (so
right-click/middle-click still do something sensible), a logged-out
click can't just be a plain navigation the way delete/upload already
aren't — `_download` (`gpx.js`) fetches instead, checks for a 401 the
same way `redirectIfUnauthorized` already does for every other write
action, and only then turns the response into a blob and clicks a
throwaway `<a download>` to trigger the actual save — a plain
navigation would otherwise land on a raw JSON error page instead of
being sent to log in. `Gpx` parses the GPX with REXML: `<wpt>`
waypoints become `Point` features (with `name`, `desc` and `time`
carried through as properties), each `<trk>` becomes one
`MultiLineString` feature (one line per `<trkseg>`), and each `<rte>`
@@ -676,7 +687,10 @@ The GPX store's read endpoints (viewing the map, the folder tree,
individual files) are public — anyone with the URL can look. Its write
endpoints (`POST /api/gpx`, `POST /api/gpx/folder`, `DELETE
/api/gpx/file/*`, `DELETE /api/gpx/folder/*`, `POST /api/gpx/move`)
require login, guarded by `require_login!` in `app.rb`.
require login, guarded by `require_login!` in `app.rb` — and so does
exporting a file (`GET /api/gpx/file/*?download=...`), even though
plain viewing of that same URL (no `?download`) doesn't; not a write
exactly, but treated the same as the others rather than left open.
Login delegates to the Gitea instance at `git.priesol.net` via OAuth2
(`GET /login` redirects to Gitea's authorize endpoint, `GET