feat: 为 Turnstile 验证添加强制执行控制功能

- 新增 TURNSTILE_ENFORCE 环境变量,可在紧急情况下跳过 BFF Turnstile 验证
- 在 CLAUDE.md 和 env.example 中添加完整的 Turnstile 配置说明
- 修改 BFF 代理逻辑,仅在启用强制执行时进行 Turnstile 验证
- 优化前端 JavaScript 代码,移除冗余的验证状态变量
- 在跳过验证时输出警告日志,便于调试和监控
This commit is contained in:
Abner
2025-11-24 17:38:12 +08:00
parent eae40231bd
commit 05318df88a
4 changed files with 32 additions and 20 deletions

View File

@@ -601,8 +601,11 @@ ALLOWED_ORIGIN=https://esim.cosr.eu.org
# 🔐 生成强随机密钥: openssl rand -hex 32
ACCESS_KEY=your_strong_random_key_here
# Cloudflare Turnstile 站点密钥 (可选)
# Cloudflare Turnstile 配置 (可选)
TURNSTILE_SITE_KEY=0x4AAAAAAA...
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000
# 设为 false 可在紧急情况下跳过 BFF Turnstile 校验
TURNSTILE_ENFORCE=true
# Node 环境
NODE_ENV=development # development | production

View File

@@ -17,6 +17,12 @@ LOG_LEVEL=info
CORS_ORIGIN=*
ALLOWED_ORIGIN=https://esim.cosr.eu.org
# Cloudflare Turnstile 配置
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# 设为 false 可在紧急情况下跳过 BFF Turnstile 校验(默认 true
TURNSTILE_ENFORCE=true
# 受保护函数访问密钥(要求调用方在 Header x-esim-key 或 body.authKey / ?authKey 携带匹配值)
# ⚠️ 必填Server 与 Functions/BFF 共享的访问密钥
# 🔐 生成强随机密钥: openssl rand -hex 32

View File

@@ -31,9 +31,13 @@ export default async (request, context) => {
});
}
// Turnstile 校验(可选,若配置了 TURNSTILE_SECRET_KEY 则启用
// Turnstile 校验(可选,可通过 TURNSTILE_ENFORCE=false 关闭
const turnstileSecret = (typeof Deno !== 'undefined' && Deno.env && Deno.env.get('TURNSTILE_SECRET_KEY')) || '';
if (turnstileSecret) {
const turnstileEnforceRaw = (typeof Deno !== 'undefined' && Deno.env && Deno.env.get('TURNSTILE_ENFORCE')) || 'true';
const turnstileEnforce = String(turnstileEnforceRaw).toLowerCase() !== 'false';
const shouldCheckTurnstile = turnstileSecret && turnstileEnforce;
if (shouldCheckTurnstile) {
try {
// 读取前端传来的 tokenbody 或 header
let cfToken = request.headers.get('x-cf-turnstile') || '';
@@ -72,6 +76,8 @@ export default async (request, context) => {
} catch (_) {
return new Response(JSON.stringify({ error: 'Turnstile Check Error' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
}
} else if (turnstileSecret && !turnstileEnforce) {
console.warn('[BFF] Turnstile enforcement disabled via TURNSTILE_ENFORCE=false, skipping challenge.');
}
// 复制请求头并添加服务端密钥头(仅内部互调使用,不影响浏览器 CORS
@@ -119,4 +125,3 @@ export default async (request, context) => {
// 直接透传响应
return response;
};

View File

@@ -47,7 +47,6 @@ if (window.TURNSTILE_SITE_KEY) {
};
let widgetId = null;
let hasExecuted = false;
let executing = false;
const exposeRefreshHandle = () => {
@@ -77,21 +76,20 @@ const markIdle = () => { executing = false; };
markIdle();
};
const runExecute = () => {
if (!window.turnstile || widgetId == null || executing) {
return;
}
executing = true;
if (hasExecuted) {
try { window.turnstile.reset(widgetId); } catch (_) {}
}
hasExecuted = true;
try {
window.turnstile.execute(widgetId);
} catch (_) {
markIdle();
}
};
const runExecute = () => {
if (!window.turnstile || widgetId == null || executing) {
return;
}
executing = true;
try {
window.turnstile.reset(widgetId);
} catch (_) {}
try {
window.turnstile.execute(widgetId);
} catch (_) {
markIdle();
}
};
const initTurnstile = () => {
if (!window.turnstile) {