Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
5.7 kB · 198 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199package auth
import ( "context" "fmt" "net/http" "strings" "time"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" // legacy client removed "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus")
// CodexAuthenticator implements the OAuth login flow for Codex accounts.type CodexAuthenticator struct { CallbackPort int}
// NewCodexAuthenticator constructs a Codex authenticator with default settings.func NewCodexAuthenticator() *CodexAuthenticator { return &CodexAuthenticator{CallbackPort: 1455}}
func (a *CodexAuthenticator) Provider() string { return "codex"}
func (a *CodexAuthenticator) RefreshLead() *time.Duration { return new(24 * time.Hour)}
func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts *LoginOptions) (*coreauth.Auth, error) { if cfg == nil { return nil, fmt.Errorf("cliproxy auth: configuration is required") } if ctx == nil { ctx = context.Background() } if opts == nil { opts = &LoginOptions{} }
if shouldUseCodexDeviceFlow(opts) { return a.loginWithDeviceFlow(ctx, cfg, opts) }
callbackPort := a.CallbackPort if opts.CallbackPort > 0 { callbackPort = opts.CallbackPort }
pkceCodes, err := codex.GeneratePKCECodes() if err != nil { return nil, fmt.Errorf("codex pkce generation failed: %w", err) }
state, err := misc.GenerateRandomState() if err != nil { return nil, fmt.Errorf("codex state generation failed: %w", err) }
oauthServer := codex.NewOAuthServer(callbackPort) if err = oauthServer.Start(); err != nil { if strings.Contains(err.Error(), "already in use") { return nil, codex.NewAuthenticationError(codex.ErrPortInUse, err) } return nil, codex.NewAuthenticationError(codex.ErrServerStartFailed, err) } defer func() { stopCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if stopErr := oauthServer.Stop(stopCtx); stopErr != nil { log.Warnf("codex oauth server stop error: %v", stopErr) } }()
authSvc := codex.NewCodexAuth(cfg)
authURL, err := authSvc.GenerateAuthURL(state, pkceCodes) if err != nil { return nil, fmt.Errorf("codex authorization url generation failed: %w", err) }
if !opts.NoBrowser { fmt.Println("Opening browser for Codex authentication") if !browser.IsAvailable() { log.Warn("No browser available; please open the URL manually") util.PrintSSHTunnelInstructions(callbackPort) fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } else if err = browser.OpenURL(authURL); err != nil { log.Warnf("Failed to open browser automatically: %v", err) util.PrintSSHTunnelInstructions(callbackPort) fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } } else { util.PrintSSHTunnelInstructions(callbackPort) fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) }
fmt.Println("Waiting for Codex authentication callback...")
callbackCh := make(chan *codex.OAuthResult, 1) callbackErrCh := make(chan error, 1) manualDescription := ""
go func() { result, errWait := oauthServer.WaitForCallback(5 * time.Minute) if errWait != nil { callbackErrCh <- errWait return } callbackCh <- result }()
var result *codex.OAuthResult var manualPromptTimer *time.Timer var manualPromptC <-chan time.Time if opts.Prompt != nil { manualPromptTimer = time.NewTimer(15 * time.Second) manualPromptC = manualPromptTimer.C defer manualPromptTimer.Stop() }
var manualInputCh <-chan string var manualInputErrCh <-chan error
waitForCallback: for { select { case result = <-callbackCh: break waitForCallback case err = <-callbackErrCh: if strings.Contains(err.Error(), "timeout") { return nil, codex.NewAuthenticationError(codex.ErrCallbackTimeout, err) } return nil, err case <-manualPromptC: manualPromptC = nil if manualPromptTimer != nil { manualPromptTimer.Stop() } select { case result = <-callbackCh: break waitForCallback case err = <-callbackErrCh: if strings.Contains(err.Error(), "timeout") { return nil, codex.NewAuthenticationError(codex.ErrCallbackTimeout, err) } return nil, err default: } manualInputCh, manualInputErrCh = misc.AsyncPrompt(opts.Prompt, "Paste the Codex callback URL (or press Enter to keep waiting): ") continue case input := <-manualInputCh: manualInputCh = nil manualInputErrCh = nil parsed, errParse := misc.ParseOAuthCallback(input) if errParse != nil { return nil, errParse } if parsed == nil { continue } manualDescription = parsed.ErrorDescription result = &codex.OAuthResult{ Code: parsed.Code, State: parsed.State, Error: parsed.Error, } break waitForCallback case errManual := <-manualInputErrCh: return nil, errManual } }
if result.Error != "" { return nil, codex.NewOAuthError(result.Error, manualDescription, http.StatusBadRequest) }
if result.State != state { return nil, codex.NewAuthenticationError(codex.ErrInvalidState, fmt.Errorf("state mismatch")) }
log.Debug("Codex authorization code received; exchanging for tokens")
authBundle, err := authSvc.ExchangeCodeForTokens(ctx, result.Code, pkceCodes) if err != nil { return nil, codex.NewAuthenticationError(codex.ErrCodeExchangeFailed, err) }
return a.buildAuthRecord(authSvc, authBundle)}