Files
MyGoNavi/internal/app/daily_secret_migration.go
Syngnat c7cf9526de 🐛 fix(security): 修复 macOS 无法打开应用及三平台依赖系统钥匙串的问题
- 密文存储:新增 dailysecret 本地存储引擎,连接/代理/AI 密钥不再依赖系统钥匙串
- 启动迁移:自动将已有钥匙串密文迁移到本地 JSON,用户无感知
- WebKit 迁移:从旧版 Wails WebKit LocalStorage 中恢复连接与代理数据
- DMG 修复:移除 --sandbox-safe 避免扩展属性污染签名,新增 xattr 清理与签名校验
- 安全适配:钥匙串不可用时标记完成而非回滚,消除无钥匙串环境下的阻塞
- 出口脱敏:所有连接/代理 API 返回前统一 sanitize 防止密文泄漏
2026-04-13 12:40:25 +08:00

224 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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{}
}