Merge pull request 'fix(captcha): close human-verification loop end-to-end' (#108) from worktree-captcha-wire-through into main
Reviewed-on: CYL/wordpress-theme-aksrt#108
This commit is contained in:
@@ -120,7 +120,7 @@ async function load() {
|
||||
form.comment_require_login = !!comment.comment_require_login
|
||||
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.rateLimitEnabled = !!moderation.rate_limit_enabled
|
||||
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: {
|
||||
key: 'permissions.download',
|
||||
title: '下载权限',
|
||||
icon: 'fas fa-download',
|
||||
description: '控制资源下载的权限和限制。',
|
||||
defaults: {
|
||||
download_require_login: false,
|
||||
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: '下载前需要验证码' },
|
||||
],
|
||||
},
|
||||
],
|
||||
description: '下载门槛由 DownloadPermissions 页维护(cms_features);人机验证服务商在登录权限页。',
|
||||
defaults: {},
|
||||
groups: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -231,8 +231,11 @@
|
||||
const state = window.AKSRTCaptchaState;
|
||||
const scene = state && state.scenes && state.scenes.download;
|
||||
if (!scene || typeof scene.verify !== 'function') {
|
||||
// captcha 组件还未就绪,直接发,由后端兜底报错
|
||||
callback();
|
||||
// captcha 组件未就绪:不直发,避免绕过服务端门槛
|
||||
if (window.console && console.warn) {
|
||||
console.warn('[AKSRT] download captcha not ready');
|
||||
}
|
||||
alert(tt('captcha_loading', '验证码组件加载中,请稍候再试'));
|
||||
return;
|
||||
}
|
||||
scene.verify(function () { callback(); });
|
||||
|
||||
@@ -232,9 +232,15 @@
|
||||
|
||||
var replyInfo = document.getElementById('comment-reply-info');
|
||||
if (replyInfo) replyInfo.style.display = 'none';
|
||||
// Reset captcha
|
||||
var captchaImg = form.querySelector('.imagecaptcha img');
|
||||
if (captchaImg) captchaImg.click();
|
||||
// Reset captcha after successful submit
|
||||
try {
|
||||
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
|
||||
if (json.wait_time && json.wait_time > 0) {
|
||||
@@ -251,6 +257,14 @@
|
||||
|
||||
if (json && json.code) {
|
||||
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);
|
||||
else setLoading(false);
|
||||
return;
|
||||
|
||||
+105
-64
@@ -163,9 +163,33 @@
|
||||
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) {
|
||||
var payload = serializeForm(form);
|
||||
if (!payload.log || !payload.pwd) {
|
||||
if (!form.querySelector('[name="log"]') || !form.querySelector('[name="pwd"]')) {
|
||||
showMessage('login', '请输入用户名和密码', 'error');
|
||||
return;
|
||||
}
|
||||
@@ -173,76 +197,93 @@
|
||||
setLoading(form, true);
|
||||
clearMessages();
|
||||
|
||||
request('/auth/modal-login', payload)
|
||||
.then(function (result) {
|
||||
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 () {
|
||||
withCaptcha('login', form, function (payload) {
|
||||
if (!payload.log || !payload.pwd) {
|
||||
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) {
|
||||
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);
|
||||
clearMessages();
|
||||
|
||||
request('/auth/modal-register', payload)
|
||||
.then(function (result) {
|
||||
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 () {
|
||||
withCaptcha('register', form, function (payload) {
|
||||
if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
|
||||
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) {
|
||||
|
||||
+44
-24
@@ -81,11 +81,12 @@
|
||||
// 收集人机验证字段
|
||||
const form = btn.closest('form')
|
||||
const captchaData = collectCaptchaFields(form)
|
||||
const scene = btn.dataset.aksrtSmsScene || 'register'
|
||||
|
||||
fetch(REST_BASE + '/auth/send-sms-code', {
|
||||
method: 'POST',
|
||||
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(({ ok, data }) => {
|
||||
@@ -97,6 +98,12 @@
|
||||
} else {
|
||||
const msg = (data.data && data.data.message) || data.message || '发送失败'
|
||||
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
|
||||
if (remaining && remaining > 0) {
|
||||
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000))
|
||||
@@ -265,33 +272,46 @@
|
||||
body = { phone, password }
|
||||
}
|
||||
|
||||
fetch(REST_BASE + endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
showToast('登录成功', 'success')
|
||||
setTimeout(() => {
|
||||
window.location.href = data.data?.redirect || '/'
|
||||
}, 500)
|
||||
} else {
|
||||
showToast(data.message || '登录失败', 'error')
|
||||
const captchaState = window.AKSRTCaptchaState
|
||||
const doLogin = () => {
|
||||
const captchaData = collectCaptchaFields(form)
|
||||
fetch(REST_BASE + endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...body, ...captchaData }),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
showToast('登录成功', 'success')
|
||||
setTimeout(() => {
|
||||
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) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showToast('网络错误', 'error')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
|
||||
captchaState.scenes.login.verify(doLogin)
|
||||
} else {
|
||||
doLogin()
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
|
||||
@@ -173,8 +173,12 @@ class AKSRT_Admin_Extension_Controller extends AKSRT_Admin_Controller_Base {
|
||||
$email_gate_enabled = false;
|
||||
}
|
||||
|
||||
$captcha_comment_enabled = !empty($settings['captcha_comment_enabled']);
|
||||
$captcha_download_enabled = !empty($settings['captcha_download_enabled']);
|
||||
$captcha_summary = function_exists('aksrt_get_captcha_status_summary')
|
||||
? 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 = [
|
||||
'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']),
|
||||
'ip_geo_enabled' => !empty($services['ip_geo']['enabled']),
|
||||
'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,
|
||||
'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' => []];
|
||||
}
|
||||
|
||||
// Captcha
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$captcha_en = class_exists('AKSRT_Captcha') && !empty($settings['captcha_enabled']);
|
||||
// Captcha — 唯一真相源 aksrt_captcha_config
|
||||
$captcha = function_exists('aksrt_get_captcha_status_summary')
|
||||
? aksrt_get_captcha_status_summary()
|
||||
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
|
||||
$services['captcha'] = [
|
||||
'enabled' => $captcha_en,
|
||||
'provider' => sanitize_key((string) ($settings['captcha_provider'] ?? '')),
|
||||
'configured' => $captcha_en,
|
||||
'available' => $captcha_en,
|
||||
'enabled' => !empty($captcha['enabled']) && !empty($captcha['configured']),
|
||||
'provider' => (string) ($captcha['provider'] ?? ''),
|
||||
'configured' => !empty($captcha['configured']),
|
||||
'available' => !empty($captcha['configured']),
|
||||
'verified' => false,
|
||||
'transport' => 'http_api',
|
||||
'missing_fields' => [],
|
||||
'scenes' => $captcha['scenes'] ?? [],
|
||||
];
|
||||
|
||||
// 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 {
|
||||
$params = $this->get_json_params($request);
|
||||
$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')) {
|
||||
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' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'verification', 'title' => '验证设置', 'fields' => [
|
||||
['key' => 'verification_comment_s', 'label' => '评论需要验证', 'type' => 'switch', 'default' => false],
|
||||
['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' => 'captcha-note', 'title' => '人机验证', 'fields' => [
|
||||
['key' => 'captcha_redirect_hint', 'label' => '配置入口', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '人机验证(腾讯云 / 阿里云 / Cloudflare / 极验)请到「功能&权限 → 登录权限」统一配置;本页不再维护验证码字段。'],
|
||||
]],
|
||||
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
||||
['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' => '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为不限制'],
|
||||
]],
|
||||
],
|
||||
|
||||
@@ -47,12 +47,10 @@ trait AKSRT_Auth_Service_Part_1a {
|
||||
return ['success' => false, 'error' => $password_check];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('register')) {
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$captcha_result = aksrt_verify_captcha($post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('register', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,12 +152,10 @@ trait AKSRT_Auth_Service_Part_1a {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('bind')) {
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$captcha_result = aksrt_verify_captcha($post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('bind', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,12 +367,10 @@ trait AKSRT_Auth_Service_Part_1a {
|
||||
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('verify_email')) {
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$captcha_result = aksrt_verify_captcha($post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('verify_email', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,12 +411,10 @@ trait AKSRT_Auth_Service_Part_1a {
|
||||
return ['success' => false, 'error' => __('该账号已完成邮箱验证,无需重复发送', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('verify_email')) {
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$captcha_result = aksrt_verify_captcha($post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('verify_email', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,12 +110,10 @@ trait AKSRT_Auth_Service_Part_1b {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('lostpassword')) {
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$captcha_result = aksrt_verify_captcha($post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('lostpassword', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +245,13 @@ trait AKSRT_Auth_Service_Part_1b {
|
||||
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 验证码
|
||||
if (class_exists('AKSRT_SMS')) {
|
||||
$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')];
|
||||
}
|
||||
|
||||
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);
|
||||
if (!$user) {
|
||||
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
|
||||
@@ -359,6 +371,13 @@ trait AKSRT_Auth_Service_Part_1b {
|
||||
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 验证码
|
||||
if (class_exists('AKSRT_SMS')) {
|
||||
$verify_result = AKSRT_SMS::get_instance()->verify_code($phone, $code);
|
||||
|
||||
@@ -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 = [
|
||||
'user_login' => $resolved_login,
|
||||
'user_password' => (string) ($post['pwd'] ?? ''),
|
||||
|
||||
+850
-1266
File diff suppressed because it is too large
Load Diff
@@ -227,6 +227,17 @@ class AKSRT_Comment_Actions {
|
||||
], 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();
|
||||
$commentdata = [
|
||||
'comment_post_ID' => $post_id,
|
||||
@@ -263,11 +274,6 @@ class AKSRT_Comment_Actions {
|
||||
$_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);
|
||||
if (is_wp_error($comment_id)) {
|
||||
|
||||
@@ -162,16 +162,17 @@ class AKSRT_Download {
|
||||
if (is_wp_error($gate)) return $gate;
|
||||
|
||||
$paid_settings = AKSRT_Settings::get_paid_content_settings();
|
||||
$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')) {
|
||||
$need_captcha = false;
|
||||
}
|
||||
if ($need_captcha && function_exists('aksrt_verify_captcha')) {
|
||||
$need_captcha = !empty($paid_settings['download_captcha']) || !empty($meta['require_captcha']);
|
||||
// 下载双开关:cms_features.download_captcha(或单资源强制)且场景未停用。
|
||||
if ($need_captcha && function_exists('aksrt_require_captcha')) {
|
||||
$cap_payload = isset($params['captcha']) && is_array($params['captcha']) ? $params['captcha'] : [];
|
||||
$verify = aksrt_verify_captcha($cap_payload);
|
||||
if ($verify !== true) {
|
||||
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
|
||||
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
|
||||
// 场景未启用时 require 直接放行;启用时强制校验 token。
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('download')) {
|
||||
$verify = aksrt_require_captcha('download', $cap_payload);
|
||||
if ($verify !== true) {
|
||||
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
|
||||
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,15 +95,15 @@ class AKSRT_Signin {
|
||||
}
|
||||
|
||||
// 人机验证(若启用)
|
||||
if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('signin')) {
|
||||
$body = $request->get_json_params();
|
||||
$cap_payload = isset($body['captcha']) && is_array($body['captcha']) ? $body['captcha'] : [];
|
||||
if (function_exists('aksrt_verify_captcha')) {
|
||||
$verify = aksrt_verify_captcha($cap_payload);
|
||||
if ($verify !== true) {
|
||||
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
|
||||
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$body = $request->get_json_params();
|
||||
$cap_payload = is_array($body) && isset($body['captcha']) && is_array($body['captcha'])
|
||||
? $body['captcha']
|
||||
: (is_array($body) ? $body : []);
|
||||
$verify = aksrt_require_captcha('signin', $cap_payload);
|
||||
if ($verify !== true) {
|
||||
$msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt');
|
||||
return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -296,10 +296,15 @@ class AKSRT_SMS {
|
||||
wp_send_json_error(['message' => __('手机号格式不正确', 'aksrt')]);
|
||||
}
|
||||
|
||||
// 人机验证钩子(对齐 zibll 的 man_machine_verification)
|
||||
$captcha_result = apply_filters('aksrt_sms_captcha_verify', true, $phone);
|
||||
if ($captcha_result !== true) {
|
||||
wp_send_json_error(['message' => $captcha_result ?: __('人机验证失败', 'aksrt')]);
|
||||
$scene = sanitize_key((string) ($_POST['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, wp_unslash($_POST));
|
||||
if ($captcha_result !== true) {
|
||||
wp_send_json_error(['message' => $captcha_result ?: __('人机验证失败', 'aksrt')]);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->send_code($phone);
|
||||
|
||||
@@ -1004,8 +1004,8 @@ add_action('wp_ajax_aksrt_send_email_code', function () {
|
||||
wp_send_json_error( array( 'message' => __( '无效的验证场景', 'aksrt' ) ) );
|
||||
}
|
||||
|
||||
if ( aksrt_is_captcha_enabled_for( $scene ) ) {
|
||||
$captcha_result = aksrt_verify_captcha( $_POST );
|
||||
if ( function_exists( 'aksrt_require_captcha' ) ) {
|
||||
$captcha_result = aksrt_require_captcha( $scene, wp_unslash( $_POST ) );
|
||||
if ( $captcha_result !== true ) {
|
||||
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' ) ) {
|
||||
$captcha_result = aksrt_verify_captcha( $_POST );
|
||||
if ( function_exists( 'aksrt_require_captcha' ) ) {
|
||||
$captcha_result = aksrt_require_captcha( 'change_password', wp_unslash( $_POST ) );
|
||||
if ( $captcha_result !== true ) {
|
||||
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' ) ) {
|
||||
$captcha_result = aksrt_verify_captcha( $_POST );
|
||||
if ( function_exists( 'aksrt_require_captcha' ) ) {
|
||||
$captcha_result = aksrt_require_captcha( 'change_email', wp_unslash( $_POST ) );
|
||||
if ( $captcha_result !== true ) {
|
||||
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_js = get_template_directory() . '/assets/js/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 {
|
||||
$s = AKSRT_Settings::get_all();
|
||||
|
||||
$verification_type = sanitize_key((string) ($s['user_verification_type'] ?? 'slider'));
|
||||
$allowed_types = ['image', 'slider', 'tcaptcha', 'geetest', 'null'];
|
||||
if (!in_array($verification_type, $allowed_types, true)) {
|
||||
$verification_type = 'slider';
|
||||
}
|
||||
$captcha = function_exists('aksrt_get_captcha_status_summary')
|
||||
? aksrt_get_captcha_status_summary()
|
||||
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
|
||||
|
||||
return [
|
||||
'verification_type' => $verification_type,
|
||||
'verification_comment_s' => !empty($s['verification_comment_s']),
|
||||
'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' => [
|
||||
'captcha' => $captcha,
|
||||
'brush_limit_post' => [
|
||||
'enabled' => !empty($s['brush_limit_post_enabled']),
|
||||
'minutes_10' => (int) ($s['brush_limit_post_minutes_10'] ?? 20),
|
||||
'day_1' => (int) ($s['brush_limit_post_day_1'] ?? 50),
|
||||
],
|
||||
'brush_limit_comment' => [
|
||||
'brush_limit_comment' => [
|
||||
'enabled' => !empty($s['brush_limit_comment_enabled']),
|
||||
'minutes_10' => (int) ($s['brush_limit_comment_minutes_10'] ?? 20),
|
||||
'day_1' => (int) ($s['brush_limit_comment_day_1'] ?? 100),
|
||||
],
|
||||
'brush_limit_upload' => [
|
||||
'brush_limit_upload' => [
|
||||
'enabled' => !empty($s['brush_limit_upload_enabled']),
|
||||
'minutes_10' => (int) ($s['brush_limit_upload_minutes_10'] ?? 50),
|
||||
'day_1' => (int) ($s['brush_limit_upload_day_1'] ?? 200),
|
||||
],
|
||||
'brush_limit_register' => [
|
||||
'brush_limit_register' => [
|
||||
'enabled' => !empty($s['brush_limit_register_enabled']),
|
||||
'minutes_10' => (int) ($s['brush_limit_register_minutes_10'] ?? 20),
|
||||
'day_1' => (int) ($s['brush_limit_register_day_1'] ?? 100),
|
||||
],
|
||||
'brush_limit_order' => [
|
||||
'brush_limit_order' => [
|
||||
'enabled' => !empty($s['brush_limit_order_enabled']),
|
||||
'minutes_10' => (int) ($s['brush_limit_order_minutes_10'] ?? 30),
|
||||
'day_1' => (int) ($s['brush_limit_order_day_1'] ?? 100),
|
||||
],
|
||||
'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),
|
||||
'error_frequency_limit' => !empty($s['error_frequency_limit'] ?? true),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取"系统工具"设置
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user