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:
@@ -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)"
|
||||
/>
|
||||
<n-button v-if="model.access_key_secret === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('access_key_secret')">清除</n-button>
|
||||
</n-form-item>
|
||||
<n-alert type="warning" style="margin-bottom: 8px;">
|
||||
请使用 RAM 子账号密钥并授权 <code>AliyunYundunAFSFullAccess</code> 权限,切勿使用主账号密钥。
|
||||
@@ -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
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
<n-input v-model:value="model.site_key" placeholder="Cloudflare Turnstile Site Key" autocomplete="off" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Secret Key">
|
||||
<n-input v-model:value="model.secret_key" type="password" show-password-on="click" autocomplete="current-password" />
|
||||
</n-form-item>
|
||||
<n-form-item label="自定义域名">
|
||||
<n-input v-model:value="model.domain" placeholder="留空使用默认域名" autocomplete="url" />
|
||||
<n-input v-model:value="model.secret_key" type="password" show-password-on="click" autocomplete="new-password" @update:value="onSecretInput('secret_key', $event)" />
|
||||
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item label="主题">
|
||||
<n-select v-model:value="model.theme" :options="themeOptions" clearable />
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<n-input v-model:value="model.captcha_id" placeholder="极验 GeeTest CaptchaID" autocomplete="off" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Captcha Key">
|
||||
<n-input v-model:value="model.captcha_key" type="password" show-password-on="click" autocomplete="current-password" />
|
||||
<n-input v-model:value="model.captcha_key" type="password" show-password-on="click" autocomplete="new-password" @update:value="onSecretInput('captcha_key', $event)" />
|
||||
<n-button v-if="model.captcha_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('captcha_key')">清除</n-button>
|
||||
</n-form-item>
|
||||
<n-tag v-if="isConfigured" type="success" size="small">✓ 已完整配置</n-tag>
|
||||
<n-tag v-else type="warning" size="small">未完成配置</n-tag>
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<n-input v-model:value="model.secret_id" placeholder="腾讯云 API SecretId" autocomplete="off" />
|
||||
</n-form-item>
|
||||
<n-form-item label="SecretKey">
|
||||
<n-input v-model:value="model.secret_key" type="text" placeholder="腾讯云 API SecretKey" autocomplete="off" show-password-on="click" />
|
||||
<n-input v-model:value="model.secret_key" type="password" placeholder="腾讯云 API SecretKey" autocomplete="new-password" show-password-on="click" @update:value="onSecretInput('secret_key', $event)" />
|
||||
<n-button v-if="model.secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('secret_key')">清除</n-button>
|
||||
</n-form-item>
|
||||
|
||||
<n-divider title-placement="left">验证码密钥</n-divider>
|
||||
@@ -17,7 +18,8 @@
|
||||
<n-input v-model:value="model.app_id" placeholder="从验证码控制台获取 CaptchaAppId" autocomplete="off" />
|
||||
</n-form-item>
|
||||
<n-form-item label="AppSecretKey">
|
||||
<n-input v-model:value="model.app_secret_key" type="text" placeholder="从验证码控制台获取 AppSecretKey" autocomplete="off" show-password-on="click" />
|
||||
<n-input v-model:value="model.app_secret_key" type="password" placeholder="从验证码控制台获取 AppSecretKey" autocomplete="new-password" show-password-on="click" @update:value="onSecretInput('app_secret_key', $event)" />
|
||||
<n-button v-if="model.app_secret_key === '***'" size="tiny" type="error" ghost style="margin-left:8px" @click="clearSecret('app_secret_key')">清除</n-button>
|
||||
<template #feedback>请从「验证管理 → 基础配置」完整复制,注意区分 I/l/1/O/0</template>
|
||||
</n-form-item>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user