mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 04:20:50 +08:00
- Updated all `github.com/luispater/CLIProxyAPI/internal/...` imports to point to `github.com/luispater/CLIProxyAPI/v5/internal/...`. - Adjusted `go.mod` to specify `module github.com/luispater/CLIProxyAPI/v5`.
179 lines
5.5 KiB
Go
179 lines
5.5 KiB
Go
// Package cmd provides command-line interface functionality for the CLI Proxy API.
|
|
// It implements the main application commands including login/authentication
|
|
// and server startup, handling the complete user onboarding and service lifecycle.
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/auth/codex"
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/browser"
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/client"
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/config"
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/misc"
|
|
"github.com/luispater/CLIProxyAPI/v5/internal/util"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// LoginOptions contains options for the Codex login process.
|
|
type LoginOptions struct {
|
|
// NoBrowser indicates whether to skip opening the browser automatically.
|
|
NoBrowser bool
|
|
}
|
|
|
|
// DoCodexLogin handles the Codex OAuth login process for OpenAI Codex services.
|
|
// It initializes the OAuth flow, opens the user's browser for authentication,
|
|
// waits for the callback, exchanges the authorization code for tokens,
|
|
// and saves the authentication information to a file.
|
|
//
|
|
// Parameters:
|
|
// - cfg: The application configuration
|
|
// - options: The login options containing browser preferences
|
|
func DoCodexLogin(cfg *config.Config, options *LoginOptions) {
|
|
if options == nil {
|
|
options = &LoginOptions{}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
log.Info("Initializing Codex authentication...")
|
|
|
|
// Generate PKCE codes
|
|
pkceCodes, err := codex.GeneratePKCECodes()
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate PKCE codes: %v", err)
|
|
return
|
|
}
|
|
|
|
// Generate random state parameter
|
|
state, err := misc.GenerateRandomState()
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate state parameter: %v", err)
|
|
return
|
|
}
|
|
|
|
// Initialize OAuth server
|
|
oauthServer := codex.NewOAuthServer(1455)
|
|
|
|
// Start OAuth callback server
|
|
if err = oauthServer.Start(); err != nil {
|
|
if strings.Contains(err.Error(), "already in use") {
|
|
authErr := codex.NewAuthenticationError(codex.ErrPortInUse, err)
|
|
log.Error(codex.GetUserFriendlyMessage(authErr))
|
|
os.Exit(13) // Exit code 13 for port-in-use error
|
|
}
|
|
authErr := codex.NewAuthenticationError(codex.ErrServerStartFailed, err)
|
|
log.Fatalf("Failed to start OAuth callback server: %v", authErr)
|
|
return
|
|
}
|
|
defer func() {
|
|
if err = oauthServer.Stop(ctx); err != nil {
|
|
log.Warnf("Failed to stop OAuth server: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Initialize Codex auth service
|
|
openaiAuth := codex.NewCodexAuth(cfg)
|
|
|
|
// Generate authorization URL
|
|
authURL, err := openaiAuth.GenerateAuthURL(state, pkceCodes)
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate authorization URL: %v", err)
|
|
return
|
|
}
|
|
|
|
// Open browser or display URL
|
|
if !options.NoBrowser {
|
|
log.Info("Opening browser for authentication...")
|
|
|
|
// Check if browser is available
|
|
if !browser.IsAvailable() {
|
|
log.Warn("No browser available on this system")
|
|
util.PrintSSHTunnelInstructions(1455)
|
|
log.Infof("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
|
} else {
|
|
if err = browser.OpenURL(authURL); err != nil {
|
|
authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err)
|
|
log.Warn(codex.GetUserFriendlyMessage(authErr))
|
|
util.PrintSSHTunnelInstructions(1455)
|
|
log.Infof("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
|
|
|
// Log platform info for debugging
|
|
platformInfo := browser.GetPlatformInfo()
|
|
log.Debugf("Browser platform info: %+v", platformInfo)
|
|
} else {
|
|
log.Debug("Browser opened successfully")
|
|
}
|
|
}
|
|
} else {
|
|
util.PrintSSHTunnelInstructions(1455)
|
|
log.Infof("Please open this URL in your browser:\n\n%s\n", authURL)
|
|
}
|
|
|
|
log.Info("Waiting for authentication callback...")
|
|
|
|
// Wait for OAuth callback
|
|
result, err := oauthServer.WaitForCallback(5 * time.Minute)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "timeout") {
|
|
authErr := codex.NewAuthenticationError(codex.ErrCallbackTimeout, err)
|
|
log.Error(codex.GetUserFriendlyMessage(authErr))
|
|
} else {
|
|
log.Errorf("Authentication failed: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if result.Error != "" {
|
|
oauthErr := codex.NewOAuthError(result.Error, "", http.StatusBadRequest)
|
|
log.Error(codex.GetUserFriendlyMessage(oauthErr))
|
|
return
|
|
}
|
|
|
|
// Validate state parameter
|
|
if result.State != state {
|
|
authErr := codex.NewAuthenticationError(codex.ErrInvalidState, fmt.Errorf("expected %s, got %s", state, result.State))
|
|
log.Error(codex.GetUserFriendlyMessage(authErr))
|
|
return
|
|
}
|
|
|
|
log.Debug("Authorization code received, exchanging for tokens...")
|
|
|
|
// Exchange authorization code for tokens
|
|
authBundle, err := openaiAuth.ExchangeCodeForTokens(ctx, result.Code, pkceCodes)
|
|
if err != nil {
|
|
authErr := codex.NewAuthenticationError(codex.ErrCodeExchangeFailed, err)
|
|
log.Errorf("Failed to exchange authorization code for tokens: %v", authErr)
|
|
log.Debug("This may be due to network issues or invalid authorization code")
|
|
return
|
|
}
|
|
|
|
// Create token storage
|
|
tokenStorage := openaiAuth.CreateTokenStorage(authBundle)
|
|
|
|
// Initialize Codex client
|
|
openaiClient, err := client.NewCodexClient(cfg, tokenStorage)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize Codex client: %v", err)
|
|
return
|
|
}
|
|
|
|
// Save token storage
|
|
if err = openaiClient.SaveTokenToFile(); err != nil {
|
|
log.Fatalf("Failed to save authentication tokens: %v", err)
|
|
return
|
|
}
|
|
|
|
log.Info("Authentication successful!")
|
|
if authBundle.APIKey != "" {
|
|
log.Info("API key obtained and saved")
|
|
}
|
|
|
|
log.Info("You can now use Codex services through this CLI")
|
|
}
|