refactor(auth): Centralize logging for saving credentials

The logic for logging the path where credentials are saved was duplicated across several client implementations.

This commit refactors this behavior by creating a new centralized function, `misc.LogSavingCredentials`, to handle this logging. The `SaveTokenToFile` method in each authentication token storage struct now calls this new function, ensuring consistent logging and reducing code duplication.

The redundant logging statements in the client-level `SaveTokenToFile` methods have been removed.
This commit is contained in:
hkfires
2025-09-19 09:29:31 +08:00
parent 39518ec633
commit 2274d7488b
10 changed files with 80 additions and 20 deletions

View File

@@ -8,6 +8,8 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
) )
// ClaudeTokenStorage stores OAuth2 token information for Anthropic Claude API authentication. // ClaudeTokenStorage stores OAuth2 token information for Anthropic Claude API authentication.
@@ -46,6 +48,7 @@ type ClaudeTokenStorage struct {
// Returns: // Returns:
// - error: An error if the operation fails, nil otherwise // - error: An error if the operation fails, nil otherwise
func (ts *ClaudeTokenStorage) SaveTokenToFile(authFilePath string) error { func (ts *ClaudeTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "claude" ts.Type = "claude"
// Create directory structure if it doesn't exist // Create directory structure if it doesn't exist

View File

@@ -8,6 +8,8 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
) )
// CodexTokenStorage stores OAuth2 token information for OpenAI Codex API authentication. // CodexTokenStorage stores OAuth2 token information for OpenAI Codex API authentication.
@@ -42,6 +44,7 @@ type CodexTokenStorage struct {
// Returns: // Returns:
// - error: An error if the operation fails, nil otherwise // - error: An error if the operation fails, nil otherwise
func (ts *CodexTokenStorage) SaveTokenToFile(authFilePath string) error { func (ts *CodexTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "codex" ts.Type = "codex"
if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory: %v", err) return fmt.Errorf("failed to create directory: %v", err)

View File

@@ -9,6 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -21,6 +22,7 @@ type GeminiWebTokenStorage struct {
// SaveTokenToFile serializes the Gemini Web token storage to a JSON file. // SaveTokenToFile serializes the Gemini Web token storage to a JSON file.
func (ts *GeminiWebTokenStorage) SaveTokenToFile(authFilePath string) error { func (ts *GeminiWebTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "gemini-web" ts.Type = "gemini-web"
if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory: %v", err) return fmt.Errorf("failed to create directory: %v", err)

View File

@@ -9,6 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -45,6 +46,7 @@ type GeminiTokenStorage struct {
// Returns: // Returns:
// - error: An error if the operation fails, nil otherwise // - error: An error if the operation fails, nil otherwise
func (ts *GeminiTokenStorage) SaveTokenToFile(authFilePath string) error { func (ts *GeminiTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "gemini" ts.Type = "gemini"
if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory: %v", err) return fmt.Errorf("failed to create directory: %v", err)

View File

@@ -8,6 +8,8 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
) )
// QwenTokenStorage stores OAuth2 token information for Alibaba Qwen API authentication. // QwenTokenStorage stores OAuth2 token information for Alibaba Qwen API authentication.
@@ -40,6 +42,7 @@ type QwenTokenStorage struct {
// Returns: // Returns:
// - error: An error if the operation fails, nil otherwise // - error: An error if the operation fails, nil otherwise
func (ts *QwenTokenStorage) SaveTokenToFile(authFilePath string) error { func (ts *QwenTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "qwen" ts.Type = "qwen"
if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory: %v", err) return fmt.Errorf("failed to create directory: %v", err)

View File

@@ -831,7 +831,6 @@ func (c *GeminiCLIClient) GetProjectList(ctx context.Context) (*interfaces.GCPPr
// - error: An error if the save operation fails, nil otherwise. // - error: An error if the save operation fails, nil otherwise.
func (c *GeminiCLIClient) SaveTokenToFile() error { func (c *GeminiCLIClient) SaveTokenToFile() error {
fileName := filepath.Join(c.cfg.AuthDir, fmt.Sprintf("%s-%s.json", c.tokenStorage.(*geminiAuth.GeminiTokenStorage).Email, c.tokenStorage.(*geminiAuth.GeminiTokenStorage).ProjectID)) fileName := filepath.Join(c.cfg.AuthDir, fmt.Sprintf("%s-%s.json", c.tokenStorage.(*geminiAuth.GeminiTokenStorage).Email, c.tokenStorage.(*geminiAuth.GeminiTokenStorage).ProjectID))
log.Infof("Saving credentials to %s", fileName)
return c.tokenStorage.SaveTokenToFile(fileName) return c.tokenStorage.SaveTokenToFile(fileName)
} }

View File

@@ -842,7 +842,6 @@ func (c *GeminiWebClient) SaveTokenToFile() error {
} }
return ts.SaveTokenToFile(c.tokenFilePath) return ts.SaveTokenToFile(c.tokenFilePath)
} }
log.Debugf("Saving Gemini Web cookie snapshot to %s", filepath.Base(util.CookieSnapshotPath(c.tokenFilePath)))
return c.snapshotManager.Persist() return c.snapshotManager.Persist()
} }

View File

@@ -0,0 +1,16 @@
package misc
import (
"path/filepath"
log "github.com/sirupsen/logrus"
)
// LogSavingCredentials emits a consistent log message when persisting auth material.
func LogSavingCredentials(path string) {
if path == "" {
return
}
// Use filepath.Clean so logs remain stable even if callers pass redundant separators.
log.Infof("Saving credentials to %s", filepath.Clean(path))
}

View File

@@ -6,15 +6,19 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
) )
const cookieSnapshotExt = ".cookie"
// CookieSnapshotPath derives the cookie snapshot file path from the main token JSON path. // CookieSnapshotPath derives the cookie snapshot file path from the main token JSON path.
// It replaces the .json suffix with .cookies, or appends .cookies if missing. // It replaces the .json suffix with .cookie, or appends .cookie if missing.
func CookieSnapshotPath(mainPath string) string { func CookieSnapshotPath(mainPath string) string {
if strings.HasSuffix(mainPath, ".json") { if strings.HasSuffix(mainPath, ".json") {
return strings.TrimSuffix(mainPath, ".json") + ".cookies" return strings.TrimSuffix(mainPath, ".json") + cookieSnapshotExt
} }
return mainPath + ".cookies" return mainPath + cookieSnapshotExt
} }
// IsRegularFile reports whether the given path exists and is a regular file. // IsRegularFile reports whether the given path exists and is a regular file.
@@ -66,9 +70,8 @@ func RemoveFile(path string) error {
return nil return nil
} }
// TryReadCookieSnapshotInto tries to read a cookie snapshot into v. // TryReadCookieSnapshotInto tries to read a cookie snapshot into v using the .cookie suffix.
// It attempts the .cookies suffix; returns (true, nil) when found and decoded, // Returns (true, nil) when a snapshot was decoded, or (false, nil) when none exists.
// or (false, nil) when none exists.
func TryReadCookieSnapshotInto(mainPath string, v any) (bool, error) { func TryReadCookieSnapshotInto(mainPath string, v any) (bool, error) {
snap := CookieSnapshotPath(mainPath) snap := CookieSnapshotPath(mainPath)
if err := ReadJSON(snap, v); err != nil { if err := ReadJSON(snap, v); err != nil {
@@ -80,13 +83,20 @@ func TryReadCookieSnapshotInto(mainPath string, v any) (bool, error) {
return true, nil return true, nil
} }
// WriteCookieSnapshot writes v to the snapshot path derived from mainPath using the .cookies suffix. // WriteCookieSnapshot writes v to the snapshot path derived from mainPath using the .cookie suffix.
func WriteCookieSnapshot(mainPath string, v any) error { func WriteCookieSnapshot(mainPath string, v any) error {
return WriteJSON(CookieSnapshotPath(mainPath), v) path := CookieSnapshotPath(mainPath)
misc.LogSavingCredentials(path)
if err := WriteJSON(path, v); err != nil {
return err
}
return nil
} }
// RemoveCookieSnapshots removes both modern and legacy snapshot files. // RemoveCookieSnapshots removes the snapshot file if it exists.
func RemoveCookieSnapshots(mainPath string) { _ = RemoveFile(CookieSnapshotPath(mainPath)) } func RemoveCookieSnapshots(mainPath string) {
_ = RemoveFile(CookieSnapshotPath(mainPath))
}
// Hooks provide customization points for snapshot lifecycle operations. // Hooks provide customization points for snapshot lifecycle operations.
type Hooks[T any] struct { type Hooks[T any] struct {

View File

@@ -9,6 +9,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
@@ -141,7 +142,7 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
isConfigEvent := event.Name == w.configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) isConfigEvent := event.Name == w.configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create)
isAuthJSON := strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json") isAuthJSON := strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json")
if !isConfigEvent && !isAuthJSON { if !isConfigEvent && !isAuthJSON {
// Ignore unrelated files (e.g., cookie snapshots *.cookies) and other noise. // Ignore unrelated files (e.g., cookie snapshots *.cookie) and other noise.
return return
} }
@@ -417,9 +418,10 @@ func (w *Watcher) clientsToSlice(clientMap map[string]interfaces.Client) []inter
// readAuthFileWithRetry attempts to read the auth file multiple times to work around // readAuthFileWithRetry attempts to read the auth file multiple times to work around
// short-lived locks on Windows while token files are being written. // short-lived locks on Windows while token files are being written.
func readAuthFileWithRetry(path string, attempts int, delay time.Duration) ([]byte, error) { func readAuthFileWithRetry(path string, attempts int, delay time.Duration) ([]byte, error) {
read := func(target string) ([]byte, error) {
var lastErr error var lastErr error
for i := 0; i < attempts; i++ { for i := 0; i < attempts; i++ {
data, err := os.ReadFile(path) data, err := os.ReadFile(target)
if err == nil { if err == nil {
return data, nil return data, nil
} }
@@ -429,6 +431,27 @@ func readAuthFileWithRetry(path string, attempts int, delay time.Duration) ([]by
} }
} }
return nil, lastErr return nil, lastErr
}
candidates := []string{
util.CookieSnapshotPath(path),
path,
}
for idx, candidate := range candidates {
data, err := read(candidate)
if err == nil {
return data, nil
}
if errors.Is(err, os.ErrNotExist) {
if idx < len(candidates)-1 {
continue
}
}
return nil, err
}
return nil, os.ErrNotExist
} }
// addOrUpdateClient handles the addition or update of a single client. // addOrUpdateClient handles the addition or update of a single client.