人机验证增加阿里云验证码 2.0

沿用腾讯天御验证码引入的多服务商模型:aliyun_captcha_enabled 作为独立
开关,与 Cloudflare Turnstile、腾讯天御三方互斥(保存校验 + 运行时
CAPTCHA_PROVIDER_CONFLICT)。后台「安全与认证」合并为单张人机验证卡片:
总开关 + 服务商单选(Turnstile / 腾讯天御 / 阿里云),选中即启用该家并
关闭其它,落库仍是三个独立开关键,由前端映射保证互斥。

阿里云侧同时支持 aliyun 中国站与国际站(alibabacloud.com):两站前端脚本、
region 取值与服务端 API 完全一致,仅账号与 AccessKey 相互独立,因此由
「服务地域」决定线路即可——中国内地走 captcha.cn-shanghai.aliyuncs.com,
非中国内地(新加坡)走 captcha.ap-southeast-1.aliyuncs.com,AccessKey
取自持有该实例的账号,无需在配置中区分站点。

- AliyunCaptchaService 对称 TencentCaptchaService:服务端校验走官方 SDK
  VerifyIntelligentCaptcha,调用异常按 fail-closed 拦截,与 Turnstile
  网络错误行为对称;保存设置时真实探测 AK/SK 有效性
- 保护面对齐腾讯扩展入口:VerifyTencentCaptchaIfEnabled 通用化为
  VerifyActionCaptchaIfEnabled,OAuth 登录启动、passkey 登录在阿里云
  启用时同样拦截;Turnstile 维持既有覆盖不扩大
- 前端 AliyunCaptchaWidget 为表单内预验证按钮(popup 模式),同时暴露
  verify() 供 OAuth 启动、passkey 等动作入口程序化弹窗;未预验证直接
  提交时弹窗兜底。SDK 按钮绑定异步完成,弹窗未出现前按 tick 重试触发,
  并轮询弹窗可见性识别用户关闭
- captchaVerifyParam 复用 turnstile_token 请求字段提交;公开设置下发
  aliyun_captcha_enabled / scene_id / prefix / region
- CSP 放行验证码 CDN:script-src/style-src 加 *.alicdn.com
This commit is contained in:
feeeei
2026-08-04 20:57:15 +08:00
parent d431c57f2e
commit 26e0a89323
57 changed files with 2445 additions and 302 deletions
+12
View File
@@ -464,6 +464,12 @@ export interface SystemSettings {
tencent_captcha_app_secret_key_configured: boolean;
tencent_captcha_cloud_secret_id_configured: boolean;
tencent_captcha_cloud_secret_key_configured: boolean;
aliyun_captcha_enabled: boolean;
aliyun_captcha_access_key_id: string;
aliyun_captcha_access_key_secret_configured: boolean;
aliyun_captcha_scene_id: string;
aliyun_captcha_prefix: string;
aliyun_captcha_region: string;
api_key_acl_trust_forwarded_ip: boolean;
forwarded_client_ip_headers: string[];
@@ -783,6 +789,12 @@ export interface UpdateSettingsRequest {
tencent_captcha_app_secret_key?: string;
tencent_captcha_cloud_secret_id?: string;
tencent_captcha_cloud_secret_key?: string;
aliyun_captcha_enabled?: boolean;
aliyun_captcha_access_key_id?: string;
aliyun_captcha_access_key_secret?: string;
aliyun_captcha_scene_id?: string;
aliyun_captcha_prefix?: string;
aliyun_captcha_region?: string;
api_key_acl_trust_forwarded_ip?: boolean;
forwarded_client_ip_headers?: string[];
linuxdo_connect_enabled?: boolean;
+2 -2
View File
@@ -14,7 +14,7 @@ import type {
SendVerifyCodeRequest,
SendVerifyCodeResponse,
PublicSettings,
TencentCaptchaRequestProof,
ActionCaptchaRequestProof,
TotpLoginResponse,
TotpLogin2FARequest
} from '@/types'
@@ -51,7 +51,7 @@ export function buildOAuthLoginStartURL(request: OAuthLoginStart): string {
export async function startOAuthLogin(
request: OAuthLoginStart,
proof: TencentCaptchaRequestProof
proof: ActionCaptchaRequestProof
): Promise<OAuthLoginStartResponse> {
const { data } = await apiClient.post<OAuthLoginStartResponse>(
`/auth/oauth/${request.provider}/start`,
+2 -2
View File
@@ -1,5 +1,5 @@
import { apiClient } from './client'
import type { AuthResponse, TencentCaptchaRequestProof } from '@/types'
import type { ActionCaptchaRequestProof, AuthResponse } from '@/types'
export interface PasskeyCredentialSummary {
id: number
@@ -104,7 +104,7 @@ function serializeAssertionCredential(credential: PublicKeyCredential): Record<s
}
}
async function login(proof?: TencentCaptchaRequestProof): Promise<AuthResponse> {
async function login(proof?: ActionCaptchaRequestProof): Promise<AuthResponse> {
requirePasskeySupport()
const { data: begin } = proof
? await apiClient.post<CeremonyOptionsResponse>('/auth/passkey/login/begin', proof)