diff --git a/infra/authentik/custom-templates/base/header_js.html b/infra/authentik/custom-templates/base/header_js.html index 612a137..bef08fd 100644 --- a/infra/authentik/custom-templates/base/header_js.html +++ b/infra/authentik/custom-templates/base/header_js.html @@ -60,12 +60,15 @@ `; + const genericLoginError = "Не удалось выполнить вход. Проверьте email и пароль или запросите доступ."; + const revokedLoginError = "Аккаунт больше не активен. Запросите доступ, если хотите подключиться снова."; + const accountStatusCache = new Map(); const authTranslations = new Map([ - ["Failed to authenticate.", "Не удалось выполнить вход."], - ["Invalid credentials.", "Неверная почта или пароль."], - ["Invalid password.", "Неверный пароль."], - ["Incorrect password.", "Неверный пароль."], - ["Authentication failed.", "Не удалось выполнить вход."], + ["Failed to authenticate.", genericLoginError], + ["Invalid credentials.", genericLoginError], + ["Invalid password.", genericLoginError], + ["Incorrect password.", genericLoginError], + ["Authentication failed.", genericLoginError], ["This field is required.", "Заполните это поле."], ["Please enter your password", "Введите пароль"], ["Please enter your password.", "Введите пароль."], @@ -385,6 +388,87 @@ form.appendChild(link); } + function normalizeEmail(value) { + return typeof value === "string" ? value.trim().toLowerCase() : ""; + } + + function getLoginEmail(root) { + return normalizeEmail(root.querySelector("#ak-identifier-input")?.value); + } + + function getLoginErrorMessages() { + return [ + "Failed to authenticate.", + "Invalid credentials.", + "Invalid password.", + "Incorrect password.", + "Authentication failed.", + "Не удалось выполнить вход.", + "Неверная почта или пароль.", + "Неверный пароль.", + genericLoginError, + revokedLoginError, + ]; + } + + function replaceLoginErrorText(root, message) { + const errorMessages = getLoginErrorMessages(); + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { + acceptNode(node) { + const text = node.nodeValue?.trim() || ""; + return errorMessages.some((errorMessage) => text.includes(errorMessage)) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT; + }, + }); + const textNodes = []; + + while (walker.nextNode()) { + textNodes.push(walker.currentNode); + } + + textNodes.forEach((node) => { + const currentValue = node.nodeValue || ""; + const trimmed = currentValue.trim(); + if (!trimmed) return; + node.nodeValue = currentValue.replace(trimmed, message); + }); + } + + function syncLoginErrorReason(root) { + if (!hasAuthError(root)) return; + + replaceLoginErrorText(root, genericLoginError); + const email = getLoginEmail(root); + if (!email) return; + + const cachedStatus = accountStatusCache.get(email); + if (cachedStatus === "revoked") { + replaceLoginErrorText(root, revokedLoginError); + return; + } + + if (cachedStatus === "unknown" || cachedStatus === "loading") return; + + accountStatusCache.set(email, "loading"); + const statusUrl = new URL("/api/public/login-account-status", getLauncherBaseUrl()); + statusUrl.searchParams.set("email", email); + + fetch(statusUrl.toString(), { cache: "no-store" }) + .then((response) => (response.ok ? response.json() : null)) + .then((payload) => { + const status = payload?.status === "revoked" ? "revoked" : "unknown"; + accountStatusCache.set(email, status); + if (status !== "revoked") return; + visitRoots(document, (candidateRoot) => { + replaceLoginErrorText(candidateRoot, revokedLoginError); + }); + }) + .catch(() => { + accountStatusCache.set(email, "unknown"); + }); + } + function hasAuthError(root) { const hasErrorElement = root.querySelector( ".pf-c-alert, .pf-m-error, .pf-c-helper-text__item.pf-m-error, [aria-invalid='true']" @@ -392,14 +476,7 @@ if (hasErrorElement) return true; const text = root.textContent || ""; - return [ - "Failed to authenticate.", - "Не удалось выполнить вход.", - "Invalid credentials.", - "Неверная почта или пароль.", - "Invalid password.", - "Неверный пароль.", - ].some((message) => text.includes(message)); + return getLoginErrorMessages().some((message) => text.includes(message)); } function restoreCardOnErrors(root) { @@ -480,6 +557,7 @@ enhanceSubmitHandoff(root); ensureAccessRequestLink(root); translateAuthText(root); + syncLoginErrorReason(root); applyPermissionDeniedLayout(root); hidePermissionDeniedReason(root); restoreCardOnErrors(root);