diff --git a/cli/go.mod b/cli/go.mod index 82f84cebb7..4f37e1232e 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/charmbracelet/lipgloss v0.5.0 + github.com/chzyer/readline v1.5.1 github.com/creack/pty v1.1.21 github.com/denisbrodbeck/machineid v1.0.1 github.com/fatih/semgroup v1.2.0 @@ -49,7 +50,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect github.com/aws/smithy-go v1.20.2 // indirect - github.com/chzyer/readline v1.5.1 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect @@ -94,7 +94,7 @@ require ( golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.183.0 // indirect diff --git a/cli/go.sum b/cli/go.sum index e0791eec28..fd08b3f1a1 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -621,8 +621,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index abf664ed0f..720c33dbfd 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -24,6 +24,7 @@ import ( "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/srp" "github.com/Infisical/infisical-merge/packages/util" + "github.com/chzyer/readline" "github.com/fatih/color" "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" @@ -229,6 +230,7 @@ var loginCmd = &cobra.Command{ fmt.Println("Logging in via browser... To login via interactive mode run [infisical login -i]") userCredentialsToBeStored, err = browserCliLogin() if err != nil { + fmt.Printf("Logging in via browser failed. %s", err.Error()) //default to cli login on error cliDefaultLogin(&userCredentialsToBeStored) } @@ -713,10 +715,63 @@ func askForMFACode() string { return mfaVerifyCode } +func askToPasteJwtToken(stdin *readline.CancelableStdin, success chan models.UserCredentials, failure chan error) { + time.Sleep(time.Second * 5) + + prompt := &promptui.Prompt{ + Label: "Did you see a prompt in your browser asking you to paste a token?", + IsConfirm: true, + Stdin: stdin, + } + + _, err := prompt.Run() + if err != nil { + if errors.Is(err, promptui.ErrAbort) { + stdin.Close() + return + } + failure <- err + return + } + + prompt = &promptui.Prompt{ + Label: "Paste your token", + Mask: '*', + Stdin: stdin, + } + infisicalPastedToken, err := prompt.Run() + if err != nil { + failure <- err + return + } + + userCredentials, err := decodePastedBase64Token(infisicalPastedToken) + if err != nil { + failure <- err + return + } + success <- *userCredentials +} + +func decodePastedBase64Token(token string) (*models.UserCredentials, error) { + data, err := base64.StdEncoding.DecodeString(token) + if err != nil { + return nil, err + } + var loginResponse models.UserCredentials + + err = json.Unmarshal(data, &loginResponse) + if err != nil { + return nil, err + } + + return &loginResponse, nil +} + // Manages the browser login flow. // Returns a UserCredentials object on success and an error on failure func browserCliLogin() (models.UserCredentials, error) { - SERVER_TIMEOUT := 60 * 10 + SERVER_TIMEOUT := 10 * 60 //create listener listener, err := net.Listen("tcp", "127.0.0.1:0") @@ -738,7 +793,6 @@ func browserCliLogin() (models.UserCredentials, error) { success := make(chan models.UserCredentials) failure := make(chan error) timeout := time.After(time.Second * time.Duration(SERVER_TIMEOUT)) - quit := make(chan bool) //terminal state oldState, err := term.GetState(int(os.Stdin.Fd())) @@ -760,24 +814,27 @@ func browserCliLogin() (models.UserCredentials, error) { log.Debug().Msgf("Callback server listening on port %d", callbackPort) + stdin := readline.NewCancelableStdin(os.Stdin) go http.Serve(listener, corsHandler) + go askToPasteJwtToken(stdin, success, failure) for { select { case loginResponse := <-success: _ = closeListener(&listener) + _ = stdin.Close() + fmt.Println("Browser login successfull") return loginResponse, nil - case <-failure: - err = closeListener(&listener) - return models.UserCredentials{}, err + case err := <-failure: + serverErr := closeListener(&listener) + stdErr := stdin.Close() + return models.UserCredentials{}, errors.Join(err, serverErr, stdErr) case <-timeout: _ = closeListener(&listener) + _ = stdin.Close() return models.UserCredentials{}, errors.New("server timeout") - - case <-quit: - return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...") } } }