Files
NewSzxcn-Email/apps/web/src/components/turnstile-box.tsx
T
LanQin_ 45122162d0 feat(auth): 重构认证流程并补充前端登录态保护
- 拆分后端认证处理与会话创建逻辑,支持登录、注册、退出、个人资料和改密接口
- 增加二次验证挑战、Turnstile 人机校验与旧版邮箱迁移清理
- 新增前端认证守卫、管理员访问控制、退出 Hook 与验证工具
- 统一抽离接口类型,优化登录页、注册页和个人资料页的认证交互
2026-06-16 10:10:21 +08:00

51 lines
1.8 KiB
TypeScript

import * as React from "react"
declare global {
interface Window {
turnstile?: {
render: (container: HTMLElement, options: { sitekey: string; callback: (token: string) => void; "expired-callback": () => void; "error-callback": () => void }) => string
remove: (widgetId: string) => void
}
}
}
export function TurnstileBox({ siteKey, onToken }: { siteKey: string; onToken: (token: string) => void }) {
const ref = React.useRef<HTMLDivElement | null>(null)
React.useEffect(() => {
if (!siteKey || !ref.current) return
let cancelled = false
let widgetId = ""
function render() {
if (cancelled || !ref.current || !window.turnstile) return
ref.current.innerHTML = ""
widgetId = window.turnstile.render(ref.current, {
sitekey: siteKey,
callback: onToken,
"expired-callback": () => onToken(""),
"error-callback": () => onToken(""),
})
}
if (window.turnstile) {
render()
} else {
const existing = document.querySelector('script[src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"]')
if (existing) {
existing.addEventListener("load", render, { once: true })
} else {
const script = document.createElement("script")
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
script.async = true
script.defer = true
script.addEventListener("load", render, { once: true })
document.head.appendChild(script)
}
}
return () => {
cancelled = true
onToken("")
if (widgetId && window.turnstile) window.turnstile.remove(widgetId)
}
}, [siteKey, onToken])
return <div className="flex justify-center rounded-md border p-2"><div ref={ref} /></div>
}