mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-27 08:05:33 +00:00
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:
@@ -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 功能
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user