117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
//go:build darwin && cgo
|
|
|
|
package ccm
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
"github.com/keybase/go-keychain"
|
|
)
|
|
|
|
func getKeychainServiceName() string {
|
|
configDirectory := os.Getenv("CLAUDE_CONFIG_DIR")
|
|
if configDirectory == "" {
|
|
return "Claude Code-credentials"
|
|
}
|
|
|
|
userInfo, err := getRealUser()
|
|
if err != nil {
|
|
return "Claude Code-credentials"
|
|
}
|
|
defaultConfigDirectory := filepath.Join(userInfo.HomeDir, ".claude")
|
|
if configDirectory == defaultConfigDirectory {
|
|
return "Claude Code-credentials"
|
|
}
|
|
|
|
hash := sha256.Sum256([]byte(configDirectory))
|
|
return "Claude Code-credentials-" + hex.EncodeToString(hash[:])[:8]
|
|
}
|
|
|
|
func platformReadCredentials(customPath string) (*oauthCredentials, error) {
|
|
if customPath != "" {
|
|
return readCredentialsFromFile(customPath)
|
|
}
|
|
|
|
userInfo, err := getRealUser()
|
|
if err == nil {
|
|
query := keychain.NewItem()
|
|
query.SetSecClass(keychain.SecClassGenericPassword)
|
|
query.SetService(getKeychainServiceName())
|
|
query.SetAccount(userInfo.Username)
|
|
query.SetMatchLimit(keychain.MatchLimitOne)
|
|
query.SetReturnData(true)
|
|
|
|
results, err := keychain.QueryItem(query)
|
|
if err == nil && len(results) == 1 {
|
|
var container struct {
|
|
ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"`
|
|
}
|
|
unmarshalErr := json.Unmarshal(results[0].Data, &container)
|
|
if unmarshalErr == nil && container.ClaudeAIAuth != nil {
|
|
return container.ClaudeAIAuth, nil
|
|
}
|
|
}
|
|
if err != nil && err != keychain.ErrorItemNotFound {
|
|
return nil, E.Cause(err, "query keychain")
|
|
}
|
|
}
|
|
|
|
defaultPath, err := getDefaultCredentialsPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return readCredentialsFromFile(defaultPath)
|
|
}
|
|
|
|
func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error {
|
|
if customPath != "" {
|
|
return writeCredentialsToFile(oauthCredentials, customPath)
|
|
}
|
|
|
|
userInfo, err := getRealUser()
|
|
if err == nil {
|
|
data, err := json.Marshal(map[string]any{"claudeAiOauth": oauthCredentials})
|
|
if err == nil {
|
|
serviceName := getKeychainServiceName()
|
|
item := keychain.NewItem()
|
|
item.SetSecClass(keychain.SecClassGenericPassword)
|
|
item.SetService(serviceName)
|
|
item.SetAccount(userInfo.Username)
|
|
item.SetData(data)
|
|
item.SetAccessible(keychain.AccessibleWhenUnlocked)
|
|
|
|
err = keychain.AddItem(item)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if err == keychain.ErrorDuplicateItem {
|
|
query := keychain.NewItem()
|
|
query.SetSecClass(keychain.SecClassGenericPassword)
|
|
query.SetService(serviceName)
|
|
query.SetAccount(userInfo.Username)
|
|
|
|
updateItem := keychain.NewItem()
|
|
updateItem.SetData(data)
|
|
|
|
updateErr := keychain.UpdateItem(query, updateItem)
|
|
if updateErr == nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultPath, err := getDefaultCredentialsPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeCredentialsToFile(oauthCredentials, defaultPath)
|
|
}
|