[优化] 移除 Turnstile 相关功能和配置

This commit is contained in:
ryan
2026-05-31 15:22:30 +08:00
parent 57616626fd
commit 4cb8928e4e
12 changed files with 15 additions and 288 deletions
-1
View File
@@ -1 +0,0 @@
1.0.x
-3
View File
@@ -30,7 +30,6 @@ var PasswordRegisterEnabled = true
var EmailVerificationEnabled = false var EmailVerificationEnabled = false
var GitHubOAuthEnabled = false var GitHubOAuthEnabled = false
var WeChatAuthEnabled = false var WeChatAuthEnabled = false
var TurnstileCheckEnabled = false
var RegisterEnabled = false var RegisterEnabled = false
var SMTPServer = "" var SMTPServer = ""
@@ -45,8 +44,6 @@ var WeChatServerAddress = ""
var WeChatServerToken = "" var WeChatServerToken = ""
var WeChatAccountQRCodeImageURL = "" var WeChatAccountQRCodeImageURL = ""
var TurnstileSiteKey = ""
var TurnstileSecretKey = ""
var AgentToken = "" var AgentToken = ""
var AgentDiscoveryToken = "" var AgentDiscoveryToken = ""
var NodeOfflineThreshold = 2 * time.Minute var NodeOfflineThreshold = 2 * time.Minute
+2 -3
View File
@@ -3,7 +3,6 @@ package controller
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin"
"net/http" "net/http"
"openflare/common" "openflare/common"
"openflare/model" "openflare/model"
@@ -11,6 +10,8 @@ import (
"openflare/utils/mail" "openflare/utils/mail"
"openflare/utils/security" "openflare/utils/security"
"openflare/utils/validation" "openflare/utils/validation"
"github.com/gin-gonic/gin"
) )
// GetStatus godoc // GetStatus godoc
@@ -39,8 +40,6 @@ func GetStatus(c *gin.Context) {
"wechat_qrcode": common.WeChatAccountQRCodeImageURL, "wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled, "wechat_login": common.WeChatAuthEnabled,
"server_address": common.ServerAddress, "server_address": common.ServerAddress,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"register_enabled": common.RegisterEnabled, "register_enabled": common.RegisterEnabled,
"password_register_enabled": common.PasswordRegisterEnabled, "password_register_enabled": common.PasswordRegisterEnabled,
"auth_sources": authSources, "auth_sources": authSources,
+3 -13
View File
@@ -3,7 +3,6 @@ package controller
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin"
"net/http" "net/http"
"openflare/common" "openflare/common"
"openflare/model" "openflare/model"
@@ -13,6 +12,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/gin-gonic/gin"
) )
var ( var (
@@ -236,10 +237,7 @@ func validateOptionWithState(option model.Option, state map[string]string) error
if option.Value == "true" && strings.TrimSpace(state["WeChatServerAddress"]) == "" { if option.Value == "true" && strings.TrimSpace(state["WeChatServerAddress"]) == "" {
return fmt.Errorf("鏃犳硶鍚敤寰俊鐧诲綍锛岃鍏堝~鍏ュ井淇$櫥褰曠浉鍏抽厤缃俊鎭紒") return fmt.Errorf("鏃犳硶鍚敤寰俊鐧诲綍锛岃鍏堝~鍏ュ井淇$櫥褰曠浉鍏抽厤缃俊鎭紒")
} }
case "TurnstileCheckEnabled":
if option.Value == "true" && strings.TrimSpace(state["TurnstileSiteKey"]) == "" {
return fmt.Errorf("鏃犳硶鍚敤 Turnstile 鏍¢獙锛岃鍏堝~鍏?Turnstile 鏍¢獙鐩稿叧閰嶇疆淇℃伅锛?")
}
} }
if err := validateRateLimitOption(option.Key, option.Value); err != nil { if err := validateRateLimitOption(option.Key, option.Value); err != nil {
@@ -341,14 +339,6 @@ func UpdateOption(c *gin.Context) {
}) })
return return
} }
case "TurnstileCheckEnabled":
if option.Value == "true" && common.TurnstileSiteKey == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
})
return
}
} }
if err = validateRateLimitOption(option.Key, option.Value); err != nil { if err = validateRateLimitOption(option.Key, option.Value); err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
@@ -1,81 +0,0 @@
package middleware
import (
"encoding/json"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"log/slog"
"net/http"
"net/url"
"openflare/common"
)
type turnstileCheckResponse struct {
Success bool `json:"success"`
}
func TurnstileCheck() gin.HandlerFunc {
return func(c *gin.Context) {
if common.TurnstileCheckEnabled {
session := sessions.Default(c)
turnstileChecked := session.Get("turnstile")
if turnstileChecked != nil {
c.Next()
return
}
response := c.Query("turnstile")
if response == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Turnstile token 为空",
})
c.Abort()
return
}
rawRes, err := http.PostForm("https://challenges.cloudflare.com/turnstile/v0/siteverify", url.Values{
"secret": {common.TurnstileSecretKey},
"response": {response},
"remoteip": {c.ClientIP()},
})
if err != nil {
slog.Error("turnstile verification request failed", "error", err)
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
c.Abort()
return
}
defer rawRes.Body.Close()
var res turnstileCheckResponse
err = json.NewDecoder(rawRes.Body).Decode(&res)
if err != nil {
slog.Error("decode turnstile verification response failed", "error", err)
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
c.Abort()
return
}
if !res.Success {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Turnstile 校验失败,请刷新重试!",
})
c.Abort()
return
}
session.Set("turnstile", true)
err = session.Save()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"message": "无法保存会话信息,请重试",
"success": false,
})
return
}
}
c.Next()
}
}
-9
View File
@@ -34,7 +34,6 @@ func InitOptionMap() {
common.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(common.EmailVerificationEnabled) common.OptionMap["EmailVerificationEnabled"] = strconv.FormatBool(common.EmailVerificationEnabled)
common.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(common.GitHubOAuthEnabled) common.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(common.GitHubOAuthEnabled)
common.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(common.WeChatAuthEnabled) common.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(common.WeChatAuthEnabled)
common.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(common.TurnstileCheckEnabled)
common.OptionMap["SMTPServer"] = "" common.OptionMap["SMTPServer"] = ""
common.OptionMap["SMTPPort"] = strconv.Itoa(common.SMTPPort) common.OptionMap["SMTPPort"] = strconv.Itoa(common.SMTPPort)
common.OptionMap["SMTPAccount"] = "" common.OptionMap["SMTPAccount"] = ""
@@ -50,8 +49,6 @@ func InitOptionMap() {
common.OptionMap["WeChatServerAddress"] = "" common.OptionMap["WeChatServerAddress"] = ""
common.OptionMap["WeChatServerToken"] = "" common.OptionMap["WeChatServerToken"] = ""
common.OptionMap["WeChatAccountQRCodeImageURL"] = "" common.OptionMap["WeChatAccountQRCodeImageURL"] = ""
common.OptionMap["TurnstileSiteKey"] = ""
common.OptionMap["TurnstileSecretKey"] = ""
common.OptionMap["AgentDiscoveryToken"] = "" common.OptionMap["AgentDiscoveryToken"] = ""
common.OptionMap["AgentHeartbeatInterval"] = strconv.Itoa(common.AgentHeartbeatInterval) common.OptionMap["AgentHeartbeatInterval"] = strconv.Itoa(common.AgentHeartbeatInterval)
common.OptionMap["AgentWebsocketUpgradeEnabled"] = strconv.FormatBool(common.AgentWebsocketUpgradeEnabled) common.OptionMap["AgentWebsocketUpgradeEnabled"] = strconv.FormatBool(common.AgentWebsocketUpgradeEnabled)
@@ -180,8 +177,6 @@ func updateOptionMap(key string, value string) {
common.GitHubOAuthEnabled = boolValue common.GitHubOAuthEnabled = boolValue
case "WeChatAuthEnabled": case "WeChatAuthEnabled":
common.WeChatAuthEnabled = boolValue common.WeChatAuthEnabled = boolValue
case "TurnstileCheckEnabled":
common.TurnstileCheckEnabled = boolValue
} }
} }
switch key { switch key {
@@ -212,10 +207,6 @@ func updateOptionMap(key string, value string) {
common.WeChatServerToken = value common.WeChatServerToken = value
case "WeChatAccountQRCodeImageURL": case "WeChatAccountQRCodeImageURL":
common.WeChatAccountQRCodeImageURL = value common.WeChatAccountQRCodeImageURL = value
case "TurnstileSiteKey":
common.TurnstileSiteKey = value
case "TurnstileSecretKey":
common.TurnstileSecretKey = value
case "AgentDiscoveryToken": case "AgentDiscoveryToken":
common.AgentDiscoveryToken = value common.AgentDiscoveryToken = value
case "AgentHeartbeatInterval": case "AgentHeartbeatInterval":
+3 -3
View File
@@ -14,8 +14,8 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/status", controller.GetStatus)
apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/notice", controller.GetNotice)
apiRouter.GET("/about", controller.GetAbout) apiRouter.GET("/about", controller.GetAbout)
apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/verification", middleware.CriticalRateLimit(), controller.SendEmailVerification)
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), controller.SendPasswordResetEmail)
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth) apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
@@ -33,7 +33,7 @@ func SetApiRouter(router *gin.Engine) {
userRoute := apiRouter.Group("/user") userRoute := apiRouter.Group("/user")
{ {
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register) userRoute.POST("/register", middleware.CriticalRateLimit(), controller.Register)
userRoute.POST("/login", middleware.CriticalRateLimit(), controller.Login) userRoute.POST("/login", middleware.CriticalRateLimit(), controller.Login)
userRoute.GET("/logout", controller.Logout) userRoute.GET("/logout", controller.Logout)
@@ -1,101 +0,0 @@
'use client';
import { useEffect, useId, useRef } from 'react';
declare global {
interface Window {
turnstile?: {
render: (
container: string | HTMLElement,
options: {
sitekey: string;
callback: (token: string) => void;
'expired-callback'?: () => void;
'error-callback'?: () => void;
theme?: 'auto' | 'light' | 'dark';
},
) => string;
remove: (widgetId: string) => void;
reset: (widgetId?: string) => void;
};
}
}
const TURNSTILE_SCRIPT_ID = 'cloudflare-turnstile-script';
const TURNSTILE_SCRIPT_SRC =
'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
interface TurnstileWidgetProps {
siteKey: string;
onVerify: (token: string) => void;
onExpire?: () => void;
onError?: () => void;
}
export function TurnstileWidget({
siteKey,
onVerify,
onExpire,
onError,
}: TurnstileWidgetProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const widgetIdRef = useRef<string | null>(null);
const elementId = useId().replace(/:/g, '-');
useEffect(() => {
let cancelled = false;
const mountWidget = () => {
if (cancelled || !containerRef.current || !window.turnstile) {
return;
}
if (widgetIdRef.current) {
window.turnstile.remove(widgetIdRef.current);
widgetIdRef.current = null;
}
widgetIdRef.current = window.turnstile.render(containerRef.current, {
sitekey: siteKey,
callback: onVerify,
'expired-callback': onExpire,
'error-callback': onError,
theme: 'auto',
});
};
const existingScript = document.getElementById(
TURNSTILE_SCRIPT_ID,
) as HTMLScriptElement | null;
if (window.turnstile) {
mountWidget();
} else if (existingScript) {
existingScript.addEventListener('load', mountWidget);
} else {
const script = document.createElement('script');
script.id = TURNSTILE_SCRIPT_ID;
script.src = TURNSTILE_SCRIPT_SRC;
script.async = true;
script.defer = true;
script.addEventListener('load', mountWidget);
document.head.appendChild(script);
}
return () => {
cancelled = true;
const script = document.getElementById(
TURNSTILE_SCRIPT_ID,
) as HTMLScriptElement | null;
if (script) {
script.removeEventListener('load', mountWidget);
}
if (widgetIdRef.current && window.turnstile) {
window.turnstile.remove(widgetIdRef.current);
widgetIdRef.current = null;
}
};
}, [onError, onExpire, onVerify, siteKey]);
return <div id={elementId} ref={containerRef} className="min-h-16" />;
}
+4 -14
View File
@@ -21,31 +21,21 @@ export function logout() {
return apiRequest<void>('/user/logout'); return apiRequest<void>('/user/logout');
} }
export function register(payload: RegisterPayload, turnstileToken?: string) { export function register(payload: RegisterPayload) {
const query = turnstileToken return apiRequest<void>('/user/register', {
? `?turnstile=${encodeURIComponent(turnstileToken)}`
: '';
return apiRequest<void>(`/user/register${query}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
} }
export function sendEmailVerification(email: string, turnstileToken?: string) { export function sendEmailVerification(email: string) {
const searchParams = new URLSearchParams({ email }); const searchParams = new URLSearchParams({ email });
if (turnstileToken) {
searchParams.set('turnstile', turnstileToken);
}
return apiRequest<void>(`/verification?${searchParams.toString()}`); return apiRequest<void>(`/verification?${searchParams.toString()}`);
} }
export function sendPasswordResetEmail(email: string, turnstileToken?: string) { export function sendPasswordResetEmail(email: string) {
const searchParams = new URLSearchParams({ email }); const searchParams = new URLSearchParams({ email });
if (turnstileToken) {
searchParams.set('turnstile', turnstileToken);
}
return apiRequest<void>(`/reset_password?${searchParams.toString()}`); return apiRequest<void>(`/reset_password?${searchParams.toString()}`);
} }
@@ -1,17 +1,15 @@
'use client'; 'use client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import Link from 'next/link'; import Link from 'next/link';
import { useState } from 'react'; import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { InlineMessage } from '@/components/feedback/inline-message'; import { InlineMessage } from '@/components/feedback/inline-message';
import { TurnstileWidget } from '@/components/forms/turnstile-widget';
import { AppCard } from '@/components/ui/app-card'; import { AppCard } from '@/components/ui/app-card';
import { sendPasswordResetEmail } from '@/features/auth/api/auth'; import { sendPasswordResetEmail } from '@/features/auth/api/auth';
import { getPublicStatus } from '@/features/auth/api/public';
import { import {
AuthButton, AuthButton,
AuthFormField, AuthFormField,
@@ -26,7 +24,6 @@ const resetRequestSchema = z.object({
type ResetRequestFormValues = z.infer<typeof resetRequestSchema>; type ResetRequestFormValues = z.infer<typeof resetRequestSchema>;
export function PasswordResetRequestForm() { export function PasswordResetRequestForm() {
const [turnstileToken, setTurnstileToken] = useState('');
const [message, setMessage] = useState<{ const [message, setMessage] = useState<{
tone: 'success' | 'danger' | 'info'; tone: 'success' | 'danger' | 'info';
text: string; text: string;
@@ -37,14 +34,9 @@ export function PasswordResetRequestForm() {
defaultValues: { email: '' }, defaultValues: { email: '' },
}); });
const statusQuery = useQuery({
queryKey: ['public-status'],
queryFn: getPublicStatus,
});
const mutation = useMutation({ const mutation = useMutation({
mutationFn: (values: ResetRequestFormValues) => mutationFn: (values: ResetRequestFormValues) =>
sendPasswordResetEmail(values.email, turnstileToken || undefined), sendPasswordResetEmail(values.email),
onSuccess: () => { onSuccess: () => {
setMessage({ tone: 'success', text: '重置邮件发送成功,请检查邮箱。' }); setMessage({ tone: 'success', text: '重置邮件发送成功,请检查邮箱。' });
form.reset(); form.reset();
@@ -59,10 +51,6 @@ export function PasswordResetRequestForm() {
const handleSubmit = form.handleSubmit((values) => { const handleSubmit = form.handleSubmit((values) => {
setMessage(null); setMessage(null);
if (statusQuery.data?.turnstile_check && !turnstileToken) {
setMessage({ tone: 'info', text: '请先完成人机验证。' });
return;
}
mutation.mutate(values); mutation.mutate(values);
}); });
@@ -86,16 +74,6 @@ export function PasswordResetRequestForm() {
) : null} ) : null}
</AuthFormField> </AuthFormField>
{statusQuery.data?.turnstile_check &&
statusQuery.data.turnstile_site_key ? (
<TurnstileWidget
siteKey={statusQuery.data.turnstile_site_key}
onVerify={(token) => setTurnstileToken(token)}
onExpire={() => setTurnstileToken('')}
onError={() => setTurnstileToken('')}
/>
) : null}
{message ? ( {message ? (
<InlineMessage tone={message.tone} message={message.text} /> <InlineMessage tone={message.tone} message={message.text} />
) : null} ) : null}
@@ -8,7 +8,6 @@ import { ErrorState } from '@/components/feedback/error-state';
import { InlineMessage } from '@/components/feedback/inline-message'; import { InlineMessage } from '@/components/feedback/inline-message';
import { LoadingState } from '@/components/feedback/loading-state'; import { LoadingState } from '@/components/feedback/loading-state';
import { AppModal } from '@/components/ui/app-modal'; import { AppModal } from '@/components/ui/app-modal';
import { TurnstileWidget } from '@/components/forms/turnstile-widget';
import { useAuth } from '@/components/providers/auth-provider'; import { useAuth } from '@/components/providers/auth-provider';
import { PageHeader } from '@/components/layout/page-header'; import { PageHeader } from '@/components/layout/page-header';
import { AppCard } from '@/components/ui/app-card'; import { AppCard } from '@/components/ui/app-card';
@@ -71,7 +70,6 @@ const defaultSystemFields = {
EmailVerificationEnabled: false, EmailVerificationEnabled: false,
GitHubOAuthEnabled: false, GitHubOAuthEnabled: false,
WeChatAuthEnabled: false, WeChatAuthEnabled: false,
TurnstileCheckEnabled: false,
SMTPServer: '', SMTPServer: '',
SMTPPort: '587', SMTPPort: '587',
SMTPAccount: '', SMTPAccount: '',
@@ -81,8 +79,6 @@ const defaultSystemFields = {
WeChatServerAddress: '', WeChatServerAddress: '',
WeChatServerToken: '', WeChatServerToken: '',
WeChatAccountQRCodeImageURL: '', WeChatAccountQRCodeImageURL: '',
TurnstileSiteKey: '',
TurnstileSecretKey: '',
}; };
const defaultOperationFields = { const defaultOperationFields = {
@@ -257,7 +253,6 @@ export function SettingsPage() {
const [accessToken, setAccessToken] = useState(''); const [accessToken, setAccessToken] = useState('');
const [emailAddress, setEmailAddress] = useState(''); const [emailAddress, setEmailAddress] = useState('');
const [emailCode, setEmailCode] = useState(''); const [emailCode, setEmailCode] = useState('');
const [emailTurnstileToken, setEmailTurnstileToken] = useState('');
const [authSourceModalOpen, setAuthSourceModalOpen] = useState(false); const [authSourceModalOpen, setAuthSourceModalOpen] = useState(false);
const [geoIPTestIP, setGeoIPTestIP] = useState('8.8.8.8'); const [geoIPTestIP, setGeoIPTestIP] = useState('8.8.8.8');
const [cleanupModalState, setCleanupModalState] = const [cleanupModalState, setCleanupModalState] =
@@ -325,8 +320,6 @@ export function SettingsPage() {
GitHubClientId: publicStatus.github_client_id || previous.GitHubClientId, GitHubClientId: publicStatus.github_client_id || previous.GitHubClientId,
WeChatAccountQRCodeImageURL: WeChatAccountQRCodeImageURL:
publicStatus.wechat_qrcode || previous.WeChatAccountQRCodeImageURL, publicStatus.wechat_qrcode || previous.WeChatAccountQRCodeImageURL,
TurnstileSiteKey:
publicStatus.turnstile_site_key || previous.TurnstileSiteKey,
})); }));
setOtherFields((previous) => ({ setOtherFields((previous) => ({
...previous, ...previous,
@@ -364,7 +357,6 @@ export function SettingsPage() {
), ),
GitHubOAuthEnabled: toBoolean(optionMap.GitHubOAuthEnabled, false), GitHubOAuthEnabled: toBoolean(optionMap.GitHubOAuthEnabled, false),
WeChatAuthEnabled: toBoolean(optionMap.WeChatAuthEnabled, false), WeChatAuthEnabled: toBoolean(optionMap.WeChatAuthEnabled, false),
TurnstileCheckEnabled: toBoolean(optionMap.TurnstileCheckEnabled, false),
SMTPServer: optionMap.SMTPServer ?? '', SMTPServer: optionMap.SMTPServer ?? '',
SMTPPort: optionMap.SMTPPort ?? '587', SMTPPort: optionMap.SMTPPort ?? '587',
SMTPAccount: optionMap.SMTPAccount ?? '', SMTPAccount: optionMap.SMTPAccount ?? '',
@@ -374,8 +366,6 @@ export function SettingsPage() {
WeChatServerAddress: optionMap.WeChatServerAddress ?? '', WeChatServerAddress: optionMap.WeChatServerAddress ?? '',
WeChatServerToken: '', WeChatServerToken: '',
WeChatAccountQRCodeImageURL: optionMap.WeChatAccountQRCodeImageURL ?? '', WeChatAccountQRCodeImageURL: optionMap.WeChatAccountQRCodeImageURL ?? '',
TurnstileSiteKey: optionMap.TurnstileSiteKey ?? '',
TurnstileSecretKey: '',
}); });
setOperationFields({ setOperationFields({
@@ -618,16 +608,8 @@ export function SettingsPage() {
return; return;
} }
if (publicStatusQuery.data?.turnstile_check && !emailTurnstileToken) {
setFeedback({ tone: 'info', message: '请先完成人机验证。' });
return;
}
void runBusyAction('email-send', async () => { void runBusyAction('email-send', async () => {
await sendEmailVerification( await sendEmailVerification(emailAddress.trim());
emailAddress.trim(),
emailTurnstileToken || undefined,
);
setFeedback({ tone: 'success', message: '验证码已发送,请检查邮箱。' }); setFeedback({ tone: 'success', message: '验证码已发送,请检查邮箱。' });
}); });
}; };
@@ -974,21 +956,6 @@ export function SettingsPage() {
placeholder="请输入邮箱验证码" placeholder="请输入邮箱验证码"
/> />
</ResourceField> </ResourceField>
{publicStatus.turnstile_check ? (
publicStatus.turnstile_site_key ? (
<TurnstileWidget
siteKey={publicStatus.turnstile_site_key}
onVerify={(token) => setEmailTurnstileToken(token)}
onExpire={() => setEmailTurnstileToken('')}
onError={() => setEmailTurnstileToken('')}
/>
) : (
<EmptyState
title="Turnstile 配置不完整"
description="当前系统已启用 Turnstile,但未配置 Site Key,邮箱绑定暂不可用。"
/>
)
) : null}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<SecondaryButton <SecondaryButton
type="button" type="button"
@@ -19,8 +19,6 @@ export interface PublicStatus {
wechat_qrcode: string; wechat_qrcode: string;
wechat_login: boolean; wechat_login: boolean;
server_address: string; server_address: string;
turnstile_check: boolean;
turnstile_site_key: string;
register_enabled: boolean; register_enabled: boolean;
password_register_enabled: boolean; password_register_enabled: boolean;
auth_sources: PublicAuthSource[]; auth_sources: PublicAuthSource[];