2024-03-13 10:08:22 +00:00
|
|
|
package setup
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
|
|
|
"time"
|
|
|
|
|
2024-03-28 09:36:39 +00:00
|
|
|
"github.com/charmbracelet/huh/spinner"
|
2024-03-13 10:08:22 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
func receiveTokenFromUI(c context.Context, serverURL string) (string, error) {
|
|
|
|
port := randomPort()
|
|
|
|
|
|
|
|
tokenReceived := make(chan string)
|
|
|
|
|
|
|
|
srv := &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port)}
|
|
|
|
srv.Handler = setupRouter(tokenReceived)
|
|
|
|
|
|
|
|
go func() {
|
2024-11-30 14:23:44 +00:00
|
|
|
log.Debug().Msgf("listening for token response on :%d", port)
|
2024-03-13 10:08:22 +00:00
|
|
|
_ = srv.ListenAndServe()
|
|
|
|
}()
|
|
|
|
|
|
|
|
defer func() {
|
2024-11-30 14:23:44 +00:00
|
|
|
log.Debug().Msg("shutting down server")
|
2024-03-13 10:08:22 +00:00
|
|
|
_ = srv.Shutdown(c)
|
|
|
|
}()
|
|
|
|
|
|
|
|
err := openBrowser(fmt.Sprintf("%s/cli/auth?port=%d", serverURL, port))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2024-03-28 09:36:39 +00:00
|
|
|
spinnerCtx, spinnerDone := context.WithCancelCause(c)
|
|
|
|
go func() {
|
|
|
|
err = spinner.New().
|
|
|
|
Title("Waiting for token ...").
|
|
|
|
Context(spinnerCtx).
|
|
|
|
Run()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2024-03-13 10:08:22 +00:00
|
|
|
// wait for token to be received or timeout
|
|
|
|
select {
|
|
|
|
case token := <-tokenReceived:
|
2024-03-28 09:36:39 +00:00
|
|
|
spinnerDone(nil)
|
2024-03-13 10:08:22 +00:00
|
|
|
return token, nil
|
|
|
|
case <-c.Done():
|
2024-03-28 09:36:39 +00:00
|
|
|
spinnerDone(nil)
|
2024-03-13 10:08:22 +00:00
|
|
|
return "", c.Err()
|
|
|
|
case <-time.After(5 * time.Minute):
|
2024-03-28 09:36:39 +00:00
|
|
|
spinnerDone(nil)
|
2024-03-13 10:08:22 +00:00
|
|
|
return "", errors.New("timed out waiting for token")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupRouter(tokenReceived chan string) *gin.Engine {
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
e := gin.New()
|
|
|
|
e.UseRawPath = true
|
|
|
|
e.Use(gin.Recovery())
|
|
|
|
|
|
|
|
e.Use(func(c *gin.Context) {
|
|
|
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
|
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
|
|
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
|
|
|
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
2024-03-15 17:00:25 +00:00
|
|
|
c.AbortWithStatus(http.StatusNoContent)
|
2024-03-13 10:08:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Next()
|
|
|
|
})
|
|
|
|
|
|
|
|
e.POST("/token", func(c *gin.Context) {
|
|
|
|
data := struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
}{}
|
|
|
|
|
|
|
|
err := c.BindJSON(&data)
|
|
|
|
if err != nil {
|
2024-11-30 14:23:44 +00:00
|
|
|
log.Debug().Err(err).Msg("failed to bind JSON")
|
2024-03-15 17:00:25 +00:00
|
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
2024-03-13 10:08:22 +00:00
|
|
|
"error": "invalid request",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tokenReceived <- data.Token
|
|
|
|
|
2024-03-15 17:00:25 +00:00
|
|
|
c.JSON(http.StatusOK, gin.H{
|
2024-03-13 10:08:22 +00:00
|
|
|
"ok": "true",
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
func openBrowser(url string) error {
|
|
|
|
var err error
|
|
|
|
|
2024-11-30 14:23:44 +00:00
|
|
|
log.Debug().Msgf("opening browser with URL: %s", url)
|
2024-03-13 10:08:22 +00:00
|
|
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "linux":
|
|
|
|
err = exec.Command("xdg-open", url).Start()
|
|
|
|
case "windows":
|
|
|
|
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
|
|
|
case "darwin":
|
|
|
|
err = exec.Command("open", url).Start()
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("unsupported platform")
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func randomPort() int {
|
2024-03-15 17:00:25 +00:00
|
|
|
const minPort = 10000
|
|
|
|
const maxPort = 65535
|
|
|
|
|
|
|
|
source := rand.NewSource(time.Now().UnixNano())
|
|
|
|
rand := rand.New(source)
|
|
|
|
return rand.Intn(maxPort-minPort+1) + minPort
|
2024-03-13 10:08:22 +00:00
|
|
|
}
|