From 3c86e249f407c0ed886cb43ad7d7fbf05dcf3f29 Mon Sep 17 00:00:00 2001 From: Jlypx Date: Mon, 20 Jul 2026 00:09:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=20IP=20=E8=AF=B7=E6=B1=82=E5=A4=B4=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../internal/handler/admin/setting_handler.go | 1 + .../setting_handler_stepup_switch_test.go | 53 +++++++++++++++++++ .../handler/admin/setting_handler_update.go | 9 +++- backend/internal/handler/dto/settings.go | 9 ++-- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index d663cd61b3..6399c8eaf1 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -152,6 +152,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { TurnstileSiteKey: settings.TurnstileSiteKey, TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured, APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP, + ForwardedClientIPHeaders: settings.ForwardedClientIPHeaders, LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled, LinuxDoConnectClientID: settings.LinuxDoConnectClientID, LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured, diff --git a/backend/internal/handler/admin/setting_handler_stepup_switch_test.go b/backend/internal/handler/admin/setting_handler_stepup_switch_test.go index 22f7ebf469..676252fc93 100644 --- a/backend/internal/handler/admin/setting_handler_stepup_switch_test.go +++ b/backend/internal/handler/admin/setting_handler_stepup_switch_test.go @@ -2,6 +2,7 @@ package admin import ( "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" @@ -155,3 +156,55 @@ func TestUpdateSettingsOmittedSecuritySwitchesKeepDisabled(t *testing.T) { require.Equal(t, "false", repo.values[service.SettingKeyStepUpEnabled]) require.Equal(t, "false", repo.values[service.SettingKeySessionBindingEnabled]) } + +func TestUpdateSettingsForwardedClientIPHeadersOmittedPreservesAndEmptyClears(t *testing.T) { + h, repo := newStepUpSwitchTestHandler(t, map[string]string{ + service.SettingKeyForwardedClientIPHeaders: `["X-Cdn-Ip","True-Client-Ip"]`, + }) + + preserved := doUpdateSettings(t, h, map[string]any{"registration_enabled": true}, nil) + require.Equal(t, http.StatusOK, preserved.Code) + require.JSONEq(t, `["X-Cdn-Ip","True-Client-Ip"]`, repo.values[service.SettingKeyForwardedClientIPHeaders]) + require.Contains(t, preserved.Body.String(), `"forwarded_client_ip_headers":["X-Cdn-Ip","True-Client-Ip"]`) + + cleared := doUpdateSettings(t, h, map[string]any{"forwarded_client_ip_headers": []string{}}, nil) + require.Equal(t, http.StatusOK, cleared.Code) + require.JSONEq(t, `[]`, repo.values[service.SettingKeyForwardedClientIPHeaders]) + require.Contains(t, cleared.Body.String(), `"forwarded_client_ip_headers":[]`) +} + +func TestUpdateSettingsMalformedForwardedClientIPHeadersRemainFailClosedWhenOmitted(t *testing.T) { + cfg := &config.Config{Default: config.DefaultConfig{UserConcurrency: 5}} + repo := &settingHandlerRepoStub{values: map[string]string{ + service.SettingKeyAPIKeyACLTrustForwardedIP: "true", + service.SettingKeyForwardedClientIPHeaders: `{"not":"an array"}`, + }} + svc := service.NewSettingService(repo, cfg) + require.ErrorContains(t, svc.LoadForwardedClientIPSettings(context.Background()), "load forwarded client ip headers") + require.False(t, cfg.ForwardedClientIPSettings().TrustForwardedIP) + h := NewSettingHandler(svc, nil, nil, nil, nil, nil, nil) + + rec := doUpdateSettings(t, h, map[string]any{"registration_enabled": true}, nil) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "false", repo.values[service.SettingKeyAPIKeyACLTrustForwardedIP]) + require.JSONEq(t, `[]`, repo.values[service.SettingKeyForwardedClientIPHeaders]) + runtimeSettings := cfg.ForwardedClientIPSettings() + require.False(t, runtimeSettings.TrustForwardedIP) + require.Empty(t, runtimeSettings.Headers) + require.Contains(t, rec.Body.String(), `"api_key_acl_trust_forwarded_ip":false`) + require.Contains(t, rec.Body.String(), `"forwarded_client_ip_headers":[]`) +} + +func TestUpdateSettingsRejectsInvalidForwardedClientIPHeader(t *testing.T) { + h, repo := newStepUpSwitchTestHandler(t, map[string]string{ + service.SettingKeyForwardedClientIPHeaders: `["X-Existing-IP"]`, + }) + + rec := doUpdateSettings(t, h, map[string]any{ + "forwarded_client_ip_headers": []string{"X Invalid"}, + }, nil) + + require.Equal(t, http.StatusBadRequest, rec.Code) + require.JSONEq(t, `["X-Existing-IP"]`, repo.values[service.SettingKeyForwardedClientIPHeaders]) +} diff --git a/backend/internal/handler/admin/setting_handler_update.go b/backend/internal/handler/admin/setting_handler_update.go index 40343ed311..2f5792e213 100644 --- a/backend/internal/handler/admin/setting_handler_update.go +++ b/backend/internal/handler/admin/setting_handler_update.go @@ -51,7 +51,8 @@ type UpdateSettingsRequest struct { TurnstileSecretKey string `json:"turnstile_secret_key"` // API Key IP 访问控制设置 - APIKeyACLTrustForwardedIP *bool `json:"api_key_acl_trust_forwarded_ip"` + APIKeyACLTrustForwardedIP *bool `json:"api_key_acl_trust_forwarded_ip"` + ForwardedClientIPHeaders *[]string `json:"forwarded_client_ip_headers"` // LinuxDo Connect OAuth 登录 LinuxDoConnectEnabled bool `json:"linuxdo_connect_enabled"` @@ -400,6 +401,10 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { if req.StepUpEnabled != nil { stepUpEnabled = *req.StepUpEnabled } + forwardedClientIPHeaders := append([]string(nil), previousSettings.ForwardedClientIPHeaders...) + if req.ForwardedClientIPHeaders != nil { + forwardedClientIPHeaders = append([]string(nil), (*req.ForwardedClientIPHeaders)...) + } // 开启敏感操作 step-up 门控属自锁风险操作:仅允许本人已启用 TOTP 的管理员会话开启, // 否则开启后操作者立即被挡在所有敏感操作之外。仅在 false→true 的开启瞬间校验, @@ -1269,6 +1274,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { } return previousSettings.APIKeyACLTrustForwardedIP }(), + ForwardedClientIPHeaders: forwardedClientIPHeaders, LinuxDoConnectEnabled: req.LinuxDoConnectEnabled, LinuxDoConnectClientID: req.LinuxDoConnectClientID, LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret, @@ -1796,6 +1802,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { TurnstileSiteKey: updatedSettings.TurnstileSiteKey, TurnstileSecretKeyConfigured: updatedSettings.TurnstileSecretKeyConfigured, APIKeyACLTrustForwardedIP: updatedSettings.APIKeyACLTrustForwardedIP, + ForwardedClientIPHeaders: updatedSettings.ForwardedClientIPHeaders, LinuxDoConnectEnabled: updatedSettings.LinuxDoConnectEnabled, LinuxDoConnectClientID: updatedSettings.LinuxDoConnectClientID, LinuxDoConnectClientSecretConfigured: updatedSettings.LinuxDoConnectClientSecretConfigured, diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index fd7e7b6a52..a62b9b07a7 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -52,10 +52,11 @@ type SystemSettings struct { SMTPFromName string `json:"smtp_from_name"` SMTPUseTLS bool `json:"smtp_use_tls"` - TurnstileEnabled bool `json:"turnstile_enabled"` - TurnstileSiteKey string `json:"turnstile_site_key"` - TurnstileSecretKeyConfigured bool `json:"turnstile_secret_key_configured"` - APIKeyACLTrustForwardedIP bool `json:"api_key_acl_trust_forwarded_ip"` + TurnstileEnabled bool `json:"turnstile_enabled"` + TurnstileSiteKey string `json:"turnstile_site_key"` + TurnstileSecretKeyConfigured bool `json:"turnstile_secret_key_configured"` + APIKeyACLTrustForwardedIP bool `json:"api_key_acl_trust_forwarded_ip"` + ForwardedClientIPHeaders []string `json:"forwarded_client_ip_headers"` LinuxDoConnectEnabled bool `json:"linuxdo_connect_enabled"` LinuxDoConnectClientID string `json:"linuxdo_connect_client_id"`