package oauth import ( "context" "fmt" "net/http" "time" "github.com/bluesky-social/indigo/atproto/auth/oauth" ) // InteractiveResult contains the result of an interactive OAuth flow type InteractiveResult struct { SessionData *oauth.ClientSessionData Session *oauth.ClientSession ClientApp *oauth.ClientApp } // InteractiveFlowWithCallback runs an interactive OAuth flow with explicit callback handling // This version allows the caller to register the callback handler before starting the flow func InteractiveFlowWithCallback( ctx context.Context, baseURL string, handle string, scopes []string, clientName string, registerCallback func(handler http.HandlerFunc) error, displayAuthURL func(string) error, ) (*InteractiveResult, error) { store := oauth.NewMemStore() // Create OAuth client app with custom scopes (or defaults if nil) // Interactive flows are typically for production use (credential helper, etc.) // For CLI tools, we use an empty keyPath since they're typically localhost (public client) // or ephemeral sessions if scopes == nil { scopes = GetDefaultScopes("*") } clientApp, err := NewClientApp(baseURL, store, scopes, "", clientName) if err != nil { return nil, fmt.Errorf("failed to create OAuth client app: %w", err) } // Channel to receive callback result resultChan := make(chan *InteractiveResult, 1) errorChan := make(chan error, 1) // Create callback handler callbackHandler := func(w http.ResponseWriter, r *http.Request) { // Process callback sessionData, err := clientApp.ProcessCallback(r.Context(), r.URL.Query()) if err != nil { errorChan <- fmt.Errorf("failed to process callback: %w", err) http.Error(w, "OAuth callback failed", http.StatusInternalServerError) return } // Resume session session, err := clientApp.ResumeSession(r.Context(), sessionData.AccountDID, sessionData.SessionID) if err != nil { errorChan <- fmt.Errorf("failed to resume session: %w", err) http.Error(w, "Failed to resume session", http.StatusInternalServerError) return } // Send result resultChan <- &InteractiveResult{ SessionData: sessionData, Session: session, ClientApp: clientApp, } // Return success to browser w.Header().Set("Content-Type", "text/html") fmt.Fprintf(w, "

Authorization Successful!

You can close this window and return to the terminal.

") } // Register callback handler if err := registerCallback(callbackHandler); err != nil { return nil, fmt.Errorf("failed to register callback: %w", err) } // Start auth flow authURL, err := clientApp.StartAuthFlow(ctx, handle) if err != nil { return nil, fmt.Errorf("failed to start auth flow: %w", err) } // Display auth URL if err := displayAuthURL(authURL); err != nil { return nil, fmt.Errorf("failed to display auth URL: %w", err) } // Wait for callback result select { case result := <-resultChan: return result, nil case err := <-errorChan: return nil, err case <-time.After(5 * time.Minute): return nil, fmt.Errorf("OAuth flow timed out after 5 minutes") } }