Release v2.2: admin auth settings, Passkey RP ID, Cloudflare and Google captcha
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block title %}Настройки — Админка{% endblock %}
|
||||
{% block admin_title %}Настройки системы{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">S3, SFTP, FTP, SMTP и лимиты загрузки</p>{% endblock %}
|
||||
{% block admin_subtitle %}<p class="admin-main__subtitle">Авторизация, captcha, S3, SFTP, FTP, SMTP и лимиты загрузки</p>{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
<form method="post" class="settings-form">
|
||||
@@ -16,6 +16,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-panel" style="margin-top:24px">
|
||||
<h2 class="admin-panel__title">Регистрация и авторизация</h2>
|
||||
<label class="form-checkbox"><input type="checkbox" name="registration_enabled" {% if settings.registration_enabled %}checked{% endif %}><span>Разрешить регистрацию</span></label>
|
||||
<label class="form-checkbox"><input type="checkbox" name="password_login_enabled" {% if settings.password_login_enabled %}checked{% endif %}><span>Вход по паролю</span></label>
|
||||
<label class="form-checkbox"><input type="checkbox" name="passkey_enabled" {% if settings.passkey_enabled %}checked{% endif %}><span>Passkey (WebAuthn)</span></label>
|
||||
</div>
|
||||
|
||||
<div class="admin-panel" style="margin-top:24px">
|
||||
<h2 class="admin-panel__title">Passkey — RP ID и Origin</h2>
|
||||
<p class="folder-hint">Для production укажите домен сайта. Значения из админки имеют приоритет над <code>.env</code>.</p>
|
||||
<div class="settings-grid">
|
||||
<div class="form-group"><label>RP ID (домен)</label><input type="text" name="webauthn_rp_id" value="{{ settings.webauthn_rp_id or '' }}" placeholder="example.com"></div>
|
||||
<div class="form-group"><label>RP Name</label><input type="text" name="webauthn_rp_name" value="{{ settings.webauthn_rp_name or 'PhotoHost' }}"></div>
|
||||
<div class="form-group"><label>Origin (полный URL)</label><input type="text" name="webauthn_origin" value="{{ settings.webauthn_origin or '' }}" placeholder="https://example.com"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-panel" style="margin-top:24px">
|
||||
<h2 class="admin-panel__title">Captcha</h2>
|
||||
<div class="form-group">
|
||||
<label for="captcha_provider">Провайдер</label>
|
||||
<select id="captcha_provider" name="captcha_provider">
|
||||
<option value="none" {% if settings.captcha_provider == 'none' %}selected{% endif %}>Отключена</option>
|
||||
<option value="turnstile" {% if settings.captcha_provider == 'turnstile' %}selected{% endif %}>Cloudflare Turnstile</option>
|
||||
<option value="recaptcha_v2" {% if settings.captcha_provider == 'recaptcha_v2' %}selected{% endif %}>Google reCAPTCHA v2</option>
|
||||
<option value="recaptcha_v3" {% if settings.captcha_provider == 'recaptcha_v3' %}selected{% endif %}>Google reCAPTCHA v3</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="form-checkbox"><input type="checkbox" name="captcha_on_login" {% if settings.captcha_on_login %}checked{% endif %}><span>На странице входа</span></label>
|
||||
<label class="form-checkbox"><input type="checkbox" name="captcha_on_register" {% if settings.captcha_on_register %}checked{% endif %}><span>На странице регистрации</span></label>
|
||||
<label class="form-checkbox"><input type="checkbox" name="captcha_on_forgot_password" {% if settings.captcha_on_forgot_password %}checked{% endif %}><span>На сбросе пароля</span></label>
|
||||
|
||||
<h3 class="admin-panel__subtitle" style="margin-top:16px">Cloudflare Turnstile</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="form-group"><label>Site Key</label><input type="text" name="turnstile_site_key" value="{{ settings.turnstile_site_key or '' }}"></div>
|
||||
<div class="form-group"><label>Secret Key</label><input type="password" name="turnstile_secret_key" placeholder="оставьте пустым, если не меняете"></div>
|
||||
</div>
|
||||
|
||||
<h3 class="admin-panel__subtitle" style="margin-top:16px">Google reCAPTCHA v2</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="form-group"><label>Site Key</label><input type="text" name="recaptcha_v2_site_key" value="{{ settings.recaptcha_v2_site_key or '' }}"></div>
|
||||
<div class="form-group"><label>Secret Key</label><input type="password" name="recaptcha_v2_secret_key" placeholder="оставьте пустым, если не меняете"></div>
|
||||
</div>
|
||||
|
||||
<h3 class="admin-panel__subtitle" style="margin-top:16px">Google reCAPTCHA v3</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="form-group"><label>Site Key</label><input type="text" name="recaptcha_v3_site_key" value="{{ settings.recaptcha_v3_site_key or '' }}"></div>
|
||||
<div class="form-group"><label>Secret Key</label><input type="password" name="recaptcha_v3_secret_key" placeholder="оставьте пустым, если не меняете"></div>
|
||||
<div class="form-group"><label>Мин. score (0–1)</label><input type="number" step="0.1" min="0" max="1" name="recaptcha_v3_min_score" value="{{ settings.recaptcha_v3_min_score or 0.5 }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-panel" style="margin-top:24px">
|
||||
<h2 class="admin-panel__title">Amazon S3 / совместимое хранилище</h2>
|
||||
<label class="form-checkbox"><input type="checkbox" name="s3_enabled" {% if settings.s3_enabled %}checked{% endif %}><span>Включить S3</span></label>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required autocomplete="email">
|
||||
</div>
|
||||
{% include "partials/captcha.html" %}
|
||||
<button type="submit" class="btn btn--primary btn--full">Отправить ссылку</button>
|
||||
</form>
|
||||
<p class="auth-card__footer"><a href="{{ url_for('auth.login') }}">← Вернуться ко входу</a></p>
|
||||
@@ -21,3 +22,7 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% include "partials/captcha_scripts.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
{% include "partials/alerts.html" %}
|
||||
|
||||
{% if auth_settings.password_login_enabled %}
|
||||
<form method="post" class="auth-form">
|
||||
<div class="form-group">
|
||||
<label for="login">Логин или email</label>
|
||||
@@ -24,16 +25,25 @@
|
||||
<input type="checkbox" name="remember">
|
||||
<span>Запомнить меня</span>
|
||||
</label>
|
||||
{% include "partials/captcha.html" %}
|
||||
<button type="submit" class="btn btn--primary btn--full">Войти</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if auth_settings.passkey_enabled %}
|
||||
<button type="button" class="btn btn--ghost btn--full" id="passkeyLoginBtn" style="margin-top:12px">
|
||||
Войти с Passkey
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<p class="auth-card__footer">
|
||||
<a href="{{ url_for('auth.forgot_password') }}">Забыли пароль?</a> ·
|
||||
{% if auth_settings.password_login_enabled %}
|
||||
<a href="{{ url_for('auth.forgot_password') }}">Забыли пароль?</a>
|
||||
{% if auth_settings.registration_enabled %} · {% endif %}
|
||||
{% endif %}
|
||||
{% if auth_settings.registration_enabled %}
|
||||
<a href="{{ url_for('auth.register') }}">Зарегистрироваться</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,5 +51,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% include "partials/captcha_scripts.html" %}
|
||||
{% if auth_settings.passkey_enabled %}
|
||||
<script src="{{ url_for('static', filename='js/passkey.js') }}"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<label for="password2">Подтверждение пароля</label>
|
||||
<input type="password" id="password2" name="password2" required minlength="6" autocomplete="new-password" placeholder="повторите пароль">
|
||||
</div>
|
||||
{% include "partials/captcha.html" %}
|
||||
<button type="submit" class="btn btn--primary btn--full">Создать аккаунт</button>
|
||||
</form>
|
||||
|
||||
@@ -38,3 +39,7 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% include "partials/captcha_scripts.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -32,8 +32,10 @@
|
||||
<a href="{{ url_for('auth.logout') }}" class="nav__link">Выйти</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}" class="nav__link">Вход</a>
|
||||
{% if auth_settings.registration_enabled %}
|
||||
<a href="{{ url_for('auth.register') }}" class="nav__link nav__link--accent">Регистрация</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
</div>
|
||||
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
{% if auth_settings.passkey_enabled %}
|
||||
<h2 class="profile-card__title">Passkey</h2>
|
||||
<p class="profile-card__hint">Вход без пароля через Face ID, Touch ID, Windows Hello или ключ безопасности.</p>
|
||||
|
||||
@@ -82,6 +83,10 @@
|
||||
<input type="text" id="passkeyName" value="Моё устройство" maxlength="120">
|
||||
</div>
|
||||
<button type="button" class="btn btn--ghost" id="addPasskeyBtn">Добавить passkey</button>
|
||||
{% else %}
|
||||
<h2 class="profile-card__title">Passkey</h2>
|
||||
<p class="profile-card__hint">Passkey отключён администратором сайта.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="auth-card auth-card--wide profile-card">
|
||||
@@ -149,5 +154,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% if auth_settings.passkey_enabled %}
|
||||
<script src="{{ url_for('static', filename='js/passkey.js') }}"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{% if captcha_config %}
|
||||
<div class="form-group captcha-widget">
|
||||
{% if captcha_config.provider == 'turnstile' %}
|
||||
<div class="cf-turnstile" data-sitekey="{{ captcha_config.site_key }}"></div>
|
||||
{% elif captcha_config.provider == 'recaptcha_v2' %}
|
||||
<div class="g-recaptcha" data-sitekey="{{ captcha_config.site_key }}"></div>
|
||||
{% elif captcha_config.provider == 'recaptcha_v3' %}
|
||||
<input type="hidden" name="g-recaptcha-response" id="recaptchaV3Token" value="">
|
||||
<p class="folder-hint">Защита reCAPTCHA v3 активна</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,36 @@
|
||||
{% if captcha_config %}
|
||||
{% if captcha_config.provider == 'turnstile' %}
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
{% elif captcha_config.provider == 'recaptcha_v2' %}
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
{% elif captcha_config.provider == 'recaptcha_v3' %}
|
||||
<script src="https://www.google.com/recaptcha/api.js?render={{ captcha_config.site_key }}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
function bindRecaptchaV3() {
|
||||
const form = document.querySelector(".auth-form");
|
||||
const tokenInput = document.getElementById("recaptchaV3Token");
|
||||
if (!form || !tokenInput || !window.grecaptcha) return;
|
||||
|
||||
form.addEventListener("submit", function (event) {
|
||||
if (tokenInput.value) return;
|
||||
event.preventDefault();
|
||||
grecaptcha.ready(function () {
|
||||
grecaptcha.execute("{{ captcha_config.site_key }}", { action: "{{ captcha_config.action }}" })
|
||||
.then(function (token) {
|
||||
tokenInput.value = token;
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (window.grecaptcha) {
|
||||
bindRecaptchaV3();
|
||||
} else {
|
||||
window.addEventListener("load", bindRecaptchaV3);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user