Files
wordpress-theme-aksrt/.zcode/plans/plan-sess_6a5711b4-7c56-4453-a8c1-805094668edc.md
T

7.8 KiB
Raw Blame History

第三方人机验证优化改造计划

约束:不写双路/兼容迁移代码;顺手清理死代码;不做真实视觉效果验收(用语法检查、契约测试、构建产物校验代替)。 已确认决策:范围全做(P0+P1+P2);下载场景 pass 放行 10 分钟;凭据不齐时仅加后台警告、前台保持放行。

改动文件总览

文件 动作
inc/modules/captcha.php 重写校验层 + 瘦身渲染 + 后台警告 + 静态缓存
assets/js/captcha-front.js 新建(从内联脚本迁移,~340 行)
assets/css/shared/captcha.css 保留不动(改为进 bundle)
tools/build-public-assets.mjs publicScripts 加 captcha-front.js;public.css 加 captcha.css
src/Admin/Schema/SchemaPermissions.php 删除 'mode' 死字段(L32-34)
assets/admin/src/schema/sections/permissions/index.js 删除 'mode' 死字段(L18 defaults、L50-56)
src/Admin/SchemaProvider.php L403 白名单删 'mode';保存后 flush 配置静态缓存
tests/widget-system/captcha-cloud-contract.test.cjs 新建契约测试

不动的文件及原因:aksrt-header-userbox.php(toast-contract 测试锁定 signin 接线)、data-aksrt-download-captcha 挂载点(templates/parts/resource-download-box.php:467、inc/helpers/aksrt-pay.php:1586)、所有业务 JS(auth/signin/comments/cms-download/user-center/front-submit 只消费 AKSRTCaptchaState.scenes[scene] 与 mount 内 token input,接口不变即零改动)。

1. inc/modules/captcha.php — 校验层

P0-1 腾讯云改走 WP HTTP API:aksrt_verify_tencent() 删除 curl 块(L374-390,含 CURLOPT_SSL_VERIFYPEER => false),改用 wp_remote_post('https://captcha.tencentcloudapi.com/', ['timeout'=>5, 'headers'=>aksrt_tencent_tc3_sign(...)生成的一维数组, 'body'=>$payload]),与另外三家一致。签名函数不动。

P1-5 错误信息收敛:四家服务商的传输层错误(wp_remote_post 返回 WP_Error、响应不可解析)统一 error_log('[AKSRT Captcha] {provider}: ' . 详情) 后返回通用文案 __('人机验证服务暂不可用,请稍后重试', 'aksrt');保留对用户有行动意义的映射文案(腾讯 CaptchaCode 表、阿里 F0xx 表)。腾讯 API Error 分支(签名错误等)也走 log+通用文案。

P1-4:阿里云 SignatureNonce 由 uniqid('', true) 改 wp_generate_uuid4()。

P1-8:四家 'timeout' 统一 5 秒(现 10 秒)。

P1-6 Turnstile 绑定场景:aksrt_verify_captcha($post_data, $scene = '') 增加第二参数(唯一调用方是 aksrt_require_captcha,无散落调用点);Cloudflare 成功响应校验:action !== 'aksrt-verify' 或 cdata !== $scene(存在才比对,lenient)→ 失败。客户端配套见 §2。

P0-2 下载 pass 放行(10 分钟):aksrt_require_captcha() 内,scene 命中放行名单时先查 transient、验证通过后写 transient:

  • aksrt_captcha_pass_ttl($scene):仅 download 返回 10 * MINUTE_IN_SECONDS,其余返回 0(单一代码路径,无散落分支)
  • key = 'aksrt_captcha_pass_' . md5($scene . '|' . get_current_user_id() . '|' . aksrt_get_client_ip() . '|' . md5(UA)),仿 aksrt_auth_rate_ 既有模式

2. assets/js/captcha-front.js — 新建(P2-9 + P1-7 客户端)

把 aksrt_render_captcha() 内联 <script>(L632-969)整体迁移为静态文件,逻辑不改名不重写:loadScriptOnce、四家 load*、AKSRTCaptchaState.scenes[scene].verify/reset 注册、setField/resetVerificationState/syncDownloadSiblings 等原样搬移。仅两处行为修正:

  • 极验 onClose 空回调改为 cancelPending('已取消验证')(P1-7,与腾讯/CF 取消体验对齐)
  • Turnstile render 配置加 cdata: C.scene(P1-6 客户端配套)

