feat(users): add account limits and API keys
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m43s

This commit is contained in:
2026-05-04 02:27:36 +03:00
parent dc379ea6a6
commit d7cbba1bf2
14 changed files with 1688 additions and 271 deletions

View File

@@ -34,8 +34,10 @@ function setBoxOptionsLocked(locked) {
function updateDisabledReasons() {
if (el.startButton) {
let reason = "";
const policyMessage = apiKeyPolicyMessage();
if (!uploadsEnabled) reason = "Guest uploads are disabled.";
else if (uploadLocked) reason = "This upload already started. Press Clear to create another box.";
else if (policyMessage) reason = policyMessage;
else if (hasQuotaError()) reason = "Over maximum upload size. Remove highlighted files or clear some files.";
else if (!files.length) reason = "There are no files selected. Please select files to upload.";
el.startButton.disabled = false;
@@ -101,6 +103,13 @@ function syncMenuChecks() {
function syncApiKeyField() {
const enabled = Boolean(el.apiKeyMode?.checked) && !uploadLocked;
el.apiKeyRow?.classList.toggle("is-visible", Boolean(el.apiKeyMode?.checked));
if (!el.apiKeyMode?.checked) {
clearTimeout(apiKeyTimer);
apiKeyValidationRun += 1;
resetAccountLimits();
updateLimitHint();
renderFiles();
}
if (el.apiKeyInput) {
el.apiKeyInput.disabled = !enabled;
el.apiKeyInput.dataset.disabledReason = enabled ? "" : "Enable Use API key for larger quota before typing an API key.";
@@ -115,30 +124,83 @@ function validateApiKeyField() {
wrapper?.classList.remove("is-checking");
if (!el.apiKeyMode?.checked) {
apiKeyValidationRun += 1;
resetAccountLimits();
el.apiKeyState.textContent = "";
updateLimitHint();
renderFiles();
return;
}
const value = el.apiKeyInput.value.trim();
if (!value) {
apiKeyValidationRun += 1;
resetAccountLimits();
el.apiKeyState.textContent = "waiting";
updateLimitHint();
renderFiles();
saveSettings();
return;
}
if (!validApiKey(value)) {
apiKeyValidationRun += 1;
resetAccountLimits();
el.apiKeyInput.value = "";
el.apiKeyState.textContent = "invalid";
updateLimitHint();
renderFiles();
saveSettings();
showToast("Invalid API key removed. Paste a valid API key to save it.", "warning");
return;
}
const runID = apiKeyValidationRun + 1;
apiKeyValidationRun = runID;
el.apiKeyInput.disabled = true;
wrapper?.classList.add("is-checking");
el.apiKeyState.textContent = "checking";
apiKeyTimer = setTimeout(() => {
wrapper?.classList.remove("is-checking");
el.apiKeyInput.disabled = uploadLocked;
if (validApiKey(value)) {
el.apiKeyState.textContent = "saved locally";
apiKeyTimer = setTimeout(async () => {
try {
const response = await fetch("/auth/me", {
headers: { Authorization: `Bearer ${value}` },
});
let payload = {};
try {
payload = await response.json();
} catch (_) {}
if (runID !== apiKeyValidationRun) return;
wrapper?.classList.remove("is-checking");
el.apiKeyInput.disabled = uploadLocked;
if (!response.ok || !payload.user) {
resetAccountLimits();
el.apiKeyInput.value = "";
el.apiKeyState.textContent = "invalid";
updateLimitHint();
renderFiles();
saveSettings();
showToast(payload.error || "API key was not accepted.", "warning");
return;
}
applyAccountLimits(payload.user);
const policyMessage = apiKeyPolicyMessage();
const fileText = maxFileBytes ? formatBytes(maxFileBytes) : "unlimited";
const boxText = maxBoxBytes ? formatBytes(maxBoxBytes) : "unlimited";
el.apiKeyState.textContent = policyMessage ? "limited by policy" : "account limits applied";
updateLimitHint();
renderFiles();
saveSettings();
} else {
el.apiKeyInput.value = "";
el.apiKeyState.textContent = "invalid";
saveSettings();
showToast("Invalid API key removed. Paste a valid API key to save it.", "warning");
setStatus(`${payload.user.username || payload.user.email} limits: file ${fileText}, box ${boxText}`);
if (policyMessage) showToast(policyMessage, "warning");
} catch (_) {
if (runID !== apiKeyValidationRun) return;
wrapper?.classList.remove("is-checking");
el.apiKeyInput.disabled = uploadLocked;
resetAccountLimits();
updateLimitHint();
renderFiles();
el.apiKeyState.textContent = "check failed";
showToast("Could not check API key limits.", "warning");
}
}, 650);
}