package login import ( "encoding/json" "fmt" "net" "net/http" "net/url" "os" "strings" "time" "github.com/porter-dev/porter/cli/cmd/utils" ) func redirect( codechan chan string, ) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, successScreen) queryParams, err := url.ParseQuery(r.URL.RawQuery) if err != nil { return } if codeParam, exists := queryParams["code"]; exists && len(codeParam) > 0 { codechan <- queryParams["code"][0] } } } func Login( host string, ) (string, error) { listener, err := net.Listen("tcp", ":0") if err != nil { panic(err) } port := listener.Addr().(*net.TCPAddr).Port errorchan := make(chan error) codechan := make(chan string) go func() { http.HandleFunc("/", redirect( codechan, )) err := http.Serve(listener, nil) errorchan <- err }() // open browser for host login var redirectHost string if utils.CheckIfWsl() { redirectHost = fmt.Sprintf("http://%s:%d", utils.GetWslHostName(), port) } else { redirectHost = fmt.Sprintf("http://localhost:%d", port) } loginURL := fmt.Sprintf("%s/api/cli/login?redirect=%s", host, url.QueryEscape(redirectHost)) err = utils.OpenBrowser(loginURL) if err != nil { fmt.Printf("Could not open browser. Please navigate to the link manually.") } for { select { case err = <-errorchan: return "", err case code := <-codechan: return ExchangeToken(host, code) } } } type ExchangeResponse struct { Token string `json:"token"` } func ExchangeToken(host, code string) (string, error) { req, err := http.NewRequest( "POST", fmt.Sprintf("%s/api/cli/login/exchange", host), strings.NewReader(fmt.Sprintf(`{"authorization_code": "%s"}`, code)), ) if err != nil { return "", err } // create a request with the authorization code req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Accept", "application/json; charset=utf-8") // look for a cloudflare access token specifically for Porter if cfToken := os.Getenv("PORTER_CF_ACCESS_TOKEN"); cfToken != "" { req.Header.Set("cf-access-token", cfToken) } client := &http.Client{ Timeout: time.Minute, } res, err := client.Do(req) if err != nil { return "", err } defer res.Body.Close() resp := &ExchangeResponse{} if err = json.NewDecoder(res.Body).Decode(resp); err != nil { return "", err } return resp.Token, nil } const successScreen = `