mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-19 12:50:51 +08:00
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:
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
31
sdk/auth/store_registry.go
Normal file
31
sdk/auth/store_registry.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user