diff --git a/cmd/server/main.go b/cmd/server/main.go index a9af038e..85bd2c61 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -17,6 +17,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/config" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" log "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" ) @@ -185,6 +186,9 @@ func main() { NoBrowser: noBrowser, } + // Register the shared token store once so all components use the same persistence backend. + sdkAuth.RegisterTokenStore(sdkAuth.NewFileTokenStore()) + // Handle different command modes based on the provided flags. if login { diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 651d87c0..dc6938bb 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -21,6 +21,7 @@ import ( // legacy client removed "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" @@ -341,6 +342,18 @@ func (h *Handler) disableAuth(ctx context.Context, id string) { } } +func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenRecord) (string, error) { + if record == nil { + return "", fmt.Errorf("token record is nil") + } + store := h.tokenStore + if store == nil { + store = sdkAuth.GetTokenStore() + h.tokenStore = store + } + return store.Save(ctx, h.cfg, record) +} + func (h *Handler) RequestAnthropicToken(c *gin.Context) { ctx := context.Background() @@ -481,15 +494,20 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { // Create token storage tokenStorage := anthropicAuth.CreateTokenStorage(bundle) - // Persist token to file directly - fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("claude-%s.json", tokenStorage.Email)) - if errSave := tokenStorage.SaveTokenToFile(fileName); errSave != nil { + record := &sdkAuth.TokenRecord{ + Provider: "claude", + FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email), + Storage: tokenStorage, + Metadata: map[string]string{"email": tokenStorage.Email}, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { log.Fatalf("Failed to save authentication tokens: %v", errSave) oauthStatus[state] = "Failed to save authentication tokens" return } - log.Info("Authentication successful!") + log.Infof("Authentication successful! Token saved to %s", savedPath) if bundle.APIKey != "" { log.Info("API key obtained and saved") } @@ -639,16 +657,24 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { } log.Info("Authentication successful.") - // Persist token to file directly - fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("gemini-%s.json", ts.Email)) - if err = ts.SaveTokenToFile(fileName); err != nil { - log.Fatalf("Failed to save token to file: %v", err) + record := &sdkAuth.TokenRecord{ + Provider: "gemini", + FileName: fmt.Sprintf("gemini-%s.json", ts.Email), + Storage: &ts, + Metadata: map[string]string{ + "email": ts.Email, + "project_id": ts.ProjectID, + }, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Fatalf("Failed to save token to file: %v", errSave) oauthStatus[state] = "Failed to save token to file" return } delete(oauthStatus, state) - log.Info("You can now use Gemini CLI services through this CLI") + log.Infof("You can now use Gemini CLI services through this CLI; token saved to %s", savedPath) }() oauthStatus[state] = "" @@ -783,13 +809,22 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { // Create token storage and persist tokenStorage := openaiAuth.CreateTokenStorage(bundle) - fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("codex-%s.json", tokenStorage.Email)) - if errSave := tokenStorage.SaveTokenToFile(fileName); errSave != nil { + record := &sdkAuth.TokenRecord{ + Provider: "codex", + FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email), + Storage: tokenStorage, + Metadata: map[string]string{ + "email": tokenStorage.Email, + "account_id": tokenStorage.AccountID, + }, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { oauthStatus[state] = "Failed to save authentication tokens" log.Fatalf("Failed to save authentication tokens: %v", errSave) return } - log.Info("Authentication successful!") + log.Infof("Authentication successful! Token saved to %s", savedPath) if bundle.APIKey != "" { log.Info("API key obtained and saved") } @@ -831,15 +866,20 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { tokenStorage := qwenAuth.CreateTokenStorage(tokenData) tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli()) - // Save token storage - fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("qwen-%s.json", tokenStorage.Email)) - if err = tokenStorage.SaveTokenToFile(fileName); err != nil { - log.Fatalf("Failed to save authentication tokens: %v", err) + record := &sdkAuth.TokenRecord{ + Provider: "qwen", + FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), + Storage: tokenStorage, + Metadata: map[string]string{"email": tokenStorage.Email}, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Fatalf("Failed to save authentication tokens: %v", errSave) oauthStatus[state] = "Failed to save authentication tokens" return } - log.Info("Authentication successful!") + log.Infof("Authentication successful! Token saved to %s", savedPath) log.Info("You can now use Qwen services through this CLI") delete(oauthStatus, state) }() diff --git a/internal/api/handlers/management/handler.go b/internal/api/handlers/management/handler.go index a90d44e2..fcb71920 100644 --- a/internal/api/handlers/management/handler.go +++ b/internal/api/handlers/management/handler.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/usage" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" "golang.org/x/crypto/bcrypt" ) @@ -31,6 +32,7 @@ type Handler struct { failedAttempts map[string]*attemptInfo // keyed by client IP authManager *coreauth.Manager usageStats *usage.RequestStatistics + tokenStore sdkAuth.TokenStore } // NewHandler creates a new management handler instance. @@ -41,6 +43,7 @@ func NewHandler(cfg *config.Config, configFilePath string, manager *coreauth.Man failedAttempts: make(map[string]*attemptInfo), authManager: manager, usageStats: usage.GetRequestStatistics(), + tokenStore: sdkAuth.GetTokenStore(), } } diff --git a/internal/cmd/auth_manager.go b/internal/cmd/auth_manager.go index b4a92a28..220aa43d 100644 --- a/internal/cmd/auth_manager.go +++ b/internal/cmd/auth_manager.go @@ -11,7 +11,7 @@ import ( // Returns: // - *sdkAuth.Manager: A configured authentication manager instance func newAuthManager() *sdkAuth.Manager { - store := sdkAuth.NewFileTokenStore() + store := sdkAuth.GetTokenStore() manager := sdkAuth.NewManager(store, sdkAuth.NewGeminiAuthenticator(), sdkAuth.NewCodexAuthenticator(), diff --git a/internal/cmd/gemini-web_auth.go b/internal/cmd/gemini-web_auth.go index 3c2ab17a..86d6e14e 100644 --- a/internal/cmd/gemini-web_auth.go +++ b/internal/cmd/gemini-web_auth.go @@ -3,15 +3,16 @@ package cmd import ( "bufio" + "context" "crypto/sha256" "encoding/hex" "fmt" "os" - "path/filepath" "strings" "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" log "github.com/sirupsen/logrus" ) @@ -48,13 +49,17 @@ func DoGeminiWebAuth(cfg *config.Config) { hasher.Write([]byte(secure1psid)) hash := hex.EncodeToString(hasher.Sum(nil)) fileName := fmt.Sprintf("gemini-web-%s.json", hash[:16]) - filePath := filepath.Join(cfg.AuthDir, fileName) - - err := tokenStorage.SaveTokenToFile(filePath) + record := &sdkAuth.TokenRecord{ + Provider: "gemini-web", + FileName: fileName, + Storage: tokenStorage, + } + store := sdkAuth.GetTokenStore() + savedPath, err := store.Save(context.Background(), cfg, record) if err != nil { log.Fatalf("Failed to save Gemini Web token to file: %v", err) return } - log.Infof("Successfully saved Gemini Web token to: %s", filePath) + log.Infof("Successfully saved Gemini Web token to: %s", savedPath) } diff --git a/sdk/auth/store_registry.go b/sdk/auth/store_registry.go new file mode 100644 index 00000000..491f25eb --- /dev/null +++ b/sdk/auth/store_registry.go @@ -0,0 +1,31 @@ +package auth + +import "sync" + +var ( + storeMu sync.RWMutex + registeredTokenStore TokenStore +) + +// RegisterTokenStore sets the global token store used by the authentication helpers. +func RegisterTokenStore(store TokenStore) { + storeMu.Lock() + registeredTokenStore = store + storeMu.Unlock() +} + +// GetTokenStore returns the globally registered token store. +func GetTokenStore() TokenStore { + storeMu.RLock() + s := registeredTokenStore + storeMu.RUnlock() + if s != nil { + return s + } + storeMu.Lock() + defer storeMu.Unlock() + if registeredTokenStore == nil { + registeredTokenStore = NewFileTokenStore() + } + return registeredTokenStore +} diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 5194608d..314d82d7 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -99,7 +99,7 @@ func (s *Service) RegisterUsagePlugin(plugin usage.Plugin) { // newDefaultAuthManager creates a default authentication manager with all supported providers. func newDefaultAuthManager() *sdkAuth.Manager { return sdkAuth.NewManager( - sdkAuth.NewFileTokenStore(), + sdkAuth.GetTokenStore(), sdkAuth.NewGeminiAuthenticator(), sdkAuth.NewCodexAuthenticator(), sdkAuth.NewClaudeAuthenticator(),