Require login to export files; allow downloading the original too
?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:
1 parent
ceaaa0bb92
commit
eca8519152
4 files changed
+123
-30
No files matched your search
+52
-11
@@ -278,17 +278,26 @@ export class GpxPanelControl {
|
||||
// disk) rather than still reading as a plain, un-converted .gpx/.fit.
|
||||
// A direct .geojson upload already ends in .geojson, so it's left
|
||||
// alone. It's an <a>, not a <span>: clicking it downloads that GeoJSON
|
||||
// (?download=1) — the only way to get it out of the app, since the
|
||||
// original .gpx/.fit is never deleted but also never re-shown once
|
||||
// converted. An <a> nested in a <label> doesn't also toggle the
|
||||
// checkbox on click (unlike a plain element), so this doesn't fight
|
||||
// the row's own toggle-on-map behavior.
|
||||
// — the only way to get it out of the app, since the original .gpx/.fit
|
||||
// is never deleted but also never re-shown once converted. An <a>
|
||||
// nested in a <label> doesn't also toggle the checkbox on click (unlike
|
||||
// a plain element), so this doesn't fight the row's own
|
||||
// toggle-on-map behavior. href kept pointing at the real endpoint (so
|
||||
// hover/right-click/middle-click still do something sensible) but the
|
||||
// actual click goes through _download, which — like every other write
|
||||
// action in this file — needs to redirect to /login on a 401 rather
|
||||
// than navigate to a raw JSON error page (this endpoint now requires
|
||||
// login the same as upload/move/delete, see app.rb).
|
||||
const displayName = file.cached && !file.name.toLowerCase().endsWith(".geojson") ? `${file.name}.geojson` : file.name;
|
||||
const name = document.createElement("a");
|
||||
name.className = "gpx-tree__file-name";
|
||||
name.href = `${gpxFileUrl(file.path)}?download=1`;
|
||||
name.href = `${gpxFileUrl(file.path)}?download=geojson`;
|
||||
name.textContent = displayName;
|
||||
name.title = `Download ${displayName}`;
|
||||
name.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
this._download(name.href, displayName);
|
||||
});
|
||||
label.appendChild(checkbox);
|
||||
(file.kinds || []).forEach((kind) => label.appendChild(this._kindIcon(kind)));
|
||||
if (!file.cached) label.appendChild(this._pendingIcon());
|
||||
@@ -324,18 +333,50 @@ export class GpxPanelControl {
|
||||
return icon;
|
||||
}
|
||||
|
||||
// Same destination as clicking the file's name (gpxFileUrl + ?download=1)
|
||||
// — a second, more obviously clickable entry point for the same action.
|
||||
// The file's name already downloads its GeoJSON (?download=geojson,
|
||||
// matching what's displayed) — this covers the other half: the
|
||||
// untouched, as-uploaded file, which otherwise has no way out of the
|
||||
// app once it stops being shown as itself. For a native .geojson
|
||||
// upload the two downloads are identical (nothing to convert), so this
|
||||
// isn't hidden for those — just redundant, not wrong.
|
||||
_downloadButton(file) {
|
||||
const link = document.createElement("a");
|
||||
link.className = "gpx-tree__download";
|
||||
link.href = `${gpxFileUrl(file.path)}?download=1`;
|
||||
link.title = "Download GeoJSON";
|
||||
link.setAttribute("aria-label", "Download GeoJSON");
|
||||
link.href = `${gpxFileUrl(file.path)}?download=original`;
|
||||
link.title = "Download original file";
|
||||
link.setAttribute("aria-label", "Download original file");
|
||||
link.innerHTML = DOWNLOAD_ICON;
|
||||
link.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
this._download(link.href, file.name);
|
||||
});
|
||||
return link;
|
||||
}
|
||||
|
||||
// fetch-based rather than a plain navigation so a 401 (not logged in —
|
||||
// both download kinds require login now, see app.rb) redirects to
|
||||
// /login like every other write action in this file
|
||||
// (redirectIfUnauthorized), instead of navigating to a raw JSON error
|
||||
// page. The filename is already known client-side, so there's no need
|
||||
// to parse it back out of a Content-Disposition header.
|
||||
async _download(url, filename) {
|
||||
const response = await fetch(url);
|
||||
if (redirectIfUnauthorized(response)) return;
|
||||
if (!response.ok) {
|
||||
window.alert("Could not download the file.");
|
||||
return;
|
||||
}
|
||||
|
||||
const blobUrl = URL.createObjectURL(await response.blob());
|
||||
const link = document.createElement("a");
|
||||
link.href = blobUrl;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
|
||||
_deleteButton(onClick) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
|
||||
Reference in new issue
Block a user