feat(auth): centralize token store management and enhance persistence

- Introduced `RegisterTokenStore` and `GetTokenStore` to centralize token store access.
- Replaced direct file operations with a unified token persistence API.
- Updated all components to use the shared token store for consistent behavior.
- Improved logging for token save operations to include file paths.
This commit is contained in:
Luis Pater
2025-09-25 03:17:50 +08:00
parent 19609db13c
commit 8fc73874de
7 changed files with 107 additions and 24 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )
@@ -185,6 +186,9 @@ func main() {
NoBrowser: noBrowser, NoBrowser: noBrowser,
} }
// Register the shared token store once so all components use the same persistence backend.
sdkAuth.RegisterTokenStore(sdkAuth.NewFileTokenStore())
// Handle different command modes based on the provided flags. // Handle different command modes based on the provided flags.
if login { if login {

View File

@@ -21,6 +21,7 @@ import (
// legacy client removed // legacy client removed
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "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" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@@ -341,6 +342,18 @@ func (h *Handler) disableAuth(ctx context.Context, id string) {
} }
} }
func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenRecord) (string, error) {
if record == nil {
return "", fmt.Errorf("token record is nil")
}
store := h.tokenStore
if store == nil {
store = sdkAuth.GetTokenStore()
h.tokenStore = store
}
return store.Save(ctx, h.cfg, record)
}
func (h *Handler) RequestAnthropicToken(c *gin.Context) { func (h *Handler) RequestAnthropicToken(c *gin.Context) {
ctx := context.Background() ctx := context.Background()
@@ -481,15 +494,20 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) {
// Create token storage // Create token storage
tokenStorage := anthropicAuth.CreateTokenStorage(bundle) tokenStorage := anthropicAuth.CreateTokenStorage(bundle)
// Persist token to file directly record := &sdkAuth.TokenRecord{
fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("claude-%s.json", tokenStorage.Email)) Provider: "claude",
if errSave := tokenStorage.SaveTokenToFile(fileName); errSave != nil { FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email),
Storage: tokenStorage,
Metadata: map[string]string{"email": tokenStorage.Email},
}
savedPath, errSave := h.saveTokenRecord(ctx, record)
if errSave != nil {
log.Fatalf("Failed to save authentication tokens: %v", errSave) log.Fatalf("Failed to save authentication tokens: %v", errSave)
oauthStatus[state] = "Failed to save authentication tokens" oauthStatus[state] = "Failed to save authentication tokens"
return return
} }
log.Info("Authentication successful!") log.Infof("Authentication successful! Token saved to %s", savedPath)
if bundle.APIKey != "" { if bundle.APIKey != "" {
log.Info("API key obtained and saved") log.Info("API key obtained and saved")
} }
@@ -639,16 +657,24 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
} }
log.Info("Authentication successful.") log.Info("Authentication successful.")
// Persist token to file directly record := &sdkAuth.TokenRecord{
fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("gemini-%s.json", ts.Email)) Provider: "gemini",
if err = ts.SaveTokenToFile(fileName); err != nil { FileName: fmt.Sprintf("gemini-%s.json", ts.Email),
log.Fatalf("Failed to save token to file: %v", err) Storage: &ts,
Metadata: map[string]string{
"email": ts.Email,
"project_id": ts.ProjectID,
},
}
savedPath, errSave := h.saveTokenRecord(ctx, record)
if errSave != nil {
log.Fatalf("Failed to save token to file: %v", errSave)
oauthStatus[state] = "Failed to save token to file" oauthStatus[state] = "Failed to save token to file"
return return
} }
delete(oauthStatus, state) delete(oauthStatus, state)
log.Info("You can now use Gemini CLI services through this CLI") log.Infof("You can now use Gemini CLI services through this CLI; token saved to %s", savedPath)
}() }()
oauthStatus[state] = "" oauthStatus[state] = ""
@@ -783,13 +809,22 @@ func (h *Handler) RequestCodexToken(c *gin.Context) {
// Create token storage and persist // Create token storage and persist
tokenStorage := openaiAuth.CreateTokenStorage(bundle) tokenStorage := openaiAuth.CreateTokenStorage(bundle)
fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("codex-%s.json", tokenStorage.Email)) record := &sdkAuth.TokenRecord{
if errSave := tokenStorage.SaveTokenToFile(fileName); errSave != nil { Provider: "codex",
FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email),
Storage: tokenStorage,
Metadata: map[string]string{
"email": tokenStorage.Email,
"account_id": tokenStorage.AccountID,
},
}
savedPath, errSave := h.saveTokenRecord(ctx, record)
if errSave != nil {
oauthStatus[state] = "Failed to save authentication tokens" oauthStatus[state] = "Failed to save authentication tokens"
log.Fatalf("Failed to save authentication tokens: %v", errSave) log.Fatalf("Failed to save authentication tokens: %v", errSave)
return return
} }
log.Info("Authentication successful!") log.Infof("Authentication successful! Token saved to %s", savedPath)
if bundle.APIKey != "" { if bundle.APIKey != "" {
log.Info("API key obtained and saved") log.Info("API key obtained and saved")
} }
@@ -831,15 +866,20 @@ func (h *Handler) RequestQwenToken(c *gin.Context) {
tokenStorage := qwenAuth.CreateTokenStorage(tokenData) tokenStorage := qwenAuth.CreateTokenStorage(tokenData)
tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli()) tokenStorage.Email = fmt.Sprintf("qwen-%d", time.Now().UnixMilli())
// Save token storage record := &sdkAuth.TokenRecord{
fileName := filepath.Join(h.cfg.AuthDir, fmt.Sprintf("qwen-%s.json", tokenStorage.Email)) Provider: "qwen",
if err = tokenStorage.SaveTokenToFile(fileName); err != nil { FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email),
log.Fatalf("Failed to save authentication tokens: %v", err) Storage: tokenStorage,
Metadata: map[string]string{"email": tokenStorage.Email},
}
savedPath, errSave := h.saveTokenRecord(ctx, record)
if errSave != nil {
log.Fatalf("Failed to save authentication tokens: %v", errSave)
oauthStatus[state] = "Failed to save authentication tokens" oauthStatus[state] = "Failed to save authentication tokens"
return return
} }
log.Info("Authentication successful!") log.Infof("Authentication successful! Token saved to %s", savedPath)
log.Info("You can now use Qwen services through this CLI") log.Info("You can now use Qwen services through this CLI")
delete(oauthStatus, state) delete(oauthStatus, state)
}() }()

