diff --git a/internal/cli/apis.go b/internal/cli/apis.go index 2b7c9eb9d..122818545 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -11,9 +11,8 @@ import ( var ( apiID = Argument{ - Name: "Id", - Help: "Id of the API.", - IsRequired: true, + Name: "Id", + Help: "Id of the API.", } apiName = Flag{ Name: "Name", @@ -47,8 +46,8 @@ func apisCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(listApisCmd(cli)) - cmd.AddCommand(showApiCmd(cli)) cmd.AddCommand(createApiCmd(cli)) + cmd.AddCommand(showApiCmd(cli)) cmd.AddCommand(updateApiCmd(cli)) cmd.AddCommand(deleteApiCmd(cli)) cmd.AddCommand(scopesCmd(cli)) @@ -80,13 +79,11 @@ Lists your existing APIs. To create one try: RunE: func(cmd *cobra.Command, args []string) error { var list *management.ResourceServerList - err := ansi.Spinner("Loading APIs", func() error { + if err := ansi.Waiting(func() error { var err error list, err = cli.api.ResourceServer.List() return err - }) - - if err != nil { + }); err != nil { return fmt.Errorf("An unexpected error occurred: %w", err) } @@ -125,13 +122,11 @@ auth0 apis show api := &management.ResourceServer{ID: &inputs.ID} - err := ansi.Spinner("Loading API", func() error { + if err := ansi.Waiting(func() error { var err error api, err = cli.api.ResourceServer.Read(inputs.ID) return err - }) - - if err != nil { + }); err != nil { return fmt.Errorf("Unable to get an API with Id '%s': %w", inputs.ID, err) } @@ -145,10 +140,9 @@ auth0 apis show func createApiCmd(cli *cli) *cobra.Command { var inputs struct { - Name string - Identifier string - Scopes []string - ScopesString string + Name string + Identifier string + Scopes []string } cmd := &cobra.Command{ @@ -162,15 +156,15 @@ auth0 apis create --name myapi --identifier http://my-api prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if err := apiName.Ask(cmd, &inputs.Name); err != nil { + if err := apiName.Ask(cmd, &inputs.Name, nil); err != nil { return err } - if err := apiIdentifier.Ask(cmd, &inputs.Identifier); err != nil { + if err := apiIdentifier.Ask(cmd, &inputs.Identifier, nil); err != nil { return err } - if err := apiScopes.Ask(cmd, &inputs.ScopesString); err != nil { + if err := apiScopes.AskMany(cmd, &inputs.Scopes, nil); err != nil { return err } @@ -179,17 +173,13 @@ auth0 apis create --name myapi --identifier http://my-api Identifier: &inputs.Identifier, } - if len(inputs.ScopesString) > 0 { - api.Scopes = apiScopesFor(commaSeparatedStringToSlice(inputs.ScopesString)) - } else if len(inputs.Scopes) > 0 { + if len(inputs.Scopes) > 0 { api.Scopes = apiScopesFor(inputs.Scopes) } - err := ansi.Spinner("Creating API", func() error { + if err := ansi.Waiting(func() error { return cli.api.ResourceServer.Create(api) - }) - - if err != nil { + }); err != nil { return fmt.Errorf("An unexpected error occurred while attempting to create an API with name '%s' and identifier '%s': %w", inputs.Name, inputs.Identifier, err) } @@ -207,10 +197,9 @@ auth0 apis create --name myapi --identifier http://my-api func updateApiCmd(cli *cli) *cobra.Command { var inputs struct { - ID string - Name string - Scopes []string - ScopesString string + ID string + Name string + Scopes []string } cmd := &cobra.Command{ @@ -225,6 +214,8 @@ auth0 apis update --name myapi prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { + var current *management.ResourceServer + if len(args) == 0 { if err := apiID.Ask(cmd, &inputs.ID); err != nil { return err @@ -233,43 +224,39 @@ auth0 apis update --name myapi inputs.ID = args[0] } - if err := apiName.AskU(cmd, &inputs.Name); err != nil { + if err := ansi.Waiting(func() error { + var err error + current, err = cli.api.ResourceServer.Read(inputs.ID) return err + }); err != nil { + return fmt.Errorf("Unable to load API. The Id %v specified doesn't exist", inputs.ID) } - if err := apiScopes.AskU(cmd, &inputs.ScopesString); err != nil { + if err := apiName.AskU(cmd, &inputs.Name, current.Name); err != nil { return err } - api := &management.ResourceServer{} - - err := ansi.Spinner("Updating API", func() error { - current, err := cli.api.ResourceServer.Read(inputs.ID) + if err := apiScopes.AskManyU(cmd, &inputs.Scopes, nil); err != nil { + return err + } - if err != nil { - return fmt.Errorf("Unable to load API. The Id %v specified doesn't exist", inputs.ID) - } + api := &management.ResourceServer{} - if len(inputs.Name) == 0 { - api.Name = current.Name - } else { - api.Name = &inputs.Name - } + if len(inputs.Name) == 0 { + api.Name = current.Name + } else { + api.Name = &inputs.Name + } - if len(inputs.Scopes) == 0 { - if len(inputs.ScopesString) == 0 { - api.Scopes = current.Scopes - } else { - api.Scopes = apiScopesFor(commaSeparatedStringToSlice(inputs.ScopesString)) - } - } else { - api.Scopes = apiScopesFor(inputs.Scopes) - } + if len(inputs.Scopes) == 0 { + api.Scopes = current.Scopes + } else { + api.Scopes = apiScopesFor(inputs.Scopes) + } + if err := ansi.Waiting(func() error { return cli.api.ResourceServer.Update(inputs.ID, api) - }) - - if err != nil { + }); err != nil { return fmt.Errorf("An unexpected error occurred while trying to update an API with Id '%s': %w", inputs.ID, err) } @@ -355,13 +342,11 @@ auth0 apis scopes list api := &management.ResourceServer{ID: &inputs.ID} - err := ansi.Spinner("Loading scopes", func() error { + if err := ansi.Waiting(func() error { var err error api, err = cli.api.ResourceServer.Read(inputs.ID) return err - }) - - if err != nil { + }); err != nil { return fmt.Errorf("An unexpected error occurred while getting scopes for an API with Id '%s': %w", inputs.ID, err) } diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 403ae5e4f..100cf1dc9 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -11,11 +11,18 @@ import ( "gopkg.in/auth0.v5/management" ) +const ( + appTypeNative = "native" + appTypeSPA = "spa" + appTypeRegularWeb = "regular_web" + appTypeNonInteractive = "non_interactive" + appDefaultURL = "http://localhost:3000" +) + var ( appID = Argument{ - Name: "Client ID", - Help: "Id of the application.", - IsRequired: true, + Name: "Client ID", + Help: "Id of the application.", } appName = Flag{ Name: "Name", @@ -64,18 +71,20 @@ var ( IsRequired: false, } appWebOrigins = Flag{ - Name: "Allowed Web Origin URLs", - LongForm: "web-origins", - ShortForm: "w", - Help: "Comma-separated list of allowed origins for use with Cross-Origin Authentication, Device Flow, and web message response mode.", - IsRequired: false, + Name: "Allowed Web Origin URLs", + LongForm: "web-origins", + ShortForm: "w", + Help: "Comma-separated list of allowed origins for use with Cross-Origin Authentication, Device Flow, and web message response mode.", + IsRequired: false, + AlwaysPrompt: true, } appLogoutURLs = Flag{ - Name: "Allowed Logout URLs", - LongForm: "logout-urls", - ShortForm: "l", - Help: "Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.", - IsRequired: false, + Name: "Allowed Logout URLs", + LongForm: "logout-urls", + ShortForm: "l", + Help: "Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.", + IsRequired: false, + AlwaysPrompt: true, } appAuthMethod = Flag{ Name: "Auth Method", @@ -102,8 +111,8 @@ func appsCmd(cli *cli) *cobra.Command { cmd.SetUsageTemplate(resourceUsageTemplate()) cmd.AddCommand(listAppsCmd(cli)) - cmd.AddCommand(showAppCmd(cli)) cmd.AddCommand(createAppCmd(cli)) + cmd.AddCommand(showAppCmd(cli)) cmd.AddCommand(updateAppCmd(cli)) cmd.AddCommand(deleteAppCmd(cli)) @@ -122,13 +131,11 @@ Lists your existing applications. To create one try: RunE: func(cmd *cobra.Command, args []string) error { var list *management.ClientList - err := ansi.Waiting(func() error { + if err := ansi.Waiting(func() error { var err error list, err = cli.api.Client.List() return err - }) - - if err != nil { + }); err != nil { return fmt.Errorf("An unexpected error occurred: %w", err) } @@ -167,13 +174,11 @@ auth0 apps show a := &management.Client{ClientID: &inputs.ID} - err := ansi.Waiting(func() error { + if err := ansi.Waiting(func() error { var err error a, err = cli.api.Client.Read(inputs.ID) return err - }) - - if err != nil { + }); err != nil { return fmt.Errorf("Unable to load application. The Id %v specified doesn't exist", inputs.ID) } @@ -252,18 +257,61 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if err := appName.Ask(cmd, &inputs.Name); err != nil { + // Prompt for app name + if err := appName.Ask(cmd, &inputs.Name, nil); err != nil { return err } - if err := appType.Select(cmd, &inputs.Type, appTypeOptions); err != nil { + // Prompt for app description + if err := appDescription.Ask(cmd, &inputs.Description, nil); err != nil { return err } - if err := appDescription.Ask(cmd, &inputs.Description); err != nil { + // Prompt for app type + if err := appType.Select(cmd, &inputs.Type, appTypeOptions, nil); err != nil { return err } + appIsM2M := apiTypeFor(inputs.Type) == appTypeNonInteractive + appIsNative := apiTypeFor(inputs.Type) == appTypeNative + appIsSPA := apiTypeFor(inputs.Type) == appTypeSPA + + // Prompt for callback URLs if app is not m2m + if !appIsM2M { + var defaultValue string + + if !appIsNative { + defaultValue = appDefaultURL + } + + if err := appCallbacks.AskMany(cmd, &inputs.Callbacks, &defaultValue); err != nil { + return err + } + } + + // Prompt for logout URLs if app is not m2m + if !appIsM2M { + var defaultValue string + + if !appIsNative { + defaultValue = appDefaultURL + } + + if err := appLogoutURLs.AskMany(cmd, &inputs.AllowedLogoutURLs, &defaultValue); err != nil { + return err + } + } + + // Prompt for allowed web origins URLs if app is SPA + if appIsSPA { + defaultValue := appDefaultURL + + if err := appWebOrigins.AskMany(cmd, &inputs.AllowedWebOrigins, &defaultValue); err != nil { + return err + } + } + + // Load values into a fresh app instance a := &management.Client{ Name: &inputs.Name, Description: &inputs.Description, @@ -277,20 +325,21 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] JWTConfiguration: &management.ClientJWTConfiguration{Algorithm: &algorithm}, } + // Set grants if len(inputs.Grants) == 0 { a.GrantTypes = apiDefaultGrantsFor(inputs.Type) } else { a.GrantTypes = apiGrantsFor(inputs.Grants) } - err := ansi.Waiting(func() error { + // Create app + if err := ansi.Waiting(func() error { return cli.api.Client.Create(a) - }) - - if err != nil { + }); err != nil { return fmt.Errorf("Unable to create application: %w", err) } + // Render result // note: a is populated with the rest of the client fields by the API during creation. revealClientSecret := auth0.StringValue(a.AppType) != "native" && auth0.StringValue(a.AppType) != "spa" cli.renderer.ApplicationCreate(a, revealClientSecret) @@ -319,7 +368,6 @@ func updateAppCmd(cli *cli) *cobra.Command { Type string Description string Callbacks []string - CallbacksString string AllowedOrigins []string AllowedWebOrigins []string AllowedLogoutURLs []string @@ -339,6 +387,9 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { + var current *management.Client + + // Get app id if len(args) == 0 { if err := appID.Ask(cmd, &inputs.ID); err != nil { return err @@ -347,97 +398,142 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] inputs.ID = args[0] } - if err := appName.AskU(cmd, &inputs.Name); err != nil { - return err - } - - if err := appType.SelectU(cmd, &inputs.Type, appTypeOptions); err != nil { + // Load app by id + if err := ansi.Waiting(func() error { + var err error + current, err = cli.api.Client.Read(inputs.ID) return err + }); err != nil { + return fmt.Errorf("Unable to load application. The Id %v specified doesn't exist", inputs.ID) } - if err := appDescription.AskU(cmd, &inputs.Description); err != nil { + // Prompt for app name + if err := appName.AskU(cmd, &inputs.Name, current.Name); err != nil { return err } - if err := appCallbacks.AskU(cmd, &inputs.CallbacksString); err != nil { + // Prompt for app type + if err := appType.SelectU(cmd, &inputs.Type, appTypeOptions, typeFor(current.AppType)); err != nil { return err } - a := &management.Client{} + appIsM2M := apiTypeFor(inputs.Type) == appTypeNonInteractive + appIsNative := apiTypeFor(inputs.Type) == appTypeNative + appIsSPA := apiTypeFor(inputs.Type) == appTypeSPA - err := ansi.Waiting(func() error { - current, err := cli.api.Client.Read(inputs.ID) + // Prompt for callback URLs if app is not m2m + if !appIsM2M { + var defaultValue string - if err != nil { - return fmt.Errorf("Unable to load application. The Id %v specified doesn't exist", inputs.ID) + if !appIsNative { + defaultValue = appDefaultURL } - if len(inputs.Name) == 0 { - a.Name = current.Name - } else { - a.Name = &inputs.Name + if len(current.Callbacks) > 0 { + defaultValue = stringSliceToCommaSeparatedString(interfaceToStringSlice(current.Callbacks)) } - if len(inputs.Description) == 0 { - a.Description = current.Description - } else { - a.Description = &inputs.Description + if err := appCallbacks.AskManyU(cmd, &inputs.Callbacks, &defaultValue); err != nil { + return err } + } - if len(inputs.Type) == 0 { - a.AppType = current.AppType - } else { - a.AppType = auth0.String(apiTypeFor(inputs.Type)) - } + // Prompt for logout URLs if app is not m2m + if !appIsM2M { + var defaultValue string - if len(inputs.Callbacks) == 0 { - if len(inputs.CallbacksString) == 0 { - a.Callbacks = current.Callbacks - } else { - a.Callbacks = stringToInterfaceSlice(commaSeparatedStringToSlice(inputs.CallbacksString)) - } - } else { - a.Callbacks = stringToInterfaceSlice(inputs.Callbacks) + if !appIsNative { + defaultValue = appDefaultURL } - if len(inputs.AllowedOrigins) == 0 { - a.AllowedOrigins = current.AllowedOrigins - } else { - a.AllowedOrigins = stringToInterfaceSlice(inputs.AllowedOrigins) + if len(current.AllowedLogoutURLs) > 0 { + defaultValue = stringSliceToCommaSeparatedString(interfaceToStringSlice(current.AllowedLogoutURLs)) } - if len(inputs.AllowedWebOrigins) == 0 { - a.WebOrigins = current.WebOrigins - } else { - a.WebOrigins = stringToInterfaceSlice(inputs.AllowedWebOrigins) + if err := appLogoutURLs.AskManyU(cmd, &inputs.AllowedLogoutURLs, &defaultValue); err != nil { + return err } + } - if len(inputs.AllowedLogoutURLs) == 0 { - a.AllowedLogoutURLs = current.AllowedLogoutURLs - } else { - a.AllowedLogoutURLs = stringToInterfaceSlice(inputs.AllowedLogoutURLs) - } + // Prompt for allowed web origins URLs if app is SPA + if appIsSPA { + defaultValue := appDefaultURL - if len(inputs.AuthMethod) == 0 { - a.TokenEndpointAuthMethod = current.TokenEndpointAuthMethod - } else { - a.TokenEndpointAuthMethod = apiAuthMethodFor(inputs.AuthMethod) + if len(current.WebOrigins) > 0 { + defaultValue = stringSliceToCommaSeparatedString(interfaceToStringSlice(current.WebOrigins)) } - if len(inputs.Grants) == 0 { - a.GrantTypes = current.GrantTypes - } else { - a.GrantTypes = apiGrantsFor(inputs.Grants) + if err := appWebOrigins.AskManyU(cmd, &inputs.AllowedWebOrigins, &defaultValue); err != nil { + return err } + } - return cli.api.Client.Update(inputs.ID, a) - }) + // Load updated values into a fresh app instance + a := &management.Client{} + + if len(inputs.Name) == 0 { + a.Name = current.Name + } else { + a.Name = &inputs.Name + } + + if len(inputs.Description) == 0 { + a.Description = current.Description + } else { + a.Description = &inputs.Description + } + + if len(inputs.Type) == 0 { + a.AppType = current.AppType + } else { + a.AppType = auth0.String(apiTypeFor(inputs.Type)) + } - if err != nil { + if len(inputs.Callbacks) == 0 { + a.Callbacks = current.Callbacks + } else { + a.Callbacks = stringToInterfaceSlice(inputs.Callbacks) + } + + if len(inputs.AllowedOrigins) == 0 { + a.AllowedOrigins = current.AllowedOrigins + } else { + a.AllowedOrigins = stringToInterfaceSlice(inputs.AllowedOrigins) + } + + if len(inputs.AllowedWebOrigins) == 0 { + a.WebOrigins = current.WebOrigins + } else { + a.WebOrigins = stringToInterfaceSlice(inputs.AllowedWebOrigins) + } + + if len(inputs.AllowedLogoutURLs) == 0 { + a.AllowedLogoutURLs = current.AllowedLogoutURLs + } else { + a.AllowedLogoutURLs = stringToInterfaceSlice(inputs.AllowedLogoutURLs) + } + + if len(inputs.AuthMethod) == 0 { + a.TokenEndpointAuthMethod = current.TokenEndpointAuthMethod + } else { + a.TokenEndpointAuthMethod = apiAuthMethodFor(inputs.AuthMethod) + } + + if len(inputs.Grants) == 0 { + a.GrantTypes = current.GrantTypes + } else { + a.GrantTypes = apiGrantsFor(inputs.Grants) + } + + // Update app + if err := ansi.Waiting(func() error { + return cli.api.Client.Update(inputs.ID, a) + }); err != nil { return fmt.Errorf("Unable to update application %v: %v", inputs.ID, err) } - revealClientSecret := auth0.StringValue(a.AppType) != "native" && auth0.StringValue(a.AppType) != "spa" + // Render result + revealClientSecret := auth0.StringValue(a.AppType) != appTypeNative && auth0.StringValue(a.AppType) != appTypeSPA cli.renderer.ApplicationUpdate(a, revealClientSecret) return nil @@ -460,14 +556,13 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] func apiTypeFor(v string) string { switch strings.ToLower(v) { case "native": - return "native" + return appTypeNative case "spa", "single page web application": - return "spa" + return appTypeSPA case "regular", "regular web application": - return "regular_web" + return appTypeRegularWeb case "m2m", "machine to machine": - return "non_interactive" - + return appTypeNonInteractive default: return v } @@ -520,19 +615,34 @@ func apiGrantsFor(s []string) []interface{} { func apiDefaultGrantsFor(t string) []interface{} { switch apiTypeFor(strings.ToLower(t)) { - case "native": + case appTypeNative: return stringToInterfaceSlice([]string{"implicit", "authorization_code", "refresh_token"}) - case "spa": + case appTypeSPA: return stringToInterfaceSlice([]string{"implicit", "authorization_code", "refresh_token"}) - case "regular_web": + case appTypeRegularWeb: return stringToInterfaceSlice([]string{"implicit", "authorization_code", "refresh_token", "client_credentials"}) - case "non_interactive": + case appTypeNonInteractive: return stringToInterfaceSlice([]string{"client_credentials"}) default: return nil } } +func typeFor(s *string) *string { + switch apiTypeFor(strings.ToLower(auth0.StringValue(s))) { + case appTypeNative: + return auth0.String("Native") + case appTypeSPA: + return auth0.String("Single Page Web Application") + case appTypeRegularWeb: + return auth0.String("Regular Web Application") + case appTypeNonInteractive: + return auth0.String("Machine to Machine") + default: + return nil + } +} + func urlsFor(s []interface{}) []string { res := make([]string, len(s)) for i, v := range s { @@ -542,7 +652,15 @@ func urlsFor(s []interface{}) []string { } func commaSeparatedStringToSlice(s string) []string { - return strings.Split(strings.Join(strings.Fields(s), ""), ",") + joined := strings.Join(strings.Fields(s), "") + if len(joined) > 0 { + return strings.Split(joined, ",") + } + return []string{} +} + +func stringSliceToCommaSeparatedString(s []string) string { + return strings.Join(s, ", ") } func stringToInterfaceSlice(s []string) []interface{} { @@ -552,3 +670,13 @@ func stringToInterfaceSlice(s []string) []interface{} { } return result } + +func interfaceToStringSlice(s []interface{}) []string { + var result []string = make([]string, len(s)) + for i, d := range s { + if val, ok := d.(string); ok { + result[i] = val + } + } + return result +} diff --git a/internal/cli/arguments.go b/internal/cli/arguments.go index 836c28d43..52e410e13 100644 --- a/internal/cli/arguments.go +++ b/internal/cli/arguments.go @@ -7,9 +7,8 @@ import ( ) type Argument struct { - Name string - Help string - IsRequired bool + Name string + Help string } func (a Argument) GetName() string { @@ -25,7 +24,7 @@ func (a Argument) GetHelp() string { } func (a Argument) GetIsRequired() bool { - return a.IsRequired + return true } func (a *Argument) Ask(cmd *cobra.Command, value interface{}) error { @@ -34,7 +33,7 @@ func (a *Argument) Ask(cmd *cobra.Command, value interface{}) error { func askArgument(cmd *cobra.Command, i commandInput, value interface{}) error { if canPrompt(cmd) { - return ask(cmd, i, value, true) + return ask(cmd, i, value, nil, true) } else { return fmt.Errorf("Missing a required argument: %s", i.GetName()) } diff --git a/internal/cli/flags.go b/internal/cli/flags.go index 5ae7fbb77..6d279c54e 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -31,20 +31,28 @@ func (f Flag) GetIsRequired() bool { return f.IsRequired } -func (f *Flag) Ask(cmd *cobra.Command, value interface{}) error { - return askFlag(cmd, f, value, false) +func (f *Flag) Ask(cmd *cobra.Command, value interface{}, defaultValue *string) error { + return askFlag(cmd, f, value, defaultValue, false) } -func (f *Flag) AskU(cmd *cobra.Command, value interface{}) error { - return askFlag(cmd, f, value, true) +func (f *Flag) AskU(cmd *cobra.Command, value interface{}, defaultValue *string) error { + return askFlag(cmd, f, value, defaultValue, true) } -func (f *Flag) Select(cmd *cobra.Command, value interface{}, options []string) error { - return selectFlag(cmd, f, value, options, false) +func (f *Flag) AskMany(cmd *cobra.Command, value interface{}, defaultValue *string) error { + return askManyFlag(cmd, f, value, defaultValue, false) } -func (f *Flag) SelectU(cmd *cobra.Command, value interface{}, options []string) error { - return selectFlag(cmd, f, value, options, true) +func (f *Flag) AskManyU(cmd *cobra.Command, value interface{}, defaultValue *string) error { + return askManyFlag(cmd, f, value, defaultValue, true) +} + +func (f *Flag) Select(cmd *cobra.Command, value interface{}, options []string, defaultValue *string) error { + return selectFlag(cmd, f, value, options, defaultValue, false) +} + +func (f *Flag) SelectU(cmd *cobra.Command, value interface{}, options []string, defaultValue *string) error { + return selectFlag(cmd, f, value, options, defaultValue, true) } func (f *Flag) RegisterString(cmd *cobra.Command, value *string, defaultValue string) { @@ -79,17 +87,31 @@ func (f *Flag) RegisterBoolU(cmd *cobra.Command, value *bool, defaultValue bool) registerBool(cmd, f, value, defaultValue, true) } -func askFlag(cmd *cobra.Command, f *Flag, value interface{}, isUpdate bool) error { +func askFlag(cmd *cobra.Command, f *Flag, value interface{}, defaultValue *string, isUpdate bool) error { if shouldAsk(cmd, f, isUpdate) { - return ask(cmd, f, value, isUpdate) + return ask(cmd, f, value, defaultValue, isUpdate) + } + + return nil +} + +func askManyFlag(cmd *cobra.Command, f *Flag, value interface{}, defaultValue *string, isUpdate bool) error { + var strInput struct { + value string } + if err := askFlag(cmd, f, &strInput.value, defaultValue, isUpdate); err != nil { + return err + } + + *value.(*[]string) = commaSeparatedStringToSlice(strInput.value) + return nil } -func selectFlag(cmd *cobra.Command, f *Flag, value interface{}, options []string, isUpdate bool) error { +func selectFlag(cmd *cobra.Command, f *Flag, value interface{}, options []string, defaultValue *string, isUpdate bool) error { if shouldAsk(cmd, f, isUpdate) { - return _select(cmd, f, value, options, isUpdate) + return _select(cmd, f, value, options, defaultValue, isUpdate) } return nil @@ -129,7 +151,7 @@ func registerBool(cmd *cobra.Command, f *Flag, value *bool, defaultValue bool, i func shouldAsk(cmd *cobra.Command, f *Flag, isUpdate bool) bool { if isUpdate { - if !f.AlwaysPrompt { + if !f.IsRequired && !f.AlwaysPrompt { return false } diff --git a/internal/cli/input.go b/internal/cli/input.go index 469bb14f6..7111bbbe1 100644 --- a/internal/cli/input.go +++ b/internal/cli/input.go @@ -2,9 +2,12 @@ package cli import ( "fmt" + "os" + "github.com/AlecAivazis/survey/v2/terminal" "github.com/auth0/auth0-cli/internal/prompt" "github.com/spf13/cobra" + "gopkg.in/auth0.v5" ) type commandInput interface { @@ -14,23 +17,23 @@ type commandInput interface { GetIsRequired() bool } -func ask(cmd *cobra.Command, i commandInput, value interface{}, isUpdate bool) error { +func ask(cmd *cobra.Command, i commandInput, value interface{}, defaultValue *string, isUpdate bool) error { isRequired := !isUpdate && i.GetIsRequired() - input := prompt.TextInput("", i.GetLabel(), i.GetHelp(), isRequired) + input := prompt.TextInput("", i.GetLabel(), i.GetHelp(), auth0.StringValue(defaultValue), isRequired) if err := prompt.AskOne(input, value); err != nil { - return unexpectedError(err) + return handleInputError(err) } return nil } -func _select(cmd *cobra.Command, i commandInput, value interface{}, options []string, isUpdate bool) error { +func _select(cmd *cobra.Command, i commandInput, value interface{}, options []string, defaultValue *string, isUpdate bool) error { isRequired := !isUpdate && i.GetIsRequired() - input := prompt.SelectInput("", i.GetLabel(), i.GetHelp(), options, isRequired) + input := prompt.SelectInput("", i.GetLabel(), i.GetHelp(), options, auth0.StringValue(defaultValue), isRequired) if err := prompt.AskOne(input, value); err != nil { - return unexpectedError(err) + return handleInputError(err) } return nil @@ -39,3 +42,11 @@ func _select(cmd *cobra.Command, i commandInput, value interface{}, options []st func inputLabel(name string) string { return fmt.Sprintf("%s:", name) } + +func handleInputError(err error) error { + if err == terminal.InterruptErr { + os.Exit(0) + } + + return unexpectedError(err) +} diff --git a/internal/cli/logout.go b/internal/cli/logout.go index 4f7d7c1fb..988b334a8 100644 --- a/internal/cli/logout.go +++ b/internal/cli/logout.go @@ -27,7 +27,7 @@ func logoutCmd(cli *cli) *cobra.Command { tenNames[i] = t.Name } - input := prompt.SelectInput("tenant", "Tenant:", "Tenant to activate", tenNames, true) + input := prompt.SelectInput("tenant", "Tenant:", "Tenant to activate", tenNames, "", true) if err := prompt.AskOne(input, &selectedTenant); err != nil { return fmt.Errorf("An unexpected error occurred: %w", err) } diff --git a/internal/cli/quickstarts.go b/internal/cli/quickstarts.go index 1b118bb89..6bc0f5a61 100644 --- a/internal/cli/quickstarts.go +++ b/internal/cli/quickstarts.go @@ -97,7 +97,7 @@ func downloadQuickstart(cli *cli) *cobra.Command { return errors.New("This command can only be run on interactive mode") } - if err := qsClientID.Ask(cmd, &inputs.ClientID); err != nil { + if err := qsClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { return err } @@ -113,7 +113,7 @@ func downloadQuickstart(cli *cli) *cobra.Command { return fmt.Errorf("An unexpected error occurred: %v", err) } // ask for input using the valid types only: - if err := qsStack.Select(cmd, &inputs.Stack, stacks); err != nil { + if err := qsStack.Select(cmd, &inputs.Stack, stacks, nil); err != nil { return err } } diff --git a/internal/cli/rules.go b/internal/cli/rules.go index 6eb0029c6..f84b4fc1e 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -47,7 +47,7 @@ var ( func rulesCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "rules", - Short: "Manage rules for clients", + Short: "Manage resources for rules", } cmd.SetUsageTemplate(resourceUsageTemplate()) @@ -106,11 +106,11 @@ auth0 rules create --name "My Rule" --template [empty-rule]" prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if err := ruleName.Ask(cmd, &inputs.Name); err != nil { + if err := ruleName.Ask(cmd, &inputs.Name, nil); err != nil { return err } - if err := ruleTemplate.Select(cmd, &inputs.Template, ruleTemplateOptions); err != nil { + if err := ruleTemplate.Select(cmd, &inputs.Template, ruleTemplateOptions, nil); err != nil { return err } @@ -261,7 +261,7 @@ func updateRuleCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "update", - Short: "update a rule", + Short: "Update a rule", Long: `Update a rule: auth0 rules update --id rul_d2VSaGlyaW5n --name "My Updated Rule" --enabled=false @@ -297,7 +297,7 @@ auth0 rules update --id rul_d2VSaGlyaW5n --name "My Updated Rule" --enabled=fal return fmt.Errorf("Failed to fetch rule with ID: %s %v", inputs.ID, err) } - if err := ruleName.AskU(cmd, &inputs.Name); err != nil { + if err := ruleName.AskU(cmd, &inputs.Name, rule.Name); err != nil { return err } @@ -372,7 +372,7 @@ func promptForRuleViaDropdown(cli *cli, cmd *cobra.Command) (id string, err erro } var name string - if err := dropdown.Select(cmd, &name, flagOptionsFromMapping(mapping)); err != nil { + if err := dropdown.Select(cmd, &name, flagOptionsFromMapping(mapping), nil); err != nil { return "", err } diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index d9956f872..8ba6c9ba9 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "github.com/auth0/auth0-cli/internal/prompt" "github.com/spf13/cobra" ) @@ -19,9 +20,9 @@ func tenantsCmd(cli *cli) *cobra.Command { func listTenantCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ - Use: "list", - Short: "List your tenants", - Long: `auth0 tenants list`, + Use: "list", + Short: "List your tenants", + Long: `auth0 tenants list`, Aliases: []string{"ls"}, RunE: func(cmd *cobra.Command, args []string) error { tens, err := cli.listTenants() @@ -41,7 +42,6 @@ func listTenantCmd(cli *cli) *cobra.Command { return cmd } - func useTenantCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "use", @@ -65,7 +65,7 @@ func useTenantCmd(cli *cli) *cobra.Command { tenNames[i] = t.Name } - input := prompt.SelectInput("tenant", "Tenant:", "Tenant to activate", tenNames, true) + input := prompt.SelectInput("tenant", "Tenant:", "Tenant to activate", tenNames, "", true) if err := prompt.AskOne(input, &selectedTenant); err != nil { return fmt.Errorf("An unexpected error occurred: %w", err) } diff --git a/internal/display/apps.go b/internal/display/apps.go index 100c93745..7775bcab1 100644 --- a/internal/display/apps.go +++ b/internal/display/apps.go @@ -97,9 +97,9 @@ func (v *applicationView) KeyValues() [][]string { []string{"TYPE", v.Type}, []string{"CLIENT SECRET", ansi.Italic(v.ClientSecret)}, []string{"CALLBACKS", callbacks}, + []string{"ALLOWED LOGOUT URLS", allowedLogoutURLs}, []string{"ALLOWED ORIGINS", allowedOrigins}, []string{"ALLOWED WEB ORIGINS", allowedWebOrigins}, - []string{"ALLOWED LOGOUT URLS", allowedLogoutURLs}, []string{"TOKEN ENDPOINT AUTH", v.AuthMethod}, []string{"GRANTS", grants}, } @@ -111,9 +111,9 @@ func (v *applicationView) KeyValues() [][]string { []string{"DESCRIPTION", v.Description}, []string{"TYPE", v.Type}, []string{"CALLBACKS", callbacks}, + []string{"ALLOWED LOGOUT URLS", allowedLogoutURLs}, []string{"ALLOWED ORIGINS", allowedOrigins}, []string{"ALLOWED WEB ORIGINS", allowedWebOrigins}, - []string{"ALLOWED LOGOUT URLS", allowedLogoutURLs}, []string{"TOKEN ENDPOINT AUTH", v.AuthMethod}, []string{"GRANTS", grants}, } diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 3d42d66a5..7c418d6f2 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -24,10 +24,10 @@ func askOne(prompt survey.Prompt, response interface{}) error { return survey.AskOne(prompt, response, stdErrWriter, icons) } -func TextInputDefault(name string, message string, help string, value string, required bool) *survey.Question { +func TextInput(name string, message string, help string, defaultValue string, required bool) *survey.Question { input := &survey.Question{ Name: name, - Prompt: &survey.Input{Message: message, Help: help, Default: value}, + Prompt: &survey.Input{Message: message, Help: help, Default: defaultValue}, } if required { @@ -37,10 +37,6 @@ func TextInputDefault(name string, message string, help string, value string, re return input } -func TextInput(name string, message string, help string, required bool) *survey.Question { - return TextInputDefault(name, message, help, "", required) -} - func BoolInput(name string, message string, help string, required bool) *survey.Question { input := &survey.Question{ Name: name, @@ -55,13 +51,13 @@ func BoolInput(name string, message string, help string, required bool) *survey. return input } -func SelectInput(name string, message string, help string, options []string, required bool) *survey.Question { +func SelectInput(name string, message string, help string, options []string, defaultValue string, required bool) *survey.Question { // force options "page" size to full, // since there's not visual clue about extra options. pageSize := len(options) input := &survey.Question{ Name: name, - Prompt: &survey.Select{Message: message, Help: help, Options: options, PageSize: pageSize}, + Prompt: &survey.Select{Message: message, Help: help, Options: options, PageSize: pageSize, Default: defaultValue}, } if required {