fix(captcha): close human-verification loop end-to-end

Rewrite captcha core around aksrt_captcha_config only, require captcha
on login/comment/SMS/phone auth and related gates, wire frontend token
collection, and remove dead dual-source security settings plus slider enqueue.
This commit is contained in:
kate522 committed 2026-07-13 19:52:30 +08:00
1 parent a0808bbe54
commit 02f77b756c
21 files changed
+1163 -1515

No files matched your search

@@ -120,7 +120,7 @@ async function load() {
form.comment_require_login = !!comment.comment_require_login form.comment_require_login = !!comment.comment_require_login
form.comment_allow_reply = comment.comment_allow_reply !== false form.comment_allow_reply = comment.comment_allow_reply !== false
status.commentCaptchaEnabled = !!captcha.enabled && !disabledScenarios.includes('comment') status.commentCaptchaEnabled = !!captcha.enabled && !!captcha.active_provider && !disabledScenarios.includes('comment')
status.moderationEnabled = !!moderation.enabled status.moderationEnabled = !!moderation.enabled
status.rateLimitEnabled = !!moderation.rate_limit_enabled status.rateLimitEnabled = !!moderation.rate_limit_enabled
status.keywordRuleCount = splitKeywords(moderation.blocked_keywords).length status.keywordRuleCount = splitKeywords(moderation.blocked_keywords).length
@@ -289,29 +289,15 @@ export const permissionsSchemas = {
}, },
/** /**
* Download permissions * Download permissions — live UI is DownloadPermissions.vue via cms_features.
* Keep empty schema so SchemaPage cannot mis-write aksrt_settings.
*/ */
download: { download: {
key: 'permissions.download', key: 'permissions.download',
title: '下载权限', title: '下载权限',
icon: 'fas fa-download', icon: 'fas fa-download',
description: '控制资源下载的权限和限制。', description: '下载门槛由 DownloadPermissions 页维护(cms_features);人机验证服务商在登录权限页。',
defaults: { defaults: {},
download_require_login: false, groups: [],
download_daily_limit: 0,
download_captcha: false,
},
groups: [
{
key: 'basic',
title: '下载设置',
icon: 'fas fa-cog',
fields: [
{ key: 'download_require_login', label: '需登录下载', type: 'switch' },
{ key: 'download_daily_limit', label: '每日下载限制', type: 'number', min: 0, help: '0 表示不限制' },
{ key: 'download_captcha', label: '下载验证码', type: 'switch', help: '下载前需要验证码' },
],
},
],
}, },
}; };
+5 -2
View File
@@ -231,8 +231,11 @@
const state = window.AKSRTCaptchaState; const state = window.AKSRTCaptchaState;
const scene = state && state.scenes && state.scenes.download; const scene = state && state.scenes && state.scenes.download;
if (!scene || typeof scene.verify !== 'function') { if (!scene || typeof scene.verify !== 'function') {
// captcha 组件还未就绪,直接发,由后端兜底报错 // captcha 组件未就绪:不直发,避免绕过服务端门槛
callback(); if (window.console && console.warn) {
console.warn('[AKSRT] download captcha not ready');
}
alert(tt('captcha_loading', '验证码组件加载中,请稍候再试'));
return; return;
} }
scene.verify(function () { callback(); }); scene.verify(function () { callback(); });
+17 -3
View File
@@ -232,9 +232,15 @@
var replyInfo = document.getElementById('comment-reply-info'); var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none'; if (replyInfo) replyInfo.style.display = 'none';
// Reset captcha // Reset captcha after successful submit
var captchaImg = form.querySelector('.imagecaptcha img'); try {
if (captchaImg) captchaImg.click(); var captchaScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (captchaScene && typeof captchaScene.reset === 'function') {
captchaScene.reset();
}
} catch (e) { /* ignore */ }
// Wait time countdown // Wait time countdown
if (json.wait_time && json.wait_time > 0) { if (json.wait_time && json.wait_time > 0) {
@@ -251,6 +257,14 @@
if (json && json.code) { if (json && json.code) {
showToast(json.message || '评论提交失败', 'error'); showToast(json.message || '评论提交失败', 'error');
try {
var failScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (failScene && typeof failScene.reset === 'function') {
failScene.reset();
}
} catch (e) { /* ignore */ }
if (json.wait_time) startCountdown(json.wait_time); if (json.wait_time) startCountdown(json.wait_time);
else setLoading(false); else setLoading(false);
return; return;
+105 -64
View File
@@ -163,9 +163,33 @@
clearMessages(); clearMessages();
} }
function withCaptcha(scene, form, next) {
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
? window.AKSRTCaptchaState.scenes
: null;
var instance = store && store[scene] ? store[scene] : null;
if (instance && typeof instance.verify === 'function') {
instance.verify(function () {
next(serializeForm(form));
});
return;
}
next(serializeForm(form));
}
function resetCaptcha(scene) {
try {
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
? window.AKSRTCaptchaState.scenes
: null;
if (store && store[scene] && typeof store[scene].reset === 'function') {
store[scene].reset();
}
} catch (e) { /* ignore */ }
}
function handleLogin(form) { function handleLogin(form) {
var payload = serializeForm(form); if (!form.querySelector('[name="log"]') || !form.querySelector('[name="pwd"]')) {
if (!payload.log || !payload.pwd) {
showMessage('login', '请输入用户名和密码', 'error'); showMessage('login', '请输入用户名和密码', 'error');
return; return;
} }
@@ -173,76 +197,93 @@
setLoading(form, true); setLoading(form, true);
clearMessages(); clearMessages();
request('/auth/modal-login', payload) withCaptcha('login', form, function (payload) {
.then(function (result) { if (!payload.log || !payload.pwd) {
if (result.ok && result.data.success) {
onAuthSuccess(result.data);
return;
}
var message = result.data.message || '登录失败';
if (result.data.verify_url) {
message += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('login', message, 'error');
})
.catch(function () {
showMessage('login', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false); setLoading(form, false);
}); showMessage('login', '请输入用户名和密码', 'error');
return;
}
request('/auth/modal-login', payload)
.then(function (result) {
if (result.ok && result.data.success) {
onAuthSuccess(result.data);
return;
}
resetCaptcha('login');
var message = result.data.message || '登录失败';
if (result.data.verify_url) {
message += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('login', message, 'error');
})
.catch(function () {
resetCaptcha('login');
showMessage('login', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false);
});
});
} }
function handleRegister(form) { function handleRegister(form) {
var payload = serializeForm(form);
if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
showMessage('register', '请完整填写注册信息', 'error');
return;
}
if (payload.user_pass !== payload.user_pass_confirm) {
showMessage('register', '两次输入的密码不一致', 'error');
return;
}
setLoading(form, true); setLoading(form, true);
clearMessages(); clearMessages();
request('/auth/modal-register', payload) withCaptcha('register', form, function (payload) {
.then(function (result) { if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
if (!result.ok || !result.data.success) {
showMessage('register', result.data.message || '注册失败', 'error');
return;
}
if (result.data.user) {
onAuthSuccess(result.data);
return;
}
if (result.data.verification_required) {
var verifyMessage = result.data.message || '注册成功,请完成邮箱验证';
if (result.data.verify_url) {
verifyMessage += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('register', verifyMessage, 'success');
form.reset();
return;
}
var loginMessage = result.data.message || '注册成功,请登录';
switchTab('login');
prefillLogin(result.data.user_login || result.data.user_email || '');
showMessage('login', loginMessage, 'success');
form.reset();
})
.catch(function () {
showMessage('register', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false); setLoading(form, false);
}); showMessage('register', '请完整填写注册信息', 'error');
return;
}
if (payload.user_pass !== payload.user_pass_confirm) {
setLoading(form, false);
showMessage('register', '两次输入的密码不一致', 'error');
return;
}
request('/auth/modal-register', payload)
.then(function (result) {
if (!result.ok || !result.data.success) {
resetCaptcha('register');
showMessage('register', result.data.message || '注册失败', 'error');
return;
}
if (result.data.user) {
onAuthSuccess(result.data);
return;
}
if (result.data.verification_required) {
var verifyMessage = result.data.message || '注册成功,请完成邮箱验证';
if (result.data.verify_url) {
verifyMessage += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
}
showMessage('register', verifyMessage, 'success');
form.reset();
resetCaptcha('register');
return;
}
var loginMessage = result.data.message || '注册成功,请登录';
switchTab('login');
prefillLogin(result.data.user_login || result.data.user_email || '');
showMessage('login', loginMessage, 'success');
form.reset();
resetCaptcha('register');
})
.catch(function () {
resetCaptcha('register');
showMessage('register', '网络错误,请稍后重试', 'error');
})
.finally(function () {
setLoading(form, false);
});
});
} }
function serializeForm(form) { function serializeForm(form) {
+44 -24
View File
@@ -81,11 +81,12 @@
// 收集人机验证字段 // 收集人机验证字段
const form = btn.closest('form') const form = btn.closest('form')
const captchaData = collectCaptchaFields(form) const captchaData = collectCaptchaFields(form)
const scene = btn.dataset.aksrtSmsScene || 'register'
fetch(REST_BASE + '/auth/send-sms-code', { fetch(REST_BASE + '/auth/send-sms-code', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, ...captchaData }), body: JSON.stringify({ phone, scene, ...captchaData }),
}) })
.then(r => r.json().then(data => ({ ok: r.ok, data }))) .then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => { .then(({ ok, data }) => {
@@ -97,6 +98,12 @@
} else { } else {
const msg = (data.data && data.data.message) || data.message || '发送失败' const msg = (data.data && data.data.message) || data.message || '发送失败'
showToast(msg, 'error') showToast(msg, 'error')
try {
const captchaState = window.AKSRTCaptchaState
if (captchaState && captchaState.scenes && captchaState.scenes[scene]) {
captchaState.scenes[scene].reset()
}
} catch (e) { /* ignore */ }
const remaining = data.data && data.data.remaining_time const remaining = data.data && data.data.remaining_time
if (remaining && remaining > 0) { if (remaining && remaining > 0) {
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000)) sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000))
@@ -265,33 +272,46 @@
body = { phone, password } body = { phone, password }
} }
fetch(REST_BASE + endpoint, { const captchaState = window.AKSRTCaptchaState
method: 'POST', const doLogin = () => {
headers: { 'Content-Type': 'application/json' }, const captchaData = collectCaptchaFields(form)
body: JSON.stringify(body), fetch(REST_BASE + endpoint, {
}) method: 'POST',
.then(r => r.json().then(data => ({ ok: r.ok, data }))) headers: { 'Content-Type': 'application/json' },
.then(({ ok, data }) => { body: JSON.stringify({ ...body, ...captchaData }),
if (ok && data.success) { })
showToast('登录成功', 'success') .then(r => r.json().then(data => ({ ok: r.ok, data })))
setTimeout(() => { .then(({ ok, data }) => {
window.location.href = data.data?.redirect || '/' if (ok && data.success) {
}, 500) showToast('登录成功', 'success')
} else { setTimeout(() => {
showToast(data.message || '登录失败', 'error') window.location.href = data.data?.redirect || '/'
}, 500)
} else {
showToast(data.message || '登录失败', 'error')
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
captchaState.scenes.login.reset()
}
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = '登录'
}
}
})
.catch(() => {
showToast('网络错误', 'error')
if (submitBtn) { if (submitBtn) {
submitBtn.disabled = false submitBtn.disabled = false
submitBtn.textContent = '登录' submitBtn.textContent = '登录'
} }
} })
}) }
.catch(() => {
showToast('网络错误', 'error') if (captchaState && captchaState.scenes && captchaState.scenes.login) {
if (submitBtn) { captchaState.scenes.login.verify(doLogin)
submitBtn.disabled = false } else {
submitBtn.textContent = '登录' doLogin()
} }
})
} }
/* ================================================================= /* =================================================================
@@ -173,8 +173,12 @@ class AKSRT_Admin_Extension_Controller extends AKSRT_Admin_Controller_Base {
$email_gate_enabled = false; $email_gate_enabled = false;
} }
$captcha_comment_enabled = !empty($settings['captcha_comment_enabled']); $captcha_summary = function_exists('aksrt_get_captcha_status_summary')
$captcha_download_enabled = !empty($settings['captcha_download_enabled']); ? aksrt_get_captcha_status_summary()
: ['enabled' => false, 'configured' => false, 'scenes' => []];
$captcha_comment_enabled = !empty($captcha_summary['scenes']['comment']);
$features = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_cms_features() : [];
$captcha_download_enabled = !empty($features['download_captcha']) && !empty($captcha_summary['scenes']['download']);
$risk_switches = [ $risk_switches = [
'show_unapproved_comments' => !empty($settings['show_unapproved_comments']), 'show_unapproved_comments' => !empty($settings['show_unapproved_comments']),
@@ -191,7 +195,8 @@ class AKSRT_Admin_Extension_Controller extends AKSRT_Admin_Controller_Base {
'content_audit_configured' => !empty($services['audit']['configured']), 'content_audit_configured' => !empty($services['audit']['configured']),
'ip_geo_enabled' => !empty($services['ip_geo']['enabled']), 'ip_geo_enabled' => !empty($services['ip_geo']['enabled']),
'ip_geo_configured' => !empty($services['ip_geo']['configured']), 'ip_geo_configured' => !empty($services['ip_geo']['configured']),
'captcha_enabled' => $captcha_comment_enabled || $captcha_download_enabled, 'captcha_enabled' => !empty($captcha_summary['enabled']) && !empty($captcha_summary['configured']),
'captcha_summary' => $captcha_summary,
'risk_switches' => $risk_switches, 'risk_switches' => $risk_switches,
'services' => $services, 'services' => $services,
], ],
@@ -452,17 +457,19 @@ class AKSRT_Admin_Extension_Controller extends AKSRT_Admin_Controller_Base {
$services['wechat'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []]; $services['wechat'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
} }
// Captcha // Captcha — 唯一真相源 aksrt_captcha_config
$settings = AKSRT_Settings::get_all(); $captcha = function_exists('aksrt_get_captcha_status_summary')
$captcha_en = class_exists('AKSRT_Captcha') && !empty($settings['captcha_enabled']); ? aksrt_get_captcha_status_summary()
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
$services['captcha'] = [ $services['captcha'] = [
'enabled' => $captcha_en, 'enabled' => !empty($captcha['enabled']) && !empty($captcha['configured']),
'provider' => sanitize_key((string) ($settings['captcha_provider'] ?? '')), 'provider' => (string) ($captcha['provider'] ?? ''),
'configured' => $captcha_en, 'configured' => !empty($captcha['configured']),
'available' => $captcha_en, 'available' => !empty($captcha['configured']),
'verified' => false, 'verified' => false,
'transport' => 'http_api', 'transport' => 'http_api',
'missing_fields' => [], 'missing_fields' => [],
'scenes' => $captcha['scenes'] ?? [],
]; ];
// IP Geo // IP Geo
@@ -181,6 +181,17 @@ trait AKSRT_Admin_User_Controller_Auth_Phone_Operations {
public function auth_send_sms_code(WP_REST_Request $request): WP_REST_Response { public function auth_send_sms_code(WP_REST_Request $request): WP_REST_Response {
$params = $this->get_json_params($request); $params = $this->get_json_params($request);
$phone = sanitize_text_field((string) ($params['phone'] ?? '')); $phone = sanitize_text_field((string) ($params['phone'] ?? ''));
$scene = sanitize_key((string) ($params['scene'] ?? 'register'));
if (!array_key_exists($scene, function_exists('aksrt_captcha_scenes') ? aksrt_captcha_scenes() : [])) {
$scene = 'register';
}
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha($scene, is_array($params) ? $params : []);
if ($captcha_result !== true) {
return $this->error((string) $captcha_result, 400);
}
}
if (!class_exists('AKSRT_SMS')) { if (!class_exists('AKSRT_SMS')) {
return $this->error('短信服务不可用'); return $this->error('短信服务不可用');
@@ -52,28 +52,8 @@ class AKSRT_Admin_Schema_Extensions_Security {
['key' => 'time_format', 'label' => '时间显示格式', 'type' => 'input', 'default' => 'n月j日 H:i'], ['key' => 'time_format', 'label' => '时间显示格式', 'type' => 'input', 'default' => 'n月j日 H:i'],
['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true], ['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
]], ]],
['key' => 'verification', 'title' => '验证设置', 'fields' => [ ['key' => 'captcha-note', 'title' => '人机验证', 'fields' => [
['key' => 'verification_comment_s', 'label' => '评论需要验证', 'type' => 'switch', 'default' => false], ['key' => 'captcha_redirect_hint', 'label' => '配置入口', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '人机验证(腾讯云 / 阿里云 / Cloudflare / 极验)请到「功能&权限 → 登录权限」统一配置;本页不再维护验证码字段。'],
['key' => 'verification_links_s', 'label' => '链接需要验证', 'type' => 'switch', 'default' => true],
['key' => 'verification_newposts_s', 'label' => '新文章需要验证', 'type' => 'switch', 'default' => false],
['key' => 'verification_bbspost_s', 'label' => '论坛帖子需要验证', 'type' => 'switch', 'default' => false],
['key' => 'verification_signin_exclude', 'label' => '登录人机验证排除账号', 'type' => 'input', 'default' => ''],
['key' => 'user_verification_type', 'label' => '验证类型', 'type' => 'radio', 'default' => 'slider', 'options' => [
['label' => '图形验证码', 'value' => 'image'],
['label' => '滑块验证', 'value' => 'slider'],
['label' => '腾讯验证码', 'value' => 'tcaptcha'],
['label' => '极验验证码', 'value' => 'geetest'],
['label' => '关闭验证', 'value' => 'null'],
]],
]],
['key' => 'captcha', 'title' => '验证码设置', 'fields' => [
['key' => 'geetest_enabled', 'label' => '极验验证码', 'type' => 'switch', 'default' => false],
['key' => 'geetest_id', 'label' => '极验 ID', 'type' => 'input', 'default' => ''],
['key' => 'geetest_key', 'label' => '极验 Key', 'type' => 'input', 'default' => ''],
['key' => 'tcaptcha_enabled', 'label' => '腾讯云验证码', 'type' => 'switch', 'default' => false],
['key' => 'tcaptcha_appid', 'label' => '腾讯云 AppID', 'type' => 'input', 'default' => ''],
['key' => 'tcaptcha_secret_id', 'label' => '腾讯云 SecretId', 'type' => 'input', 'default' => ''],
['key' => 'tcaptcha_secret_key', 'label' => '腾讯云 SecretKey', 'type' => 'input', 'default' => ''],
]], ]],
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [ ['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false], ['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false],
@@ -137,7 +137,7 @@ class AKSRT_Admin_Schema_Permissions {
['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'], ['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'],
]], ]],
['key' => 'captcha', 'title' => '安全设置', 'fields' => [ ['key' => 'captcha', 'title' => '安全设置', 'fields' => [
['key' => 'download_captcha', 'label' => '下载需要验证码', 'type' => 'switch', 'default' => false], ['key' => 'download_captcha_hint', 'label' => '下载验证码', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '下载验证码门槛在「功能&权限 → 下载权限」页(cms_features.download_captcha)维护;服务商与场景在「登录权限」。'],
['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'], ['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'],
]], ]],
], ],
@@ -47,12 +47,10 @@ trait AKSRT_Auth_Service_Part_1a {
return ['success' => false, 'error' => $password_check]; return ['success' => false, 'error' => $password_check];
} }
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('register')) { if (function_exists('aksrt_require_captcha')) {
if (function_exists('aksrt_verify_captcha')) { $captcha_result = aksrt_require_captcha('register', $post);
$captcha_result = aksrt_verify_captcha($post); if ($captcha_result !== true) {
if ($captcha_result !== true) { return ['success' => false, 'error' => $captcha_result];
return ['success' => false, 'error' => $captcha_result];
}
} }
} }
@@ -154,12 +152,10 @@ trait AKSRT_Auth_Service_Part_1a {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')]; return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
} }
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('bind')) { if (function_exists('aksrt_require_captcha')) {
if (function_exists('aksrt_verify_captcha')) { $captcha_result = aksrt_require_captcha('bind', $post);
$captcha_result = aksrt_verify_captcha($post); if ($captcha_result !== true) {
if ($captcha_result !== true) { return ['success' => false, 'error' => $captcha_result];
return ['success' => false, 'error' => $captcha_result];
}
} }
} }
@@ -371,12 +367,10 @@ trait AKSRT_Auth_Service_Part_1a {
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')]; return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
} }
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('verify_email')) { if (function_exists('aksrt_require_captcha')) {
if (function_exists('aksrt_verify_captcha')) { $captcha_result = aksrt_require_captcha('verify_email', $post);
$captcha_result = aksrt_verify_captcha($post); if ($captcha_result !== true) {
if ($captcha_result !== true) { return ['success' => false, 'error' => $captcha_result];
return ['success' => false, 'error' => $captcha_result];
}
} }
} }
@@ -417,12 +411,10 @@ trait AKSRT_Auth_Service_Part_1a {
return ['success' => false, 'error' => __('该账号已完成邮箱验证,无需重复发送', 'aksrt')]; return ['success' => false, 'error' => __('该账号已完成邮箱验证,无需重复发送', 'aksrt')];
} }
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('verify_email')) { if (function_exists('aksrt_require_captcha')) {
if (function_exists('aksrt_verify_captcha')) { $captcha_result = aksrt_require_captcha('verify_email', $post);
$captcha_result = aksrt_verify_captcha($post); if ($captcha_result !== true) {
if ($captcha_result !== true) { return ['success' => false, 'error' => $captcha_result];
return ['success' => false, 'error' => $captcha_result];
}
} }
} }
@@ -110,12 +110,10 @@ trait AKSRT_Auth_Service_Part_1b {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')]; return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
} }
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('lostpassword')) { if (function_exists('aksrt_require_captcha')) {
if (function_exists('aksrt_verify_captcha')) { $captcha_result = aksrt_require_captcha('lostpassword', $post);
$captcha_result = aksrt_verify_captcha($post); if ($captcha_result !== true) {
if ($captcha_result !== true) { return ['success' => false, 'error' => $captcha_result];
return ['success' => false, 'error' => $captcha_result];
}
} }
} }
@@ -247,6 +245,13 @@ trait AKSRT_Auth_Service_Part_1b {
return ['success' => false, 'error' => __('密码至少 6 位', 'aksrt')]; return ['success' => false, 'error' => __('密码至少 6 位', 'aksrt')];
} }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('register', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
// 验证 SMS 验证码 // 验证 SMS 验证码
if (class_exists('AKSRT_SMS')) { if (class_exists('AKSRT_SMS')) {
$verify_result = AKSRT_SMS::get_instance()->verify_code($phone, $code); $verify_result = AKSRT_SMS::get_instance()->verify_code($phone, $code);
@@ -321,6 +326,13 @@ trait AKSRT_Auth_Service_Part_1b {
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')]; return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
} }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('login', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
$user = self::get_user_by_phone($phone); $user = self::get_user_by_phone($phone);
if (!$user) { if (!$user) {
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')]; return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
@@ -359,6 +371,13 @@ trait AKSRT_Auth_Service_Part_1b {
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')]; return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
} }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('login', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
// 验证 SMS 验证码 // 验证 SMS 验证码
if (class_exists('AKSRT_SMS')) { if (class_exists('AKSRT_SMS')) {
$verify_result = AKSRT_SMS::get_instance()->verify_code($phone, $code); $verify_result = AKSRT_SMS::get_instance()->verify_code($phone, $code);
+7
View File
@@ -368,6 +368,13 @@ final class AKSRT_Auth_Service {
} }
} }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('login', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
$creds = [ $creds = [
'user_login' => $resolved_login, 'user_login' => $resolved_login,
'user_password' => (string) ($post['pwd'] ?? ''), 'user_password' => (string) ($post['pwd'] ?? ''),
File diff suppressed because it is too large. Load diff
+11 -5
View File
@@ -227,6 +227,17 @@ class AKSRT_Comment_Actions {
], 403); ], 403);
} }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('comment', $params);
if ($captcha_result !== true) {
return new \WP_REST_Response([
'success' => false,
'code' => 'captcha_failed',
'message' => (string) $captcha_result,
], 400);
}
}
$user = wp_get_current_user(); $user = wp_get_current_user();
$commentdata = [ $commentdata = [
'comment_post_ID' => $post_id, 'comment_post_ID' => $post_id,
@@ -263,11 +274,6 @@ class AKSRT_Comment_Actions {
$_POST[$k] = $params[$k]; $_POST[$k] = $params[$k];
} }
} }
foreach ($params as $k => $v) {
if (is_string($k) && (str_contains($k, 'captcha') || str_starts_with($k, 'g-recaptcha') || str_starts_with($k, 'cf-turnstile') || str_starts_with($k, 'h-captcha'))) {
$_POST[$k] = $v;
}
}
$comment_id = wp_new_comment(wp_slash($commentdata), true); $comment_id = wp_new_comment(wp_slash($commentdata), true);
if (is_wp_error($comment_id)) { if (is_wp_error($comment_id)) {
+10 -9
View File
@@ -162,16 +162,17 @@ class AKSRT_Download {
if (is_wp_error($gate)) return $gate; if (is_wp_error($gate)) return $gate;
$paid_settings = AKSRT_Settings::get_paid_content_settings(); $paid_settings = AKSRT_Settings::get_paid_content_settings();
$need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']); $need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']);
if ($need_captcha && function_exists('aksrt_is_captcha_enabled_for') && !aksrt_is_captcha_enabled_for('download')) { // 下载双开关:cms_features.download_captcha(或单资源强制)且场景未停用。
$need_captcha = false; if ($need_captcha && function_exists('aksrt_require_captcha')) {
}
if ($need_captcha && function_exists('aksrt_verify_captcha')) {
$cap_payload = isset($params['captcha']) && is_array($params['captcha']) ? $params['captcha'] : []; $cap_payload = isset($params['captcha']) && is_array($params['captcha']) ? $params['captcha'] : [];
$verify = aksrt_verify_captcha($cap_payload); // 场景未启用时 require 直接放行;启用时强制校验 token。
if ($verify !== true) { if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download')) {
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt'); $verify = aksrt_require_captcha('download', $cap_payload);
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]); if ($verify !== true) {
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
}
} }
} }
+9 -9
View File
@@ -95,15 +95,15 @@ class AKSRT_Signin {
} }
// 人机验证(若启用) // 人机验证(若启用)
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('signin')) { if (function_exists('aksrt_require_captcha')) {
$body = $request->get_json_params(); $body = $request->get_json_params();
$cap_payload = isset($body['captcha']) && is_array($body['captcha']) ? $body['captcha'] : []; $cap_payload = is_array($body) && isset($body['captcha']) && is_array($body['captcha'])
if (function_exists('aksrt_verify_captcha')) { ? $body['captcha']
$verify = aksrt_verify_captcha($cap_payload); : (is_array($body) ? $body : []);
if ($verify !== true) { $verify = aksrt_require_captcha('signin', $cap_payload);
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt'); if ($verify !== true) {
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]); $msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
} return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
} }
} }
+9 -4
View File
@@ -296,10 +296,15 @@ class AKSRT_SMS {
wp_send_json_error(['message' => __('手机号格式不正确', 'aksrt')]); wp_send_json_error(['message' => __('手机号格式不正确', 'aksrt')]);
} }
// 人机验证钩子(对齐 zibll 的 man_machine_verification) $scene = sanitize_key((string) ($_POST['scene'] ?? 'register'));
$captcha_result = apply_filters('aksrt_sms_captcha_verify', true, $phone); if (!array_key_exists($scene, function_exists('aksrt_captcha_scenes') ? aksrt_captcha_scenes() : [])) {
if ($captcha_result !== true) { $scene = 'register';
wp_send_json_error(['message' => $captcha_result ?: __('人机验证失败', 'aksrt')]); }
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha($scene, wp_unslash($_POST));
if ($captcha_result !== true) {
wp_send_json_error(['message' => $captcha_result ?: __('人机验证失败', 'aksrt')]);
}
} }
$result = $this->send_code($phone); $result = $this->send_code($phone);
+6 -6
View File
@@ -1004,8 +1004,8 @@ add_action('wp_ajax_aksrt_send_email_code', function () {
wp_send_json_error( array( 'message' => __( '无效的验证场景', 'aksrt' ) ) ); wp_send_json_error( array( 'message' => __( '无效的验证场景', 'aksrt' ) ) );
} }
if ( aksrt_is_captcha_enabled_for( $scene ) ) { if ( function_exists( 'aksrt_require_captcha' ) ) {
$captcha_result = aksrt_verify_captcha( $_POST ); $captcha_result = aksrt_require_captcha( $scene, wp_unslash( $_POST ) );
if ( $captcha_result !== true ) { if ( $captcha_result !== true ) {
wp_send_json_error( array( 'message' => $captcha_result ) ); wp_send_json_error( array( 'message' => $captcha_result ) );
} }
@@ -1113,8 +1113,8 @@ add_action('wp_ajax_aksrt_change_password', function () {
} }
// 人机验证 // 人机验证
if ( aksrt_is_captcha_enabled_for( 'change_password' ) ) { if ( function_exists( 'aksrt_require_captcha' ) ) {
$captcha_result = aksrt_verify_captcha( $_POST ); $captcha_result = aksrt_require_captcha( 'change_password', wp_unslash( $_POST ) );
if ( $captcha_result !== true ) { if ( $captcha_result !== true ) {
wp_send_json_error( array( 'message' => $captcha_result ) ); wp_send_json_error( array( 'message' => $captcha_result ) );
} }
@@ -1178,8 +1178,8 @@ add_action('wp_ajax_aksrt_change_email', function () {
} }
// 人机验证 // 人机验证
if ( aksrt_is_captcha_enabled_for( 'change_email' ) ) { if ( function_exists( 'aksrt_require_captcha' ) ) {
$captcha_result = aksrt_verify_captcha( $_POST ); $captcha_result = aksrt_require_captcha( 'change_email', wp_unslash( $_POST ) );
if ( $captcha_result !== true ) { if ( $captcha_result !== true ) {
wp_send_json_error( array( 'message' => $captcha_result ) ); wp_send_json_error( array( 'message' => $captcha_result ) );
} }
@@ -886,18 +886,6 @@ trait AKSRT_Scripts_Part_1b {
); );
} }
// Slider CAPTCHA (canvas-based puzzle verification)
$slider_captcha_js = get_template_directory() . '/assets/js/slider-captcha.js';
if (file_exists($slider_captcha_js)) {
wp_enqueue_script(
'aksrt-slider-captcha',
$base_uri . '/assets/js/slider-captcha.js',
[],
(string) filemtime($slider_captcha_js),
true
);
}
// Instant page prefetch (hover to preload) // Instant page prefetch (hover to preload)
$instant_page_js = get_template_directory() . '/assets/js/instant-page.js'; $instant_page_js = get_template_directory() . '/assets/js/instant-page.js';
if (file_exists($instant_page_js)) { if (file_exists($instant_page_js)) {
@@ -182,63 +182,47 @@ class AKSRT_Settings_Capabilities {
} }
/** /**
* 获取"网站安全"设置 * 获取"网站安全"设置(人机验证见 aksrt_get_captcha_status_summary)。
*/ */
public static function get_security_settings(): array { public static function get_security_settings(): array {
$s = AKSRT_Settings::get_all(); $s = AKSRT_Settings::get_all();
$verification_type = sanitize_key((string) ($s['user_verification_type'] ?? 'slider')); $captcha = function_exists('aksrt_get_captcha_status_summary')
$allowed_types = ['image', 'slider', 'tcaptcha', 'geetest', 'null']; ? aksrt_get_captcha_status_summary()
if (!in_array($verification_type, $allowed_types, true)) { : ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
$verification_type = 'slider';
}
return [ return [
'verification_type' => $verification_type, 'captcha' => $captcha,
'verification_comment_s' => !empty($s['verification_comment_s']), 'brush_limit_post' => [
'verification_newposts_s' => !empty($s['verification_newposts_s']),
'verification_bbspost_s' => !empty($s['verification_bbspost_s']),
'verification_links_s' => !empty($s['verification_links_s'] ?? true),
'verification_signin_exclude' => sanitize_text_field((string) ($s['verification_signin_exclude'] ?? '')),
'geetest_enabled' => !empty($s['geetest_enabled']),
'geetest_id' => sanitize_text_field((string) ($s['geetest_id'] ?? '')),
'geetest_key' => sanitize_text_field((string) ($s['geetest_key'] ?? '')),
'tcaptcha_enabled' => !empty($s['tcaptcha_enabled']),
'tcaptcha_appid' => sanitize_text_field((string) ($s['tcaptcha_appid'] ?? '')),
'tcaptcha_secret_key' => sanitize_text_field((string) ($s['tcaptcha_secret_key'] ?? '')),
'brush_limit_post' => [
'enabled' => !empty($s['brush_limit_post_enabled']), 'enabled' => !empty($s['brush_limit_post_enabled']),
'minutes_10' => (int) ($s['brush_limit_post_minutes_10'] ?? 20), 'minutes_10' => (int) ($s['brush_limit_post_minutes_10'] ?? 20),
'day_1' => (int) ($s['brush_limit_post_day_1'] ?? 50), 'day_1' => (int) ($s['brush_limit_post_day_1'] ?? 50),
], ],
'brush_limit_comment' => [ 'brush_limit_comment' => [
'enabled' => !empty($s['brush_limit_comment_enabled']), 'enabled' => !empty($s['brush_limit_comment_enabled']),
'minutes_10' => (int) ($s['brush_limit_comment_minutes_10'] ?? 20), 'minutes_10' => (int) ($s['brush_limit_comment_minutes_10'] ?? 20),
'day_1' => (int) ($s['brush_limit_comment_day_1'] ?? 100), 'day_1' => (int) ($s['brush_limit_comment_day_1'] ?? 100),
], ],
'brush_limit_upload' => [ 'brush_limit_upload' => [
'enabled' => !empty($s['brush_limit_upload_enabled']), 'enabled' => !empty($s['brush_limit_upload_enabled']),
'minutes_10' => (int) ($s['brush_limit_upload_minutes_10'] ?? 50), 'minutes_10' => (int) ($s['brush_limit_upload_minutes_10'] ?? 50),
'day_1' => (int) ($s['brush_limit_upload_day_1'] ?? 200), 'day_1' => (int) ($s['brush_limit_upload_day_1'] ?? 200),
], ],
'brush_limit_register' => [ 'brush_limit_register' => [
'enabled' => !empty($s['brush_limit_register_enabled']), 'enabled' => !empty($s['brush_limit_register_enabled']),
'minutes_10' => (int) ($s['brush_limit_register_minutes_10'] ?? 20), 'minutes_10' => (int) ($s['brush_limit_register_minutes_10'] ?? 20),
'day_1' => (int) ($s['brush_limit_register_day_1'] ?? 100), 'day_1' => (int) ($s['brush_limit_register_day_1'] ?? 100),
], ],
'brush_limit_order' => [ 'brush_limit_order' => [
'enabled' => !empty($s['brush_limit_order_enabled']), 'enabled' => !empty($s['brush_limit_order_enabled']),
'minutes_10' => (int) ($s['brush_limit_order_minutes_10'] ?? 30), 'minutes_10' => (int) ($s['brush_limit_order_minutes_10'] ?? 30),
'day_1' => (int) ($s['brush_limit_order_day_1'] ?? 100), 'day_1' => (int) ($s['brush_limit_order_day_1'] ?? 100),
], ],
'error_frequency_limit' => !empty($s['error_frequency_limit'] ?? true), 'error_frequency_limit' => !empty($s['error_frequency_limit'] ?? true),
'user_disable_admin' => !empty($s['user_disable_admin'] ?? true),
'disabled_pingback' => !empty($s['disabled_pingback'] ?? true),
'disabled_xmlrpc' => !empty($s['disabled_xmlrpc'] ?? true),
'close_attachment_page' => !empty($s['close_attachment_page'] ?? true),
]; ];
} }
/** /**
* 获取"系统工具"设置 * 获取"系统工具"设置
*/ */