fix(auth): reject invalid bearer tokens instead of falling back

Modify the authentication handler to return an unauthorized error when
an invalid or disabled bearer token is provided, rather than silently
falling back to an anonymous request.

This ensures that clients attempting to authenticate but failing (due to
expired, malformed, or disabled tokens) are explicitly notified of the
auth failure instead of proceeding anonymously. True anonymous requests
without any Authorization header remain supported.
This commit is contained in:
2026-05-31 13:02:58 +03:00
parent d99f8ee82a
commit 61b7c283a4
28 changed files with 3503 additions and 3300 deletions

View File

@@ -0,0 +1,252 @@
(function () {
const form = document.querySelector("#upload-form");
const dropZone = document.querySelector(".drop-zone");
const fileInput = document.querySelector("#file-input");
const fileSummary = document.querySelector("#file-summary");
const progress = document.querySelector("#upload-progress");
const uploadStatus = document.querySelector("#upload-status");
const result = document.querySelector("#upload-result");
const resultMeta = document.querySelector("#result-meta");
const resultList = document.querySelector("#result-list");
const uploadQueue = document.querySelector("#upload-queue");
const totalProgressBar = document.querySelector("#total-progress-bar");
const copyURL = document.querySelector("#copy-url");
const openBox = document.querySelector("#open-box");
const manageLink = document.querySelector("#manage-link");
if (!form || !dropZone || !fileInput) {
return;
}
let latestBoxURL = "";
let selectedFiles = [];
["dragenter", "dragover"].forEach((eventName) => {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.add("is-dragging");
});
});
["dragleave", "drop"].forEach((eventName) => {
dropZone.addEventListener(eventName, (event) => {
event.preventDefault();
dropZone.classList.remove("is-dragging");
});
});
dropZone.addEventListener("drop", (event) => {
if (event.dataTransfer && event.dataTransfer.files.length > 0) {
fileInput.files = event.dataTransfer.files;
updateSelectedState(event.dataTransfer.files);
}
});
fileInput.addEventListener("change", () => updateSelectedState(fileInput.files));
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (!fileInput.files || fileInput.files.length === 0) {
updateStatus("Choose at least one file first.");
return;
}
const submit = form.querySelector("button[type='submit']");
const formData = new FormData(form);
selectedFiles = Array.from(fileInput.files);
renderQueue(selectedFiles, "queued");
setLoading(true, submit);
try {
const payload = await uploadWithProgress(form.action, formData, selectedFiles);
renderResult(payload);
form.reset();
updateSelectedState([]);
} catch (error) {
updateStatus(error.message || "Upload failed");
} finally {
setLoading(false, submit);
}
});
if (copyURL) {
copyURL.addEventListener("click", () => {
window.Warpbox.copyText(latestBoxURL, copyURL, "Copied");
});
}
function updateSelectedState(files) {
selectedFiles = Array.from(files || []);
const count = selectedFiles.length || 0;
const title = dropZone.querySelector(".drop-title");
if (title) {
title.textContent = count === 0 ? "Drop files to upload" : count === 1 ? "1 file selected" : `${count} files selected`;
}
if (fileSummary) {
fileSummary.textContent = count === 0 ? "Choose one or more files to begin." : `${count} file${count === 1 ? "" : "s"} ready.`;
}
if (count > 0) {
renderQueue(selectedFiles, "queued");
} else if (uploadQueue) {
uploadQueue.hidden = true;
uploadQueue.replaceChildren();
}
}
function setLoading(isLoading, submit) {
if (progress) {
progress.hidden = !isLoading;
}
if (submit) {
submit.disabled = isLoading;
submit.textContent = isLoading ? "Uploading..." : "Upload files";
}
updateStatus(isLoading ? "Transferring files..." : "");
setTotalProgress(isLoading ? 0 : 100);
}
function updateStatus(message) {
if (uploadStatus) {
uploadStatus.textContent = message;
}
}
function renderResult(payload) {
if (!result || !resultList || !resultMeta || !openBox) {
return;
}
latestBoxURL = payload.boxUrl;
result.hidden = false;
openBox.href = payload.boxUrl;
resultMeta.textContent = `${payload.files.length} file${payload.files.length === 1 ? "" : "s"} · expires ${window.Warpbox.formatDate(payload.expiresAt)}`;
if (manageLink) {
const anchor = manageLink.querySelector("a");
manageLink.hidden = !payload.manageUrl;
if (anchor && payload.manageUrl) {
anchor.href = payload.manageUrl;
}
}
resultList.replaceChildren();
payload.files.forEach((file) => {
resultList.append(createFileRow({
name: file.name,
meta: `${file.size} · ${file.url}`,
progress: 100,
status: "complete",
}));
});
}
function uploadWithProgress(url, formData, files) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open("POST", url);
request.setRequestHeader("Accept", "application/json");
request.upload.addEventListener("progress", (event) => {
if (!event.lengthComputable) {
updateStatus("Uploading...");
return;
}
const percent = Math.round((event.loaded / event.total) * 100);
updateStatus(`${percent}%`);
setTotalProgress(percent);
setFileProgress(files, percent);
});
request.addEventListener("load", () => {
let payload = {};
try {
payload = JSON.parse(request.responseText || "{}");
} catch (error) {
reject(new Error("Upload response could not be read"));
return;
}
if (request.status < 200 || request.status >= 300) {
reject(new Error(payload.error || "Upload failed"));
return;
}
setTotalProgress(100);
setFileProgress(files, 100);
resolve(payload);
});
request.addEventListener("error", () => reject(new Error("Network error during upload")));
request.addEventListener("abort", () => reject(new Error("Upload aborted")));
request.send(formData);
});
}
function renderQueue(files, status) {
if (!uploadQueue) {
return;
}
uploadQueue.hidden = files.length === 0;
uploadQueue.replaceChildren();
files.forEach((file) => {
uploadQueue.append(createFileRow({
name: file.name,
meta: window.Warpbox.formatBytes(file.size),
progress: status === "queued" ? 0 : 100,
status,
}));
});
}
function createFileRow(file) {
const row = document.createElement("div");
row.className = "result-item upload-file-row";
row.dataset.fileName = file.name;
const body = document.createElement("span");
const name = document.createElement("strong");
name.className = "file-name";
name.textContent = file.name;
name.title = file.name;
const meta = document.createElement("code");
meta.textContent = file.meta;
body.append(name, meta);
const side = document.createElement("div");
side.className = "file-progress-side";
const percent = document.createElement("span");
percent.className = "file-progress-percent";
percent.textContent = `${file.progress}%`;
const bar = document.createElement("div");
bar.className = "progress file-progress";
const fill = document.createElement("span");
fill.style.transform = `scaleX(${file.progress / 100})`;
bar.append(fill);
side.append(percent, bar);
row.append(body, side);
return row;
}
function setTotalProgress(percent) {
if (totalProgressBar) {
totalProgressBar.style.transform = `scaleX(${Math.max(0, Math.min(100, percent)) / 100})`;
}
}
function setFileProgress(files, totalPercent) {
if (!uploadQueue) {
return;
}
const count = files.length || 1;
const completedFloat = (Math.max(0, Math.min(100, totalPercent)) / 100) * count;
uploadQueue.querySelectorAll(".upload-file-row").forEach((row, index) => {
const progress = Math.max(0, Math.min(100, Math.round((completedFloat - index) * 100)));
const percent = row.querySelector(".file-progress-percent");
const fill = row.querySelector(".file-progress span");
if (percent) {
percent.textContent = `${progress}%`;
}
if (fill) {
fill.style.transform = `scaleX(${progress / 100})`;
}
});
}
})();