fix: 迁移客户端 IP 兼容开关

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jlypx
2026-07-19 21:41:45 +08:00
co-authored by Sisyphus
parent 6aa6b3a968
commit 93717394b2
4 changed files with 196 additions and 10 deletions
@@ -165,6 +165,7 @@ const (
// API Key IP 访问控制设置
SettingKeyAPIKeyACLTrustForwardedIP = "api_key_acl_trust_forwarded_ip" // API Key IP 白/黑名单是否信任转发 IP
settingKeyForwardedClientIPModeV2 = "forwarded_client_ip_mode_v2_migrated"
// TOTP 双因素认证设置
SettingKeyTotpEnabled = "totp_enabled" // 是否启用 TOTP 2FA 功能
+3 -2
View File
@@ -54,7 +54,8 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeyLoginAgreementMode: defaultLoginAgreementMode,
SettingKeyLoginAgreementUpdatedAt: defaultLoginAgreementDate,
SettingKeyLoginAgreementDocuments: loginAgreementDocumentsJSON,
SettingKeyAPIKeyACLTrustForwardedIP: "false",
SettingKeyAPIKeyACLTrustForwardedIP: "true",
settingKeyForwardedClientIPModeV2: "true",
SettingKeySiteName: "Sub2API",
SettingKeySiteLogo: "",
SettingKeyPurchaseSubscriptionEnabled: "false",
@@ -249,7 +250,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
if value, ok := settings[SettingKeyAPIKeyACLTrustForwardedIP]; ok {
apiKeyACLTrustForwardedIP = value == "true"
} else if s != nil && s.cfg != nil {
apiKeyACLTrustForwardedIP = s.cfg.Security.TrustForwardedIPForAPIKeyACL
apiKeyACLTrustForwardedIP = s.cfg.ForwardedClientIPTrustEnabled()
}
result := &SystemSettings{
RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true",
+28 -8
View File
@@ -2,7 +2,6 @@ package service
import (
"context"
"errors"
"fmt"
"sync/atomic"
@@ -220,15 +219,36 @@ func (s *SettingService) LoadAPIKeyACLTrustForwardedIPSetting(ctx context.Contex
if s == nil || s.cfg == nil || s.settingRepo == nil {
return nil
}
value, err := s.settingRepo.GetValue(ctx, SettingKeyAPIKeyACLTrustForwardedIP)
values, err := s.settingRepo.GetMultiple(ctx, []string{
SettingKeyAPIKeyACLTrustForwardedIP,
settingKeyForwardedClientIPModeV2,
})
if err != nil {
if errors.Is(err, ErrSettingNotFound) {
s.cfg.SetTrustForwardedIPForAPIKeyACL(s.cfg.Security.TrustForwardedIPForAPIKeyACL)
return nil
}
return fmt.Errorf("get api key acl forwarded ip setting: %w", err)
s.cfg.SetTrustForwardedIPForAPIKeyACL(false)
return fmt.Errorf("get forwarded client ip settings: %w", err)
}
enabled := value == "true"
enabled := s.cfg.Security.TrustForwardedIPForAPIKeyACL
storedValue, hasStoredValue := values[SettingKeyAPIKeyACLTrustForwardedIP]
if hasStoredValue {
enabled = storedValue == "true"
}
if values[settingKeyForwardedClientIPModeV2] != "true" {
updates := map[string]string{settingKeyForwardedClientIPModeV2: "true"}
// Before this migration, new installations persisted false by default.
// Restore compatibility only when no trusted-proxy policy was configured.
if hasStoredValue && !enabled && !s.cfg.Server.TrustedProxiesConfigured {
enabled = true
updates[SettingKeyAPIKeyACLTrustForwardedIP] = "true"
}
if err := s.settingRepo.SetMultiple(ctx, updates); err != nil {
s.cfg.SetTrustForwardedIPForAPIKeyACL(enabled)
return fmt.Errorf("migrate forwarded client ip setting: %w", err)
}
}
s.cfg.SetTrustForwardedIPForAPIKeyACL(enabled)
return nil
}
@@ -5,6 +5,7 @@ package service
import (
"context"
"encoding/json"
"errors"
"math"
"strconv"
"testing"
@@ -87,6 +88,58 @@ func (s *settingGetAllRepoStub) Delete(ctx context.Context, key string) error {
panic("unexpected Delete call")
}
type forwardedIPMigrationRepoStub struct {
values map[string]string
updates map[string]string
getMultipleErr error
setMultipleErr error
}
func (s *forwardedIPMigrationRepoStub) Get(context.Context, string) (*Setting, error) {
panic("unexpected Get call")
}
func (s *forwardedIPMigrationRepoStub) GetValue(context.Context, string) (string, error) {
panic("unexpected GetValue call")
}
func (s *forwardedIPMigrationRepoStub) Set(context.Context, string, string) error {
panic("unexpected Set call")
}
func (s *forwardedIPMigrationRepoStub) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
if s.getMultipleErr != nil {
return nil, s.getMultipleErr
}
result := make(map[string]string, len(keys))
for _, key := range keys {
if value, ok := s.values[key]; ok {
result[key] = value
}
}
return result, nil
}
func (s *forwardedIPMigrationRepoStub) SetMultiple(_ context.Context, values map[string]string) error {
if s.setMultipleErr != nil {
return s.setMultipleErr
}
s.updates = make(map[string]string, len(values))
for key, value := range values {
s.values[key] = value
s.updates[key] = value
}
return nil
}
func (s *forwardedIPMigrationRepoStub) GetAll(context.Context) (map[string]string, error) {
panic("unexpected GetAll call")
}
func (s *forwardedIPMigrationRepoStub) Delete(context.Context, string) error {
panic("unexpected Delete call")
}
type settingAntigravityUARepoStub struct {
values map[string]string
}
@@ -519,6 +572,117 @@ func TestSettingService_ParseSettings_APIKeyACLTrustForwardedIPFallsBackToConfig
require.True(t, got.APIKeyACLTrustForwardedIP)
}
func TestSettingService_ParseSettings_APIKeyACLTrustForwardedIPUsesStoredValue(t *testing.T) {
cfg := &config.Config{}
cfg.SetTrustForwardedIPForAPIKeyACL(true)
svc := NewSettingService(&settingUpdateRepoStub{}, cfg)
got := svc.parseSettings(map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"})
require.False(t, got.APIKeyACLTrustForwardedIP)
}
func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingMigration(t *testing.T) {
tests := []struct {
name string
values map[string]string
trustedProxiesSet bool
configDefault bool
wantEnabled bool
wantForwardedIPUpdate string
wantMigrationMarkerSet bool
}{
{
name: "missing setting follows configured default",
values: map[string]string{},
configDefault: true,
wantEnabled: true,
wantMigrationMarkerSet: true,
},
{
name: "legacy false without proxy config migrates to compatibility",
values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"},
wantEnabled: true,
wantForwardedIPUpdate: "true",
wantMigrationMarkerSet: true,
},
{
name: "legacy false with explicit proxy config stays secure",
values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"},
trustedProxiesSet: true,
wantEnabled: false,
wantMigrationMarkerSet: true,
},
{
name: "completed migration preserves later false choice",
values: map[string]string{
SettingKeyAPIKeyACLTrustForwardedIP: "false",
settingKeyForwardedClientIPModeV2: "true",
},
wantEnabled: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
repo := &forwardedIPMigrationRepoStub{values: test.values}
cfg := &config.Config{Server: config.ServerConfig{TrustedProxiesConfigured: test.trustedProxiesSet}}
cfg.Security.TrustForwardedIPForAPIKeyACL = test.configDefault
svc := NewSettingService(repo, cfg)
require.NoError(t, svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background()))
require.Equal(t, test.wantEnabled, cfg.TrustForwardedIPForAPIKeyACL())
require.Equal(t, test.wantForwardedIPUpdate, repo.updates[SettingKeyAPIKeyACLTrustForwardedIP])
if test.wantMigrationMarkerSet {
require.Equal(t, "true", repo.updates[settingKeyForwardedClientIPModeV2])
} else {
require.Nil(t, repo.updates)
}
})
}
}
func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingReadFailureFailsClosed(t *testing.T) {
repo := &forwardedIPMigrationRepoStub{
getMultipleErr: errors.New("database unavailable"),
}
cfg := &config.Config{}
cfg.SetTrustForwardedIPForAPIKeyACL(true)
svc := NewSettingService(repo, cfg)
err := svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background())
require.ErrorContains(t, err, "get forwarded client ip settings")
require.False(t, cfg.TrustForwardedIPForAPIKeyACL())
}
func TestSettingService_LoadAPIKeyACLTrustForwardedIPSettingWriteFailureUsesComputedMode(t *testing.T) {
tests := []struct {
name string
trustedProxiesSet bool
wantEnabled bool
}{
{name: "compatibility migration remains effective", wantEnabled: true},
{name: "explicit proxy policy remains secure", trustedProxiesSet: true, wantEnabled: false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
repo := &forwardedIPMigrationRepoStub{
values: map[string]string{SettingKeyAPIKeyACLTrustForwardedIP: "false"},
setMultipleErr: errors.New("database unavailable"),
}
cfg := &config.Config{Server: config.ServerConfig{TrustedProxiesConfigured: test.trustedProxiesSet}}
svc := NewSettingService(repo, cfg)
err := svc.LoadAPIKeyACLTrustForwardedIPSetting(context.Background())
require.ErrorContains(t, err, "migrate forwarded client ip setting")
require.Equal(t, test.wantEnabled, cfg.TrustForwardedIPForAPIKeyACL())
})
}
}
func TestSettingService_GetAntigravityUserAgentVersion_Precedence(t *testing.T) {
t.Run("后台设置优先", func(t *testing.T) {
svc := NewSettingService(&settingAntigravityUARepoStub{values: map[string]string{