Files
streamctl/pkg/oauthhandler/oauth2_handler.go
2025-06-22 14:59:00 +01:00

125 lines
2.8 KiB
Go

package oauthhandler
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os/exec"
"runtime"
"strings"
"github.com/facebookincubator/go-belt/tool/logger"
"github.com/xaionaro-go/observability"
)
type OAuthHandlerArgument struct {
AuthURL string
ListenPort uint16
ExchangeFn func(ctx context.Context, code string) error
}
func OAuth2HandlerViaCLI(ctx context.Context, arg OAuthHandlerArgument) error {
fmt.Printf(
"It is required to get an oauth2 token. "+
"Please open the link below in the browser:\n\n\t%s\n\n",
arg.AuthURL,
)
fmt.Printf("Enter the code: ")
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code %v", err)
}
return arg.ExchangeFn(ctx, code)
}
func OAuth2HandlerViaBrowser(ctx context.Context, arg OAuthHandlerArgument) error {
ctx, cancelFn := context.WithCancel(ctx)
defer cancelFn()
codeCh, _, err := NewCodeReceiver(ctx, arg.ListenPort)
if err != nil {
return err
}
err = LaunchBrowser(ctx, arg.AuthURL)
if err != nil {
return err
}
fmt.Printf(
"Your browser has been launched (URL: %s).\nPlease approve the permissions.\n",
arg.AuthURL,
)
// Wait for the web server to get the code.
code := <-codeCh
return arg.ExchangeFn(ctx, code)
}
func NewCodeReceiver(
ctx context.Context,
listenPort uint16,
) (chan string, uint16, error) {
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", listenPort))
if err != nil {
return nil, 0, err
}
codeCh := make(chan string)
srv := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
codeCh <- code
w.Header().Set("Content-Type", "text/plain")
if code == "" {
fmt.Fprintf(w, "No code received :(\r\n\r\nYou can close this browser window.")
return
}
fmt.Fprintf(
w,
"Received code: %v\r\n\r\nYou can now safely close this browser window.",
code,
)
}),
}
observability.Go(ctx, func(ctx context.Context) {
<-ctx.Done()
listener.Close()
srv.Close()
close(codeCh)
})
observability.Go(ctx, func(ctx context.Context) {
srv.Serve(listener)
})
return codeCh, uint16(listener.Addr().(*net.TCPAddr).Port), nil
}
func LaunchBrowser(
ctx context.Context,
url string,
) error {
var args []string
switch runtime.GOOS {
case "darwin":
args = []string{"open"}
case "linux":
args = []string{"xdg-open"}
case "windows":
args = []string{"rundll32", "url.dll,FileProtocolHandler"}
case "android":
args = []string{"am", "start", "-a", "android.intent.action.VIEW", "-d"}
default:
return fmt.Errorf("unsupported platform: <%s>", runtime.GOOS)
}
args = append(args, url)
logger.Debugf(ctx, "launching a browser using command '%s'", strings.Join(args, " "))
return exec.Command(args[0], args[1:]...).Start()
}