From ff4ec1eb433a1225364a37cd30be069b9858a5c9 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 2 Sep 2026 13:03:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(captcha):=20=E6=89=93=E9=80=9A=E4=BA=BA?= =?UTF-8?q?=E6=9C=BA=E9=AA=8C=E8=AF=81=2011=20=E4=B8=AA=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E7=9A=84=E5=89=8D=E5=90=8E=E7=AB=AF=E5=AE=8C=E6=95=B4=E9=93=BE?= =?UTF-8?q?=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一验证入口 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 --- .gitignore | 3 + assets/admin/src/api/modules/schema.js | 6 + .../src/components/captcha/AliyunConfig.vue | 14 +- .../components/captcha/CloudflareConfig.vue | 16 +- .../src/components/captcha/GeetestConfig.vue | 13 +- .../src/components/captcha/TencentConfig.vue | 16 +- assets/admin/src/pages/SchemaPage.vue | 137 +-- .../src/schema/sections/permissions/index.js | 13 +- assets/js/auth.js | 77 +- assets/js/build/public.js | 805 ++++++++++-------- assets/js/captcha-front.js | 697 +++++++++------ assets/js/cms-download.js | 89 +- assets/js/core/comments.js | 108 +-- assets/js/front-submit.js | 42 +- assets/js/routes/user-center.js | 241 ++++-- assets/js/signin.js | 63 +- .../settings-bindings.php | 7 +- inc/modules/captcha.php | 625 ++++++++++---- inc/modules/user-parts/account-ajax.php | 21 +- src/Admin/ImportExportController.php | 54 +- src/Admin/ImportExportControllerPart1.php | 14 +- src/Admin/ImportExportControllerPart2.php | 2 +- src/Admin/Schema/SchemaPermissions.php | 34 +- src/Admin/SchemaProvider.php | 126 ++- .../UserControllerAuthPhoneOperations.php | 87 +- src/Auth/Moderation.php | 25 +- src/Auth/OAuth.php | 129 ++- src/Auth/Service.php | 9 +- src/Auth/ServicePart1a.php | 21 +- src/Auth/ServicePart1b.php | 13 +- src/Auth/WeixinGzh.php | 49 +- src/Content/Download.php | 19 +- src/Core/CommentActions.php | 33 +- src/Core/FrontSubmit.php | 13 +- src/Credits/Signin.php | 21 +- src/Settings/Settings.php | 21 +- src/Sms/SMS.php | 56 -- templates/parts/auth-content.php | 5 +- .../auth-source-contract.test.cjs | 12 +- .../captcha-business-contract.test.cjs | 64 ++ .../captcha-runtime-harness.test.cjs | 109 +++ .../captcha-security-harness.php | 181 ++++ .../front-submit-editor-contract.test.cjs | 12 + .../oauth-security-contract.test.cjs | 14 + .../widget-system/oauth-security-harness.php | 13 + 45 files changed, 2694 insertions(+), 1435 deletions(-) create mode 100644 tests/widget-system/captcha-business-contract.test.cjs create mode 100644 tests/widget-system/captcha-runtime-harness.test.cjs create mode 100644 tests/widget-system/captcha-security-harness.php diff --git a/.gitignore b/.gitignore index 1e827085..63bbc3c9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ !/tests/widget-system/page-display-save-harness.php !/tests/widget-system/admin-ui-contract.test.cjs !/tests/widget-system/auth-source-contract.test.cjs +!/tests/widget-system/captcha-business-contract.test.cjs +!/tests/widget-system/captcha-security-harness.php +!/tests/widget-system/captcha-runtime-harness.test.cjs !/tests/widget-system/oauth-security-contract.test.cjs !/tests/widget-system/oauth-security-harness.php !/tests/widget-system/user-badges-contract.test.cjs diff --git a/assets/admin/src/api/modules/schema.js b/assets/admin/src/api/modules/schema.js index 98789349..6a4004c2 100644 --- a/assets/admin/src/api/modules/schema.js +++ b/assets/admin/src/api/modules/schema.js @@ -3,6 +3,12 @@ export const schemaApiMethods = { getSchemaPage(domain, section) { return this.request(`/settings/schema/${this.normalizeDomainKey(domain)}/${encodeURIComponent(section)}`) }, + testCaptcha(provider, token) { + return this.request('/captcha/test', { + method: 'POST', + body: JSON.stringify({ provider, token }), + }) + }, saveSchemaPage(domain, section, data, version = '1.0.0') { return this.request(`/settings/schema/${this.normalizeDomainKey(domain)}/${encodeURIComponent(section)}`, { method: 'POST', diff --git a/assets/admin/src/components/captcha/AliyunConfig.vue b/assets/admin/src/components/captcha/AliyunConfig.vue index f216b1ac..4c1f5d1c 100644 --- a/assets/admin/src/components/captcha/AliyunConfig.vue +++ b/assets/admin/src/components/captcha/AliyunConfig.vue @@ -40,9 +40,11 @@ v-model:value="model.access_key_secret" type="password" show-password-on="click" - autocomplete="current-password" + autocomplete="new-password" placeholder="阿里云 RAM 用户 AccessKey Secret" + @update:value="onSecretInput('access_key_secret', $event)" /> + 清除 请使用 RAM 子账号密钥并授权 AliyunYundunAFSFullAccess 权限,切勿使用主账号密钥。 @@ -58,6 +60,16 @@ import { computed } from 'vue' const model = defineModel({ type: Object, default: () => ({}) }) +const emit = defineEmits(['clear-secret', 'restore-secret']) + +function clearSecret(field) { + emit('clear-secret', field) + model.value = { ...model.value, [field]: '' } +} + +function onSecretInput(field, value) { + if (value !== '') emit('restore-secret', field) +} const isConfigured = computed(() => { const c = model.value diff --git a/assets/admin/src/components/captcha/CloudflareConfig.vue b/assets/admin/src/components/captcha/CloudflareConfig.vue index 38b3bf9f..fe78c690 100644 --- a/assets/admin/src/components/captcha/CloudflareConfig.vue +++ b/assets/admin/src/components/captcha/CloudflareConfig.vue @@ -5,10 +5,8 @@ - - - - + + 清除 @@ -26,6 +24,16 @@ import { computed } from 'vue' const model = defineModel({ type: Object, default: () => ({}) }) +const emit = defineEmits(['clear-secret', 'restore-secret']) + +function clearSecret(field) { + emit('clear-secret', field) + model.value = { ...model.value, [field]: '' } +} + +function onSecretInput(field, value) { + if (value !== '') emit('restore-secret', field) +} const isConfigured = computed(() => { const c = model.value diff --git a/assets/admin/src/components/captcha/GeetestConfig.vue b/assets/admin/src/components/captcha/GeetestConfig.vue index caf96c8f..e367eb15 100644 --- a/assets/admin/src/components/captcha/GeetestConfig.vue +++ b/assets/admin/src/components/captcha/GeetestConfig.vue @@ -5,7 +5,8 @@ - + + 清除 ✓ 已完整配置 未完成配置 @@ -17,6 +18,16 @@ import { computed } from 'vue' const model = defineModel({ type: Object, default: () => ({ product: 'bind' }) }) +const emit = defineEmits(['clear-secret', 'restore-secret']) + +function clearSecret(field) { + emit('clear-secret', field) + model.value = { ...model.value, [field]: '' } +} + +function onSecretInput(field, value) { + if (value !== '') emit('restore-secret', field) +} const isConfigured = computed(() => { const c = model.value diff --git a/assets/admin/src/components/captcha/TencentConfig.vue b/assets/admin/src/components/captcha/TencentConfig.vue index 9effbfb5..9ccf6041 100644 --- a/assets/admin/src/components/captcha/TencentConfig.vue +++ b/assets/admin/src/components/captcha/TencentConfig.vue @@ -9,7 +9,8 @@ - + + 清除 验证码密钥 @@ -17,7 +18,8 @@ - + + 清除 @@ -34,6 +36,16 @@ import { computed } from 'vue' const model = defineModel({ type: Object, default: () => ({}) }) +const emit = defineEmits(['clear-secret', 'restore-secret']) + +function clearSecret(field) { + emit('clear-secret', field) + model.value = { ...model.value, [field]: '' } +} + +function onSecretInput(field, value) { + if (value !== '') emit('restore-secret', field) +} const isConfigured = computed(() => { const c = model.value diff --git a/assets/admin/src/pages/SchemaPage.vue b/assets/admin/src/pages/SchemaPage.vue index 56bd0886..9f650219 100644 --- a/assets/admin/src/pages/SchemaPage.vue +++ b/assets/admin/src/pages/SchemaPage.vue @@ -19,28 +19,28 @@ v-if="isPermissionsLogin" #field-tencent="{ value, setValue }" > - +