mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat(oauth): add support for customizable OAuth callback ports - Introduced `oauth-callback-port` flag to override default callback ports. - Updated SDK and login flows for `iflow`, `gemini`, `antigravity`, `codex`, `claude`, and `openai` to respect configurable callback ports. - Refactored internal OAuth servers to dynamically assign ports based on the provided options. - Revised tests and documentation to reflect the new flag and behavior.
This commit is contained in:
@@ -61,6 +61,7 @@ func main() {
|
|||||||
var iflowLogin bool
|
var iflowLogin bool
|
||||||
var iflowCookie bool
|
var iflowCookie bool
|
||||||
var noBrowser bool
|
var noBrowser bool
|
||||||
|
var oauthCallbackPort int
|
||||||
var antigravityLogin bool
|
var antigravityLogin bool
|
||||||
var projectID string
|
var projectID string
|
||||||
var vertexImport string
|
var vertexImport string
|
||||||
@@ -75,6 +76,7 @@ func main() {
|
|||||||
flag.BoolVar(&iflowLogin, "iflow-login", false, "Login to iFlow using OAuth")
|
flag.BoolVar(&iflowLogin, "iflow-login", false, "Login to iFlow using OAuth")
|
||||||
flag.BoolVar(&iflowCookie, "iflow-cookie", false, "Login to iFlow using Cookie")
|
flag.BoolVar(&iflowCookie, "iflow-cookie", false, "Login to iFlow using Cookie")
|
||||||
flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth")
|
flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth")
|
||||||
|
flag.IntVar(&oauthCallbackPort, "oauth-callback-port", 0, "Override OAuth callback port (defaults to provider-specific port)")
|
||||||
flag.BoolVar(&antigravityLogin, "antigravity-login", false, "Login to Antigravity using OAuth")
|
flag.BoolVar(&antigravityLogin, "antigravity-login", false, "Login to Antigravity using OAuth")
|
||||||
flag.StringVar(&projectID, "project_id", "", "Project ID (Gemini only, not required)")
|
flag.StringVar(&projectID, "project_id", "", "Project ID (Gemini only, not required)")
|
||||||
flag.StringVar(&configPath, "config", DefaultConfigPath, "Configure File Path")
|
flag.StringVar(&configPath, "config", DefaultConfigPath, "Configure File Path")
|
||||||
@@ -426,6 +428,7 @@ func main() {
|
|||||||
// Create login options to be used in authentication flows.
|
// Create login options to be used in authentication flows.
|
||||||
options := &cmd.LoginOptions{
|
options := &cmd.LoginOptions{
|
||||||
NoBrowser: noBrowser,
|
NoBrowser: noBrowser,
|
||||||
|
CallbackPort: oauthCallbackPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the shared token store once so all components use the same persistence backend.
|
// Register the shared token store once so all components use the same persistence backend.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
geminiOauthClientID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
geminiOauthClientID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
||||||
geminiOauthClientSecret = "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"
|
geminiOauthClientSecret = "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"
|
||||||
|
geminiDefaultCallbackPort = 8085
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -50,6 +51,7 @@ type GeminiAuth struct {
|
|||||||
// WebLoginOptions customizes the interactive OAuth flow.
|
// WebLoginOptions customizes the interactive OAuth flow.
|
||||||
type WebLoginOptions struct {
|
type WebLoginOptions struct {
|
||||||
NoBrowser bool
|
NoBrowser bool
|
||||||
|
CallbackPort int
|
||||||
Prompt func(string) (string, error)
|
Prompt func(string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +74,12 @@ func NewGeminiAuth() *GeminiAuth {
|
|||||||
// - *http.Client: An HTTP client configured with authentication
|
// - *http.Client: An HTTP client configured with authentication
|
||||||
// - error: An error if the client configuration fails, nil otherwise
|
// - error: An error if the client configuration fails, nil otherwise
|
||||||
func (g *GeminiAuth) GetAuthenticatedClient(ctx context.Context, ts *GeminiTokenStorage, cfg *config.Config, opts *WebLoginOptions) (*http.Client, error) {
|
func (g *GeminiAuth) GetAuthenticatedClient(ctx context.Context, ts *GeminiTokenStorage, cfg *config.Config, opts *WebLoginOptions) (*http.Client, error) {
|
||||||
|
callbackPort := geminiDefaultCallbackPort
|
||||||
|
if opts != nil && opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
callbackURL := fmt.Sprintf("http://localhost:%d/oauth2callback", callbackPort)
|
||||||
|
|
||||||
// Configure proxy settings for the HTTP client if a proxy URL is provided.
|
// Configure proxy settings for the HTTP client if a proxy URL is provided.
|
||||||
proxyURL, err := url.Parse(cfg.ProxyURL)
|
proxyURL, err := url.Parse(cfg.ProxyURL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -106,7 +114,7 @@ func (g *GeminiAuth) GetAuthenticatedClient(ctx context.Context, ts *GeminiToken
|
|||||||
conf := &oauth2.Config{
|
conf := &oauth2.Config{
|
||||||
ClientID: geminiOauthClientID,
|
ClientID: geminiOauthClientID,
|
||||||
ClientSecret: geminiOauthClientSecret,
|
ClientSecret: geminiOauthClientSecret,
|
||||||
RedirectURL: "http://localhost:8085/oauth2callback", // This will be used by the local server.
|
RedirectURL: callbackURL, // This will be used by the local server.
|
||||||
Scopes: geminiOauthScopes,
|
Scopes: geminiOauthScopes,
|
||||||
Endpoint: google.Endpoint,
|
Endpoint: google.Endpoint,
|
||||||
}
|
}
|
||||||
@@ -218,14 +226,20 @@ func (g *GeminiAuth) createTokenStorage(ctx context.Context, config *oauth2.Conf
|
|||||||
// - *oauth2.Token: The OAuth2 token obtained from the authorization flow
|
// - *oauth2.Token: The OAuth2 token obtained from the authorization flow
|
||||||
// - error: An error if the token acquisition fails, nil otherwise
|
// - error: An error if the token acquisition fails, nil otherwise
|
||||||
func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, opts *WebLoginOptions) (*oauth2.Token, error) {
|
func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, opts *WebLoginOptions) (*oauth2.Token, error) {
|
||||||
|
callbackPort := geminiDefaultCallbackPort
|
||||||
|
if opts != nil && opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
callbackURL := fmt.Sprintf("http://localhost:%d/oauth2callback", callbackPort)
|
||||||
|
|
||||||
// Use a channel to pass the authorization code from the HTTP handler to the main function.
|
// Use a channel to pass the authorization code from the HTTP handler to the main function.
|
||||||
codeChan := make(chan string, 1)
|
codeChan := make(chan string, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
// Create a new HTTP server with its own multiplexer.
|
// Create a new HTTP server with its own multiplexer.
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
server := &http.Server{Addr: ":8085", Handler: mux}
|
server := &http.Server{Addr: fmt.Sprintf(":%d", callbackPort), Handler: mux}
|
||||||
config.RedirectURL = "http://localhost:8085/oauth2callback"
|
config.RedirectURL = callbackURL
|
||||||
|
|
||||||
mux.HandleFunc("/oauth2callback", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/oauth2callback", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.URL.Query().Get("error"); err != "" {
|
if err := r.URL.Query().Get("error"); err != "" {
|
||||||
@@ -277,13 +291,13 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config,
|
|||||||
// Check if browser is available
|
// Check if browser is available
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available on this system")
|
log.Warn("No browser available on this system")
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
||||||
} else {
|
} else {
|
||||||
if err := browser.OpenURL(authURL); err != nil {
|
if err := browser.OpenURL(authURL); err != nil {
|
||||||
authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err)
|
authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err)
|
||||||
log.Warn(codex.GetUserFriendlyMessage(authErr))
|
log.Warn(codex.GetUserFriendlyMessage(authErr))
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL)
|
||||||
|
|
||||||
// Log platform info for debugging
|
// Log platform info for debugging
|
||||||
@@ -294,7 +308,7 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Please open this URL in your browser:\n\n%s\n", authURL)
|
fmt.Printf("Please open this URL in your browser:\n\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func DoClaudeLogin(cfg *config.Config, options *LoginOptions) {
|
|||||||
|
|
||||||
authOpts := &sdkAuth.LoginOptions{
|
authOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: promptFn,
|
Prompt: promptFn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func DoAntigravityLogin(cfg *config.Config, options *LoginOptions) {
|
|||||||
manager := newAuthManager()
|
manager := newAuthManager()
|
||||||
authOpts := &sdkAuth.LoginOptions{
|
authOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: promptFn,
|
Prompt: promptFn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func DoIFlowLogin(cfg *config.Config, options *LoginOptions) {
|
|||||||
|
|
||||||
authOpts := &sdkAuth.LoginOptions{
|
authOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: promptFn,
|
Prompt: promptFn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) {
|
|||||||
loginOpts := &sdkAuth.LoginOptions{
|
loginOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
ProjectID: trimmedProjectID,
|
ProjectID: trimmedProjectID,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: callbackPrompt,
|
Prompt: callbackPrompt,
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) {
|
|||||||
geminiAuth := gemini.NewGeminiAuth()
|
geminiAuth := gemini.NewGeminiAuth()
|
||||||
httpClient, errClient := geminiAuth.GetAuthenticatedClient(ctx, storage, cfg, &gemini.WebLoginOptions{
|
httpClient, errClient := geminiAuth.GetAuthenticatedClient(ctx, storage, cfg, &gemini.WebLoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Prompt: callbackPrompt,
|
Prompt: callbackPrompt,
|
||||||
})
|
})
|
||||||
if errClient != nil {
|
if errClient != nil {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ type LoginOptions struct {
|
|||||||
// NoBrowser indicates whether to skip opening the browser automatically.
|
// NoBrowser indicates whether to skip opening the browser automatically.
|
||||||
NoBrowser bool
|
NoBrowser bool
|
||||||
|
|
||||||
|
// CallbackPort overrides the local OAuth callback port when set (>0).
|
||||||
|
CallbackPort int
|
||||||
|
|
||||||
// Prompt allows the caller to provide interactive input when needed.
|
// Prompt allows the caller to provide interactive input when needed.
|
||||||
Prompt func(prompt string) (string, error)
|
Prompt func(prompt string) (string, error)
|
||||||
}
|
}
|
||||||
@@ -44,6 +47,7 @@ func DoCodexLogin(cfg *config.Config, options *LoginOptions) {
|
|||||||
|
|
||||||
authOpts := &sdkAuth.LoginOptions{
|
authOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: promptFn,
|
Prompt: promptFn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ func DoQwenLogin(cfg *config.Config, options *LoginOptions) {
|
|||||||
|
|
||||||
authOpts := &sdkAuth.LoginOptions{
|
authOpts := &sdkAuth.LoginOptions{
|
||||||
NoBrowser: options.NoBrowser,
|
NoBrowser: options.NoBrowser,
|
||||||
|
CallbackPort: options.CallbackPort,
|
||||||
Metadata: map[string]string{},
|
Metadata: map[string]string{},
|
||||||
Prompt: promptFn,
|
Prompt: promptFn,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ func (AntigravityAuthenticator) Login(ctx context.Context, cfg *config.Config, o
|
|||||||
opts = &LoginOptions{}
|
opts = &LoginOptions{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbackPort := antigravityCallbackPort
|
||||||
|
if opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
|
||||||
httpClient := util.SetProxy(&cfg.SDKConfig, &http.Client{})
|
httpClient := util.SetProxy(&cfg.SDKConfig, &http.Client{})
|
||||||
|
|
||||||
state, err := misc.GenerateRandomState()
|
state, err := misc.GenerateRandomState()
|
||||||
@@ -67,7 +72,7 @@ func (AntigravityAuthenticator) Login(ctx context.Context, cfg *config.Config, o
|
|||||||
return nil, fmt.Errorf("antigravity: failed to generate state: %w", err)
|
return nil, fmt.Errorf("antigravity: failed to generate state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv, port, cbChan, errServer := startAntigravityCallbackServer()
|
srv, port, cbChan, errServer := startAntigravityCallbackServer(callbackPort)
|
||||||
if errServer != nil {
|
if errServer != nil {
|
||||||
return nil, fmt.Errorf("antigravity: failed to start callback server: %w", errServer)
|
return nil, fmt.Errorf("antigravity: failed to start callback server: %w", errServer)
|
||||||
}
|
}
|
||||||
@@ -224,13 +229,16 @@ type callbackResult struct {
|
|||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
func startAntigravityCallbackServer() (*http.Server, int, <-chan callbackResult, error) {
|
func startAntigravityCallbackServer(port int) (*http.Server, int, <-chan callbackResult, error) {
|
||||||
addr := fmt.Sprintf(":%d", antigravityCallbackPort)
|
if port <= 0 {
|
||||||
|
port = antigravityCallbackPort
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf(":%d", port)
|
||||||
listener, err := net.Listen("tcp", addr)
|
listener, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
port := listener.Addr().(*net.TCPAddr).Port
|
port = listener.Addr().(*net.TCPAddr).Port
|
||||||
resultCh := make(chan callbackResult, 1)
|
resultCh := make(chan callbackResult, 1)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
opts = &LoginOptions{}
|
opts = &LoginOptions{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbackPort := a.CallbackPort
|
||||||
|
if opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
|
||||||
pkceCodes, err := claude.GeneratePKCECodes()
|
pkceCodes, err := claude.GeneratePKCECodes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("claude pkce generation failed: %w", err)
|
return nil, fmt.Errorf("claude pkce generation failed: %w", err)
|
||||||
@@ -57,7 +62,7 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
return nil, fmt.Errorf("claude state generation failed: %w", err)
|
return nil, fmt.Errorf("claude state generation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthServer := claude.NewOAuthServer(a.CallbackPort)
|
oauthServer := claude.NewOAuthServer(callbackPort)
|
||||||
if err = oauthServer.Start(); err != nil {
|
if err = oauthServer.Start(); err != nil {
|
||||||
if strings.Contains(err.Error(), "already in use") {
|
if strings.Contains(err.Error(), "already in use") {
|
||||||
return nil, claude.NewAuthenticationError(claude.ErrPortInUse, err)
|
return nil, claude.NewAuthenticationError(claude.ErrPortInUse, err)
|
||||||
@@ -84,15 +89,15 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
fmt.Println("Opening browser for Claude authentication")
|
fmt.Println("Opening browser for Claude authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
log.Warn("No browser available; please open the URL manually")
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
} else if err = browser.OpenURL(authURL); err != nil {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
log.Warnf("Failed to open browser automatically: %v", err)
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
opts = &LoginOptions{}
|
opts = &LoginOptions{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbackPort := a.CallbackPort
|
||||||
|
if opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
|
||||||
pkceCodes, err := codex.GeneratePKCECodes()
|
pkceCodes, err := codex.GeneratePKCECodes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("codex pkce generation failed: %w", err)
|
return nil, fmt.Errorf("codex pkce generation failed: %w", err)
|
||||||
@@ -57,7 +62,7 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
return nil, fmt.Errorf("codex state generation failed: %w", err)
|
return nil, fmt.Errorf("codex state generation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthServer := codex.NewOAuthServer(a.CallbackPort)
|
oauthServer := codex.NewOAuthServer(callbackPort)
|
||||||
if err = oauthServer.Start(); err != nil {
|
if err = oauthServer.Start(); err != nil {
|
||||||
if strings.Contains(err.Error(), "already in use") {
|
if strings.Contains(err.Error(), "already in use") {
|
||||||
return nil, codex.NewAuthenticationError(codex.ErrPortInUse, err)
|
return nil, codex.NewAuthenticationError(codex.ErrPortInUse, err)
|
||||||
@@ -83,15 +88,15 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
fmt.Println("Opening browser for Codex authentication")
|
fmt.Println("Opening browser for Codex authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
log.Warn("No browser available; please open the URL manually")
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
} else if err = browser.OpenURL(authURL); err != nil {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
log.Warnf("Failed to open browser automatically: %v", err)
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
geminiAuth := gemini.NewGeminiAuth()
|
geminiAuth := gemini.NewGeminiAuth()
|
||||||
_, err := geminiAuth.GetAuthenticatedClient(ctx, &ts, cfg, &gemini.WebLoginOptions{
|
_, err := geminiAuth.GetAuthenticatedClient(ctx, &ts, cfg, &gemini.WebLoginOptions{
|
||||||
NoBrowser: opts.NoBrowser,
|
NoBrowser: opts.NoBrowser,
|
||||||
|
CallbackPort: opts.CallbackPort,
|
||||||
Prompt: opts.Prompt,
|
Prompt: opts.Prompt,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -42,9 +42,14 @@ func (a *IFlowAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
opts = &LoginOptions{}
|
opts = &LoginOptions{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbackPort := iflow.CallbackPort
|
||||||
|
if opts.CallbackPort > 0 {
|
||||||
|
callbackPort = opts.CallbackPort
|
||||||
|
}
|
||||||
|
|
||||||
authSvc := iflow.NewIFlowAuth(cfg)
|
authSvc := iflow.NewIFlowAuth(cfg)
|
||||||
|
|
||||||
oauthServer := iflow.NewOAuthServer(iflow.CallbackPort)
|
oauthServer := iflow.NewOAuthServer(callbackPort)
|
||||||
if err := oauthServer.Start(); err != nil {
|
if err := oauthServer.Start(); err != nil {
|
||||||
if strings.Contains(err.Error(), "already in use") {
|
if strings.Contains(err.Error(), "already in use") {
|
||||||
return nil, fmt.Errorf("iflow authentication server port in use: %w", err)
|
return nil, fmt.Errorf("iflow authentication server port in use: %w", err)
|
||||||
@@ -64,21 +69,21 @@ func (a *IFlowAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
return nil, fmt.Errorf("iflow auth: failed to generate state: %w", err)
|
return nil, fmt.Errorf("iflow auth: failed to generate state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authURL, redirectURI := authSvc.AuthorizationURL(state, iflow.CallbackPort)
|
authURL, redirectURI := authSvc.AuthorizationURL(state, callbackPort)
|
||||||
|
|
||||||
if !opts.NoBrowser {
|
if !opts.NoBrowser {
|
||||||
fmt.Println("Opening browser for iFlow authentication")
|
fmt.Println("Opening browser for iFlow authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
log.Warn("No browser available; please open the URL manually")
|
||||||
util.PrintSSHTunnelInstructions(iflow.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
} else if err = browser.OpenURL(authURL); err != nil {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
log.Warnf("Failed to open browser automatically: %v", err)
|
||||||
util.PrintSSHTunnelInstructions(iflow.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(iflow.CallbackPort)
|
util.PrintSSHTunnelInstructions(callbackPort)
|
||||||
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported")
|
|||||||
type LoginOptions struct {
|
type LoginOptions struct {
|
||||||
NoBrowser bool
|
NoBrowser bool
|
||||||
ProjectID string
|
ProjectID string
|
||||||
|
CallbackPort int
|
||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
Prompt func(prompt string) (string, error)
|
Prompt func(prompt string) (string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user