feat(captcha): 打通人机验证 11 个场景的前后端完整链路
- 统一验证入口 aksrt_require_captcha(),返回 true|WP_Error,启用场景配置缺失时 fail-closed 拒绝 - 前端公共 API 重构为 window.AKSRTCaptcha(verify/collect/reset/run),单飞锁防并发重复验证,一次性 proof 绑定浏览器 cookie+场景+服务商+主机名 - challenge/exchange REST 端点:challenge 原子占用防票据放大厂商调用,增加频率限制 - 新增 OAuth bind pass:服务端签发 5 分钟一次性令牌,绑定用户+服务商+浏览器,微信扫码延迟消费 - 覆盖场景:评论、登录、注册、绑定第三方、绑定手机、找回密码、修改密码、更改邮箱、资料下载、签到、前台投稿 - 移除 Service 层重复验证与 _auth_code_verified/_defer_login 等客户端可伪造标志,删除下载共享 pass 死代码 - 修复腾讯 TC3 签名 scope、Cloudflare action/cdata/hostname 严格校验、阿里云验签错误码映射 - 后台 Schema 保存对 secret 字段做掩码/保留合并;导入导出过滤 aksrt_captcha_config - 重建 public bundle 与 admin SPA;新增 captcha 运行时/安全/业务契约测试与 harness
This commit is contained in:
1 parent
d2e95acd75
commit
ff4ec1eb43
45 files changed
+2694
-1435
No files matched your search
+25
-83
@@ -192,8 +192,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 执行实际的 AJAX 请求 — local insert instead of full-page re-fetch
|
||||
function performSubmit() {
|
||||
// 执行实际的 REST 请求 — local insert instead of full-page re-fetch
|
||||
function performSubmit(captchaPayload) {
|
||||
setLoading(true);
|
||||
|
||||
var formData = collectFormData();
|
||||
@@ -205,6 +205,7 @@
|
||||
// Try REST endpoint for local-insert HTML response
|
||||
var restBody = {};
|
||||
formData.forEach(function (v, k) { restBody[k] = v; });
|
||||
Object.assign(restBody, captchaPayload || {});
|
||||
if (isEdit) {
|
||||
restBody.comment_id = parseInt(editId.value, 10);
|
||||
restBody.content = restBody.comment || restBody.content || '';
|
||||
@@ -245,15 +246,6 @@
|
||||
|
||||
var replyInfo = document.getElementById('comment-reply-info');
|
||||
if (replyInfo) replyInfo.style.display = 'none';
|
||||
// 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) {
|
||||
@@ -282,14 +274,6 @@
|
||||
|
||||
if (json && json.code) {
|
||||
notyf(json.message || '评论提交失败', 'danger');
|
||||
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;
|
||||
@@ -299,9 +283,14 @@
|
||||
setLoading(false);
|
||||
setTimeout(function () { reloadCommentList(); }, 500);
|
||||
})
|
||||
.catch(function () {
|
||||
// Fallback: submit to wp-comments-post.php directly
|
||||
performLegacySubmit();
|
||||
.catch(function (error) {
|
||||
console.error('[AKSRT Comment] 提交失败:', error);
|
||||
notyf((error && error.message) || '网络连接失败,请检查网络后重试', 'danger');
|
||||
setLoading(false);
|
||||
})
|
||||
.finally(function () {
|
||||
if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset('comment', form);
|
||||
if (!countdownTimer && isSubmitting) setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -373,44 +362,6 @@
|
||||
placeholder.remove();
|
||||
}
|
||||
}
|
||||
// Legacy fallback: POST to wp-comments-post.php
|
||||
function performLegacySubmit() {
|
||||
var formData = collectFormData();
|
||||
var actionUrl = form.getAttribute('action') || '/wp-comments-post.php';
|
||||
|
||||
fetch(actionUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.text().then(function (text) {
|
||||
return { status: response.status, text: text, ok: response.ok };
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
handleResponse(result.text, result.status);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error('[AKSRT Comment] 提交失败:', error);
|
||||
|
||||
if (error.message === 'Failed to fetch' || error.name === 'TypeError') {
|
||||
notyf('网络连接失败,请检查网络后重试', 'danger');
|
||||
} else {
|
||||
notyf(error.message || '评论提交失败,请重试', 'danger');
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
})
|
||||
.finally(function () {
|
||||
// 兜底:避免异常分支漏掉状态恢复导致按钮卡在"发送中"
|
||||
if (!countdownTimer && isSubmitting) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 核心表单提交事件处理器
|
||||
function handleSubmit(e) {
|
||||
// 如果已经在提交中或倒计时中,防止重复提交
|
||||
@@ -423,29 +374,20 @@
|
||||
// 阻止默认表单提交
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
// 检查验证码状态(如果有验证码组件)
|
||||
var captchaScene = window.AKSRTCaptchaState &&
|
||||
window.AKSRTCaptchaState.scenes &&
|
||||
window.AKSRTCaptchaState.scenes.comment;
|
||||
|
||||
if (captchaScene && typeof captchaScene.verify === 'function') {
|
||||
// 有验证码:先触发验证流程
|
||||
setLoading(true);
|
||||
|
||||
captchaScene.verify(function (verified) {
|
||||
if (verified !== false) {
|
||||
// 验证通过,执行 AJAX 提交
|
||||
performSubmit();
|
||||
} else {
|
||||
// 验证失败
|
||||
notyf('人机验证未通过,请重试', 'warning');
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 无验证码或已经验证过:直接提交
|
||||
performSubmit();
|
||||
}
|
||||
var captcha = window.AKSRTCaptcha;
|
||||
setLoading(true);
|
||||
(captcha ? captcha.run('comment', form) : Promise.resolve({})).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
notyf('人机验证未通过,请重试', 'warning');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
performSubmit(captchaPayload);
|
||||
}).catch(function () {
|
||||
notyf('人机验证未通过,请重试', 'warning');
|
||||
setLoading(false);
|
||||
if (captcha) captcha.reset('comment', form);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user