From d01c4904ff023ddf9300a73fb099d9855745aa68 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Mon, 29 Sep 2025 09:31:21 +0800 Subject: [PATCH] refactor(auth): replace `TokenRecord` with `coreauth.Auth` and migrate `TokenStore` to `coreauth.Store` - Replaced `TokenRecord` with `coreauth.Auth` for centralized and consistent authentication data structures. - Migrated `TokenStore` interface to `coreauth.Store` for alignment with core CLIProxy authentication. - Updated related login methods, token persistence logic, and file storage handling to use the new `coreauth.Auth` model. --- examples/custom-provider/main.go | 6 +- .../api/handlers/management/auth_files.go | 32 ++-- internal/api/handlers/management/handler.go | 2 +- internal/cmd/gemini-web_auth.go | 11 +- sdk/auth/claude.go | 8 +- sdk/auth/codex.go | 8 +- sdk/auth/filestore.go | 142 +++++++++--------- sdk/auth/gemini-web.go | 3 +- sdk/auth/gemini.go | 8 +- sdk/auth/interfaces.go | 17 +-- sdk/auth/manager.go | 19 ++- sdk/auth/qwen.go | 8 +- sdk/auth/store_registry.go | 24 +-- sdk/cliproxy/auth/manager.go | 3 +- sdk/cliproxy/auth/store.go | 4 +- sdk/cliproxy/auth/types.go | 6 + sdk/cliproxy/builder.go | 6 +- 17 files changed, 166 insertions(+), 141 deletions(-) diff --git a/examples/custom-provider/main.go b/examples/custom-provider/main.go index 1b4592c2..e6a76475 100644 --- a/examples/custom-provider/main.go +++ b/examples/custom-provider/main.go @@ -160,11 +160,7 @@ func main() { if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok { dirSetter.SetBaseDir(cfg.AuthDir) } - store, ok := tokenStore.(coreauth.Store) - if !ok { - panic("token store does not implement coreauth.Store") - } - core := coreauth.NewManager(store, nil, nil) + core := coreauth.NewManager(tokenStore, nil, nil) core.RegisterExecutor(MyExecutor{}) hooks := cliproxy.Hooks{ diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 24383e29..cd970963 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -344,7 +344,7 @@ func (h *Handler) disableAuth(ctx context.Context, id string) { } } -func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenRecord) (string, error) { +func (h *Handler) saveTokenRecord(ctx context.Context, record *coreauth.Auth) (string, error) { if record == nil { return "", fmt.Errorf("token record is nil") } @@ -353,7 +353,12 @@ func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenReco store = sdkAuth.GetTokenStore() h.tokenStore = store } - return store.Save(ctx, h.cfg, record) + if h.cfg != nil { + if dirSetter, ok := store.(interface{ SetBaseDir(string) }); ok { + dirSetter.SetBaseDir(h.cfg.AuthDir) + } + } + return store.Save(ctx, record) } func (h *Handler) RequestAnthropicToken(c *gin.Context) { @@ -496,11 +501,12 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { // Create token storage tokenStorage := anthropicAuth.CreateTokenStorage(bundle) - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fmt.Sprintf("claude-%s.json", tokenStorage.Email), Provider: "claude", FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email), Storage: tokenStorage, - Metadata: map[string]string{"email": tokenStorage.Email}, + Metadata: map[string]any{"email": tokenStorage.Email}, } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { @@ -659,11 +665,12 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { } fmt.Println("Authentication successful.") - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fmt.Sprintf("gemini-%s.json", ts.Email), Provider: "gemini", FileName: fmt.Sprintf("gemini-%s.json", ts.Email), Storage: &ts, - Metadata: map[string]string{ + Metadata: map[string]any{ "email": ts.Email, "project_id": ts.ProjectID, }, @@ -724,7 +731,8 @@ func (h *Handler) CreateGeminiWebToken(c *gin.Context) { // Provide a stable label (gemini-web-) for logging and identification tokenStorage.Label = strings.TrimSuffix(fileName, ".json") - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fileName, Provider: "gemini-web", FileName: fileName, Storage: tokenStorage, @@ -869,11 +877,12 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { // Create token storage and persist tokenStorage := openaiAuth.CreateTokenStorage(bundle) - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fmt.Sprintf("codex-%s.json", tokenStorage.Email), Provider: "codex", FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email), Storage: tokenStorage, - Metadata: map[string]string{ + Metadata: map[string]any{ "email": tokenStorage.Email, "account_id": tokenStorage.AccountID, }, @@ -926,11 +935,12 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { tokenStorage := qwenAuth.CreateTokenStorage(tokenData) tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli()) - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), Provider: "qwen", FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), Storage: tokenStorage, - Metadata: map[string]string{"email": tokenStorage.Email}, + Metadata: map[string]any{"email": tokenStorage.Email}, } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { diff --git a/internal/api/handlers/management/handler.go b/internal/api/handlers/management/handler.go index f36aaa3d..2e81c349 100644 --- a/internal/api/handlers/management/handler.go +++ b/internal/api/handlers/management/handler.go @@ -33,7 +33,7 @@ type Handler struct { failedAttempts map[string]*attemptInfo // keyed by client IP authManager *coreauth.Manager usageStats *usage.RequestStatistics - tokenStore sdkAuth.TokenStore + tokenStore coreauth.Store localPassword string } diff --git a/internal/cmd/gemini-web_auth.go b/internal/cmd/gemini-web_auth.go index 70cdb8ee..d00d2ab6 100644 --- a/internal/cmd/gemini-web_auth.go +++ b/internal/cmd/gemini-web_auth.go @@ -18,6 +18,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "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" ) // banner prints a simple ASCII banner for clarity without ANSI colors. @@ -173,13 +174,19 @@ func DoGeminiWebAuth(cfg *config.Config) { Secure1PSIDTS: secure1psidts, Label: label, } - record := &sdkAuth.TokenRecord{ + record := &coreauth.Auth{ + ID: fileName, Provider: "gemini-web", FileName: fileName, Storage: tokenStorage, } store := sdkAuth.GetTokenStore() - savedPath, err := store.Save(context.Background(), cfg, record) + if cfg != nil { + if dirSetter, ok := store.(interface{ SetBaseDir(string) }); ok { + dirSetter.SetBaseDir(cfg.AuthDir) + } + } + savedPath, err := store.Save(context.Background(), record) if err != nil { fmt.Println("!! Failed to save Gemini Web token to file:", err) return diff --git a/sdk/auth/claude.go b/sdk/auth/claude.go index df042640..da9e5065 100644 --- a/sdk/auth/claude.go +++ b/sdk/auth/claude.go @@ -13,6 +13,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -35,7 +36,7 @@ func (a *ClaudeAuthenticator) RefreshLead() *time.Duration { return &d } -func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) { +func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { if cfg == nil { return nil, fmt.Errorf("cliproxy auth: configuration is required") } @@ -127,7 +128,7 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt } fileName := fmt.Sprintf("claude-%s.json", tokenStorage.Email) - metadata := map[string]string{ + metadata := map[string]any{ "email": tokenStorage.Email, } @@ -136,7 +137,8 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt fmt.Println("Claude API key obtained and stored") } - return &TokenRecord{ + return &coreauth.Auth{ + ID: fileName, Provider: a.Provider(), FileName: fileName, Storage: tokenStorage, diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index 24bb549d..138c2141 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -13,6 +13,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -35,7 +36,7 @@ func (a *CodexAuthenticator) RefreshLead() *time.Duration { return &d } -func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) { +func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { if cfg == nil { return nil, fmt.Errorf("cliproxy auth: configuration is required") } @@ -126,7 +127,7 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts } fileName := fmt.Sprintf("codex-%s.json", tokenStorage.Email) - metadata := map[string]string{ + metadata := map[string]any{ "email": tokenStorage.Email, } @@ -135,7 +136,8 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts fmt.Println("Codex API key obtained and stored") } - return &TokenRecord{ + return &coreauth.Auth{ + ID: fileName, Provider: a.Provider(), FileName: fileName, Storage: tokenStorage, diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index da63b86d..3c2d60c4 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -11,7 +11,6 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) @@ -35,27 +34,71 @@ func (s *FileTokenStore) SetBaseDir(dir string) { s.dirLock.Unlock() } -// Save writes the token storage to the resolved file path. -func (s *FileTokenStore) Save(ctx context.Context, cfg *config.Config, record *TokenRecord) (string, error) { - if record == nil || record.Storage == nil { - return "", fmt.Errorf("cliproxy auth: token record is incomplete") +// Save persists token storage and metadata to the resolved auth file path. +func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (string, error) { + if auth == nil { + return "", fmt.Errorf("auth filestore: auth is nil") } - target := strings.TrimSpace(record.FileName) - if target == "" { - return "", fmt.Errorf("cliproxy auth: missing file name for provider %s", record.Provider) - } - if !filepath.IsAbs(target) { - baseDir := s.baseDirFromConfig(cfg) - if baseDir != "" { - target = filepath.Join(baseDir, target) - } - } - s.mu.Lock() - defer s.mu.Unlock() - if err := record.Storage.SaveTokenToFile(target); err != nil { + + path, err := s.resolveAuthPath(auth) + if err != nil { return "", err } - return target, nil + if path == "" { + return "", fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID) + } + + if auth.Disabled { + if _, statErr := os.Stat(path); os.IsNotExist(statErr) { + return "", nil + } + } + + s.mu.Lock() + defer s.mu.Unlock() + + if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return "", fmt.Errorf("auth filestore: create dir failed: %w", err) + } + + switch { + case auth.Storage != nil: + if err = auth.Storage.SaveTokenToFile(path); err != nil { + return "", err + } + case auth.Metadata != nil: + raw, errMarshal := json.Marshal(auth.Metadata) + if errMarshal != nil { + return "", fmt.Errorf("auth filestore: marshal metadata failed: %w", errMarshal) + } + if existing, errRead := os.ReadFile(path); errRead == nil { + if jsonEqual(existing, raw) { + return path, nil + } + } else if errRead != nil && !os.IsNotExist(errRead) { + return "", fmt.Errorf("auth filestore: read existing failed: %w", errRead) + } + tmp := path + ".tmp" + if errWrite := os.WriteFile(tmp, raw, 0o600); errWrite != nil { + return "", fmt.Errorf("auth filestore: write temp failed: %w", errWrite) + } + if errRename := os.Rename(tmp, path); errRename != nil { + return "", fmt.Errorf("auth filestore: rename failed: %w", errRename) + } + default: + return "", fmt.Errorf("auth filestore: nothing to persist for %s", auth.ID) + } + + if auth.Attributes == nil { + auth.Attributes = make(map[string]string) + } + auth.Attributes["path"] = path + + if strings.TrimSpace(auth.FileName) == "" { + auth.FileName = auth.ID + } + + return path, nil } // List enumerates all auth JSON files under the configured directory. @@ -90,50 +133,6 @@ func (s *FileTokenStore) List(ctx context.Context) ([]*cliproxyauth.Auth, error) return entries, nil } -// SaveAuth writes the auth metadata back to its source file location. -func (s *FileTokenStore) SaveAuth(ctx context.Context, auth *cliproxyauth.Auth) error { - if auth == nil { - return fmt.Errorf("auth filestore: auth is nil") - } - path, err := s.resolveAuthPath(auth) - if err != nil { - return err - } - if path == "" { - return fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID) - } - // If the auth has been disabled and the original file was removed, avoid recreating it on disk. - if auth.Disabled { - if _, statErr := os.Stat(path); statErr != nil { - if os.IsNotExist(statErr) { - return nil - } - } - } - s.mu.Lock() - defer s.mu.Unlock() - if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - return fmt.Errorf("auth filestore: create dir failed: %w", err) - } - raw, err := json.Marshal(auth.Metadata) - if err != nil { - return fmt.Errorf("auth filestore: marshal metadata failed: %w", err) - } - if existing, errRead := os.ReadFile(path); errRead == nil { - if jsonEqual(existing, raw) { - return nil - } - } - tmp := path + ".tmp" - if err = os.WriteFile(tmp, raw, 0o600); err != nil { - return fmt.Errorf("auth filestore: write temp failed: %w", err) - } - if err = os.Rename(tmp, path); err != nil { - return fmt.Errorf("auth filestore: rename failed: %w", err) - } - return nil -} - // Delete removes the auth file. func (s *FileTokenStore) Delete(ctx context.Context, id string) error { id = strings.TrimSpace(id) @@ -185,6 +184,7 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth, auth := &cliproxyauth.Auth{ ID: id, Provider: provider, + FileName: id, Label: s.labelFor(metadata), Status: cliproxyauth.StatusActive, Attributes: map[string]string{"path": path}, @@ -220,6 +220,15 @@ func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error return p, nil } } + if fileName := strings.TrimSpace(auth.FileName); fileName != "" { + if filepath.IsAbs(fileName) { + return fileName, nil + } + if dir := s.baseDirSnapshot(); dir != "" { + return filepath.Join(dir, fileName), nil + } + return fileName, nil + } if auth.ID == "" { return "", fmt.Errorf("auth filestore: missing id") } @@ -249,13 +258,6 @@ func (s *FileTokenStore) labelFor(metadata map[string]any) string { return "" } -func (s *FileTokenStore) baseDirFromConfig(cfg *config.Config) string { - if cfg != nil && strings.TrimSpace(cfg.AuthDir) != "" { - return strings.TrimSpace(cfg.AuthDir) - } - return s.baseDirSnapshot() -} - func (s *FileTokenStore) baseDirSnapshot() string { s.dirLock.RLock() defer s.dirLock.RUnlock() diff --git a/sdk/auth/gemini-web.go b/sdk/auth/gemini-web.go index c56bc969..4e10e368 100644 --- a/sdk/auth/gemini-web.go +++ b/sdk/auth/gemini-web.go @@ -6,6 +6,7 @@ import ( "time" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) // GeminiWebAuthenticator provides a minimal wrapper so core components can treat @@ -16,7 +17,7 @@ func NewGeminiWebAuthenticator() *GeminiWebAuthenticator { return &GeminiWebAuth func (a *GeminiWebAuthenticator) Provider() string { return "gemini-web" } -func (a *GeminiWebAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) { +func (a *GeminiWebAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { _ = ctx _ = cfg _ = opts diff --git a/sdk/auth/gemini.go b/sdk/auth/gemini.go index 420465e6..7110101f 100644 --- a/sdk/auth/gemini.go +++ b/sdk/auth/gemini.go @@ -8,6 +8,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" // legacy client removed "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. @@ -26,7 +27,7 @@ func (a *GeminiAuthenticator) RefreshLead() *time.Duration { return nil } -func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) { +func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { if cfg == nil { return nil, fmt.Errorf("cliproxy auth: configuration is required") } @@ -51,14 +52,15 @@ func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opt // Skip onboarding here; rely on upstream configuration fileName := fmt.Sprintf("%s-%s.json", ts.Email, ts.ProjectID) - metadata := map[string]string{ + metadata := map[string]any{ "email": ts.Email, "project_id": ts.ProjectID, } fmt.Println("Gemini authentication successful") - return &TokenRecord{ + return &coreauth.Auth{ + ID: fileName, Provider: a.Provider(), FileName: fileName, Storage: &ts, diff --git a/sdk/auth/interfaces.go b/sdk/auth/interfaces.go index 7e6a268e..7a7868e1 100644 --- a/sdk/auth/interfaces.go +++ b/sdk/auth/interfaces.go @@ -5,8 +5,8 @@ import ( "errors" "time" - baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported") @@ -20,22 +20,9 @@ type LoginOptions struct { Prompt func(prompt string) (string, error) } -// TokenRecord represents credential material produced by an authenticator. -type TokenRecord struct { - Provider string - FileName string - Storage baseauth.TokenStorage - Metadata map[string]string -} - -// TokenStore persists token records. -type TokenStore interface { - Save(ctx context.Context, cfg *config.Config, record *TokenRecord) (string, error) -} - // Authenticator manages login and optional refresh flows for a provider. type Authenticator interface { Provider() string - Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) + Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) RefreshLead() *time.Duration } diff --git a/sdk/auth/manager.go b/sdk/auth/manager.go index 2e7e39b6..c6469a7d 100644 --- a/sdk/auth/manager.go +++ b/sdk/auth/manager.go @@ -5,17 +5,18 @@ import ( "fmt" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) // Manager aggregates authenticators and coordinates persistence via a token store. type Manager struct { authenticators map[string]Authenticator - store TokenStore + store coreauth.Store } // NewManager constructs a manager with the provided token store and authenticators. // If store is nil, the caller must set it later using SetStore. -func NewManager(store TokenStore, authenticators ...Authenticator) *Manager { +func NewManager(store coreauth.Store, authenticators ...Authenticator) *Manager { mgr := &Manager{ authenticators: make(map[string]Authenticator), store: store, @@ -38,12 +39,12 @@ func (m *Manager) Register(a Authenticator) { } // SetStore updates the token store used for persistence. -func (m *Manager) SetStore(store TokenStore) { +func (m *Manager) SetStore(store coreauth.Store) { m.store = store } -// Login executes the provider login flow and persists the resulting token record. -func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*TokenRecord, string, error) { +// Login executes the provider login flow and persists the resulting auth record. +func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, string, error) { auth, ok := m.authenticators[provider] if !ok { return nil, "", fmt.Errorf("cliproxy auth: authenticator %s not registered", provider) @@ -61,7 +62,13 @@ func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config return record, "", nil } - savedPath, err := m.store.Save(ctx, cfg, record) + if cfg != nil { + if dirSetter, ok := m.store.(interface{ SetBaseDir(string) }); ok { + dirSetter.SetBaseDir(cfg.AuthDir) + } + } + + savedPath, err := m.store.Save(ctx, record) if err != nil { return record, "", err } diff --git a/sdk/auth/qwen.go b/sdk/auth/qwen.go index 7940660c..151fba68 100644 --- a/sdk/auth/qwen.go +++ b/sdk/auth/qwen.go @@ -10,6 +10,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" // legacy client removed "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -30,7 +31,7 @@ func (a *QwenAuthenticator) RefreshLead() *time.Duration { return &d } -func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) { +func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { if cfg == nil { return nil, fmt.Errorf("cliproxy auth: configuration is required") } @@ -97,13 +98,14 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts // no legacy client construction fileName := fmt.Sprintf("qwen-%s.json", tokenStorage.Email) - metadata := map[string]string{ + metadata := map[string]any{ "email": tokenStorage.Email, } fmt.Println("Qwen authentication successful") - return &TokenRecord{ + return &coreauth.Auth{ + ID: fileName, Provider: a.Provider(), FileName: fileName, Storage: tokenStorage, diff --git a/sdk/auth/store_registry.go b/sdk/auth/store_registry.go index 491f25eb..760449f8 100644 --- a/sdk/auth/store_registry.go +++ b/sdk/auth/store_registry.go @@ -1,31 +1,35 @@ package auth -import "sync" +import ( + "sync" + + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" +) var ( - storeMu sync.RWMutex - registeredTokenStore TokenStore + storeMu sync.RWMutex + registeredStore coreauth.Store ) // RegisterTokenStore sets the global token store used by the authentication helpers. -func RegisterTokenStore(store TokenStore) { +func RegisterTokenStore(store coreauth.Store) { storeMu.Lock() - registeredTokenStore = store + registeredStore = store storeMu.Unlock() } // GetTokenStore returns the globally registered token store. -func GetTokenStore() TokenStore { +func GetTokenStore() coreauth.Store { storeMu.RLock() - s := registeredTokenStore + s := registeredStore storeMu.RUnlock() if s != nil { return s } storeMu.Lock() defer storeMu.Unlock() - if registeredTokenStore == nil { - registeredTokenStore = NewFileTokenStore() + if registeredStore == nil { + registeredStore = NewFileTokenStore() } - return registeredTokenStore + return registeredStore } diff --git a/sdk/cliproxy/auth/manager.go b/sdk/cliproxy/auth/manager.go index edda9273..92eda109 100644 --- a/sdk/cliproxy/auth/manager.go +++ b/sdk/cliproxy/auth/manager.go @@ -818,7 +818,8 @@ func (m *Manager) persist(ctx context.Context, auth *Auth) error { if auth.Metadata == nil { return nil } - return m.store.SaveAuth(ctx, auth) + _, err := m.store.Save(ctx, auth) + return err } // StartAutoRefresh launches a background loop that evaluates auth freshness diff --git a/sdk/cliproxy/auth/store.go b/sdk/cliproxy/auth/store.go index 97cdf65a..0594a77d 100644 --- a/sdk/cliproxy/auth/store.go +++ b/sdk/cliproxy/auth/store.go @@ -6,8 +6,8 @@ import "context" type Store interface { // List returns all auth records stored in the backend. List(ctx context.Context) ([]*Auth, error) - // SaveAuth persists the provided auth record, replacing any existing one with same ID. - SaveAuth(ctx context.Context, auth *Auth) error + // Save persists the provided auth record, replacing any existing one with same ID. + Save(ctx context.Context, auth *Auth) (string, error) // Delete removes the auth record identified by id. Delete(ctx context.Context, id string) error } diff --git a/sdk/cliproxy/auth/types.go b/sdk/cliproxy/auth/types.go index 75fb5643..453e1716 100644 --- a/sdk/cliproxy/auth/types.go +++ b/sdk/cliproxy/auth/types.go @@ -6,6 +6,8 @@ import ( "strings" "sync" "time" + + baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth" ) // Auth encapsulates the runtime state and metadata associated with a single credential. @@ -14,6 +16,10 @@ type Auth struct { ID string `json:"id"` // Provider is the upstream provider key (e.g. "gemini", "claude"). Provider string `json:"provider"` + // FileName stores the relative or absolute path of the backing auth file. + FileName string `json:"-"` + // Storage holds the token persistence implementation used during login flows. + Storage baseauth.TokenStorage `json:"-"` // Label is an optional human readable label for logging. Label string `json:"label,omitempty"` // Status is the lifecycle status managed by the AuthManager. diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index 48120543..e1a1d503 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -197,11 +197,7 @@ func (b *Builder) Build() (*Service, error) { if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok && b.cfg != nil { dirSetter.SetBaseDir(b.cfg.AuthDir) } - store, ok := tokenStore.(coreauth.Store) - if !ok { - return nil, fmt.Errorf("cliproxy: token store does not implement coreauth.Store") - } - coreManager = coreauth.NewManager(store, nil, nil) + coreManager = coreauth.NewManager(tokenStore, nil, nil) } // Attach a default RoundTripper provider so providers can opt-in per-auth transports. coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider())