Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions internal/api/handlers/management/auth_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -1565,8 +1565,8 @@ func (h *Handler) RequestQwenToken(c *gin.Context) {
return
}

// Create token storage
tokenStorage := qwenAuth.CreateTokenStorage(tokenData)
// Create token storage - default to false for use_global_proxy for qwen
tokenStorage := qwenAuth.CreateTokenStorage(tokenData, false)

tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli())
record := &coreauth.Auth{
Expand Down Expand Up @@ -1742,7 +1742,8 @@ func (h *Handler) RequestIFlowCookieToken(c *gin.Context) {

tokenData.Cookie = cookieValue

tokenStorage := authSvc.CreateCookieTokenStorage(tokenData)
// For management API, default to false for use_global_proxy for iFlow
tokenStorage := authSvc.CreateCookieTokenStorage(tokenData, false)
email := strings.TrimSpace(tokenStorage.Email)
if email == "" {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "failed to extract email from token"})
Expand Down
15 changes: 8 additions & 7 deletions internal/auth/iflow/iflow_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ func ShouldRefreshAPIKey(expireTime string) (bool, time.Duration, error) {
}

// CreateCookieTokenStorage converts cookie-based token data into persistence storage
func (ia *IFlowAuth) CreateCookieTokenStorage(data *IFlowTokenData) *IFlowTokenStorage {
func (ia *IFlowAuth) CreateCookieTokenStorage(data *IFlowTokenData, useGlobalProxy bool) *IFlowTokenStorage {
if data == nil {
return nil
}
Expand All @@ -502,12 +502,13 @@ func (ia *IFlowAuth) CreateCookieTokenStorage(data *IFlowTokenData) *IFlowTokenS
}

return &IFlowTokenStorage{
APIKey: data.APIKey,
Email: data.Email,
Expire: data.Expire,
Cookie: cookieToSave,
LastRefresh: time.Now().Format(time.RFC3339),
Type: "iflow",
APIKey: data.APIKey,
Email: data.Email,
Expire: data.Expire,
Cookie: cookieToSave,
LastRefresh: time.Now().Format(time.RFC3339),
Type: "iflow",
UseGlobalProxy: useGlobalProxy,
}
}

Expand Down
26 changes: 16 additions & 10 deletions internal/auth/iflow/iflow_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import (

// IFlowTokenStorage persists iFlow OAuth credentials alongside the derived API key.
type IFlowTokenStorage struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
LastRefresh string `json:"last_refresh"`
Expire string `json:"expired"`
APIKey string `json:"api_key"`
Email string `json:"email"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
Cookie string `json:"cookie"`
Type string `json:"type"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
LastRefresh string `json:"last_refresh"`
Expire string `json:"expired"`
APIKey string `json:"api_key"`
Email string `json:"email"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
Cookie string `json:"cookie"`
Type string `json:"type"`
UseGlobalProxy bool `json:"use_global_proxy"`
}

// SaveTokenToFile serialises the token storage to disk.
Expand All @@ -42,3 +43,8 @@ func (ts *IFlowTokenStorage) SaveTokenToFile(authFilePath string) error {
}
return nil
}

// SetUseGlobalProxy implements the GlobalProxySetter interface
func (ts *IFlowTokenStorage) SetUseGlobalProxy(value bool) {
ts.UseGlobalProxy = value
}
13 changes: 7 additions & 6 deletions internal/auth/qwen/qwen_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,14 @@ func (o *QwenAuth) RefreshTokensWithRetry(ctx context.Context, refreshToken stri
}

// CreateTokenStorage creates a QwenTokenStorage object from a QwenTokenData object.
func (o *QwenAuth) CreateTokenStorage(tokenData *QwenTokenData) *QwenTokenStorage {
func (o *QwenAuth) CreateTokenStorage(tokenData *QwenTokenData, useGlobalProxy bool) *QwenTokenStorage {
storage := &QwenTokenStorage{
AccessToken: tokenData.AccessToken,
RefreshToken: tokenData.RefreshToken,
LastRefresh: time.Now().Format(time.RFC3339),
ResourceURL: tokenData.ResourceURL,
Expire: tokenData.Expire,
AccessToken: tokenData.AccessToken,
RefreshToken: tokenData.RefreshToken,
LastRefresh: time.Now().Format(time.RFC3339),
ResourceURL: tokenData.ResourceURL,
Expire: tokenData.Expire,
UseGlobalProxy: useGlobalProxy,
}

return storage
Expand Down
7 changes: 7 additions & 0 deletions internal/auth/qwen/qwen_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type QwenTokenStorage struct {
Type string `json:"type"`
// Expire is the timestamp when the current access token expires.
Expire string `json:"expired"`
// UseGlobalProxy indicates whether to use the global proxy from config.yaml.
UseGlobalProxy bool `json:"use_global_proxy"`
}

// SaveTokenToFile serializes the Qwen token storage to a JSON file.
Expand Down Expand Up @@ -61,3 +63,8 @@ func (ts *QwenTokenStorage) SaveTokenToFile(authFilePath string) error {
}
return nil
}

// SetUseGlobalProxy implements the GlobalProxySetter interface
func (ts *QwenTokenStorage) SetUseGlobalProxy(value bool) {
ts.UseGlobalProxy = value
}
13 changes: 12 additions & 1 deletion internal/cmd/iflow_cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/iflow"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
)

// DoIFlowCookieAuth performs the iFlow cookie-based authentication.
Expand Down Expand Up @@ -49,6 +50,16 @@ func DoIFlowCookieAuth(cfg *config.Config, options *LoginOptions) {
return
}

// Ask if user wants to use global proxy
useGlobalProxy := false // Default to false for iFlow
if options != nil && options.Prompt != nil {
// Create sdk auth LoginOptions from cmd LoginOptions to use AskUseGlobalProxy
sdkOpts := &auth.LoginOptions{
Prompt: options.Prompt,
}
useGlobalProxy = auth.AskUseGlobalProxy(sdkOpts, false)
}

// Authenticate with cookie
auth := iflow.NewIFlowAuth(cfg)
ctx := context.Background()
Expand All @@ -60,7 +71,7 @@ func DoIFlowCookieAuth(cfg *config.Config, options *LoginOptions) {
}

// Create token storage
tokenStorage := auth.CreateCookieTokenStorage(tokenData)
tokenStorage := auth.CreateCookieTokenStorage(tokenData, useGlobalProxy)

// Get auth file path using email in filename
authFilePath := getAuthFilePath(cfg, "iflow", tokenData.Email)
Expand Down
10 changes: 5 additions & 5 deletions internal/runtime/executor/proxy_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (

// newProxyAwareHTTPClient creates an HTTP client with proper proxy configuration priority:
// 1. Use auth.ProxyURL if configured (highest priority)
// 2. Use cfg.ProxyURL if auth proxy is not configured
// 3. Use RoundTripper from context if neither are configured
// 2. Use cfg.ProxyURL if auth.UseGlobalProxy is true and auth.ProxyURL is not configured
// 3. Use RoundTripper from context if no proxy is configured
//
// Parameters:
// - ctx: The context containing optional RoundTripper
Expand All @@ -33,14 +33,14 @@ func newProxyAwareHTTPClient(ctx context.Context, cfg *config.Config, auth *clip
httpClient.Timeout = timeout
}

// Priority 1: Use auth.ProxyURL if configured
// Priority 1: Use auth.ProxyURL if configured (highest priority)
var proxyURL string
if auth != nil {
proxyURL = strings.TrimSpace(auth.ProxyURL)
}

// Priority 2: Use cfg.ProxyURL if auth proxy is not configured
if proxyURL == "" && cfg != nil {
// Priority 2: Use cfg.ProxyURL if auth.UseGlobalProxy is true and no auth proxy is set
if proxyURL == "" && auth != nil && auth.UseGlobalProxy && cfg != nil {
proxyURL = strings.TrimSpace(cfg.ProxyURL)
}

Expand Down
6 changes: 6 additions & 0 deletions sdk/auth/filestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth,
if email, ok := metadata["email"].(string); ok && email != "" {
auth.Attributes["email"] = email
}
// Load use_global_proxy from metadata, default to true if not set
// This maintains backward compatibility for existing auth files
auth.UseGlobalProxy = true // Default to true
if useGlobalProxy, ok := metadata["use_global_proxy"].(bool); ok {
auth.UseGlobalProxy = useGlobalProxy
}
return auth, nil
}

Expand Down
55 changes: 55 additions & 0 deletions sdk/auth/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package auth
import (
"context"
"fmt"
"strings"

"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
Expand Down Expand Up @@ -43,6 +44,11 @@ func (m *Manager) SetStore(store coreauth.Store) {
m.store = store
}

// GlobalProxySetter is an interface for token storage to set the use_global_proxy value
type GlobalProxySetter interface {
SetUseGlobalProxy(bool)
}

// 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]
Expand All @@ -58,6 +64,23 @@ func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config
return nil, "", fmt.Errorf("cliproxy auth: authenticator %s returned nil record", provider)
}

// Always ask about using global proxy if auth doesn't have proxy URL configured
// This allows the setting to work even if user adds proxy to config.yaml later
if record.ProxyURL == "" {
// Determine default value based on provider
defaultValue := true
if provider == "qwen" || provider == "iflow" {
defaultValue = false
}
shouldUseGlobalProxy := AskUseGlobalProxy(opts, defaultValue)
record.UseGlobalProxy = shouldUseGlobalProxy

// Update the storage if it implements GlobalProxySetter
if storage, ok := record.Storage.(GlobalProxySetter); ok {
storage.SetUseGlobalProxy(shouldUseGlobalProxy)
}
}

if m.store == nil {
return record, "", nil
}
Expand All @@ -74,3 +97,35 @@ func (m *Manager) Login(ctx context.Context, provider string, cfg *config.Config
}
return record, savedPath, nil
}

// AskUseGlobalProxy asks the user whether to use the global proxy from config.yaml.
func AskUseGlobalProxy(opts *LoginOptions, defaultValue bool) bool {
if opts == nil || opts.Prompt == nil {
// If no prompt function available, return the default value
return defaultValue
}

fmt.Println()
fmt.Println("Would you like to use the global proxy from config.yaml for this authentication?")
fmt.Println("(This allows the proxy to be applied automatically if you add it to config.yaml later)")
if defaultValue {
fmt.Println("yes/no, default: yes")
} else {
fmt.Println("yes/no, default: no")
}

answer, err := opts.Prompt("Use global proxy? ")
if err != nil {
// If we can't get user input, return the default value
return defaultValue
}

answer = strings.TrimSpace(strings.ToLower(answer))
if defaultValue {
// If default is true, only turn off if user says no
return answer != "n" && answer != "no"
} else {
// If default is false, only turn on if user says yes
return answer == "y" || answer == "yes"
}
}
4 changes: 3 additions & 1 deletion sdk/auth/qwen.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
return nil, fmt.Errorf("qwen authentication failed: %w", err)
}

tokenStorage := authSvc.CreateTokenStorage(tokenData)
// Note: use_global_proxy will be set by manager.go's Login method
// Default to false for qwen
tokenStorage := authSvc.CreateTokenStorage(tokenData, false)

email := ""
if opts.Metadata != nil {
Expand Down
2 changes: 2 additions & 0 deletions sdk/cliproxy/auth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type Auth struct {
Unavailable bool `json:"unavailable"`
// ProxyURL overrides the global proxy setting for this auth if provided.
ProxyURL string `json:"proxy_url,omitempty"`
// UseGlobalProxy indicates whether to use the global proxy from config.yaml.
UseGlobalProxy bool `json:"use_global_proxy"`
// Attributes stores provider specific metadata needed by executors (immutable configuration).
Attributes map[string]string `json:"attributes,omitempty"`
// Metadata stores runtime mutable provider state (e.g. tokens, cookies).
Expand Down