激活模型:扫描 document.querySelectorAll('[data-aksrt-captcha-config]'),JSON.parse(el.getAttribute(...)) 逐个 init;DOMContentLoaded 后再补扫一次并暴露 window.AKSRTCaptchaState.rescan() 供未来 ajax 注入使用。保持 vanilla JS、无新依赖。

对应 PHP 渲染端瘦身:aksrt_render_captcha() 只输出

  1. 原 wrapper 结构不变(.aksrt-captcha-wrap + #uid box + #vid captcha_verified 隐藏域),
  2. wrapper 上挂 data-aksrt-captcha-config(esc_attr(wp_json_encode($js_config))),
  3. 删除内联 <script> 与逐次 <link rel="stylesheet">。

TOKEN_FIELDS 常量、i18n 字符串原样进 JS 文件(与仓库其它 bundle 内硬编码中文一致)。

3. 构建与后台(P2-10 + bundle 接线)

  • tools/build-public-assets.mjs:publicScripts 追加 'assets/js/captcha-front.js';cssBundles['public.css'] 追加 'assets/css/shared/captcha.css'。走既有 bundle 版本机制,无需新增 enqueue 代码。
  • SchemaPermissions.php:删除 'mode' 字段定义(L32-34,单选项 always 的死字段)。
  • assets/admin/src/schema/sections/permissions/index.js:删除 mode 的 defaults(L18)与字段定义(L50-56)。
  • SchemaProvider.php L403:归一化白名单去掉 'mode';write_section_settings() 更新 aksrt_captcha_config 后调用新助手 aksrt_captcha_config_reset() 清静态缓存(function_exists 守卫,与现有 captcha 专用块并排)。
  • DB 里残留的 mode 键不做迁移清洗(读路径白名单已忽略它,符合"不做迁移"约束)。

4. inc/modules/captcha.php — 后台警告与缓存

  • P0-3:新增顶层 add_action('admin_notices', ...):manage_options 权限 + enabled 为真 + active_provider 非空 + aksrt_captcha_provider_is_configured() 为假时,输出 notice-error 横幅,文案指向「功能&权限 → 人机验证」补全配置(复用现有 misconfig 提示文案)。前台行为不变(保持放行)。
  • P2-11:aksrt_get_captcha_config() 加 static $cache;新增 aksrt_captcha_config_reset();写入路径(SchemaProvider)调用 reset。aksrt_get_captcha_status_summary() 的 12 场景循环随之受益。

5. 新契约测试 tests/widget-system/captcha-cloud-contract.test.cjs

仿 toast-contract 的正则断言风格:

  • captcha.php:不匹配 curl_init|CURLOPT_SSL_VERIFYPEER|machine-verification|slidercaptcha|imagecaptcha;匹配 wp_remote_post(≥4 处)、wp_generate_uuid4、'timeout'\s*=>\s*5、aksrt_captcha_pass_;渲染函数不匹配 /<script|<link/(内联抽取完成)
  • captcha-front.js 存在且含 AKSRTCaptchaState、data-aksrt-captcha-config
  • build-public-assets.mjs 的 publicScripts 含 captcha-front.js、public.css 含 shared/captcha.css
  • SchemaPermissions.php 与 admin index.js 的 captcha 段不匹配 mode
  • SchemaProvider.php 不匹配 'mode'

6. 验证(不做视觉验收)

  1. php -l 全部改动的 PHP 文件
  2. node --check captcha-front.js
  3. node tools/build-public-assets.mjs 重建 bundle
  4. grep 全仓清扫:无 curl_init、无 'mode'(captcha 相关文件)、无 legacy 标记
  5. node --test tests/widget-system/*.test.cjs 全量跑,预期仅存量 page-display-contract 一项失败(与本次无关,已验证在 HEAD 上同样失败)

明确不做

  • 不做视觉验收/浏览器实测(按要求)
  • 不写新旧配置迁移、不清洗 DB 残留 mode 键
  • 不动腾讯 trerror 容灾票据策略(维持拒绝)
  • 不做阿里云签名 V3 升级、Turnstile appearance/retry 配置项(列为后续可选项)
  • 不动 ExtensionController 的 extensions 状态端点(SPA 未消费属另一话题)

建议提交拆分(如需提交)

  1. fix(captcha): use WP HTTP API with TLS verification for tencent
  2. feat(captcha): short-lived download pass + turnstile binding + error hardening
  3. refactor(captcha): extract inline runtime to captcha-front.js bundle
  4. chore(captcha): drop dead mode field, config cache, contract tests