add recaptcha option
This commit is contained in:
@@ -206,5 +206,10 @@
|
||||
"password": "smtp_password",
|
||||
"from": "admin@example.com",
|
||||
"mail_reset_password": false
|
||||
},
|
||||
"recaptcha": {
|
||||
"enabled": false,
|
||||
"site_key": "YOUR_RECAPTCHA_V2_SITE_KEY",
|
||||
"secret_key": "YOUR_RECAPTCHA_V2_SECRET_KEY"
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,23 @@ export default (router, tpl) => {
|
||||
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 activationToken = null;
|
||||
|
||||
|
||||
@@ -1092,6 +1092,8 @@ process.on('uncaughtException', err => {
|
||||
enable_userhall_image_upload: cfg.websrv.enable_userhall_image_upload !== false,
|
||||
abyss_enabled: cfg.websrv.abyss_enabled !== false,
|
||||
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,
|
||||
allowed_mimes: Object.keys(cfg.mimes).concat([...new Set(Object.values(cfg.mimes))].map(ext => `.${ext}`)).join(','),
|
||||
mimes_json: JSON.stringify(cfg.mimes),
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
<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 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>
|
||||
<body type="login">
|
||||
<form class="login-form" method="post" action="/register" novalidate>
|
||||
@@ -33,6 +41,9 @@
|
||||
<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>
|
||||
</p>
|
||||
@if(recaptcha_enabled)
|
||||
<div id="page-register-recaptcha" style="margin: 10px 0;"></div>
|
||||
@endif
|
||||
<input type="submit" value="{{ t('auth.register_title') }}" />
|
||||
@endif
|
||||
|
||||
|
||||
@@ -348,6 +348,9 @@
|
||||
<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>
|
||||
</p>
|
||||
@if(recaptcha_enabled)
|
||||
<div id="modal-register-recaptcha" style="margin: 10px 0;"></div>
|
||||
@endif
|
||||
<button type="submit">{{ t('auth.create_account') }}</button>
|
||||
<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>
|
||||
@@ -391,3 +394,41 @@
|
||||
</div>
|
||||
|
||||
@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
|
||||
|
||||
Reference in New Issue
Block a user