View File

@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/usage" "github.com/router-for-me/CLIProxyAPI/v6/internal/usage"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -31,6 +32,7 @@ type Handler struct {
failedAttempts map[string]*attemptInfo // keyed by client IP failedAttempts map[string]*attemptInfo // keyed by client IP
authManager *coreauth.Manager authManager *coreauth.Manager
usageStats *usage.RequestStatistics usageStats *usage.RequestStatistics
tokenStore sdkAuth.TokenStore
} }
// NewHandler creates a new management handler instance. // NewHandler creates a new management handler instance.
@@ -41,6 +43,7 @@ func NewHandler(cfg *config.Config, configFilePath string, manager *coreauth.Man
failedAttempts: make(map[string]*attemptInfo), failedAttempts: make(map[string]*attemptInfo),
authManager: manager, authManager: manager,
usageStats: usage.GetRequestStatistics(), usageStats: usage.GetRequestStatistics(),
tokenStore: sdkAuth.GetTokenStore(),
} }
} }

View File

@@ -11,7 +11,7 @@ import (
// Returns: // Returns:
// - *sdkAuth.Manager: A configured authentication manager instance // - *sdkAuth.Manager: A configured authentication manager instance
func newAuthManager() *sdkAuth.Manager { func newAuthManager() *sdkAuth.Manager {
store := sdkAuth.NewFileTokenStore() store := sdkAuth.GetTokenStore()
manager := sdkAuth.NewManager(store, manager := sdkAuth.NewManager(store,
sdkAuth.NewGeminiAuthenticator(), sdkAuth.NewGeminiAuthenticator(),
sdkAuth.NewCodexAuthenticator(), sdkAuth.NewCodexAuthenticator(),

View File

@@ -3,15 +3,16 @@ package cmd
import ( import (
"bufio" "bufio"
"context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -48,13 +49,17 @@ func DoGeminiWebAuth(cfg *config.Config) {
hasher.Write([]byte(secure1psid)) hasher.Write([]byte(secure1psid))
hash := hex.EncodeToString(hasher.Sum(nil)) hash := hex.EncodeToString(hasher.Sum(nil))
fileName := fmt.Sprintf("gemini-web-%s.json", hash[:16]) fileName := fmt.Sprintf("gemini-web-%s.json", hash[:16])
filePath := filepath.Join(cfg.AuthDir, fileName) record := &sdkAuth.TokenRecord{
Provider: "gemini-web",
err := tokenStorage.SaveTokenToFile(filePath) FileName: fileName,
Storage: tokenStorage,
}
store := sdkAuth.GetTokenStore()
savedPath, err := store.Save(context.Background(), cfg, record)
if err != nil { if err != nil {
log.Fatalf("Failed to save Gemini Web token to file: %v", err) log.Fatalf("Failed to save Gemini Web token to file: %v", err)
return return
} }
log.Infof("Successfully saved Gemini Web token to: %s", filePath) log.Infof("Successfully saved Gemini Web token to: %s", savedPath)
} }

View File

@@ -0,0 +1,31 @@
package auth
import "sync"
var (
storeMu sync.RWMutex
registeredTokenStore TokenStore
)
// RegisterTokenStore sets the global token store used by the authentication helpers.
func RegisterTokenStore(store TokenStore) {
storeMu.Lock()
registeredTokenStore = store
storeMu.Unlock()
}
// GetTokenStore returns the globally registered token store.
func GetTokenStore() TokenStore {
storeMu.RLock()
s := registeredTokenStore
storeMu.RUnlock()
if s != nil {
return s
}
storeMu.Lock()
defer storeMu.Unlock()
if registeredTokenStore == nil {
registeredTokenStore = NewFileTokenStore()
}
return registeredTokenStore
}

View File

@@ -99,7 +99,7 @@ func (s *Service) RegisterUsagePlugin(plugin usage.Plugin) {
// newDefaultAuthManager creates a default authentication manager with all supported providers. // newDefaultAuthManager creates a default authentication manager with all supported providers.
func newDefaultAuthManager() *sdkAuth.Manager { func newDefaultAuthManager() *sdkAuth.Manager {
return sdkAuth.NewManager( return sdkAuth.NewManager(
sdkAuth.NewFileTokenStore(), sdkAuth.GetTokenStore(),
sdkAuth.NewGeminiAuthenticator(), sdkAuth.NewGeminiAuthenticator(),
sdkAuth.NewCodexAuthenticator(), sdkAuth.NewCodexAuthenticator(),
sdkAuth.NewClaudeAuthenticator(), sdkAuth.NewClaudeAuthenticator(),