add recaptcha option

This commit is contained in:
2026-05-24 16:44:20 +02:00
parent 393db5fe2a
commit 4a155bc5f4
5 changed files with 76 additions and 1 deletions

View File

@@ -206,5 +206,10 @@
"password": "smtp_password", "password": "smtp_password",
"from": "admin@example.com", "from": "admin@example.com",
"mail_reset_password": false "mail_reset_password": false
},
"recaptcha": {
"enabled": false,
"site_key": "YOUR_RECAPTCHA_V2_SITE_KEY",
"secret_key": "YOUR_RECAPTCHA_V2_SECRET_KEY"
} }
} }

View File

@@ -88,7 +88,23 @@ export default (router, tpl) => {
return renderError("Passwords do not match."); return renderError("Passwords do not match.");
} }
// Registration Logic // reCAPTCHA verification
if (cfg.recaptcha?.enabled && cfg.recaptcha?.secret_key) {
const rcToken = req.post['g-recaptcha-response'];
if (!rcToken) return renderError("Please complete the reCAPTCHA.");
try {
const verifyRes = await fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ secret: cfg.recaptcha.secret_key, response: rcToken, remoteip: ip })
});
const { success } = await verifyRes.json();
if (!success) return renderError("reCAPTCHA verification failed. Please try again.");
} catch (e) {
console.error('[REGISTER] reCAPTCHA error:', e.message);
return renderError("reCAPTCHA check failed. Please try again.");
}
}
let activated = true; let activated = true;
let activationToken = null; let activationToken = null;

View File

@@ -1092,6 +1092,8 @@ process.on('uncaughtException', err => {
enable_userhall_image_upload: cfg.websrv.enable_userhall_image_upload !== false, enable_userhall_image_upload: cfg.websrv.enable_userhall_image_upload !== false,
abyss_enabled: cfg.websrv.abyss_enabled !== false, abyss_enabled: cfg.websrv.abyss_enabled !== false,
smtp_enabled: !!(cfg.smtp && cfg.smtp.enabled && cfg.smtp.mail_reset_password), smtp_enabled: !!(cfg.smtp && cfg.smtp.enabled && cfg.smtp.mail_reset_password),
recaptcha_enabled: !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.site_key),
recaptcha_site_key: (cfg.recaptcha && cfg.recaptcha.site_key) || '',
show_background_cfg: cfg.websrv.background !== false, show_background_cfg: cfg.websrv.background !== false,
allowed_mimes: Object.keys(cfg.mimes).concat([...new Set(Object.values(cfg.mimes))].map(ext => `.${ext}`)).join(','), allowed_mimes: Object.keys(cfg.mimes).concat([...new Set(Object.values(cfg.mimes))].map(ext => `.${ext}`)).join(','),
mimes_json: JSON.stringify(cfg.mimes), mimes_json: JSON.stringify(cfg.mimes),

View File

@@ -6,6 +6,14 @@
<title>register</title> <title>register</title>
<link rel="icon" @if(custom_favicon && custom_favicon.length > 0)href="{{ custom_favicon }}"@else type="image/gif" href="/s/img/favicon.gif"@endif /> <link rel="icon" @if(custom_favicon && custom_favicon.length > 0)href="{{ custom_favicon }}"@else type="image/gif" href="/s/img/favicon.gif"@endif />
<link href="/s/css/f0ckm.css" rel="stylesheet" /> <link href="/s/css/f0ckm.css" rel="stylesheet" />
@if(recaptcha_enabled)
<script>
function onRecaptchaPageReady() {
grecaptcha.render('page-register-recaptcha', { sitekey: '{{ recaptcha_site_key }}', theme: 'dark' });
}
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaPageReady&render=explicit" async defer></script>
@endif
</head> </head>
<body type="login"> <body type="login">
<form class="login-form" method="post" action="/register" novalidate> <form class="login-form" method="post" action="/register" novalidate>
@@ -33,6 +41,9 @@
<input type="checkbox" id="tos-page" name="tos" required /> <input type="checkbox" id="tos-page" name="tos" required />
<label for="tos-page">@if(private_society){{ t('auth.tos_private_simple') }}@else{{ t('auth.tos_public') }} <a href="/terms" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_terms') }}</a>, <a href="/rules" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_rules') }}</a> {{ t('auth.tos_age') }}@endif</label> <label for="tos-page">@if(private_society){{ t('auth.tos_private_simple') }}@else{{ t('auth.tos_public') }} <a href="/terms" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_terms') }}</a>, <a href="/rules" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_rules') }}</a> {{ t('auth.tos_age') }}@endif</label>
</p> </p>
@if(recaptcha_enabled)
<div id="page-register-recaptcha" style="margin: 10px 0;"></div>
@endif
<input type="submit" value="{{ t('auth.register_title') }}" /> <input type="submit" value="{{ t('auth.register_title') }}" />
@endif @endif

View File

@@ -348,6 +348,9 @@
<input type="checkbox" id="tos-modal" name="tos" required /> <input type="checkbox" id="tos-modal" name="tos" required />
<label for="tos-modal">@if(private_society){{ t('auth.tos_private') }}@else{{ t('auth.tos_public') }} <a href="/terms" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_terms') }}</a>, <a href="/rules" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_rules') }}</a> {{ t('auth.tos_age') }}@endif</label> <label for="tos-modal">@if(private_society){{ t('auth.tos_private') }}@else{{ t('auth.tos_public') }} <a href="/terms" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_terms') }}</a>, <a href="/rules" target="_blank" style="color: var(--accent); text-decoration: underline;">{{ t('auth.tos_rules') }}</a> {{ t('auth.tos_age') }}@endif</label>
</p> </p>
@if(recaptcha_enabled)
<div id="modal-register-recaptcha" style="margin: 10px 0;"></div>
@endif
<button type="submit">{{ t('auth.create_account') }}</button> <button type="submit">{{ t('auth.create_account') }}</button>
<div style="text-align: center; margin-top: 20px;"> <div style="text-align: center; margin-top: 20px;">
<a href="#" id="register-to-login" style="font-size: 0.9em; color: var(--accent); text-decoration: underline;">{{ t('auth.back_to_login') }}</a> <a href="#" id="register-to-login" style="font-size: 0.9em; color: var(--accent); text-decoration: underline;">{{ t('auth.back_to_login') }}</a>
@@ -391,3 +394,41 @@
</div> </div>
@endif @endif
@if(recaptcha_enabled)
<script>
(function() {
var _rcModalWidgetId = null;
var _rcLoaded = false;
function renderModalRecaptcha() {
var el = document.getElementById('modal-register-recaptcha');
if (!el || !window.grecaptcha) return;
if (_rcModalWidgetId !== null) {
try { grecaptcha.reset(_rcModalWidgetId); } catch(e) {}
} else {
_rcModalWidgetId = grecaptcha.render(el, { sitekey: '{{ recaptcha_site_key }}', theme: 'dark' });
}
}
window.onRecaptchaModalReady = function() {
_rcLoaded = true;
renderModalRecaptcha();
};
document.addEventListener('DOMContentLoaded', function() {
var modal = document.getElementById('register-modal');
if (!modal) return;
var obs = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === 'style') {
if (modal.style.display !== 'none' && _rcLoaded) renderModalRecaptcha();
}
});
});
obs.observe(modal, { attributes: true });
});
})();
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaModalReady&render=explicit" async defer></script>
@endif