mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-11 17:09:49 +08:00
- 密文存储:新增 dailysecret 本地存储引擎,连接/代理/AI 密钥不再依赖系统钥匙串 - 启动迁移:自动将已有钥匙串密文迁移到本地 JSON,用户无感知 - WebKit 迁移:从旧版 Wails WebKit LocalStorage 中恢复连接与代理数据 - DMG 修复:移除 --sandbox-safe 避免扩展属性污染签名,新增 xattr 清理与签名校验 - 安全适配:钥匙串不可用时标记完成而非回滚,消除无钥匙串环境下的阻塞 - 出口脱敏:所有连接/代理 API 返回前统一 sanitize 防止密文泄漏
224 lines
6.1 KiB
Go
224 lines
6.1 KiB
Go
package app
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
|
||
"GoNavi-Wails/internal/connection"
|
||
"GoNavi-Wails/internal/logger"
|
||
"GoNavi-Wails/internal/secretstore"
|
||
)
|
||
|
||
func migrateDailySecretsIfNeeded(a *App) error {
|
||
return migrateDailySecretsIfNeededWithHome(a, os.UserHomeDir)
|
||
}
|
||
|
||
func migrateDarwinDailySecretsIfNeeded(a *App) error {
|
||
return migrateDailySecretsIfNeeded(a)
|
||
}
|
||
|
||
func migrateDailySecretsIfNeededWithHome(a *App, resolveHomeDir func() (string, error)) error {
|
||
if a == nil {
|
||
return nil
|
||
}
|
||
|
||
var legacy legacyWebKitVisibleConfig
|
||
if resolveHomeDir != nil {
|
||
homeDir, err := resolveHomeDir()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
legacyConfig, _, err := findLegacyWebKitVisibleConfig(homeDir)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
legacy = legacyConfig
|
||
}
|
||
|
||
repo := a.savedConnectionRepository()
|
||
if err := migrateSavedConnectionSecrets(repo, legacy); err != nil {
|
||
return err
|
||
}
|
||
return migrateGlobalProxySecret(a, legacy)
|
||
}
|
||
|
||
func migrateDarwinDailySecretsIfNeededWithHome(a *App, resolveHomeDir func() (string, error)) error {
|
||
return migrateDailySecretsIfNeededWithHome(a, resolveHomeDir)
|
||
}
|
||
|
||
func migrateSavedConnectionSecrets(repo *savedConnectionRepository, legacy legacyWebKitVisibleConfig) error {
|
||
if repo == nil {
|
||
return nil
|
||
}
|
||
|
||
items, err := repo.load()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
changed := false
|
||
for index, item := range items {
|
||
bundle, found, err := repo.resolveMigrationConnectionBundle(item, legacy)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if found && bundle.hasAny() {
|
||
if err := repo.saveSecretBundle(item.ID, bundle); err != nil {
|
||
return err
|
||
}
|
||
normalized := item
|
||
normalized.Config = stripConnectionSecretFields(normalized.Config)
|
||
normalized.SecretRef = ""
|
||
applyConnectionBundleFlags(&normalized, bundle)
|
||
items[index] = normalized
|
||
changed = true
|
||
continue
|
||
}
|
||
|
||
inline := extractConnectionSecretBundle(item.Config)
|
||
if !inline.hasAny() && !savedConnectionViewHasSecrets(item) && strings.TrimSpace(item.SecretRef) == "" {
|
||
continue
|
||
}
|
||
if err := repo.deleteSecretBundle(item.ID); err != nil {
|
||
return err
|
||
}
|
||
item.Config = stripConnectionSecretFields(item.Config)
|
||
item.SecretRef = ""
|
||
applyConnectionBundleFlags(&item, connectionSecretBundle{})
|
||
items[index] = item
|
||
changed = true
|
||
logger.Warnf("日常连接密文未回填:连接=%s,已停用旧系统密文引用,请重新保存连接密码", strings.TrimSpace(item.ID))
|
||
}
|
||
|
||
if changed {
|
||
return repo.saveAll(items)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (r *savedConnectionRepository) resolveMigrationConnectionBundle(view connection.SavedConnectionView, legacy legacyWebKitVisibleConfig) (connectionSecretBundle, bool, error) {
|
||
inline := extractConnectionSecretBundle(view.Config)
|
||
if inline.hasAny() {
|
||
return inline, true, nil
|
||
}
|
||
|
||
stored, ok, err := r.dailySecrets().GetConnection(view.ID)
|
||
if err != nil {
|
||
return connectionSecretBundle{}, false, err
|
||
}
|
||
if ok {
|
||
return fromDailyConnectionBundle(stored), true, nil
|
||
}
|
||
|
||
legacyBundle := findLegacyConnectionSecretBundle(legacy.Connections, view.ID)
|
||
if legacyBundle.hasAny() {
|
||
return legacyBundle, true, nil
|
||
}
|
||
|
||
if !shouldReadLegacySecretStoreForDailySecrets() {
|
||
return connectionSecretBundle{}, false, nil
|
||
}
|
||
|
||
if strings.TrimSpace(view.SecretRef) == "" {
|
||
return connectionSecretBundle{}, false, nil
|
||
}
|
||
bundle, err := r.loadSecretBundleFromStore(view)
|
||
if err == nil {
|
||
return bundle, true, nil
|
||
}
|
||
if os.IsNotExist(err) || secretstore.IsUnavailable(err) {
|
||
return connectionSecretBundle{}, false, nil
|
||
}
|
||
return connectionSecretBundle{}, false, err
|
||
}
|
||
|
||
func migrateGlobalProxySecret(a *App, legacy legacyWebKitVisibleConfig) error {
|
||
view, err := a.loadStoredGlobalProxyView()
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil
|
||
}
|
||
return err
|
||
}
|
||
|
||
bundle, found, err := a.resolveMigrationGlobalProxyBundle(view, legacy)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if found && strings.TrimSpace(bundle.Password) != "" {
|
||
if err := a.dailySecretStore().PutGlobalProxy(toDailyGlobalProxyBundle(bundle)); err != nil {
|
||
return err
|
||
}
|
||
normalized := view
|
||
normalized.Password = ""
|
||
normalized.SecretRef = ""
|
||
normalized.HasPassword = true
|
||
if normalized != view {
|
||
return a.persistGlobalProxyView(normalized)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
inline := extractGlobalProxySecretBundle(view)
|
||
if !view.HasPassword && strings.TrimSpace(view.SecretRef) == "" && strings.TrimSpace(inline.Password) == "" {
|
||
return nil
|
||
}
|
||
if err := a.dailySecretStore().DeleteGlobalProxy(); err != nil {
|
||
return err
|
||
}
|
||
view.Password = ""
|
||
view.SecretRef = ""
|
||
view.HasPassword = false
|
||
logger.Warnf("日常全局代理密文未回填,已停用旧系统密文引用,如需继续使用请重新保存代理密码")
|
||
return a.persistGlobalProxyView(view)
|
||
}
|
||
|
||
func (a *App) resolveMigrationGlobalProxyBundle(view connection.GlobalProxyView, legacy legacyWebKitVisibleConfig) (globalProxySecretBundle, bool, error) {
|
||
inline := extractGlobalProxySecretBundle(view)
|
||
if strings.TrimSpace(inline.Password) != "" {
|
||
return inline, true, nil
|
||
}
|
||
|
||
stored, ok, err := a.dailySecretStore().GetGlobalProxy()
|
||
if err != nil {
|
||
return globalProxySecretBundle{}, false, err
|
||
}
|
||
if ok {
|
||
return fromDailyGlobalProxyBundle(stored), true, nil
|
||
}
|
||
|
||
if legacy.GlobalProxy != nil && strings.TrimSpace(legacy.GlobalProxy.Password) != "" {
|
||
return globalProxySecretBundle{Password: legacy.GlobalProxy.Password}, true, nil
|
||
}
|
||
|
||
if !shouldReadLegacySecretStoreForDailySecrets() {
|
||
return globalProxySecretBundle{}, false, nil
|
||
}
|
||
|
||
if strings.TrimSpace(view.SecretRef) == "" {
|
||
return globalProxySecretBundle{}, false, nil
|
||
}
|
||
bundle, err := a.loadGlobalProxySecretBundleFromStore(view)
|
||
if err == nil {
|
||
return bundle, true, nil
|
||
}
|
||
if os.IsNotExist(err) || secretstore.IsUnavailable(err) {
|
||
return globalProxySecretBundle{}, false, nil
|
||
}
|
||
return globalProxySecretBundle{}, false, err
|
||
}
|
||
|
||
func findLegacyConnectionSecretBundle(items []connection.LegacySavedConnection, id string) connectionSecretBundle {
|
||
targetID := strings.TrimSpace(id)
|
||
if targetID == "" {
|
||
return connectionSecretBundle{}
|
||
}
|
||
for _, item := range items {
|
||
if strings.TrimSpace(item.ID) != targetID {
|
||
continue
|
||
}
|
||
return extractConnectionSecretBundle(item.Config)
|
||
}
|
||
return connectionSecretBundle{}
|
||
}
|