A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at codeberg-source 102 lines 3.1 kB view raw
1package oauth 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "time" 8 9 "github.com/bluesky-social/indigo/atproto/auth/oauth" 10) 11 12// InteractiveResult contains the result of an interactive OAuth flow 13type InteractiveResult struct { 14 SessionData *oauth.ClientSessionData 15 Session *oauth.ClientSession 16 ClientApp *oauth.ClientApp 17} 18 19// InteractiveFlowWithCallback runs an interactive OAuth flow with explicit callback handling 20// This version allows the caller to register the callback handler before starting the flow 21func InteractiveFlowWithCallback( 22 ctx context.Context, 23 baseURL string, 24 handle string, 25 scopes []string, 26 registerCallback func(handler http.HandlerFunc) error, 27 displayAuthURL func(string) error, 28) (*InteractiveResult, error) { 29 store := oauth.NewMemStore() 30 31 // Create OAuth client app with custom scopes (or defaults if nil) 32 // Interactive flows are typically for production use (credential helper, etc.) 33 // For CLI tools, we use an empty keyPath since they're typically localhost (public client) 34 // or ephemeral sessions 35 if scopes == nil { 36 scopes = GetDefaultScopes("*") 37 } 38 clientApp, err := NewClientApp(baseURL, store, scopes, "", "AT Container Registry") 39 if err != nil { 40 return nil, fmt.Errorf("failed to create OAuth client app: %w", err) 41 } 42 43 // Channel to receive callback result 44 resultChan := make(chan *InteractiveResult, 1) 45 errorChan := make(chan error, 1) 46 47 // Create callback handler 48 callbackHandler := func(w http.ResponseWriter, r *http.Request) { 49 // Process callback 50 sessionData, err := clientApp.ProcessCallback(r.Context(), r.URL.Query()) 51 if err != nil { 52 errorChan <- fmt.Errorf("failed to process callback: %w", err) 53 http.Error(w, "OAuth callback failed", http.StatusInternalServerError) 54 return 55 } 56 57 // Resume session 58 session, err := clientApp.ResumeSession(r.Context(), sessionData.AccountDID, sessionData.SessionID) 59 if err != nil { 60 errorChan <- fmt.Errorf("failed to resume session: %w", err) 61 http.Error(w, "Failed to resume session", http.StatusInternalServerError) 62 return 63 } 64 65 // Send result 66 resultChan <- &InteractiveResult{ 67 SessionData: sessionData, 68 Session: session, 69 ClientApp: clientApp, 70 } 71 72 // Return success to browser 73 w.Header().Set("Content-Type", "text/html") 74 fmt.Fprintf(w, "<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>") 75 } 76 77 // Register callback handler 78 if err := registerCallback(callbackHandler); err != nil { 79 return nil, fmt.Errorf("failed to register callback: %w", err) 80 } 81 82 // Start auth flow 83 authURL, err := clientApp.StartAuthFlow(ctx, handle) 84 if err != nil { 85 return nil, fmt.Errorf("failed to start auth flow: %w", err) 86 } 87 88 // Display auth URL 89 if err := displayAuthURL(authURL); err != nil { 90 return nil, fmt.Errorf("failed to display auth URL: %w", err) 91 } 92 93 // Wait for callback result 94 select { 95 case result := <-resultChan: 96 return result, nil 97 case err := <-errorChan: 98 return nil, err 99 case <-time.After(5 * time.Minute): 100 return nil, fmt.Errorf("OAuth flow timed out after 5 minutes") 101 } 102}