From 263db6bf30c4c0d0bc94c677b54d5c93937b3e9e Mon Sep 17 00:00:00 2001 From: tianqijiuyun-latiao <69459608+tianqijiuyun-latiao@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:04:42 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(security):=20=E6=9A=B4?= =?UTF-8?q?=E9=9C=B2=E8=BF=9E=E6=8E=A5=E9=85=8D=E7=BD=AE=E4=B8=8E=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E7=9A=84=E5=AF=86=E9=92=A5=E5=AD=98=E5=82=A8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/global_proxy.go | 18 +++- internal/app/methods_saved_connections.go | 44 +++++++++ .../app/methods_saved_connections_test.go | 94 +++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 internal/app/methods_saved_connections.go create mode 100644 internal/app/methods_saved_connections_test.go diff --git a/internal/app/global_proxy.go b/internal/app/global_proxy.go index 016fb26..ffb5b35 100644 --- a/internal/app/global_proxy.go +++ b/internal/app/global_proxy.go @@ -123,11 +123,26 @@ func proxyConfigEqual(a, b connection.ProxyConfig) bool { a.Password == b.Password } +func currentGlobalProxyView() connection.GlobalProxyView { + snapshot := currentGlobalProxyConfig() + if !snapshot.Enabled { + return connection.GlobalProxyView{Enabled: false} + } + return connection.GlobalProxyView{ + Enabled: true, + Type: snapshot.Proxy.Type, + Host: snapshot.Proxy.Host, + Port: snapshot.Proxy.Port, + User: snapshot.Proxy.User, + HasPassword: strings.TrimSpace(snapshot.Proxy.Password) != "", + } +} + func (a *App) GetGlobalProxyConfig() connection.QueryResult { return connection.QueryResult{ Success: true, Message: "OK", - Data: currentGlobalProxyConfig(), + Data: currentGlobalProxyView(), } } @@ -312,3 +327,4 @@ func buildProxyURLFromConfig(proxyConfig connection.ProxyConfig) (*url.URL, erro } return proxyURL, nil } + diff --git a/internal/app/methods_saved_connections.go b/internal/app/methods_saved_connections.go new file mode 100644 index 0000000..d8d916d --- /dev/null +++ b/internal/app/methods_saved_connections.go @@ -0,0 +1,44 @@ +package app + +import "GoNavi-Wails/internal/connection" + +func (a *App) savedConnectionRepository() *savedConnectionRepository { + return newSavedConnectionRepository(a.configDir, a.secretStore) +} + +func (a *App) GetSavedConnections() ([]connection.SavedConnectionView, error) { + return a.savedConnectionRepository().List() +} + +func (a *App) SaveConnection(input connection.SavedConnectionInput) (connection.SavedConnectionView, error) { + return a.savedConnectionRepository().Save(input) +} + +func (a *App) DeleteConnection(id string) error { + return a.savedConnectionRepository().Delete(id) +} + +func (a *App) DuplicateConnection(id string) (connection.SavedConnectionView, error) { + return a.savedConnectionRepository().Duplicate(id) +} + +func (a *App) ImportLegacyConnections(items []connection.LegacySavedConnection) ([]connection.SavedConnectionView, error) { + result := make([]connection.SavedConnectionView, 0, len(items)) + repo := a.savedConnectionRepository() + for _, item := range items { + view, err := repo.Save(connection.SavedConnectionInput(item)) + if err != nil { + return nil, err + } + result = append(result, view) + } + return result, nil +} + +func (a *App) SaveGlobalProxy(input connection.SaveGlobalProxyInput) (connection.GlobalProxyView, error) { + return a.saveGlobalProxy(input) +} + +func (a *App) ImportLegacyGlobalProxy(input connection.LegacyGlobalProxyInput) (connection.GlobalProxyView, error) { + return a.saveGlobalProxy(connection.SaveGlobalProxyInput(input)) +} diff --git a/internal/app/methods_saved_connections_test.go b/internal/app/methods_saved_connections_test.go new file mode 100644 index 0000000..4b117cc --- /dev/null +++ b/internal/app/methods_saved_connections_test.go @@ -0,0 +1,94 @@ +package app + +import ( + "testing" + + "GoNavi-Wails/internal/connection" +) + +func TestSaveConnectionMethodReturnsSecretlessView(t *testing.T) { + app := NewAppWithSecretStore(newFakeAppSecretStore()) + app.configDir = t.TempDir() + + result, err := app.SaveConnection(connection.SavedConnectionInput{ + ID: "conn-1", + Name: "Primary", + Config: connection.ConnectionConfig{ + ID: "conn-1", + Type: "postgres", + Host: "db.local", + Port: 5432, + User: "postgres", + Password: "postgres-secret", + }, + }) + if err != nil { + t.Fatal(err) + } + if result.Config.Password != "" { + t.Fatal("SaveConnection must not return plaintext password") + } + if !result.HasPrimaryPassword { + t.Fatal("expected HasPrimaryPassword=true") + } +} + +func TestDuplicateConnectionClonesSecretBundle(t *testing.T) { + app := NewAppWithSecretStore(newFakeAppSecretStore()) + app.configDir = t.TempDir() + + _, err := app.SaveConnection(connection.SavedConnectionInput{ + ID: "conn-1", + Name: "Primary", + Config: connection.ConnectionConfig{ + ID: "conn-1", + Type: "postgres", + Host: "db.local", + Port: 5432, + User: "postgres", + Password: "postgres-secret", + }, + }) + if err != nil { + t.Fatal(err) + } + + duplicate, err := app.DuplicateConnection("conn-1") + if err != nil { + t.Fatal(err) + } + if duplicate.ID == "conn-1" { + t.Fatal("duplicate should have a new id") + } + + resolved, err := app.resolveConnectionSecrets(duplicate.Config) + if err != nil { + t.Fatal(err) + } + if resolved.Password != "postgres-secret" { + t.Fatalf("expected duplicated secret bundle, got %q", resolved.Password) + } +} + +func TestSaveGlobalProxyReturnsSecretlessView(t *testing.T) { + app := NewAppWithSecretStore(newFakeAppSecretStore()) + app.configDir = t.TempDir() + + view, err := app.SaveGlobalProxy(connection.SaveGlobalProxyInput{ + Enabled: true, + Type: "http", + Host: "127.0.0.1", + Port: 8080, + User: "ops", + Password: "proxy-secret", + }) + if err != nil { + t.Fatal(err) + } + if view.Password != "" { + t.Fatal("global proxy view must not expose plaintext password") + } + if !view.HasPassword { + t.Fatal("expected hasPassword=true") + } +}