Добавлена Яндекс.Капча

This commit is contained in:
mi
2026-07-23 18:53:32 +03:00
parent 31efbf3b69
commit 8c3c454998
36 changed files with 836 additions and 35 deletions
@@ -23,3 +23,5 @@ otpInvalid=Код неверен, истёк или уже использова
otpCooldown=Повторно отправить СМС можно после обнуления таймера.
otpLimited=Слишком много попыток. Повторите позже.
otpUnavailable=Сервис подтверждения временно недоступен. Повторите позже.
captchaInvalid=Не удалось подтвердить, что запрос отправил человек. Пройдите проверку ещё раз.
captchaUnavailable=Проверка пока не загрузилась. Проверьте соединение и повторите.
@@ -2,7 +2,7 @@
<@layout.registrationLayout displayMessage=false; section>
<#if section = "header">${msg("otpTitle")}
<#elseif section = "form">
<link rel="stylesheet" href="${url.resourcesPath}/css/han-login.css?v=4"/>
<link rel="stylesheet" href="${url.resourcesPath}/css/han-login.css?v=6"/>
<div class="han-auth-screen han-otp-screen">
<button class="han-back-button" type="button" onclick="window.history.back()">
<span aria-hidden="true">←</span>
@@ -22,6 +22,18 @@
<input type="hidden" name="han_os_name" class="han-os-name" value="${hanOsName!""}"/>
<input type="hidden" name="han_os_version" class="han-os-version" value="${hanOsVersion!""}"/>
<input type="hidden" name="han_app_version" class="han-app-version" value="${hanAppVersion!""}"/>
<#if captchaEnabled!false>
<input id="han-captcha-token" name="smart-token" type="hidden" value=""/>
<div id="han-captcha-container" class="han-captcha"
data-sitekey="${captchaClientKey!""}"
data-form-id="kc-otp-form"
data-submit-id="han-resend-button"
data-resend-only="true"></div>
<div id="han-captcha-client-error" class="han-error" role="alert" hidden>
<span class="han-error-icon">!</span>
<span>${msg("captchaUnavailable")}</span>
</div>
</#if>
<div id="han-otp-inputs" class="han-otp-inputs <#if message?has_content>han-shake</#if>"
style="grid-template-columns: repeat(${otpCodeLength!6}, minmax(0, 1fr));">
<#list 0..((otpCodeLength!6) - 1) as index>
@@ -54,6 +66,10 @@
</button>
</form>
</div>
<script src="${url.resourcesPath}/js/han-login.js?v=4"></script>
<script src="${url.resourcesPath}/js/han-login.js?v=5"></script>
<#if captchaEnabled!false>
<script src="https://smartcaptcha.cloud.yandex.ru/captcha.js?render=onload&amp;onload=hanCaptchaOnload"
async defer></script>
</#if>
</#if>
</@layout.registrationLayout>
@@ -2,7 +2,7 @@
<@layout.registrationLayout displayMessage=false; section>
<#if section = "header">${msg("phoneTitle")}
<#elseif section = "form">
<link rel="stylesheet" href="${url.resourcesPath}/css/han-login.css?v=4"/>
<link rel="stylesheet" href="${url.resourcesPath}/css/han-login.css?v=6"/>
<div class="han-auth-screen han-phone-screen">
<div class="han-auth-main">
<div class="han-wordmark" aria-label="HAN">
@@ -31,6 +31,18 @@
<p class="han-field-hint">${msg("phoneCountry")}</p>
</div>
<#if captchaEnabled!false>
<input id="han-captcha-token" name="smart-token" type="hidden" value=""/>
<div id="han-captcha-container" class="han-captcha"
data-sitekey="${captchaClientKey!""}"
data-form-id="kc-phone-form"
data-submit-id="han-phone-submit"></div>
<div id="han-captcha-client-error" class="han-error" role="alert" hidden>
<span class="han-error-icon">!</span>
<span>${msg("captchaUnavailable")}</span>
</div>
</#if>
<#if message?has_content>
<div class="han-error" role="alert">
<span class="han-error-icon">!</span>
@@ -52,6 +64,10 @@
<span>${msg("privacyPolicy")}</span>
</p>
</div>
<script src="${url.resourcesPath}/js/han-login.js?v=4"></script>
<script src="${url.resourcesPath}/js/han-login.js?v=5"></script>
<#if captchaEnabled!false>
<script src="https://smartcaptcha.cloud.yandex.ru/captcha.js?render=onload&amp;onload=hanCaptchaOnload"
async defer></script>
</#if>
</#if>
</@layout.registrationLayout>
@@ -238,6 +238,14 @@ body.login-pf {
line-height: 1;
}
.han-captcha {
min-height: 1px;
}
.han-captcha + .han-error {
margin-top: 16px;
}
.han-legal {
margin: 32px 0 0;
color: var(--han-muted);
@@ -267,6 +275,10 @@ body.login-pf {
line-height: 1.4;
}
.han-error[hidden] {
display: none !important;
}
.han-error-icon {
display: inline-flex;
width: 17px;
@@ -127,11 +127,97 @@
updateCountdown();
if (expiresAt > Date.now()) timer = window.setInterval(updateCountdown, 1000);
resend.addEventListener("click", function () {
if (document.getElementById("han-captcha-container")) return;
window.setTimeout(function () { resend.disabled = true; }, 0);
});
}
function initCaptcha() {
var container = document.getElementById("han-captcha-container");
if (!container) return;
var form = document.getElementById(container.getAttribute("data-form-id"));
var tokenInput = document.getElementById("han-captcha-token");
var error = document.getElementById("han-captcha-client-error");
var resendOnly = container.getAttribute("data-resend-only") === "true";
var widgetId = null;
var executing = false;
var pendingSubmitter = null;
function showError() {
executing = false;
if (pendingSubmitter) pendingSubmitter.disabled = false;
pendingSubmitter = null;
if (tokenInput) tokenInput.value = "";
if (error) error.hidden = false;
}
window.hanCaptchaOnload = function () {
if (!window.smartCaptcha || !form || !tokenInput) {
showError();
return;
}
try {
widgetId = window.smartCaptcha.render(container, {
sitekey: container.getAttribute("data-sitekey"),
invisible: true,
hl: "ru",
callback: function (token) {
if (!token || !pendingSubmitter) {
showError();
return;
}
var submitter = pendingSubmitter;
pendingSubmitter = null;
executing = false;
tokenInput.value = token;
if (submitter.name) {
var action = document.createElement("input");
action.type = "hidden";
action.name = submitter.name;
action.value = submitter.value;
form.appendChild(action);
}
HTMLFormElement.prototype.submit.call(form);
}
});
window.smartCaptcha.subscribe(widgetId, "network-error", showError);
window.smartCaptcha.subscribe(widgetId, "javascript-error", showError);
window.smartCaptcha.subscribe(widgetId, "token-expired", function () {
tokenInput.value = "";
if (executing) showError();
});
} catch (_error) {
showError();
}
};
if (!form || !tokenInput) return;
form.addEventListener("submit", function (event) {
var submitter = event.submitter;
var requiresCaptcha = !resendOnly
|| (submitter && submitter.name === "otp_action" && submitter.value === "resend");
if (!requiresCaptcha) return;
event.preventDefault();
if (executing) return;
if (error) error.hidden = true;
if (widgetId === null || !window.smartCaptcha) {
showError();
return;
}
pendingSubmitter = submitter || document.getElementById(container.getAttribute("data-submit-id"));
executing = true;
if (pendingSubmitter) pendingSubmitter.disabled = true;
tokenInput.value = "";
try {
window.smartCaptcha.execute(widgetId);
} catch (_error) {
showError();
}
});
}
initDeviceMetadata();
initPhoneForm();
initOtpForm();
initCaptcha();
})();