History

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.

Luis Pater committed 1 year agov6.0.10

Commit d01c4901 parent 8cfa22817 files · +164 −139Browse files

Unified · Split

17 files
  1. examples/custom-provider/main.go +1 −5
  2. internal/api/handlers/management/auth_files.go +21 −11
  3. internal/api/handlers/management/handler.go +1 −1
  4. internal/cmd/gemini-web_auth.go +9 −2
  5. sdk/auth/claude.go +5 −3
  6. sdk/auth/codex.go +5 −3
  7. sdk/auth/filestore.go +70 −68
  8. sdk/auth/gemini-web.go +2 −1
  9. sdk/auth/gemini.go +5 −3
  10. sdk/auth/interfaces.go +2 −15
  11. sdk/auth/manager.go +13 −6
  12. sdk/auth/qwen.go +5 −3
  13. sdk/auth/store_registry.go +14 −10
  14. sdk/cliproxy/auth/manager.go +2 −1
  15. sdk/cliproxy/auth/store.go +2 −2
  16. sdk/cliproxy/auth/types.go +6 −0
  17. sdk/cliproxy/builder.go +1 −5
examples/custom-provider/main.go+1−5
@@ -160,11 +160,7 @@ func main() { · 159 unmodified lines
160160 if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok {
161161 dirSetter.SetBaseDir(cfg.AuthDir)
162162 }
163− store, ok := tokenStore.(coreauth.Store)
164− if !ok {
165− panic("token store does not implement coreauth.Store")
166− }
167− core := coreauth.NewManager(store, nil, nil)
163+ core := coreauth.NewManager(tokenStore, nil, nil)
168164 core.RegisterExecutor(MyExecutor{})
169165
170166 hooks := cliproxy.Hooks{
internal/api/handlers/management/auth_files.go+21−11
@@ -344,7 +344,7 @@ func (h *Handler) disableAuth(ctx context.Context, id string) { · 343 unmodified lines
344344 }
345345 }
346346
347−func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenRecord) (string, error) {
347+func (h *Handler) saveTokenRecord(ctx context.Context, record *coreauth.Auth) (string, error) {
348348 if record == nil {
349349 return "", fmt.Errorf("token record is nil")
350350 }
@@ -353,7 +353,12 @@ func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenReco · 2 unmodified lines
353353 store = sdkAuth.GetTokenStore()
354354 h.tokenStore = store
355355 }
356− return store.Save(ctx, h.cfg, record)
356+ if h.cfg != nil {
357+ if dirSetter, ok := store.(interface{ SetBaseDir(string) }); ok {
358+ dirSetter.SetBaseDir(h.cfg.AuthDir)
359+ }
360+ }
361+ return store.Save(ctx, record)
357362 }
358363
359364 func (h *Handler) RequestAnthropicToken(c *gin.Context) {
@@ -496,11 +501,12 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { · 136 unmodified lines
496501
497502 // Create token storage
498503 tokenStorage := anthropicAuth.CreateTokenStorage(bundle)
499− record := &sdkAuth.TokenRecord{
504+ record := &coreauth.Auth{
505+ ID: fmt.Sprintf("claude-%s.json", tokenStorage.Email),
500506 Provider: "claude",
501507 FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email),
502508 Storage: tokenStorage,
503− Metadata: map[string]string{"email": tokenStorage.Email},
509+ Metadata: map[string]any{"email": tokenStorage.Email},
504510 }
505511 savedPath, errSave := h.saveTokenRecord(ctx, record)
506512 if errSave != nil {
@@ -659,11 +665,12 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { · 152 unmodified lines
659665 }
660666 fmt.Println("Authentication successful.")
661667
662− record := &sdkAuth.TokenRecord{
668+ record := &coreauth.Auth{
669+ ID: fmt.Sprintf("gemini-%s.json", ts.Email),
663670 Provider: "gemini",
664671 FileName: fmt.Sprintf("gemini-%s.json", ts.Email),
665672 Storage: &ts,
666− Metadata: map[string]string{
673+ Metadata: map[string]any{
667674 "email": ts.Email,
668675 "project_id": ts.ProjectID,
669676 },
@@ -724,7 +731,8 @@ func (h *Handler) CreateGeminiWebToken(c *gin.Context) { · 54 unmodified lines
724731 // Provide a stable label (gemini-web-<hash>) for logging and identification
725732 tokenStorage.Label = strings.TrimSuffix(fileName, ".json")
726733
727− record := &sdkAuth.TokenRecord{
734+ record := &coreauth.Auth{
735+ ID: fileName,
728736 Provider: "gemini-web",
729737 FileName: fileName,
730738 Storage: tokenStorage,
@@ -869,11 +877,12 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { · 138 unmodified lines
869877
870878 // Create token storage and persist
871879 tokenStorage := openaiAuth.CreateTokenStorage(bundle)
872− record := &sdkAuth.TokenRecord{
880+ record := &coreauth.Auth{
881+ ID: fmt.Sprintf("codex-%s.json", tokenStorage.Email),
873882 Provider: "codex",
874883 FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email),
875884 Storage: tokenStorage,
876− Metadata: map[string]string{
885+ Metadata: map[string]any{
877886 "email": tokenStorage.Email,
878887 "account_id": tokenStorage.AccountID,
879888 },
@@ -926,11 +935,12 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { · 46 unmodified lines
926935 tokenStorage := qwenAuth.CreateTokenStorage(tokenData)
927936
928937 tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli())
929− record := &sdkAuth.TokenRecord{
938+ record := &coreauth.Auth{
939+ ID: fmt.Sprintf("qwen-%s.json", tokenStorage.Email),
930940 Provider: "qwen",
931941 FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email),
932942 Storage: tokenStorage,
933− Metadata: map[string]string{"email": tokenStorage.Email},
943+ Metadata: map[string]any{"email": tokenStorage.Email},
934944 }
935945 savedPath, errSave := h.saveTokenRecord(ctx, record)
936946 if errSave != nil {
internal/api/handlers/management/handler.go+1−1
@@ -33,7 +33,7 @@ type Handler struct { · 32 unmodified lines
3333 failedAttempts map[string]*attemptInfo // keyed by client IP
3434 authManager *coreauth.Manager
3535 usageStats *usage.RequestStatistics
36− tokenStore sdkAuth.TokenStore
36+ tokenStore coreauth.Store
3737
3838 localPassword string
3939 }
internal/cmd/gemini-web_auth.go+9−2
@@ -18,6 +18,7 @@ import ( · 17 unmodified lines
1818 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
1919 "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
2020 sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
21+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
2122 )
2223
2324 // banner prints a simple ASCII banner for clarity without ANSI colors.
@@ -173,13 +174,19 @@ func DoGeminiWebAuth(cfg *config.Config) { · 149 unmodified lines
173174 Secure1PSIDTS: secure1psidts,
174175 Label: label,
175176 }
176− record := &sdkAuth.TokenRecord{
177+ record := &coreauth.Auth{
178+ ID: fileName,
177179 Provider: "gemini-web",
178180 FileName: fileName,
179181 Storage: tokenStorage,
180182 }
181183 store := sdkAuth.GetTokenStore()
182− savedPath, err := store.Save(context.Background(), cfg, record)
184+ if cfg != nil {
185+ if dirSetter, ok := store.(interface{ SetBaseDir(string) }); ok {
186+ dirSetter.SetBaseDir(cfg.AuthDir)
187+ }
188+ }
189+ savedPath, err := store.Save(context.Background(), record)
183190 if err != nil {
184191 fmt.Println("!! Failed to save Gemini Web token to file:", err)
185192 return
sdk/auth/claude.go+5−3
@@ -13,6 +13,7 @@ import ( · 12 unmodified lines
1313 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
1414 "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
1515 "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
16+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1617 log "github.com/sirupsen/logrus"
1718 )
1819
@@ -35,7 +36,7 @@ func (a *ClaudeAuthenticator) RefreshLead() *time.Duration { · 16 unmodified lines
3536 return &d
3637 }
3738
38−func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) {
39+func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
3940 if cfg == nil {
4041 return nil, fmt.Errorf("cliproxy auth: configuration is required")
4142 }
@@ -127,7 +128,7 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 85 unmodified lines
127128 }
128129
129130 fileName := fmt.Sprintf("claude-%s.json", tokenStorage.Email)
130− metadata := map[string]string{
131+ metadata := map[string]any{
131132 "email": tokenStorage.Email,
132133 }
133134
@@ -136,7 +137,8 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 2 unmodified lines
136137 fmt.Println("Claude API key obtained and stored")
137138 }
138139
139− return &TokenRecord{
140+ return &coreauth.Auth{
141+ ID: fileName,
140142 Provider: a.Provider(),
141143 FileName: fileName,
142144 Storage: tokenStorage,
sdk/auth/codex.go+5−3
@@ -13,6 +13,7 @@ import ( · 12 unmodified lines
1313 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
1414 "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
1515 "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
16+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1617 log "github.com/sirupsen/logrus"
1718 )
1819
@@ -35,7 +36,7 @@ func (a *CodexAuthenticator) RefreshLead() *time.Duration { · 16 unmodified lines
3536 return &d
3637 }
3738
38−func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) {
39+func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
3940 if cfg == nil {
4041 return nil, fmt.Errorf("cliproxy auth: configuration is required")
4142 }
@@ -126,7 +127,7 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 84 unmodified lines
126127 }
127128
128129 fileName := fmt.Sprintf("codex-%s.json", tokenStorage.Email)
129− metadata := map[string]string{
130+ metadata := map[string]any{
130131 "email": tokenStorage.Email,
131132 }
132133
@@ -135,7 +136,8 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 2 unmodified lines
135136 fmt.Println("Codex API key obtained and stored")
136137 }
137138
138− return &TokenRecord{
139+ return &coreauth.Auth{
140+ ID: fileName,
139141 Provider: a.Provider(),
140142 FileName: fileName,
141143 Storage: tokenStorage,
sdk/auth/filestore.go+70−68
@@ -11,7 +11,6 @@ import ( · 10 unmodified lines
1111 "sync"
1212 "time"
1313
14− "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
1514 cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1615 )
1716
@@ -35,27 +34,71 @@ func (s *FileTokenStore) SetBaseDir(dir string) { · 17 unmodified lines
3534 s.dirLock.Unlock()
3635 }
3736
38−// Save writes the token storage to the resolved file path.
39−func (s *FileTokenStore) Save(ctx context.Context, cfg *config.Config, record *TokenRecord) (string, error) {
40− if record == nil || record.Storage == nil {
41− return "", fmt.Errorf("cliproxy auth: token record is incomplete")
42− }
43− target := strings.TrimSpace(record.FileName)
44− if target == "" {
45− return "", fmt.Errorf("cliproxy auth: missing file name for provider %s", record.Provider)
46− }
47− if !filepath.IsAbs(target) {
48− baseDir := s.baseDirFromConfig(cfg)
49− if baseDir != "" {
50− target = filepath.Join(baseDir, target)
37+// Save persists token storage and metadata to the resolved auth file path.
38+func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (string, error) {
39+ if auth == nil {
40+ return "", fmt.Errorf("auth filestore: auth is nil")
41+ }
42+
43+ path, err := s.resolveAuthPath(auth)
44+ if err != nil {
45+ return "", err
46+ }
47+ if path == "" {
48+ return "", fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID)
49+ }
50+
51+ if auth.Disabled {
52+ if _, statErr := os.Stat(path); os.IsNotExist(statErr) {
53+ return "", nil
5154 }
5255 }
56+
5357 s.mu.Lock()
5458 defer s.mu.Unlock()
55− if err := record.Storage.SaveTokenToFile(target); err != nil {
56− return "", err
59+
60+ if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
61+ return "", fmt.Errorf("auth filestore: create dir failed: %w", err)
5762 }
58− return target, nil
63+
64+ switch {
65+ case auth.Storage != nil:
66+ if err = auth.Storage.SaveTokenToFile(path); err != nil {
67+ return "", err
68+ }
69+ case auth.Metadata != nil:
70+ raw, errMarshal := json.Marshal(auth.Metadata)
71+ if errMarshal != nil {
72+ return "", fmt.Errorf("auth filestore: marshal metadata failed: %w", errMarshal)
73+ }
74+ if existing, errRead := os.ReadFile(path); errRead == nil {
75+ if jsonEqual(existing, raw) {
76+ return path, nil
77+ }
78+ } else if errRead != nil && !os.IsNotExist(errRead) {
79+ return "", fmt.Errorf("auth filestore: read existing failed: %w", errRead)
80+ }
81+ tmp := path + ".tmp"
82+ if errWrite := os.WriteFile(tmp, raw, 0o600); errWrite != nil {
83+ return "", fmt.Errorf("auth filestore: write temp failed: %w", errWrite)
84+ }
85+ if errRename := os.Rename(tmp, path); errRename != nil {
86+ return "", fmt.Errorf("auth filestore: rename failed: %w", errRename)
87+ }
88+ default:
89+ return "", fmt.Errorf("auth filestore: nothing to persist for %s", auth.ID)
90+ }
91+
92+ if auth.Attributes == nil {
93+ auth.Attributes = make(map[string]string)
94+ }
95+ auth.Attributes["path"] = path
96+
97+ if strings.TrimSpace(auth.FileName) == "" {
98+ auth.FileName = auth.ID
99+ }
100+
101+ return path, nil
59102 }
60103
61104 // List enumerates all auth JSON files under the configured directory.
@@ -90,50 +133,6 @@ func (s *FileTokenStore) List(ctx context.Context) ([]*cliproxyauth.Auth, error) · 28 unmodified lines
90133 return entries, nil
91134 }
92135
93−// SaveAuth writes the auth metadata back to its source file location.
94−func (s *FileTokenStore) SaveAuth(ctx context.Context, auth *cliproxyauth.Auth) error {
95− if auth == nil {
96− return fmt.Errorf("auth filestore: auth is nil")
97− }
98− path, err := s.resolveAuthPath(auth)
99− if err != nil {
100− return err
101− }
102− if path == "" {
103− return fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID)
104− }
105− // If the auth has been disabled and the original file was removed, avoid recreating it on disk.
106− if auth.Disabled {
107− if _, statErr := os.Stat(path); statErr != nil {
108− if os.IsNotExist(statErr) {
109− return nil
110− }
111− }
112− }
113− s.mu.Lock()
114− defer s.mu.Unlock()
115− if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
116− return fmt.Errorf("auth filestore: create dir failed: %w", err)
117− }
118− raw, err := json.Marshal(auth.Metadata)
119− if err != nil {
120− return fmt.Errorf("auth filestore: marshal metadata failed: %w", err)
121− }
122− if existing, errRead := os.ReadFile(path); errRead == nil {
123− if jsonEqual(existing, raw) {
124− return nil
125− }
126− }
127− tmp := path + ".tmp"
128− if err = os.WriteFile(tmp, raw, 0o600); err != nil {
129− return fmt.Errorf("auth filestore: write temp failed: %w", err)
130− }
131− if err = os.Rename(tmp, path); err != nil {
132− return fmt.Errorf("auth filestore: rename failed: %w", err)
133− }
134− return nil
135−}
136−
137136 // Delete removes the auth file.
138137 func (s *FileTokenStore) Delete(ctx context.Context, id string) error {
139138 id = strings.TrimSpace(id)
@@ -185,6 +184,7 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth, · 45 unmodified lines
185184 auth := &cliproxyauth.Auth{
186185 ID: id,
187186 Provider: provider,
187+ FileName: id,
188188 Label: s.labelFor(metadata),
189189 Status: cliproxyauth.StatusActive,
190190 Attributes: map[string]string{"path": path},
@@ -220,6 +220,15 @@ func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error · 29 unmodified lines
220220 return p, nil
221221 }
222222 }
223+ if fileName := strings.TrimSpace(auth.FileName); fileName != "" {
224+ if filepath.IsAbs(fileName) {
225+ return fileName, nil
226+ }
227+ if dir := s.baseDirSnapshot(); dir != "" {
228+ return filepath.Join(dir, fileName), nil
229+ }
230+ return fileName, nil
231+ }
223232 if auth.ID == "" {
224233 return "", fmt.Errorf("auth filestore: missing id")
225234 }
@@ -249,13 +258,6 @@ func (s *FileTokenStore) labelFor(metadata map[string]any) string { · 23 unmodified lines
249258 return ""
250259 }
251260
252−func (s *FileTokenStore) baseDirFromConfig(cfg *config.Config) string {
253− if cfg != nil && strings.TrimSpace(cfg.AuthDir) != "" {
254− return strings.TrimSpace(cfg.AuthDir)
255− }
256− return s.baseDirSnapshot()
257−}
258−
259261 func (s *FileTokenStore) baseDirSnapshot() string {
260262 s.dirLock.RLock()
261263 defer s.dirLock.RUnlock()
sdk/auth/gemini-web.go+2−1
@@ -6,6 +6,7 @@ import ( · 5 unmodified lines
66 "time"
77
88 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
9+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
910 )
1011
1112 // GeminiWebAuthenticator provides a minimal wrapper so core components can treat
@@ -16,7 +17,7 @@ func NewGeminiWebAuthenticator() *GeminiWebAuthenticator { return &GeminiWebAuth · 4 unmodified lines
1617
1718 func (a *GeminiWebAuthenticator) Provider() string { return "gemini-web" }
1819
19−func (a *GeminiWebAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) {
20+func (a *GeminiWebAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
2021 _ = ctx
2122 _ = cfg
2223 _ = opts
sdk/auth/gemini.go+5−3
@@ -8,6 +8,7 @@ import ( · 7 unmodified lines
88 "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
99 // legacy client removed
1010 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
11+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1112 )
1213
1314 // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts.
@@ -26,7 +27,7 @@ func (a *GeminiAuthenticator) RefreshLead() *time.Duration { · 12 unmodified lines
2627 return nil
2728 }
2829
29−func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) {
30+func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
3031 if cfg == nil {
3132 return nil, fmt.Errorf("cliproxy auth: configuration is required")
3233 }
@@ -51,14 +52,15 @@ func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 18 unmodified lines
5152 // Skip onboarding here; rely on upstream configuration
5253
5354 fileName := fmt.Sprintf("%s-%s.json", ts.Email, ts.ProjectID)
54− metadata := map[string]string{
55+ metadata := map[string]any{
5556 "email": ts.Email,
5657 "project_id": ts.ProjectID,
5758 }
5859
5960 fmt.Println("Gemini authentication successful")
6061
61− return &TokenRecord{
62+ return &coreauth.Auth{
63+ ID: fileName,
6264 Provider: a.Provider(),
6365 FileName: fileName,
6466 Storage: &ts,
sdk/auth/interfaces.go+2−15
@@ -5,8 +5,8 @@ import ( · 4 unmodified lines
55 "errors"
66 "time"
77
8− baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth"
98 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
9+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1010 )
1111
1212 var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported")
@@ -20,22 +20,9 @@ type LoginOptions struct { · 7 unmodified lines
2020 Prompt func(prompt string) (string, error)
2121 }
2222
23−// TokenRecord represents credential material produced by an authenticator.
24−type TokenRecord struct {
25− Provider string
26− FileName string
27− Storage baseauth.TokenStorage
28− Metadata map[string]string
29−}
30−
31−// TokenStore persists token records.
32−type TokenStore interface {
33− Save(ctx context.Context, cfg *config.Config, record *TokenRecord) (string, error)
34−}
35−
3623 // Authenticator manages login and optional refresh flows for a provider.
3724 type Authenticator interface {
3825 Provider() string
39− Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error)
26+ Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error)
4027 RefreshLead() *time.Duration
4128 }
sdk/auth/manager.go+13−6
@@ -5,17 +5,18 @@ import ( · 4 unmodified lines
55 "fmt"
66
77 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
8+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
89 )
910
1011 // Manager aggregates authenticators and coordinates persistence via a token store.
1112 type Manager struct {
1213 authenticators map[string]Authenticator
13− store TokenStore
14+ store coreauth.Store
1415 }
1516
1617 // NewManager constructs a manager with the provided token store and authenticators.
1718 // If store is nil, the caller must set it later using SetStore.
18−func NewManager(store TokenStore, authenticators ...Authenticator) *Manager {
19+func NewManager(store coreauth.Store, authenticators ...Authenticator) *Manager {
1920 mgr := &Manager{
2021 authenticators: make(map[string]Authenticator),
2122 store: store,
@@ -38,12 +39,12 @@ func (m *Manager) Register(a Authenticator) { · 16 unmodified lines
3839 }
3940
4041 // SetStore updates the token store used for persistence.
41−func (m *Manager) SetStore(store TokenStore) {
42+func (m *Manager) SetStore(store coreauth.Store) {
4243 m.store = store
4344 }
4445
45−// Login executes the provider login flow and persists the resulting token record.
46−func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*TokenRecord, string, error) {
46+// Login executes the provider login flow and persists the resulting auth record.
47+func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, string, error) {
4748 auth, ok := m.authenticators[provider]
4849 if !ok {
4950 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 · 11 unmodified lines
6162 return record, "", nil
6263 }
6364
64− savedPath, err := m.store.Save(ctx, cfg, record)
65+ if cfg != nil {
66+ if dirSetter, ok := m.store.(interface{ SetBaseDir(string) }); ok {
67+ dirSetter.SetBaseDir(cfg.AuthDir)
68+ }
69+ }
70+
71+ savedPath, err := m.store.Save(ctx, record)
6572 if err != nil {
6673 return record, "", err
6774 }
sdk/auth/qwen.go+5−3
@@ -10,6 +10,7 @@ import ( · 9 unmodified lines
1010 "github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
1111 // legacy client removed
1212 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
13+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
1314 log "github.com/sirupsen/logrus"
1415 )
1516
@@ -30,7 +31,7 @@ func (a *QwenAuthenticator) RefreshLead() *time.Duration { · 14 unmodified lines
3031 return &d
3132 }
3233
33−func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*TokenRecord, error) {
34+func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) {
3435 if cfg == nil {
3536 return nil, fmt.Errorf("cliproxy auth: configuration is required")
3637 }
@@ -97,13 +98,14 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 60 unmodified lines
9798 // no legacy client construction
9899
99100 fileName := fmt.Sprintf("qwen-%s.json", tokenStorage.Email)
100− metadata := map[string]string{
101+ metadata := map[string]any{
101102 "email": tokenStorage.Email,
102103 }
103104
104105 fmt.Println("Qwen authentication successful")
105106
106− return &TokenRecord{
107+ return &coreauth.Auth{
108+ ID: fileName,
107109 Provider: a.Provider(),
108110 FileName: fileName,
109111 Storage: tokenStorage,
sdk/auth/store_registry.go+14−10
@@ -1,31 +1,35 @@
11 package auth
22
3−import "sync"
3+import (
4+ "sync"
5+
6+ coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
7+)
48
59 var (
6− storeMu sync.RWMutex
7− registeredTokenStore TokenStore
10+ storeMu sync.RWMutex
11+ registeredStore coreauth.Store
812 )
913
1014 // RegisterTokenStore sets the global token store used by the authentication helpers.
11−func RegisterTokenStore(store TokenStore) {
15+func RegisterTokenStore(store coreauth.Store) {
1216 storeMu.Lock()
13− registeredTokenStore = store
17+ registeredStore = store
1418 storeMu.Unlock()
1519 }
1620
1721 // GetTokenStore returns the globally registered token store.
18−func GetTokenStore() TokenStore {
22+func GetTokenStore() coreauth.Store {
1923 storeMu.RLock()
20− s := registeredTokenStore
24+ s := registeredStore
2125 storeMu.RUnlock()
2226 if s != nil {
2327 return s
2428 }
2529 storeMu.Lock()
2630 defer storeMu.Unlock()
27− if registeredTokenStore == nil {
28− registeredTokenStore = NewFileTokenStore()
31+ if registeredStore == nil {
32+ registeredStore = NewFileTokenStore()
2933 }
30− return registeredTokenStore
34+ return registeredStore
3135 }
sdk/cliproxy/auth/manager.go+2−1
@@ -818,7 +818,8 @@ func (m *Manager) persist(ctx context.Context, auth *Auth) error { · 817 unmodified lines
818818 if auth.Metadata == nil {
819819 return nil
820820 }
821− return m.store.SaveAuth(ctx, auth)
821+ _, err := m.store.Save(ctx, auth)
822+ return err
822823 }
823824
824825 // StartAutoRefresh launches a background loop that evaluates auth freshness
sdk/cliproxy/auth/store.go+2−2
@@ -6,8 +6,8 @@ import "context" · 5 unmodified lines
66 type Store interface {
77 // List returns all auth records stored in the backend.
88 List(ctx context.Context) ([]*Auth, error)
9− // SaveAuth persists the provided auth record, replacing any existing one with same ID.
10− SaveAuth(ctx context.Context, auth *Auth) error
9+ // Save persists the provided auth record, replacing any existing one with same ID.
10+ Save(ctx context.Context, auth *Auth) (string, error)
1111 // Delete removes the auth record identified by id.
1212 Delete(ctx context.Context, id string) error
1313 }
sdk/cliproxy/auth/types.go+6−0
@@ -6,6 +6,8 @@ import ( · 5 unmodified lines
66 "strings"
77 "sync"
88 "time"
9+
10+ baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth"
911 )
1012
1113 // Auth encapsulates the runtime state and metadata associated with a single credential.
@@ -14,6 +16,10 @@ type Auth struct { · 2 unmodified lines
1416 ID string `json:"id"`
1517 // Provider is the upstream provider key (e.g. "gemini", "claude").
1618 Provider string `json:"provider"`
19+ // FileName stores the relative or absolute path of the backing auth file.
20+ FileName string `json:"-"`
21+ // Storage holds the token persistence implementation used during login flows.
22+ Storage baseauth.TokenStorage `json:"-"`
1723 // Label is an optional human readable label for logging.
1824 Label string `json:"label,omitempty"`
1925 // Status is the lifecycle status managed by the AuthManager.
sdk/cliproxy/builder.go+1−5
@@ -197,11 +197,7 @@ func (b *Builder) Build() (*Service, error) { · 196 unmodified lines
197197 if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok && b.cfg != nil {
198198 dirSetter.SetBaseDir(b.cfg.AuthDir)
199199 }
200− store, ok := tokenStore.(coreauth.Store)
201− if !ok {
202− return nil, fmt.Errorf("cliproxy: token store does not implement coreauth.Store")
203− }
204− coreManager = coreauth.NewManager(store, nil, nil)
200+ coreManager = coreauth.NewManager(tokenStore, nil, nil)
205201 }
206202 // Attach a default RoundTripper provider so providers can opt-in per-auth transports.
207203 coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider())