Авторизация: форма входа, cookie-сессия, защита /api/* (кроме health/login/me/logout)

This commit is contained in:
2026-08-26 00:38:23 +07:00
parent 5dcc4ce1ad
commit d9eccfb4b7
7 changed files with 482 additions and 17 deletions

View File

@@ -36,6 +36,14 @@ const jobStatusText = document.querySelector("#job-status-text");
const downloadLink = document.querySelector("#download-link");
const downloadCurrent = document.querySelector("#download-current");
const authOverlay = document.querySelector("#auth-overlay");
const loginForm = document.querySelector("#login-form");
const loginUsername = document.querySelector("#login-username");
const loginPassword = document.querySelector("#login-password");
const loginError = document.querySelector("#login-error");
const loginSubmit = document.querySelector("#login-submit");
const logoutButton = document.querySelector("#logout-button");
let currentVersionId = null;
let historyVersions = [];
let sourceObjectUrl = null;
@@ -134,6 +142,15 @@ function resetResult() {
downloadLink.removeAttribute("download");
}
function clearEditorState() {
historyVersions = [];
currentVersionId = null;
clearSourcePreview();
resetResult();
renderHistory();
updateDownloadCurrent(null);
}
function clearSourcePreview() {
if (sourceObjectUrl) {
URL.revokeObjectURL(sourceObjectUrl);
@@ -196,7 +213,7 @@ async function selectFile(file) {
form.append("photo", file, file.name);
try {
const response = await fetch("/api/history/import", {
const response = await apiFetch("/api/history/import", {
method: "POST",
body: form,
cache: "no-store"
@@ -274,6 +291,21 @@ async function readJsonResponse(response) {
return response.json();
}
async function apiFetch(url, options = {}) {
const response = await fetch(url, {
...options,
credentials: "same-origin"
});
if (response.status === 401) {
clearEditorState();
showAuthOverlay();
throw new Error("Требуется авторизация.");
}
return response;
}
async function runJob(kind) {
if (isBusy) {
return;
@@ -315,7 +347,7 @@ async function runJob(kind) {
);
try {
const response = await fetch(endpoint, {
const response = await apiFetch(endpoint, {
method: "POST",
body: form,
cache: "no-store"
@@ -476,7 +508,7 @@ function selectHistoryVersion(version) {
async function loadHistoryOnStart() {
try {
const response = await fetch("/api/history", {
const response = await apiFetch("/api/history", {
cache: "no-store"
});
@@ -507,12 +539,90 @@ async function loadHistoryOnStart() {
}
}
function showAuthOverlay() {
authOverlay.hidden = false;
logoutButton.hidden = true;
loginError.hidden = true;
loginPassword.value = "";
loginUsername.focus();
}
function hideAuthOverlay() {
authOverlay.hidden = true;
logoutButton.hidden = false;
}
async function checkAuth() {
try {
const response = await apiFetch("/api/me", { cache: "no-store" });
const data = await readJsonResponse(response);
if (data.ok) {
hideAuthOverlay();
loadHistoryOnStart();
return;
}
} catch {
// Сеть недоступна или 401 — считаем пользователя не вошедшим.
}
showAuthOverlay();
}
async function submitLogin(event) {
event.preventDefault();
loginError.hidden = true;
loginSubmit.disabled = true;
try {
const response = await fetch("/api/login", {
method: "POST",
credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify({
username: loginUsername.value.trim(),
password: loginPassword.value
})
});
const data = await readJsonResponse(response);
if (!response.ok || !data.ok) {
throw new Error(data.error || "Неверный логин или пароль.");
}
loginPassword.value = "";
hideAuthOverlay();
loadHistoryOnStart();
} catch (error) {
loginError.textContent =
error instanceof Error ? error.message : "Неверный логин или пароль.";
loginError.hidden = false;
loginPassword.focus();
} finally {
loginSubmit.disabled = false;
}
}
async function logout() {
try {
await fetch("/api/logout", { method: "POST", credentials: "same-origin" });
} catch {
// Выходим локально даже при ошибке сети.
}
clearEditorState();
showAuthOverlay();
}
async function checkHealth() {
setHealthState("loading", "Проверяем сервер обработки…");
try {
const response = await fetch("/api/health", {
cache: "no-store"
cache: "no-store",
credentials: "same-origin"
});
const data = await readJsonResponse(response);
@@ -601,6 +711,9 @@ promptInput.addEventListener("input", () => {
editButton.addEventListener("click", () => runJob("edit"));
upscaleButton.addEventListener("click", () => runJob("upscale"));
loginForm.addEventListener("submit", submitLogin);
logoutButton.addEventListener("click", logout);
window.addEventListener("beforeunload", () => {
if (sourceObjectUrl) {
URL.revokeObjectURL(sourceObjectUrl);
@@ -608,4 +721,4 @@ window.addEventListener("beforeunload", () => {
});
checkHealth();
loadHistoryOnStart();
checkAuth();