diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index d469dcbb..c4859383 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -109,7 +109,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService) proxyHandler := admin.NewProxyHandler(adminService) adminRedeemHandler := admin.NewRedeemHandler(adminService) - settingHandler := admin.NewSettingHandler(settingService, emailService) + settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService) updateCache := repository.NewUpdateCache(redisClient) gitHubReleaseClient := repository.NewGitHubReleaseClient() serviceBuildInfo := provideServiceBuildInfo(buildInfo) diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 14b569de..e533aef1 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -10,15 +10,17 @@ import ( // SettingHandler 系统设置处理器 type SettingHandler struct { - settingService *service.SettingService - emailService *service.EmailService + settingService *service.SettingService + emailService *service.EmailService + turnstileService *service.TurnstileService } // NewSettingHandler 创建系统设置处理器 -func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService) *SettingHandler { +func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService) *SettingHandler { return &SettingHandler{ - settingService: settingService, - emailService: emailService, + settingService: settingService, + emailService: emailService, + turnstileService: turnstileService, } } @@ -108,6 +110,36 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { req.SmtpPort = 587 } + // Turnstile 参数验证 + if req.TurnstileEnabled { + // 检查必填字段 + if req.TurnstileSiteKey == "" { + response.BadRequest(c, "Turnstile Site Key is required when enabled") + return + } + if req.TurnstileSecretKey == "" { + response.BadRequest(c, "Turnstile Secret Key is required when enabled") + return + } + + // 获取当前设置,检查参数是否有变化 + currentSettings, err := h.settingService.GetAllSettings(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + + // 当 site_key 或 secret_key 任一变化时验证(避免配置错误导致无法登录) + siteKeyChanged := currentSettings.TurnstileSiteKey != req.TurnstileSiteKey + secretKeyChanged := currentSettings.TurnstileSecretKey != req.TurnstileSecretKey + if siteKeyChanged || secretKeyChanged { + if err := h.turnstileService.ValidateSecretKey(c.Request.Context(), req.TurnstileSecretKey); err != nil { + response.ErrorFrom(c, err) + return + } + } + } + settings := &service.SystemSettings{ RegistrationEnabled: req.RegistrationEnabled, EmailVerifyEnabled: req.EmailVerifyEnabled, diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go index 5a243bfc..3912c8fb 100644 --- a/backend/internal/server/api_contract_test.go +++ b/backend/internal/server/api_contract_test.go @@ -385,7 +385,7 @@ func newContractDeps(t *testing.T) *contractDeps { authHandler := handler.NewAuthHandler(cfg, nil, userService) apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService) usageHandler := handler.NewUsageHandler(usageService, apiKeyService) - adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil) + adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil) jwtAuth := func(c *gin.Context) { c.Set(string(middleware.ContextKeyUser), middleware.AuthSubject{ diff --git a/backend/internal/service/turnstile_service.go b/backend/internal/service/turnstile_service.go index 2a68c11b..cfb87c57 100644 --- a/backend/internal/service/turnstile_service.go +++ b/backend/internal/service/turnstile_service.go @@ -11,6 +11,7 @@ import ( var ( ErrTurnstileVerificationFailed = infraerrors.BadRequest("TURNSTILE_VERIFICATION_FAILED", "turnstile verification failed") ErrTurnstileNotConfigured = infraerrors.ServiceUnavailable("TURNSTILE_NOT_CONFIGURED", "turnstile not configured") + ErrTurnstileInvalidSecretKey = infraerrors.BadRequest("TURNSTILE_INVALID_SECRET_KEY", "invalid turnstile secret key") ) // TurnstileVerifier 验证 Turnstile token 的接口 @@ -83,3 +84,22 @@ func (s *TurnstileService) VerifyToken(ctx context.Context, token string, remote func (s *TurnstileService) IsEnabled(ctx context.Context) bool { return s.settingService.IsTurnstileEnabled(ctx) } + +// ValidateSecretKey 验证 Turnstile Secret Key 是否有效 +func (s *TurnstileService) ValidateSecretKey(ctx context.Context, secretKey string) error { + // 发送一个测试token的验证请求来检查secret_key是否有效 + result, err := s.verifier.VerifyToken(ctx, secretKey, "test-validation", "") + if err != nil { + return fmt.Errorf("validate secret key: %w", err) + } + + // 检查是否有 invalid-input-secret 错误 + for _, code := range result.ErrorCodes { + if code == "invalid-input-secret" { + return ErrTurnstileInvalidSecretKey + } + } + + // 其他错误(如 invalid-input-response)说明 secret key 是有效的 + return nil +}