Windows 端使用内存版 MemStore(keychain_other.go, 构建标签 !darwin), 进程退出后密码丢失,导致每次启动都需重新输入密码才能连接。 新增 keychain_windows.go,基于 Windows 凭据管理器(Credential Manager) 实现 Store 接口,密码经 Windows 登录凭据加密并持久化,跨重启保留。 keychain_other.go 构建标签收窄为 !darwin && !windows,作为 Linux 回退。 macOS 版本不受影响(仍由 keychain_darwin.go 独占编译)。
95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
//go:build windows
|
|
|
|
package keychain
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/danieljoos/wincred"
|
|
)
|
|
|
|
// keyPrefixes distinguish password vs token entries in the credential
|
|
// store, mirroring the macOS implementation.
|
|
const (
|
|
passwordAccountPrefix = "password:"
|
|
tokenAccountPrefix = "token:"
|
|
)
|
|
|
|
// WinStore implements Store using the Windows Credential Manager.
|
|
type WinStore struct{}
|
|
|
|
// New returns a Windows Credential Manager-backed Store.
|
|
func New() Store {
|
|
return WinStore{}
|
|
}
|
|
|
|
// targetName builds the credential TargetName from the account prefix
|
|
// and profile name: "<BundleID>/<prefix><profile>".
|
|
func targetName(account string) string {
|
|
return ServiceName + "/" + account
|
|
}
|
|
|
|
func (WinStore) setItem(account, secret string) error {
|
|
cred := wincred.NewGenericCredential(targetName(account))
|
|
cred.CredentialBlob = []byte(secret)
|
|
if err := cred.Write(); err != nil {
|
|
return fmt.Errorf("wincred write %s: %w", account, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (WinStore) getItem(account string) (string, error) {
|
|
cred, err := wincred.GetGenericCredential(targetName(account))
|
|
if err != nil {
|
|
if errors.Is(err, wincred.ErrElementNotFound) {
|
|
return "", ErrNotFound
|
|
}
|
|
return "", fmt.Errorf("wincred read %s: %w", account, err)
|
|
}
|
|
return string(cred.CredentialBlob), nil
|
|
}
|
|
|
|
func (WinStore) deleteItem(account string) error {
|
|
cred, err := wincred.GetGenericCredential(targetName(account))
|
|
if err != nil {
|
|
if errors.Is(err, wincred.ErrElementNotFound) {
|
|
return nil // already gone — treat as success
|
|
}
|
|
return fmt.Errorf("wincred read %s: %w", account, err)
|
|
}
|
|
if err := cred.Delete(); err != nil {
|
|
return fmt.Errorf("wincred delete %s: %w", account, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s WinStore) SetPassword(profileName, password string) error {
|
|
return s.setItem(passwordAccountPrefix+profileName, password)
|
|
}
|
|
|
|
func (s WinStore) GetPassword(profileName string) (string, error) {
|
|
return s.getItem(passwordAccountPrefix + profileName)
|
|
}
|
|
|
|
func (s WinStore) DeletePassword(profileName string) error {
|
|
return s.deleteItem(passwordAccountPrefix + profileName)
|
|
}
|
|
|
|
func (s WinStore) SetToken(profileName, token string) error {
|
|
return s.setItem(tokenAccountPrefix+profileName, token)
|
|
}
|
|
|
|
func (s WinStore) GetToken(profileName string) (string, error) {
|
|
return s.getItem(tokenAccountPrefix + profileName)
|
|
}
|
|
|
|
func (s WinStore) DeleteToken(profileName string) error {
|
|
return s.deleteItem(tokenAccountPrefix + profileName)
|
|
}
|
|
|
|
func (s WinStore) DeleteAll(profileName string) error {
|
|
_ = s.DeletePassword(profileName)
|
|
return s.DeleteToken(profileName)
|
|
}
|