A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
77
fork

Configure Feed

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

at 97d1b3cdd50e4727e5db3c498f4e8bb73851fd39 107 lines 3.2 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 App *App 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 // Create temporary file store for this flow 30 store, err := NewFileStore("/tmp/atcr-oauth-temp.json") 31 if err != nil { 32 return nil, fmt.Errorf("failed to create OAuth store: %w", err) 33 } 34 35 // Create OAuth app with custom scopes (or defaults if nil) 36 // Interactive flows are typically for production use (credential helper, etc.) 37 // so we default to testMode=false 38 var app *App 39 if scopes != nil { 40 app, err = NewAppWithScopes(baseURL, store, scopes) 41 } else { 42 app, err = NewApp(baseURL, store, "*", false) 43 } 44 if err != nil { 45 return nil, fmt.Errorf("failed to create OAuth app: %w", err) 46 } 47 48 // Channel to receive callback result 49 resultChan := make(chan *InteractiveResult, 1) 50 errorChan := make(chan error, 1) 51 52 // Create callback handler 53 callbackHandler := func(w http.ResponseWriter, r *http.Request) { 54 // Process callback 55 sessionData, err := app.ProcessCallback(r.Context(), r.URL.Query()) 56 if err != nil { 57 errorChan <- fmt.Errorf("failed to process callback: %w", err) 58 http.Error(w, "OAuth callback failed", http.StatusInternalServerError) 59 return 60 } 61 62 // Resume session 63 session, err := app.ResumeSession(r.Context(), sessionData.AccountDID, sessionData.SessionID) 64 if err != nil { 65 errorChan <- fmt.Errorf("failed to resume session: %w", err) 66 http.Error(w, "Failed to resume session", http.StatusInternalServerError) 67 return 68 } 69 70 // Send result 71 resultChan <- &InteractiveResult{ 72 SessionData: sessionData, 73 Session: session, 74 App: app, 75 } 76 77 // Return success to browser 78 w.Header().Set("Content-Type", "text/html") 79 fmt.Fprintf(w, "<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>") 80 } 81 82 // Register callback handler 83 if err := registerCallback(callbackHandler); err != nil { 84 return nil, fmt.Errorf("failed to register callback: %w", err) 85 } 86 87 // Start auth flow 88 authURL, err := app.StartAuthFlow(ctx, handle) 89 if err != nil { 90 return nil, fmt.Errorf("failed to start auth flow: %w", err) 91 } 92 93 // Display auth URL 94 if err := displayAuthURL(authURL); err != nil { 95 return nil, fmt.Errorf("failed to display auth URL: %w", err) 96 } 97 98 // Wait for callback result 99 select { 100 case result := <-resultChan: 101 return result, nil 102 case err := <-errorChan: 103 return nil, err 104 case <-time.After(5 * time.Minute): 105 return nil, fmt.Errorf("OAuth flow timed out after 5 minutes") 106 } 107}