@dxv-系统/翻转
用于服务器和客户端的 Cloudflare Turnstile: /siteverify 核心,用于 Express 和 Next.js App 路由器的路由保护,以及一个反应部件.
npm i @dxv-systems/turnstile
入境点
| 导入 | 目录 |
|---|---|
@dxv-systems/turnstile | 核心 : verifyTurnstileToken, (中文(简体) ), evaluateTurnstile, (中文(简体) ), clientIp, (中文(简体) ), turnstileHeaders, (中文(简体) ), TURNSTILE_HEADER, (中文(简体) ), TURNSTILE_TEST_SITE_KEY |
@dxv-systems/turnstile/express | requireTurnstile 中间软件 |
@dxv-systems/turnstile/next | assertTurnstile 用于获取风格的路由处理器 |
@dxv-systems/turnstile/react | Turnstile 部件组件,加 turnstileHeaders, (中文(简体) ), TURNSTILE_HEADER, (中文(简体) ), TURNSTILE_TEST_SITE_KEY |
客户端发送符号在 X-Turnstile-Token 请求标题( e)TURNSTILE_HEADER) (中文(简体) ).
服务器
两名警卫都采取相同的两个选择,并共享一个政策执行,所以他们的行为都是一样的.
| 选项 | 含义 |
|---|---|
secret | 例如, 您的曲折密钥 process.env.TURNSTILE_SECRET_KEY。 。 。 . undefined 是外部生产的有效状态. |
failClosed | 真是个 没有秘密 意味着. true:以503来否认,而不是服务于无人看守的路线. false:没有秘密,没有警卫. |
快递
import { requireTurnstile } from "@dxv-systems/turnstile/express";
app.post("/api/login", requireTurnstile({
secret: process.env.TURNSTILE_SECRET_KEY,
failClosed: isProduction(),
}), handler);
一个被拒绝的请求得到一个JSON的身体 { message } ,其状况见下表.
Next.js App路由器
import { assertTurnstile } from "@dxv-systems/turnstile/next";
export async function POST(req: NextRequest) {
const denied = await assertTurnstile(req, {
secret: process.env.TURNSTILE_SECRET_KEY,
failClosed: isProduction(),
});
if (denied) return denied;
const body = Body.parse(await req.json());
// ...
}
assertTurnstile 使用纯网络 Request 返回平面 Response (或 null 请求何时通过)。 它不导入 Next, 所以它在任何抓取式处理器中有效 。 它从不读取尸体, 所以你的处理者仍然可以分析它.
否认的
每个案件都未能结案,只有一个故意例外.
| 情况 | 结果 |
|---|---|
没有秘密, failClosed: false | 允许:明显的"这里没有警卫"状态 |
没有秘密, failClosed: true | 503和错误日志 |
| 请求时没有令牌 | 403,不打电话 /siteverify |
| 驳回质疑 | 403, 登录错误代码 |
/siteverify 无法到达、 时间已过期或返回 5xx | 503:停电不能成为绕道 |
秘密错误( O)invalid-input-secret (以千美元计) | 503, 不是403: 这是服务器配置错误, 不是机器人 |
failClosed 管理一个 仅无秘密。一旦一个秘密被设定,一个缺失的代币和一个无法使用的支票都否认.
客户端地址
remoteip 从 x-real-ip 只请求标题, 永远不要 x-forwarded-for。来电者可以自己发送 x-forwarded-for,并代理它附着而不是替换它,所以其最左侧的条目是调用控制. 确定您的反向代理或主机集 x-real-ip 到客户端地址.
没有退路。 Cloudflare 得分符号对 remoteip因此,一个错误的数值比没有更糟糕。 当信头缺席时,没有 remoteip 发送.
决定 failClosed
这个包从不读取环境. 应用程序如何识别自己的生产部署 不同每个主机,所以你通过 failClosed 你自己来 典型的选择 : true 在每一个有真正部件和秘密的环境(生产、中转), false 在本地开发和丢弃预览环境中.
小心点 NODE_ENV: 许多设置 "production" 用于每个构件,包括预览,所以它往往本身不是一个可靠的信号.
核心 API
需要其它框架时直接使用核心 :
verifyTurnstileToken(token, secret, remoteIp?)电话/siteverify并决心{ ok: true }, (中文(简体) ),{ ok: false, reason: "rejected", codes }或者说{ ok: false, reason: "unavailable", detail }从不扔出.evaluateTurnstile({ secret, failClosed, token, remoteIp?, path? })执行上述政策并决心{ allow: true }或者说{ allow: false, status: 403 | 503, message }。 。 。 .path仅在日志行中使用.clientIp(get)读取x-real-ip通过一个标题获取器(name) => string | null | undefined。 。 。 .
客户端
import { Turnstile, turnstileHeaders, type TurnstileHandle } from "@dxv-systems/turnstile/react";
const [token, setToken] = useState<string | null>(null);
const widget = useRef<TurnstileHandle>(null);
async function onSubmit() {
try {
await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json", ...turnstileHeaders(token) },
body: JSON.stringify({ email, password }),
});
} finally {
widget.current?.reset(); // tokens are single-use
}
}
<Turnstile ref={widget} siteKey={SITE_KEY} failedMessage="Verification could not load." onToken={setToken} />
<button type="submit" disabled={!token}>Sign in</button>
道具 : siteKey, (中文(简体) ), onToken, (中文(简体) ), failedMessage选择 className 财务报告和审定财务报表 theme。 。 。 . siteKey 财务报告和审定财务报表 failedMessage 是道具,不是从环境读取:每个捆绑者的公共可变前缀不同(VITE_*, (中文(简体) ), NEXT_PUBLIC_*),并非每个应用程序都有i18n运行时间.
主题
theme 默认为 "light"不是Cloudflare自己来的 "auto"。 。 。 . "auto" 遵循操作系统的 prefers-color-scheme而不是你的应用的主题。 仅限光的应用程序会向每个访问者显示一个黑暗部件,其OS处于黑暗模式.
过 "auto" 如果您的应用遵循系统方案,或 "dark" 如果它只是阴暗的。 带有自己的主题切换的应用程序应该通过当前主题, 所以部件在更改时会重新递归 .
测试地点密钥
如果没有真正的部件,例如在本地开发中或预览主机名中无法匹配部件域允许列表,请返回到 TURNSTILE_TEST_SITE_KEYCloudflare总是通过钥匙。 这不会削弱生产: 站点密钥是公用, 服务器是闸门, 用测试密钥刻出的一个符号失败 /siteverify 对一个真正的秘密.
把它包装在你的应用程序
旋转 ./react 而不是直接将其导入页面:
// components/turnstile.tsx
"use client"; // Next only
import { Turnstile as Base, TURNSTILE_TEST_SITE_KEY, type TurnstileHandle } from "@dxv-systems/turnstile/react";
export const Turnstile = forwardRef<TurnstileHandle, { onToken: (t: string | null) => void }>(
function Turnstile(props, ref) {
return (
<Base
ref={ref}
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || TURNSTILE_TEST_SITE_KEY}
failedMessage={t("turnstile.failed")}
{...props}
/>
);
},
);
包装器提供站点钥匙和副本. 接下来也是 "use client" 边界。 包裹是自己的 "use client" 指令在建筑中幸存下来,但在建筑后着陆 "use strict" 因此,不要依赖它来为您设定边界.
模块格式
软件包是通用JS,所以在运行时间不能 require 仅依靠无害环境管理。 这个 ./react 条目不导入服务器代码, 因此 /siteverify 逻辑不包含浏览器捆绑.