Skip to content

Commit

Permalink
fix: UX updates for try-login (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Carey authored Jan 26, 2021
1 parent ab2942a commit df22431
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 41 deletions.
143 changes: 108 additions & 35 deletions internal/cli/try_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/auth0/auth0-cli/internal/auth"
"github.com/auth0/auth0-cli/internal/auth0"
"github.com/auth0/auth0-cli/internal/open"
"github.com/auth0/auth0-cli/internal/prompt"
"github.com/spf13/cobra"
"gopkg.in/auth0.v5/management"
)
Expand Down Expand Up @@ -39,48 +40,52 @@ Launch a browser to try out your universal login box for the given client.
var userInfo *auth.UserInfo
var tokenResponse *auth.TokenResponse

err := ansi.Spinner("Trying login", func() error {
var err error
tenant, err := cli.getTenant()
tenant, err := cli.getTenant()
if err != nil {
return err
}

// use the client ID as passed in by the user, or default to the
// "CLI Login Testing" client if none passed. This client is only
// used for testing login from the CLI and will be created if it
// does not exist.
if clientID == "" {
client, err := getOrCreateCLITesterClient(cli.api.Client)
if err != nil {
return err
}
clientID = client.GetClientID()
}

// use the client ID as passed in by the user, or default to the
// "CLI Login Testing" client if none passed. This client is only
// used for testing login from the CLI and will be created if it
// does not exist.
if clientID == "" {
client, err := getOrCreateCLITesterClient(cli.api.Client)
if err != nil {
return err
}
clientID = client.GetClientID()
}
client, err := cli.api.Client.Read(clientID)
if err != nil {
return err
}

client, err := cli.api.Client.Read(clientID)
if err != nil {
return err
}
cli.renderer.Infof("A browser window will open to begin this client's login flow.")
cli.renderer.Infof("Once login is complete, you can return to the CLI to view user profile information and tokens.\n")

// check if the chosen client includes our local callback URL in its
// allowed list. If not we'll need to add it (after asking the user
// for permission).
needsLocalCallbackURL := !checkForLocalCallbackURL(client)
if needsLocalCallbackURL {
cli.renderer.Warnf("The client you are using does not currently allow callbacks to localhost.")
cli.renderer.Warnf("To complete the login flow the CLI needs to redirect logins to a local server and record the result.\n")
cli.renderer.Warnf("The client will be modified to update the allowed callback URLs, we'll remove them when done.")
cli.renderer.Warnf("If you do not wish to modify the client, you can abort now.\n")
}

// check if the client's initiate_login_uri matches the one for our
// "CLI Login Testing" app. If so, then initiate the login via the
// `/authorize` endpoint, if not, open a browser at the client's
// configured URL. If none is specified, return an error to the
// caller explaining the problem.
if client.GetInitiateLoginURI() == "" {
return fmt.Errorf(
"client %s does not specify a URL with which to initiate login",
client.GetClientID(),
)
}
if confirmed := prompt.Confirm("Do you wish to proceed?"); !confirmed {
return nil
}
fmt.Fprint(cli.renderer.MessageWriter, "\n")

if client.GetInitiateLoginURI() != cliLoginTestingInitiateLoginURI {
if connectionName != "" {
cli.renderer.Warnf("Specific connections are not supported when using a non-default client, ignoring.")
cli.renderer.Warnf("You should ensure the connection you wish to test is enabled for the client you want to use in the Auth0 Dashboard.")
err = ansi.Spinner("Waiting for login flow to complete", func() error {
if needsLocalCallbackURL {
if err := addLocalCallbackURLToClient(cli.api.Client, client); err != nil {
return err
}
return open.URL(client.GetInitiateLoginURI())
}

// Build a login URL and initiate login in a browser window.
Expand Down Expand Up @@ -116,14 +121,26 @@ Launch a browser to try out your universal login box for the given client.
// Use the access token to fetch user information from the /userinfo
// endpoint.
userInfo, err = auth.FetchUserInfo(tenant.Domain, tokenResponse.AccessToken)
if err != nil {
return err
}

return err
// if we added the local callback URL to the client then we need to
// remove it when we're done
if needsLocalCallbackURL {
if err := removeLocalCallbackURLFromClient(cli.api.Client, client); err != nil {
return err
}
}

return nil
})

if err != nil {
return err
}

fmt.Fprint(cli.renderer.MessageWriter, "\n")
cli.renderer.TryLogin(userInfo, tokenResponse, reveal)
return nil
},
Expand Down Expand Up @@ -161,6 +178,62 @@ func getOrCreateCLITesterClient(clientManager auth0.ClientAPI) (*management.Clie
return client, clientManager.Create(client)
}

// check if a client is already configured with our local callback URL
func checkForLocalCallbackURL(client *management.Client) bool {
for _, rawCallbackURL := range client.Callbacks {
callbackURL := rawCallbackURL.(string)
if callbackURL == cliLoginTestingCallbackURL {
return true
}
}

return false
}

// adds the localhost callback URL to a given client
func addLocalCallbackURLToClient(clientManager auth0.ClientAPI, client *management.Client) error {
for _, rawCallbackURL := range client.Callbacks {
callbackURL := rawCallbackURL.(string)
if callbackURL == cliLoginTestingCallbackURL {
return nil
}
}

updatedClient := &management.Client{
Callbacks: append(client.Callbacks, cliLoginTestingCallbackURL),
}
// reflect the changes in the original client instance so when we check it
// later it has the proper values in Callbacks
client.Callbacks = updatedClient.Callbacks
return clientManager.Update(client.GetClientID(), updatedClient)
}

func removeLocalCallbackURLFromClient(clientManager auth0.ClientAPI, client *management.Client) error {
callbacks := []interface{}{}
for _, rawCallbackURL := range client.Callbacks {
callbackURL := rawCallbackURL.(string)
if callbackURL != cliLoginTestingCallbackURL {
callbacks = append(callbacks, callbackURL)
}
}

// no callback URLs to remove, so don't attempt to do so
if len(client.Callbacks) == len(callbacks) {
return nil
}

// can't update a client to have 0 callback URLs, so don't attempt it
if len(callbacks) == 1 {
return nil
}

updatedClient := &management.Client{
Callbacks: callbacks,
}
return clientManager.Update(client.GetClientID(), updatedClient)

}

// buildInitiateLoginURL constructs a URL + query string that can be used to
// initiate a login-flow from the CLI.
func buildInitiateLoginURL(domain, clientID, connectionName string) (string, error) {
Expand Down
16 changes: 10 additions & 6 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package prompt

import (
"os"

"github.com/AlecAivazis/survey/v2"
)

func Ask(inputs []*survey.Question, response interface{}, options ...survey.AskOpt) error {
return survey.Ask(inputs, response, options...)
var stdErrWriter = survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)

func Ask(inputs []*survey.Question, response interface{}) error {
return survey.Ask(inputs, response, stdErrWriter)
}

func TextInput(name string, message string, required bool) *survey.Question {
input := &survey.Question{
Name: name,
Prompt: &survey.Input{Message: message},
Name: name,
Prompt: &survey.Input{Message: message},
Transform: survey.Title,
}

if required {
input.Validate = survey.Required
}
Expand All @@ -28,7 +32,7 @@ func Confirm(message string) bool {
Message: message,
}

if err := survey.AskOne(prompt, &result); err != nil {
if err := survey.AskOne(prompt, &result, stdErrWriter); err != nil {
return false
}

Expand Down

0 comments on commit df22431

Please sign in to comment.