From 314f6fd3f4707941ccfdab2b1f4447e843146ba7 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 25 May 2026 16:01:10 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=92=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/reference/configuration.md | 2 - docs/reference/configuration.md | 2 - openflare_server/controller/user.go | 72 +--- openflare_server/model/option.go | 3 - openflare_server/model/option_test.go | 30 -- openflare_server/router/api_phase1_test.go | 4 +- openflare_server/service/auth_source_test.go | 41 +-- .../web/app/(public)/register/page.tsx | 12 - .../features/auth/components/login-form.tsx | 15 - .../auth/components/register-form.tsx | 332 ------------------ .../settings/components/settings-page.tsx | 12 +- 11 files changed, 10 insertions(+), 515 deletions(-) delete mode 100644 openflare_server/model/option_test.go delete mode 100644 openflare_server/web/app/(public)/register/page.tsx delete mode 100644 openflare_server/web/features/auth/components/register-form.tsx diff --git a/docs/en/reference/configuration.md b/docs/en/reference/configuration.md index a51f9ff0..9df1e4fb 100644 --- a/docs/en/reference/configuration.md +++ b/docs/en/reference/configuration.md @@ -44,8 +44,6 @@ The settings page maintains these hot-updatable options: | `NodeOfflineThreshold` | Node offline threshold in milliseconds | `120000` | | `AgentUpdateRepo` | Agent update repository | `Rain-kl/OpenFlare` | | `GeoIPProvider` | Node/IP region provider | `ipinfo` | -| `RegisterEnabled` | Allow new user registration | `false` | -| `PasswordRegisterEnabled` | Allow password registration | `true` | | `DatabaseAutoCleanupEnabled` | Enable daily observability cleanup | `false` | | `DatabaseAutoCleanupRetentionDays` | Retention days | `30` | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 98aa6b6f..90b67c6a 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -63,8 +63,6 @@ go run . --port 3000 --log-dir ./logs | `NodeOfflineThreshold` | 节点离线阈值(毫秒) | `120000` | | `AgentUpdateRepo` | Agent 自更新仓库 | `Rain-kl/OpenFlare` | | `GeoIPProvider` | 节点/IP 归属解析方式 | `ipinfo` | -| `RegisterEnabled` | 是否允许新用户注册 | `false` | -| `PasswordRegisterEnabled` | 是否允许通过密码方式注册 | `true` | | `DatabaseAutoCleanupEnabled` | 是否启用每日自动清理观测数据 | `false` | | `DatabaseAutoCleanupRetentionDays` | 自动清理保留天数,至少 1 天 | `30` | | `GlobalApiRateLimitNum` / `GlobalApiRateLimitDuration` | 全局 API 限流次数 / 时间窗口 | `300` / `180` | diff --git a/openflare_server/controller/user.go b/openflare_server/controller/user.go index 91d7d465..9dc589d8 100644 --- a/openflare_server/controller/user.go +++ b/openflare_server/controller/user.go @@ -2,9 +2,6 @@ package controller import ( "encoding/json" - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "github.com/google/uuid" "net/http" "openflare/common" "openflare/model" @@ -12,6 +9,10 @@ import ( "openflare/utils/validation" "strconv" "strings" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/google/uuid" ) type LoginRequest struct { @@ -115,70 +116,9 @@ func Logout(c *gin.Context) { } func Register(c *gin.Context) { - if !common.RegisterEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "管理员关闭了新用户注册", - "success": false, - }) - return - } - if !common.PasswordRegisterEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册", - "success": false, - }) - return - } - var user model.User - err := json.NewDecoder(c.Request.Body).Decode(&user) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) - return - } - if err := validation.Validate.Struct(&user); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "输入不合法 " + err.Error(), - }) - return - } - if common.EmailVerificationEnabled { - if user.Email == "" || user.VerificationCode == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "管理员开启了邮箱验证,请输入邮箱地址和验证码", - }) - return - } - if !security.VerifyCodeWithKey(user.Email, user.VerificationCode, security.EmailVerificationPurpose) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "验证码错误或已过期", - }) - return - } - } - cleanUser := model.User{ - Username: user.Username, - Password: user.Password, - DisplayName: user.Username, - } - if common.EmailVerificationEnabled { - cleanUser.Email = user.Email - } - if err := cleanUser.Insert(); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) - return - } c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", + "message": "非法请求", + "success": false, }) return } diff --git a/openflare_server/model/option.go b/openflare_server/model/option.go index 84be27c7..0afd9c84 100644 --- a/openflare_server/model/option.go +++ b/openflare_server/model/option.go @@ -35,7 +35,6 @@ func InitOptionMap() { common.OptionMap["GitHubOAuthEnabled"] = strconv.FormatBool(common.GitHubOAuthEnabled) common.OptionMap["WeChatAuthEnabled"] = strconv.FormatBool(common.WeChatAuthEnabled) common.OptionMap["TurnstileCheckEnabled"] = strconv.FormatBool(common.TurnstileCheckEnabled) - common.OptionMap["RegisterEnabled"] = strconv.FormatBool(common.RegisterEnabled) common.OptionMap["SMTPServer"] = "" common.OptionMap["SMTPPort"] = strconv.Itoa(common.SMTPPort) common.OptionMap["SMTPAccount"] = "" @@ -187,8 +186,6 @@ func updateOptionMap(key string, value string) { common.WeChatAuthEnabled = boolValue case "TurnstileCheckEnabled": common.TurnstileCheckEnabled = boolValue - case "RegisterEnabled": - common.RegisterEnabled = boolValue } } switch key { diff --git a/openflare_server/model/option_test.go b/openflare_server/model/option_test.go deleted file mode 100644 index a206adb0..00000000 --- a/openflare_server/model/option_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package model - -import ( - "openflare/common" - "testing" -) - -func TestInitOptionMapDefaultsRegisterDisabled(t *testing.T) { - previousRegisterEnabled := common.RegisterEnabled - previousOptionMap := common.OptionMap - previousDB := DB - t.Cleanup(func() { - common.RegisterEnabled = previousRegisterEnabled - common.OptionMap = previousOptionMap - DB = previousDB - }) - - DB = openTestSQLiteDB(t, "options-defaults.db") - common.RegisterEnabled = false - common.OptionMap = nil - - InitOptionMap() - - if got := common.OptionMap["RegisterEnabled"]; got != "false" { - t.Fatalf("expected RegisterEnabled default to be false, got %q", got) - } - if common.RegisterEnabled { - t.Fatal("expected RegisterEnabled to remain false after InitOptionMap") - } -} diff --git a/openflare_server/router/api_phase1_test.go b/openflare_server/router/api_phase1_test.go index d023b27f..06e87df5 100644 --- a/openflare_server/router/api_phase1_test.go +++ b/openflare_server/router/api_phase1_test.go @@ -382,8 +382,8 @@ func TestPhase1HTTPSAndCertificateImportLifecycle(t *testing.T) { t.Fatalf("expected active config to expose main_config, got %#v", activeConfig["main_config"]) } supportFiles, ok := activeConfig["support_files"].([]any) - if !ok || len(supportFiles) != 2 { - t.Fatalf("expected active config to expose 2 support files, got %#v", activeConfig["support_files"]) + if !ok || len(supportFiles) != 3 { + t.Fatalf("expected active config to expose 3 support files, got %#v", activeConfig["support_files"]) } } diff --git a/openflare_server/service/auth_source_test.go b/openflare_server/service/auth_source_test.go index 786696ce..ef7b06cb 100644 --- a/openflare_server/service/auth_source_test.go +++ b/openflare_server/service/auth_source_test.go @@ -3,17 +3,11 @@ package service import ( "testing" - "openflare/common" "openflare/model" ) func TestCompleteOAuthLoginRequiresLinkWhenRegistrationDisabled(t *testing.T) { setupServiceTestDB(t) - previousRegisterEnabled := common.RegisterEnabled - common.RegisterEnabled = false - t.Cleanup(func() { - common.RegisterEnabled = previousRegisterEnabled - }) source := createTestAuthSource(t) result, pending, err := CompleteOAuthLogin(source, &OAuthProfile{ @@ -48,43 +42,10 @@ func TestCompleteOAuthLoginRequiresLinkWhenRegistrationDisabled(t *testing.T) { } } -func TestCompleteOAuthLoginAutoRegistersWhenEnabled(t *testing.T) { - setupServiceTestDB(t) - previousRegisterEnabled := common.RegisterEnabled - common.RegisterEnabled = true - t.Cleanup(func() { - common.RegisterEnabled = previousRegisterEnabled - }) - - source := createTestAuthSource(t) - result, pending, err := CompleteOAuthLogin(source, &OAuthProfile{ - ExternalID: "external-2", - ExternalUsername: "oidc-user", - DisplayName: "OIDC User", - Email: "oidc@example.com", - }, nil) - if err != nil { - t.Fatalf("CompleteOAuthLogin failed: %v", err) - } - if pending != nil { - t.Fatalf("expected no pending account when registration is enabled") - } - if result.Status != "registered" || result.User == nil { - t.Fatalf("expected registered user result, got %#v", result) - } - account, err := model.FindExternalAccount(source.ID, "external-2") - if err != nil { - t.Fatalf("expected external account to be linked: %v", err) - } - if account.UserID != result.User.Id { - t.Fatalf("expected external account user %d, got %d", result.User.Id, account.UserID) - } -} - func createTestAuthSource(t *testing.T) *model.AuthSource { t.Helper() source := &model.AuthSource{ - Name: "Test OIDC", + Name: "test-oidc", Type: model.AuthSourceTypeOIDC, DisplayName: "Test OIDC", ClientID: "client-id", diff --git a/openflare_server/web/app/(public)/register/page.tsx b/openflare_server/web/app/(public)/register/page.tsx deleted file mode 100644 index af7bab0d..00000000 --- a/openflare_server/web/app/(public)/register/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Suspense } from 'react'; - -import { LoadingState } from '@/components/feedback/loading-state'; -import { RegisterForm } from '@/features/auth/components/register-form'; - -export default function RegisterPage() { - return ( - }> - - - ); -} diff --git a/openflare_server/web/features/auth/components/login-form.tsx b/openflare_server/web/features/auth/components/login-form.tsx index b71ea9ae..cc11e37c 100644 --- a/openflare_server/web/features/auth/components/login-form.tsx +++ b/openflare_server/web/features/auth/components/login-form.tsx @@ -63,10 +63,6 @@ export function LoginForm() { queryFn: getPublicStatus, }); - const canUsePasswordRegister = - (statusQuery.data?.register_enabled ?? false) && - (statusQuery.data?.password_register_enabled ?? false); - const loginMutation = useMutation({ mutationFn: login, onSuccess: (user) => { @@ -166,17 +162,6 @@ export function LoginForm() { > {TEXT.forgotPassword} - {canUsePasswordRegister ? ( - <> - | - - {TEXT.register} - - - ) : null} diff --git a/openflare_server/web/features/auth/components/register-form.tsx b/openflare_server/web/features/auth/components/register-form.tsx deleted file mode 100644 index e167ac89..00000000 --- a/openflare_server/web/features/auth/components/register-form.tsx +++ /dev/null @@ -1,332 +0,0 @@ -'use client'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { useMemo, useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { InlineMessage } from '@/components/feedback/inline-message'; -import { TurnstileWidget } from '@/components/forms/turnstile-widget'; -import { AppCard } from '@/components/ui/app-card'; -import { - register as registerUser, - sendEmailVerification, -} from '@/features/auth/api/auth'; -import { getPublicStatus } from '@/features/auth/api/public'; -import { - AuthButton, - AuthFormField, - AuthInput, - SecondaryButton, -} from '@/features/auth/components/auth-form-primitives'; -import { PublicAuthGuard } from '@/features/auth/components/public-auth-guard'; - -const TEXT = { - title: '\u65b0\u7528\u6237\u6ce8\u518c', - description: - '\u517c\u5bb9\u73b0\u6709\u5bc6\u7801\u6ce8\u518c\u94fe\u8def\uff0c\u540e\u7eed\u53ef\u7ee7\u7eed\u6269\u5c55\u7b2c\u4e09\u65b9\u6ce8\u518c\u3002', - usernameRequired: '\u8bf7\u8f93\u5165\u7528\u6237\u540d', - usernameTooLong: '\u7528\u6237\u540d\u6700\u957f 12 \u4f4d', - passwordTooShort: '\u5bc6\u7801\u81f3\u5c11 8 \u4f4d', - passwordTooLong: '\u5bc6\u7801\u6700\u957f 20 \u4f4d', - passwordRepeatRequired: '\u8bf7\u518d\u6b21\u8f93\u5165\u5bc6\u7801', - passwordMismatch: '\u4e24\u6b21\u8f93\u5165\u7684\u5bc6\u7801\u4e0d\u4e00\u81f4', - emailInvalid: '\u8bf7\u8f93\u5165\u6709\u6548\u90ae\u7bb1\u5730\u5740', - codeRequired: '\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801', - registerFailed: '\u6ce8\u518c\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002', - emailRequired: '\u8bf7\u8f93\u5165\u90ae\u7bb1\u5730\u5740', - verificationSent: - '\u9a8c\u8bc1\u7801\u53d1\u9001\u6210\u529f\uff0c\u8bf7\u68c0\u67e5\u90ae\u7bb1\u3002', - verificationFailed: - '\u9a8c\u8bc1\u7801\u53d1\u9001\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002', - turnstileRequired: '\u8bf7\u5148\u5b8c\u6210\u4eba\u673a\u9a8c\u8bc1\u3002', - registerClosed: - '\u7ba1\u7406\u5458\u5df2\u5173\u95ed\u65b0\u7528\u6237\u6ce8\u518c\u3002', - passwordRegisterClosed: - '\u7ba1\u7406\u5458\u5df2\u5173\u95ed\u5bc6\u7801\u6ce8\u518c\uff0c\u8bf7\u4f7f\u7528\u7b2c\u4e09\u65b9\u767b\u5f55\u5165\u53e3\u5b8c\u6210\u6ce8\u518c\u3002', - hasAccount: '\u5df2\u6709\u8d26\u53f7\uff1f', - backToLogin: - '\u8fd4\u56de\u767b\u5f55\u9875\u67e5\u770b\u53ef\u7528\u5165\u53e3\uff1a', - clickLogin: '\u70b9\u51fb\u767b\u5f55', - username: '\u7528\u6237\u540d', - usernameHint: '\u6700\u957f 12 \u4f4d', - password: '\u5bc6\u7801', - passwordHint: '\u6700\u77ed 8 \u4f4d\uff0c\u6700\u957f 20 \u4f4d', - passwordConfirm: '\u786e\u8ba4\u5bc6\u7801', - email: '\u90ae\u7bb1\u5730\u5740', - emailCode: '\u90ae\u7bb1\u9a8c\u8bc1\u7801', - getCode: '\u83b7\u53d6\u9a8c\u8bc1\u7801', - gettingCode: '\u53d1\u9001\u4e2d...', - register: '\u6ce8\u518c', - registering: '\u6ce8\u518c\u4e2d...', -}; - -const baseSchemaObject = z.object({ - username: z.string().min(1, TEXT.usernameRequired).max(12, TEXT.usernameTooLong), - password: z.string().min(8, TEXT.passwordTooShort).max(20, TEXT.passwordTooLong), - password2: z.string().min(8, TEXT.passwordRepeatRequired), - email: z.string().optional(), - verification_code: z.string().optional(), -}); - -const baseSchema = baseSchemaObject.refine( - (data) => data.password === data.password2, - { - message: TEXT.passwordMismatch, - path: ['password2'], - }, -); - -type RegisterFormValues = z.infer; - -export function RegisterForm() { - const router = useRouter(); - const [turnstileToken, setTurnstileToken] = useState(''); - const [message, setMessage] = useState<{ - tone: 'success' | 'danger' | 'info'; - text: string; - } | null>(null); - - const statusQuery = useQuery({ - queryKey: ['public-status'], - queryFn: getPublicStatus, - }); - - const needsEmailVerification = statusQuery.data?.email_verification ?? false; - const needsTurnstile = statusQuery.data?.turnstile_check ?? false; - const registerEnabled = statusQuery.data?.register_enabled ?? false; - const passwordRegisterEnabled = - statusQuery.data?.password_register_enabled ?? false; - - const schema = useMemo(() => { - if (!needsEmailVerification) { - return baseSchema; - } - - return baseSchemaObject - .extend({ - email: z.string().email(TEXT.emailInvalid), - verification_code: z.string().min(1, TEXT.codeRequired), - }) - .refine((data) => data.password === data.password2, { - message: TEXT.passwordMismatch, - path: ['password2'], - }); - }, [needsEmailVerification]); - - const form = useForm({ - resolver: zodResolver(schema), - defaultValues: { - username: '', - password: '', - password2: '', - email: '', - verification_code: '', - }, - }); - - const registerMutation = useMutation({ - mutationFn: (values: RegisterFormValues) => - registerUser( - { - username: values.username, - password: values.password, - email: values.email, - verification_code: values.verification_code, - }, - turnstileToken || undefined, - ), - onSuccess: () => { - router.replace('/login'); - }, - onError: (error: Error) => { - setMessage({ tone: 'danger', text: error.message || TEXT.registerFailed }); - }, - }); - - const verificationMutation = useMutation({ - mutationFn: async () => { - const email = form.getValues('email'); - if (!email) { - form.setError('email', { message: TEXT.emailRequired }); - return; - } - await sendEmailVerification(email, turnstileToken || undefined); - }, - onSuccess: () => { - setMessage({ tone: 'success', text: TEXT.verificationSent }); - }, - onError: (error: Error) => { - setMessage({ - tone: 'danger', - text: error.message || TEXT.verificationFailed, - }); - }, - }); - - const handleSubmit = form.handleSubmit((values) => { - setMessage(null); - if (needsTurnstile && !turnstileToken) { - setMessage({ tone: 'info', text: TEXT.turnstileRequired }); - return; - } - registerMutation.mutate(values); - }); - - return ( - - - {!registerEnabled ? ( -
- -
- {TEXT.hasAccount} - - {TEXT.clickLogin} - -
-
- ) : !passwordRegisterEnabled ? ( -
- -
- {TEXT.backToLogin} - - {TEXT.clickLogin} - -
-
- ) : ( - <> -
- - - {form.formState.errors.username ? ( - - {form.formState.errors.username.message} - - ) : null} - - - - - {form.formState.errors.password ? ( - - {form.formState.errors.password.message} - - ) : null} - - - - - {form.formState.errors.password2 ? ( - - {form.formState.errors.password2.message} - - ) : null} - - - {needsEmailVerification ? ( - <> - - - {form.formState.errors.email ? ( - - {form.formState.errors.email.message} - - ) : null} - - - -
- - { - if (needsTurnstile && !turnstileToken) { - setMessage({ - tone: 'info', - text: TEXT.turnstileRequired, - }); - return; - } - setMessage(null); - verificationMutation.mutate(); - }} - disabled={verificationMutation.isPending} - > - {verificationMutation.isPending - ? TEXT.gettingCode - : TEXT.getCode} - -
- {form.formState.errors.verification_code ? ( - - {form.formState.errors.verification_code.message} - - ) : null} -
- - ) : null} - - {needsTurnstile && statusQuery.data?.turnstile_site_key ? ( - setTurnstileToken(token)} - onExpire={() => setTurnstileToken('')} - onError={() => setTurnstileToken('')} - /> - ) : null} - - {message ? ( - - ) : null} - - - {registerMutation.isPending ? TEXT.registering : TEXT.register} - - - -
- {TEXT.hasAccount} - - {TEXT.clickLogin} - -
- - )} -
-
- ); -} diff --git a/openflare_server/web/features/settings/components/settings-page.tsx b/openflare_server/web/features/settings/components/settings-page.tsx index a7ce6da5..c7e3b8b2 100644 --- a/openflare_server/web/features/settings/components/settings-page.tsx +++ b/openflare_server/web/features/settings/components/settings-page.tsx @@ -72,7 +72,6 @@ const defaultSystemFields = { GitHubOAuthEnabled: false, WeChatAuthEnabled: false, TurnstileCheckEnabled: false, - RegisterEnabled: false, SMTPServer: '', SMTPPort: '587', SMTPAccount: '', @@ -365,7 +364,6 @@ export function SettingsPage() { GitHubOAuthEnabled: toBoolean(optionMap.GitHubOAuthEnabled, false), WeChatAuthEnabled: toBoolean(optionMap.WeChatAuthEnabled, false), TurnstileCheckEnabled: toBoolean(optionMap.TurnstileCheckEnabled, false), - RegisterEnabled: toBoolean(optionMap.RegisterEnabled, false), SMTPServer: optionMap.SMTPServer ?? '', SMTPPort: optionMap.SMTPPort ?? '587', SMTPAccount: optionMap.SMTPAccount ?? '', @@ -1571,15 +1569,7 @@ export function SettingsPage() { } disabled={busyKey === 'toggle-EmailVerificationEnabled'} /> - - handleToggleOption('RegisterEnabled', checked) - } - disabled={busyKey === 'toggle-RegisterEnabled'} - /> +
当前已配置 {authSourcesQuery.data?.length ?? 0} 个认证源。