- 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.
Unified · Split
| @@ -160,11 +160,7 @@ func main() { · 159 unmodified lines | |||
| 160 | if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok { | 160 | if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok { |
| 161 | dirSetter.SetBaseDir(cfg.AuthDir) | 161 | dirSetter.SetBaseDir(cfg.AuthDir) |
| 162 | } | 162 | } |
| 163 | store, ok := tokenStore.(coreauth.Store) | 163 | core := coreauth.NewManager(tokenStore, nil, nil) |
| 164 | if !ok { | ||
| 165 | panic("token store does not implement coreauth.Store") | ||
| 166 | } | ||
| 167 | core := coreauth.NewManager(store, nil, nil) | ||
| 168 | core.RegisterExecutor(MyExecutor{}) | 164 | core.RegisterExecutor(MyExecutor{}) |
| 169 | 165 | ||
| 170 | hooks := cliproxy.Hooks{ | 166 | hooks := cliproxy.Hooks{ |
| @@ -344,7 +344,7 @@ func (h *Handler) disableAuth(ctx context.Context, id string) { · 343 unmodified lines | |||
| 344 | } | 344 | } |
| 345 | } | 345 | } |
| 346 | 346 | ||
| 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) { |
| 348 | if record == nil { | 348 | if record == nil { |
| 349 | return "", fmt.Errorf("token record is nil") | 349 | return "", fmt.Errorf("token record is nil") |
| 350 | } | 350 | } |
| @@ -353,7 +353,12 @@ func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenReco · 2 unmodified lines | |||
| 353 | store = sdkAuth.GetTokenStore() | 353 | store = sdkAuth.GetTokenStore() |
| 354 | h.tokenStore = store | 354 | h.tokenStore = store |
| 355 | } | 355 | } |
| 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) | ||
| 357 | } | 362 | } |
| 358 | 363 | ||
| 359 | func (h *Handler) RequestAnthropicToken(c *gin.Context) { | 364 | func (h *Handler) RequestAnthropicToken(c *gin.Context) { |
| @@ -496,11 +501,12 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { · 136 unmodified lines | |||
| 496 | 501 | ||
| 497 | // Create token storage | 502 | // Create token storage |
| 498 | tokenStorage := anthropicAuth.CreateTokenStorage(bundle) | 503 | tokenStorage := anthropicAuth.CreateTokenStorage(bundle) |
| 499 | record := &sdkAuth.TokenRecord{ | 504 | record := &coreauth.Auth{ |
| 505 | ID: fmt.Sprintf("claude-%s.json", tokenStorage.Email), | ||
| 500 | Provider: "claude", | 506 | Provider: "claude", |
| 501 | FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email), | 507 | FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email), |
| 502 | Storage: tokenStorage, | 508 | Storage: tokenStorage, |
| 503 | Metadata: map[string]string{"email": tokenStorage.Email}, | 509 | Metadata: map[string]any{"email": tokenStorage.Email}, |
| 504 | } | 510 | } |
| 505 | savedPath, errSave := h.saveTokenRecord(ctx, record) | 511 | savedPath, errSave := h.saveTokenRecord(ctx, record) |
| 506 | if errSave != nil { | 512 | if errSave != nil { |
| @@ -659,11 +665,12 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { · 152 unmodified lines | |||
| 659 | } | 665 | } |
| 660 | fmt.Println("Authentication successful.") | 666 | fmt.Println("Authentication successful.") |
| 661 | 667 | ||
| 662 | record := &sdkAuth.TokenRecord{ | 668 | record := &coreauth.Auth{ |
| 669 | ID: fmt.Sprintf("gemini-%s.json", ts.Email), | ||
| 663 | Provider: "gemini", | 670 | Provider: "gemini", |
| 664 | FileName: fmt.Sprintf("gemini-%s.json", ts.Email), | 671 | FileName: fmt.Sprintf("gemini-%s.json", ts.Email), |
| 665 | Storage: &ts, | 672 | Storage: &ts, |
| 666 | Metadata: map[string]string{ | 673 | Metadata: map[string]any{ |
| 667 | "email": ts.Email, | 674 | "email": ts.Email, |
| 668 | "project_id": ts.ProjectID, | 675 | "project_id": ts.ProjectID, |
| 669 | }, | 676 | }, |
| @@ -724,7 +731,8 @@ func (h *Handler) CreateGeminiWebToken(c *gin.Context) { · 54 unmodified lines | |||
| 724 | // Provide a stable label (gemini-web-<hash>) for logging and identification | 731 | // Provide a stable label (gemini-web-<hash>) for logging and identification |
| 725 | tokenStorage.Label = strings.TrimSuffix(fileName, ".json") | 732 | tokenStorage.Label = strings.TrimSuffix(fileName, ".json") |
| 726 | 733 | ||
| 727 | record := &sdkAuth.TokenRecord{ | 734 | record := &coreauth.Auth{ |
| 735 | ID: fileName, | ||
| 728 | Provider: "gemini-web", | 736 | Provider: "gemini-web", |
| 729 | FileName: fileName, | 737 | FileName: fileName, |
| 730 | Storage: tokenStorage, | 738 | Storage: tokenStorage, |
| @@ -869,11 +877,12 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { · 138 unmodified lines | |||
| 869 | 877 | ||
| 870 | // Create token storage and persist | 878 | // Create token storage and persist |
| 871 | tokenStorage := openaiAuth.CreateTokenStorage(bundle) | 879 | tokenStorage := openaiAuth.CreateTokenStorage(bundle) |
| 872 | record := &sdkAuth.TokenRecord{ | 880 | record := &coreauth.Auth{ |
| 881 | ID: fmt.Sprintf("codex-%s.json", tokenStorage.Email), | ||
| 873 | Provider: "codex", | 882 | Provider: "codex", |
| 874 | FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email), | 883 | FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email), |
| 875 | Storage: tokenStorage, | 884 | Storage: tokenStorage, |
| 876 | Metadata: map[string]string{ | 885 | Metadata: map[string]any{ |
| 877 | "email": tokenStorage.Email, | 886 | "email": tokenStorage.Email, |
| 878 | "account_id": tokenStorage.AccountID, | 887 | "account_id": tokenStorage.AccountID, |
| 879 | }, | 888 | }, |
| @@ -926,11 +935,12 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { · 46 unmodified lines | |||
| 926 | tokenStorage := qwenAuth.CreateTokenStorage(tokenData) | 935 | tokenStorage := qwenAuth.CreateTokenStorage(tokenData) |
| 927 | 936 | ||
| 928 | tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli()) | 937 | 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), | ||
| 930 | Provider: "qwen", | 940 | Provider: "qwen", |
| 931 | FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), | 941 | FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), |
| 932 | Storage: tokenStorage, | 942 | Storage: tokenStorage, |
| 933 | Metadata: map[string]string{"email": tokenStorage.Email}, | 943 | Metadata: map[string]any{"email": tokenStorage.Email}, |
| 934 | } | 944 | } |
| 935 | savedPath, errSave := h.saveTokenRecord(ctx, record) | 945 | savedPath, errSave := h.saveTokenRecord(ctx, record) |
| 936 | if errSave != nil { | 946 | if errSave != nil { |
| @@ -33,7 +33,7 @@ type Handler struct { · 32 unmodified lines | |||
| 33 | failedAttempts map[string]*attemptInfo // keyed by client IP | 33 | failedAttempts map[string]*attemptInfo // keyed by client IP |
| 34 | authManager *coreauth.Manager | 34 | authManager *coreauth.Manager |
| 35 | usageStats *usage.RequestStatistics | 35 | usageStats *usage.RequestStatistics |
| 36 | tokenStore sdkAuth.TokenStore | 36 | tokenStore coreauth.Store |
| 37 | 37 | ||
| 38 | localPassword string | 38 | localPassword string |
| 39 | } | 39 | } |
| @@ -18,6 +18,7 @@ import ( · 17 unmodified lines | |||
| 18 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 18 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 19 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" | 19 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
| 20 | sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" | 20 | sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" |
| 21 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 21 | ) | 22 | ) |
| 22 | 23 | ||
| 23 | // banner prints a simple ASCII banner for clarity without ANSI colors. | 24 | // banner prints a simple ASCII banner for clarity without ANSI colors. |
| @@ -173,13 +174,19 @@ func DoGeminiWebAuth(cfg *config.Config) { · 149 unmodified lines | |||
| 173 | Secure1PSIDTS: secure1psidts, | 174 | Secure1PSIDTS: secure1psidts, |
| 174 | Label: label, | 175 | Label: label, |
| 175 | } | 176 | } |
| 176 | record := &sdkAuth.TokenRecord{ | 177 | record := &coreauth.Auth{ |
| 178 | ID: fileName, | ||
| 177 | Provider: "gemini-web", | 179 | Provider: "gemini-web", |
| 178 | FileName: fileName, | 180 | FileName: fileName, |
| 179 | Storage: tokenStorage, | 181 | Storage: tokenStorage, |
| 180 | } | 182 | } |
| 181 | store := sdkAuth.GetTokenStore() | 183 | 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) | ||
| 183 | if err != nil { | 190 | if err != nil { |
| 184 | fmt.Println("!! Failed to save Gemini Web token to file:", err) | 191 | fmt.Println("!! Failed to save Gemini Web token to file:", err) |
| 185 | return | 192 | return |
| @@ -13,6 +13,7 @@ import ( · 12 unmodified lines | |||
| 13 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 13 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 14 | "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" | 14 | "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" |
| 15 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" | 15 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
| 16 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 16 | log "github.com/sirupsen/logrus" | 17 | log "github.com/sirupsen/logrus" |
| 17 | ) | 18 | ) |
| 18 | 19 | ||
| @@ -35,7 +36,7 @@ func (a *ClaudeAuthenticator) RefreshLead() *time.Duration { · 16 unmodified lines | |||
| 35 | return &d | 36 | return &d |
| 36 | } | 37 | } |
| 37 | 38 | ||
| 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) { |
| 39 | if cfg == nil { | 40 | if cfg == nil { |
| 40 | return nil, fmt.Errorf("cliproxy auth: configuration is required") | 41 | return nil, fmt.Errorf("cliproxy auth: configuration is required") |
| 41 | } | 42 | } |
| @@ -127,7 +128,7 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 85 unmodified lines | |||
| 127 | } | 128 | } |
| 128 | 129 | ||
| 129 | fileName := fmt.Sprintf("claude-%s.json", tokenStorage.Email) | 130 | fileName := fmt.Sprintf("claude-%s.json", tokenStorage.Email) |
| 130 | metadata := map[string]string{ | 131 | metadata := map[string]any{ |
| 131 | "email": tokenStorage.Email, | 132 | "email": tokenStorage.Email, |
| 132 | } | 133 | } |
| 133 | 134 | ||
| @@ -136,7 +137,8 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 2 unmodified lines | |||
| 136 | fmt.Println("Claude API key obtained and stored") | 137 | fmt.Println("Claude API key obtained and stored") |
| 137 | } | 138 | } |
| 138 | 139 | ||
| 139 | return &TokenRecord{ | 140 | return &coreauth.Auth{ |
| 141 | ID: fileName, | ||
| 140 | Provider: a.Provider(), | 142 | Provider: a.Provider(), |
| 141 | FileName: fileName, | 143 | FileName: fileName, |
| 142 | Storage: tokenStorage, | 144 | Storage: tokenStorage, |
| @@ -13,6 +13,7 @@ import ( · 12 unmodified lines | |||
| 13 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 13 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 14 | "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" | 14 | "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" |
| 15 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" | 15 | "github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
| 16 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 16 | log "github.com/sirupsen/logrus" | 17 | log "github.com/sirupsen/logrus" |
| 17 | ) | 18 | ) |
| 18 | 19 | ||
| @@ -35,7 +36,7 @@ func (a *CodexAuthenticator) RefreshLead() *time.Duration { · 16 unmodified lines | |||
| 35 | return &d | 36 | return &d |
| 36 | } | 37 | } |
| 37 | 38 | ||
| 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) { |
| 39 | if cfg == nil { | 40 | if cfg == nil { |
| 40 | return nil, fmt.Errorf("cliproxy auth: configuration is required") | 41 | return nil, fmt.Errorf("cliproxy auth: configuration is required") |
| 41 | } | 42 | } |
| @@ -126,7 +127,7 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 84 unmodified lines | |||
| 126 | } | 127 | } |
| 127 | 128 | ||
| 128 | fileName := fmt.Sprintf("codex-%s.json", tokenStorage.Email) | 129 | fileName := fmt.Sprintf("codex-%s.json", tokenStorage.Email) |
| 129 | metadata := map[string]string{ | 130 | metadata := map[string]any{ |
| 130 | "email": tokenStorage.Email, | 131 | "email": tokenStorage.Email, |
| 131 | } | 132 | } |
| 132 | 133 | ||
| @@ -135,7 +136,8 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 2 unmodified lines | |||
| 135 | fmt.Println("Codex API key obtained and stored") | 136 | fmt.Println("Codex API key obtained and stored") |
| 136 | } | 137 | } |
| 137 | 138 | ||
| 138 | return &TokenRecord{ | 139 | return &coreauth.Auth{ |
| 140 | ID: fileName, | ||
| 139 | Provider: a.Provider(), | 141 | Provider: a.Provider(), |
| 140 | FileName: fileName, | 142 | FileName: fileName, |
| 141 | Storage: tokenStorage, | 143 | Storage: tokenStorage, |
| @@ -11,7 +11,6 @@ import ( · 10 unmodified lines | |||
| 11 | "sync" | 11 | "sync" |
| 12 | "time" | 12 | "time" |
| 13 | 13 | ||
| 14 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | ||
| 15 | cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | 14 | cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" |
| 16 | ) | 15 | ) |
| 17 | 16 | ||
| @@ -35,27 +34,71 @@ func (s *FileTokenStore) SetBaseDir(dir string) { · 17 unmodified lines | |||
| 35 | s.dirLock.Unlock() | 34 | s.dirLock.Unlock() |
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | // Save writes the token storage to the resolved file path. | 37 | // Save persists token storage and metadata to the resolved auth file path. |
| 39 | func (s *FileTokenStore) Save(ctx context.Context, cfg *config.Config, record *TokenRecord) (string, error) { | 38 | func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (string, error) { |
| 40 | if record == nil || record.Storage == nil { | 39 | if auth == nil { |
| 41 | return "", fmt.Errorf("cliproxy auth: token record is incomplete") | 40 | return "", fmt.Errorf("auth filestore: auth is nil") |
| 42 | } | 41 | } |
| 43 | target := strings.TrimSpace(record.FileName) | 42 | |
| 44 | if target == "" { | 43 | path, err := s.resolveAuthPath(auth) |
| 45 | return "", fmt.Errorf("cliproxy auth: missing file name for provider %s", record.Provider) | 44 | if err != nil { |
| 46 | } | 45 | return "", err |
| 47 | if !filepath.IsAbs(target) { | 46 | } |
| 48 | baseDir := s.baseDirFromConfig(cfg) | 47 | if path == "" { |
| 49 | if baseDir != "" { | 48 | return "", fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID) |
| 50 | target = filepath.Join(baseDir, target) | 49 | } |
| 50 | |||
| 51 | if auth.Disabled { | ||
| 52 | if _, statErr := os.Stat(path); os.IsNotExist(statErr) { | ||
| 53 | return "", nil | ||
| 51 | } | 54 | } |
| 52 | } | 55 | } |
| 56 | |||
| 53 | s.mu.Lock() | 57 | s.mu.Lock() |
| 54 | defer s.mu.Unlock() | 58 | defer s.mu.Unlock() |
| 55 | if err := record.Storage.SaveTokenToFile(target); err != nil { | 59 | |
| 56 | return "", err | 60 | if err = os.MkdirAll(filepath.Dir(path), 0o700); err != nil { |
| 61 | return "", fmt.Errorf("auth filestore: create dir failed: %w", err) | ||
| 57 | } | 62 | } |
| 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 | ||
| 59 | } | 102 | } |
| 60 | 103 | ||
| 61 | // List enumerates all auth JSON files under the configured directory. | 104 | // 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 | |||
| 90 | return entries, nil | 133 | return entries, nil |
| 91 | } | 134 | } |
| 92 | 135 | ||
| 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 | |||
| 137 | // Delete removes the auth file. | 136 | // Delete removes the auth file. |
| 138 | func (s *FileTokenStore) Delete(ctx context.Context, id string) error { | 137 | func (s *FileTokenStore) Delete(ctx context.Context, id string) error { |
| 139 | id = strings.TrimSpace(id) | 138 | id = strings.TrimSpace(id) |
| @@ -185,6 +184,7 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth, · 45 unmodified lines | |||
| 185 | auth := &cliproxyauth.Auth{ | 184 | auth := &cliproxyauth.Auth{ |
| 186 | ID: id, | 185 | ID: id, |
| 187 | Provider: provider, | 186 | Provider: provider, |
| 187 | FileName: id, | ||
| 188 | Label: s.labelFor(metadata), | 188 | Label: s.labelFor(metadata), |
| 189 | Status: cliproxyauth.StatusActive, | 189 | Status: cliproxyauth.StatusActive, |
| 190 | Attributes: map[string]string{"path": path}, | 190 | Attributes: map[string]string{"path": path}, |
| @@ -220,6 +220,15 @@ func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error · 29 unmodified lines | |||
| 220 | return p, nil | 220 | return p, nil |
| 221 | } | 221 | } |
| 222 | } | 222 | } |
| 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 | } | ||
| 223 | if auth.ID == "" { | 232 | if auth.ID == "" { |
| 224 | return "", fmt.Errorf("auth filestore: missing id") | 233 | return "", fmt.Errorf("auth filestore: missing id") |
| 225 | } | 234 | } |
| @@ -249,13 +258,6 @@ func (s *FileTokenStore) labelFor(metadata map[string]any) string { · 23 unmodified lines | |||
| 249 | return "" | 258 | return "" |
| 250 | } | 259 | } |
| 251 | 260 | ||
| 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 | |||
| 259 | func (s *FileTokenStore) baseDirSnapshot() string { | 261 | func (s *FileTokenStore) baseDirSnapshot() string { |
| 260 | s.dirLock.RLock() | 262 | s.dirLock.RLock() |
| 261 | defer s.dirLock.RUnlock() | 263 | defer s.dirLock.RUnlock() |
| @@ -6,6 +6,7 @@ import ( · 5 unmodified lines | |||
| 6 | "time" | 6 | "time" |
| 7 | 7 | ||
| 8 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 8 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 9 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 9 | ) | 10 | ) |
| 10 | 11 | ||
| 11 | // GeminiWebAuthenticator provides a minimal wrapper so core components can treat | 12 | // GeminiWebAuthenticator provides a minimal wrapper so core components can treat |
| @@ -16,7 +17,7 @@ func NewGeminiWebAuthenticator() *GeminiWebAuthenticator { return &GeminiWebAuth · 4 unmodified lines | |||
| 16 | 17 | ||
| 17 | func (a *GeminiWebAuthenticator) Provider() string { return "gemini-web" } | 18 | func (a *GeminiWebAuthenticator) Provider() string { return "gemini-web" } |
| 18 | 19 | ||
| 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) { |
| 20 | _ = ctx | 21 | _ = ctx |
| 21 | _ = cfg | 22 | _ = cfg |
| 22 | _ = opts | 23 | _ = opts |
| @@ -8,6 +8,7 @@ import ( · 7 unmodified lines | |||
| 8 | "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" | 8 | "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" |
| 9 | // legacy client removed | 9 | // legacy client removed |
| 10 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 10 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 11 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 11 | ) | 12 | ) |
| 12 | 13 | ||
| 13 | // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. | 14 | // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. |
| @@ -26,7 +27,7 @@ func (a *GeminiAuthenticator) RefreshLead() *time.Duration { · 12 unmodified lines | |||
| 26 | return nil | 27 | return nil |
| 27 | } | 28 | } |
| 28 | 29 | ||
| 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) { |
| 30 | if cfg == nil { | 31 | if cfg == nil { |
| 31 | return nil, fmt.Errorf("cliproxy auth: configuration is required") | 32 | return nil, fmt.Errorf("cliproxy auth: configuration is required") |
| 32 | } | 33 | } |
| @@ -51,14 +52,15 @@ func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opt · 18 unmodified lines | |||
| 51 | // Skip onboarding here; rely on upstream configuration | 52 | // Skip onboarding here; rely on upstream configuration |
| 52 | 53 | ||
| 53 | fileName := fmt.Sprintf("%s-%s.json", ts.Email, ts.ProjectID) | 54 | fileName := fmt.Sprintf("%s-%s.json", ts.Email, ts.ProjectID) |
| 54 | metadata := map[string]string{ | 55 | metadata := map[string]any{ |
| 55 | "email": ts.Email, | 56 | "email": ts.Email, |
| 56 | "project_id": ts.ProjectID, | 57 | "project_id": ts.ProjectID, |
| 57 | } | 58 | } |
| 58 | 59 | ||
| 59 | fmt.Println("Gemini authentication successful") | 60 | fmt.Println("Gemini authentication successful") |
| 60 | 61 | ||
| 61 | return &TokenRecord{ | 62 | return &coreauth.Auth{ |
| 63 | ID: fileName, | ||
| 62 | Provider: a.Provider(), | 64 | Provider: a.Provider(), |
| 63 | FileName: fileName, | 65 | FileName: fileName, |
| 64 | Storage: &ts, | 66 | Storage: &ts, |
| @@ -5,8 +5,8 @@ import ( · 4 unmodified lines | |||
| 5 | "errors" | 5 | "errors" |
| 6 | "time" | 6 | "time" |
| 7 | 7 | ||
| 8 | baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth" | ||
| 9 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 8 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 9 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 10 | ) | 10 | ) |
| 11 | 11 | ||
| 12 | var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported") | 12 | var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported") |
| @@ -20,22 +20,9 @@ type LoginOptions struct { · 7 unmodified lines | |||
| 20 | Prompt func(prompt string) (string, error) | 20 | Prompt func(prompt string) (string, error) |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 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 | |||
| 36 | // Authenticator manages login and optional refresh flows for a provider. | 23 | // Authenticator manages login and optional refresh flows for a provider. |
| 37 | type Authenticator interface { | 24 | type Authenticator interface { |
| 38 | Provider() string | 25 | 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) |
| 40 | RefreshLead() *time.Duration | 27 | RefreshLead() *time.Duration |
| 41 | } | 28 | } |
| @@ -5,17 +5,18 @@ import ( · 4 unmodified lines | |||
| 5 | "fmt" | 5 | "fmt" |
| 6 | 6 | ||
| 7 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 7 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 8 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 8 | ) | 9 | ) |
| 9 | 10 | ||
| 10 | // Manager aggregates authenticators and coordinates persistence via a token store. | 11 | // Manager aggregates authenticators and coordinates persistence via a token store. |
| 11 | type Manager struct { | 12 | type Manager struct { |
| 12 | authenticators map[string]Authenticator | 13 | authenticators map[string]Authenticator |
| 13 | store TokenStore | 14 | store coreauth.Store |
| 14 | } | 15 | } |
| 15 | 16 | ||
| 16 | // NewManager constructs a manager with the provided token store and authenticators. | 17 | // NewManager constructs a manager with the provided token store and authenticators. |
| 17 | // If store is nil, the caller must set it later using SetStore. | 18 | // 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 { |
| 19 | mgr := &Manager{ | 20 | mgr := &Manager{ |
| 20 | authenticators: make(map[string]Authenticator), | 21 | authenticators: make(map[string]Authenticator), |
| 21 | store: store, | 22 | store: store, |
| @@ -38,12 +39,12 @@ func (m *Manager) Register(a Authenticator) { · 16 unmodified lines | |||
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | // SetStore updates the token store used for persistence. | 41 | // SetStore updates the token store used for persistence. |
| 41 | func (m *Manager) SetStore(store TokenStore) { | 42 | func (m *Manager) SetStore(store coreauth.Store) { |
| 42 | m.store = store | 43 | m.store = store |
| 43 | } | 44 | } |
| 44 | 45 | ||
| 45 | // Login executes the provider login flow and persists the resulting token record. | 46 | // Login executes the provider login flow and persists the resulting auth record. |
| 46 | func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*TokenRecord, string, error) { | 47 | func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, string, error) { |
| 47 | auth, ok := m.authenticators[provider] | 48 | auth, ok := m.authenticators[provider] |
| 48 | if !ok { | 49 | if !ok { |
| 49 | return nil, "", fmt.Errorf("cliproxy auth: authenticator %s not registered", provider) | 50 | 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 | |||
| 61 | return record, "", nil | 62 | return record, "", nil |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 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) | ||
| 65 | if err != nil { | 72 | if err != nil { |
| 66 | return record, "", err | 73 | return record, "", err |
| 67 | } | 74 | } |
| @@ -10,6 +10,7 @@ import ( · 9 unmodified lines | |||
| 10 | "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" | 10 | "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" |
| 11 | // legacy client removed | 11 | // legacy client removed |
| 12 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" | 12 | "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| 13 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 13 | log "github.com/sirupsen/logrus" | 14 | log "github.com/sirupsen/logrus" |
| 14 | ) | 15 | ) |
| 15 | 16 | ||
| @@ -30,7 +31,7 @@ func (a *QwenAuthenticator) RefreshLead() *time.Duration { · 14 unmodified lines | |||
| 30 | return &d | 31 | return &d |
| 31 | } | 32 | } |
| 32 | 33 | ||
| 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) { |
| 34 | if cfg == nil { | 35 | if cfg == nil { |
| 35 | return nil, fmt.Errorf("cliproxy auth: configuration is required") | 36 | return nil, fmt.Errorf("cliproxy auth: configuration is required") |
| 36 | } | 37 | } |
| @@ -97,13 +98,14 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts · 60 unmodified lines | |||
| 97 | // no legacy client construction | 98 | // no legacy client construction |
| 98 | 99 | ||
| 99 | fileName := fmt.Sprintf("qwen-%s.json", tokenStorage.Email) | 100 | fileName := fmt.Sprintf("qwen-%s.json", tokenStorage.Email) |
| 100 | metadata := map[string]string{ | 101 | metadata := map[string]any{ |
| 101 | "email": tokenStorage.Email, | 102 | "email": tokenStorage.Email, |
| 102 | } | 103 | } |
| 103 | 104 | ||
| 104 | fmt.Println("Qwen authentication successful") | 105 | fmt.Println("Qwen authentication successful") |
| 105 | 106 | ||
| 106 | return &TokenRecord{ | 107 | return &coreauth.Auth{ |
| 108 | ID: fileName, | ||
| 107 | Provider: a.Provider(), | 109 | Provider: a.Provider(), |
| 108 | FileName: fileName, | 110 | FileName: fileName, |
| 109 | Storage: tokenStorage, | 111 | Storage: tokenStorage, |
| @@ -1,31 +1,35 @@ | |||
| 1 | package auth | 1 | package auth |
| 2 | 2 | ||
| 3 | import "sync" | 3 | import ( |
| 4 | "sync" | ||
| 5 | |||
| 6 | coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" | ||
| 7 | ) | ||
| 4 | 8 | ||
| 5 | var ( | 9 | var ( |
| 6 | storeMu sync.RWMutex | 10 | storeMu sync.RWMutex |
| 7 | registeredTokenStore TokenStore | 11 | registeredStore coreauth.Store |
| 8 | ) | 12 | ) |
| 9 | 13 | ||
| 10 | // RegisterTokenStore sets the global token store used by the authentication helpers. | 14 | // RegisterTokenStore sets the global token store used by the authentication helpers. |
| 11 | func RegisterTokenStore(store TokenStore) { | 15 | func RegisterTokenStore(store coreauth.Store) { |
| 12 | storeMu.Lock() | 16 | storeMu.Lock() |
| 13 | registeredTokenStore = store | 17 | registeredStore = store |
| 14 | storeMu.Unlock() | 18 | storeMu.Unlock() |
| 15 | } | 19 | } |
| 16 | 20 | ||
| 17 | // GetTokenStore returns the globally registered token store. | 21 | // GetTokenStore returns the globally registered token store. |
| 18 | func GetTokenStore() TokenStore { | 22 | func GetTokenStore() coreauth.Store { |
| 19 | storeMu.RLock() | 23 | storeMu.RLock() |
| 20 | s := registeredTokenStore | 24 | s := registeredStore |
| 21 | storeMu.RUnlock() | 25 | storeMu.RUnlock() |
| 22 | if s != nil { | 26 | if s != nil { |
| 23 | return s | 27 | return s |
| 24 | } | 28 | } |
| 25 | storeMu.Lock() | 29 | storeMu.Lock() |
| 26 | defer storeMu.Unlock() | 30 | defer storeMu.Unlock() |
| 27 | if registeredTokenStore == nil { | 31 | if registeredStore == nil { |
| 28 | registeredTokenStore = NewFileTokenStore() | 32 | registeredStore = NewFileTokenStore() |
| 29 | } | 33 | } |
| 30 | return registeredTokenStore | 34 | return registeredStore |
| 31 | } | 35 | } |
| @@ -818,7 +818,8 @@ func (m *Manager) persist(ctx context.Context, auth *Auth) error { · 817 unmodified lines | |||
| 818 | if auth.Metadata == nil { | 818 | if auth.Metadata == nil { |
| 819 | return nil | 819 | return nil |
| 820 | } | 820 | } |
| 821 | return m.store.SaveAuth(ctx, auth) | 821 | _, err := m.store.Save(ctx, auth) |
| 822 | return err | ||
| 822 | } | 823 | } |
| 823 | 824 | ||
| 824 | // StartAutoRefresh launches a background loop that evaluates auth freshness | 825 | // StartAutoRefresh launches a background loop that evaluates auth freshness |
| @@ -6,8 +6,8 @@ import "context" · 5 unmodified lines | |||
| 6 | type Store interface { | 6 | type Store interface { |
| 7 | // List returns all auth records stored in the backend. | 7 | // List returns all auth records stored in the backend. |
| 8 | List(ctx context.Context) ([]*Auth, error) | 8 | List(ctx context.Context) ([]*Auth, error) |
| 9 | // SaveAuth persists the provided auth record, replacing any existing one with same ID. | 9 | // Save persists the provided auth record, replacing any existing one with same ID. |
| 10 | SaveAuth(ctx context.Context, auth *Auth) error | 10 | Save(ctx context.Context, auth *Auth) (string, error) |
| 11 | // Delete removes the auth record identified by id. | 11 | // Delete removes the auth record identified by id. |
| 12 | Delete(ctx context.Context, id string) error | 12 | Delete(ctx context.Context, id string) error |
| 13 | } | 13 | } |
| @@ -6,6 +6,8 @@ import ( · 5 unmodified lines | |||
| 6 | "strings" | 6 | "strings" |
| 7 | "sync" | 7 | "sync" |
| 8 | "time" | 8 | "time" |
| 9 | |||
| 10 | baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth" | ||
| 9 | ) | 11 | ) |
| 10 | 12 | ||
| 11 | // Auth encapsulates the runtime state and metadata associated with a single credential. | 13 | // Auth encapsulates the runtime state and metadata associated with a single credential. |
| @@ -14,6 +16,10 @@ type Auth struct { · 2 unmodified lines | |||
| 14 | ID string `json:"id"` | 16 | ID string `json:"id"` |
| 15 | // Provider is the upstream provider key (e.g. "gemini", "claude"). | 17 | // Provider is the upstream provider key (e.g. "gemini", "claude"). |
| 16 | Provider string `json:"provider"` | 18 | 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:"-"` | ||
| 17 | // Label is an optional human readable label for logging. | 23 | // Label is an optional human readable label for logging. |
| 18 | Label string `json:"label,omitempty"` | 24 | Label string `json:"label,omitempty"` |
| 19 | // Status is the lifecycle status managed by the AuthManager. | 25 | // Status is the lifecycle status managed by the AuthManager. |
| @@ -197,11 +197,7 @@ func (b *Builder) Build() (*Service, error) { · 196 unmodified lines | |||
| 197 | if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok && b.cfg != nil { | 197 | if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok && b.cfg != nil { |
| 198 | dirSetter.SetBaseDir(b.cfg.AuthDir) | 198 | dirSetter.SetBaseDir(b.cfg.AuthDir) |
| 199 | } | 199 | } |
| 200 | store, ok := tokenStore.(coreauth.Store) | 200 | coreManager = coreauth.NewManager(tokenStore, nil, nil) |
| 201 | if !ok { | ||
| 202 | return nil, fmt.Errorf("cliproxy: token store does not implement coreauth.Store") | ||
| 203 | } | ||
| 204 | coreManager = coreauth.NewManager(store, nil, nil) | ||
| 205 | } | 201 | } |
| 206 | // Attach a default RoundTripper provider so providers can opt-in per-auth transports. | 202 | // Attach a default RoundTripper provider so providers can opt-in per-auth transports. |
| 207 | coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) | 203 | coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) |