diff --git a/internal/cli/apis.go b/internal/cli/apis.go index 1e6b50d27..6203ed4b7 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -13,11 +13,28 @@ import ( const ( apiID = "id" - apiName = "name" - apiIdentifier = "identifier" apiScopes = "scopes" ) +var ( + apiName = Flag{ + Name: "Name", + LongForm: "name", + ShortForm: "n", + DefaultValue: "", + Help: "Name of the API.", + IsRequired: true, + } + apiIdentifier = Flag{ + Name: "Identifier", + LongForm: "identifier", + ShortForm: "i", + DefaultValue: "", + Help: "Identifier of the API. Cannot be changed once set.", + IsRequired: true, + } +) + func apisCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "apis", @@ -147,26 +164,12 @@ auth0 apis create --name myapi --identifier http://my-api prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if shouldPrompt(cmd, apiName) { - input := prompt.TextInput( - apiName, "Name:", - "Name of the API. You can change the name later in the API settings.", - true) - - if err := prompt.AskOne(input, &flags); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := apiName.Ask(cmd, &flags.Name); err != nil { + return err } - if shouldPrompt(cmd, apiIdentifier) { - input := prompt.TextInput( - apiIdentifier, "Identifier:", - "Identifier of the API. Cannot be changed once set.", - true) - - if err := prompt.AskOne(input, &flags); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := apiIdentifier.Ask(cmd, &flags.Identifier); err != nil { + return err } if shouldPrompt(cmd, apiScopes) { @@ -199,10 +202,9 @@ auth0 apis create --name myapi --identifier http://my-api }, } - cmd.Flags().StringVarP(&flags.Name, apiName, "n", "", "Name of the API.") - cmd.Flags().StringVarP(&flags.Identifier, apiIdentifier, "i", "", "Identifier of the API.") + apiName.RegisterString(cmd, &flags.Name) + apiIdentifier.RegisterString(cmd, &flags.Identifier) cmd.Flags().StringVarP(&flags.Scopes, apiScopes, "s", "", "Space-separated list of scopes.") - mustRequireFlags(cmd, apiName, apiIdentifier) return cmd } @@ -240,12 +242,8 @@ auth0 apis update --name myapi inputs.ID = args[0] } - if shouldPromptWhenFlagless(cmd, apiName) { - input := prompt.TextInput(apiName, "Name:", "Name of the API.", true) - - if err := prompt.AskOne(input, &inputs); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := apiName.AskU(cmd, &inputs.Name); err != nil { + return err } if shouldPromptWhenFlagless(cmd, apiScopes) { @@ -289,7 +287,7 @@ auth0 apis update --name myapi }, } - cmd.Flags().StringVarP(&inputs.Name, apiName, "n", "", "Name of the API.") + apiName.RegisterStringU(cmd, &inputs.Name) cmd.Flags().StringVarP(&inputs.Scopes, apiScopes, "s", "", "Space-separated list of scopes.") return cmd diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 7dd488acf..1a3137dde 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -13,10 +13,35 @@ import ( ) const ( - appID = "id" - appName = "name" - appType = "type" - appDescription = "description" + appID = "id" + appType = "type" +) + +var ( + appName = Flag{ + Name: "Name", + LongForm: "name", + ShortForm: "n", + DefaultValue: "", + Help: "Name of the application.", + IsRequired: true, + } + appDescription = Flag{ + Name: "Description", + LongForm: "description", + ShortForm: "d", + DefaultValue: "", + Help: "Description of the application. Max character count is 140.", + IsRequired: false, + } + appAuthMethod = Flag{ + Name: "Auth Method", + LongForm: "auth-method", + ShortForm: "a", + DefaultValue: "", + Help: "Defines the requested authentication method for the token endpoint. Possible values are 'None' (public application without a client secret), 'Post' (application uses HTTP POST parameters) or 'Basic' (application uses HTTP Basic).", + IsRequired: false, + } ) func appsCmd(cli *cli) *cobra.Command { @@ -189,15 +214,8 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] prepareInteractivity(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { - if shouldPrompt(cmd, appName) { - input := prompt.TextInput( - appName, "Name:", - "Name of the application. You can change the name later in the application settings.", - true) - - if err := prompt.AskOne(input, &flags); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := appName.Ask(cmd, &flags.Name); err != nil { + return err } if shouldPrompt(cmd, appType) { @@ -216,12 +234,8 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] } } - if shouldPrompt(cmd, appDescription) { - input := prompt.TextInput(appDescription, "Description:", "Description of the application.", false) - - if err := prompt.AskOne(input, &flags); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := appDescription.Ask(cmd, &flags.Description); err != nil { + return err } a := &management.Client{ @@ -259,20 +273,20 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] }, } - cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "Name of the application.") + appName.RegisterString(cmd, &flags.Name) cmd.Flags().StringVarP(&flags.Type, "type", "t", "", "Type of application:\n"+ "- native: mobile, desktop, CLI and smart device apps running natively.\n"+ "- spa (single page application): a JavaScript front-end app that uses an API.\n"+ "- regular: Traditional web app using redirects.\n"+ "- m2m (machine to machine): CLIs, daemons or services running on your backend.") - cmd.Flags().StringVarP(&flags.Description, "description", "d", "", "Description of the application. Max character count is 140.") + appDescription.RegisterString(cmd, &flags.Description) cmd.Flags().StringSliceVarP(&flags.Callbacks, "callbacks", "c", nil, "After the user authenticates we will only call back to any of these URLs. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol (https://) otherwise the callback may fail in some cases. With the exception of custom URI schemes for native apps, all callbacks should use protocol https://.") cmd.Flags().StringSliceVarP(&flags.AllowedOrigins, "origins", "o", nil, "Comma-separated list of URLs allowed to make requests from JavaScript to Auth0 API (typically used with CORS). By default, all your callback URLs will be allowed. This field allows you to enter other origins if necessary. You can also use wildcards at the subdomain level (e.g., https://*.contoso.com). Query strings and hash information are not taken into account when validating these URLs.") cmd.Flags().StringSliceVarP(&flags.AllowedWebOrigins, "web-origins", "w", nil, "Comma-separated list of allowed origins for use with Cross-Origin Authentication, Device Flow, and web message response mode.") cmd.Flags().StringSliceVarP(&flags.AllowedLogoutURLs, "logout-urls", "l", nil, "Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.") - cmd.Flags().StringVarP(&flags.AuthMethod, "auth-method", "a", "", "Defines the requested authentication method for the token endpoint. Possible values are 'None' (public application without a client secret), 'Post' (application uses HTTP POST parameters) or 'Basic' (application uses HTTP Basic).") + appAuthMethod.RegisterString(cmd, &flags.AuthMethod) cmd.Flags().StringSliceVarP(&flags.Grants, "grants", "g", nil, "List of grant types supported for this application. Can include code, implicit, refresh-token, credentials, password, password-realm, mfa-oob, mfa-otp, mfa-recovery-code, and device-code.") - mustRequireFlags(cmd, appName, appType) + mustRequireFlags(cmd, appType) return cmd } @@ -318,12 +332,8 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] inputs.ID = args[0] } - if shouldPromptWhenFlagless(cmd, appName) { - input := prompt.TextInput(appName, "Name:", "Name of the application", true) - - if err := prompt.AskOne(input, &inputs); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := appName.AskU(cmd, &inputs.Name); err != nil { + return err } if shouldPromptWhenFlagless(cmd, appType) { @@ -342,12 +352,8 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] } } - if shouldPromptWhenFlagless(cmd, appDescription) { - input := prompt.TextInput(appDescription, "Description:", "Description of the application.", false) - - if err := prompt.AskOne(input, &inputs); err != nil { - return fmt.Errorf("An unexpected error occurred: %w", err) - } + if err := appDescription.AskU(cmd, &inputs.Description); err != nil { + return err } if shouldPromptWhenFlagless(cmd, "CallbacksString") { @@ -439,18 +445,18 @@ auth0 apps update --name myapp --type [native|spa|regular|m2m] }, } - cmd.Flags().StringVarP(&inputs.Name, "name", "n", "", "Name of the application.") + appName.RegisterStringU(cmd, &inputs.Name) cmd.Flags().StringVarP(&inputs.Type, "type", "t", "", "Type of application:\n"+ "- native: mobile, desktop, CLI and smart device apps running natively.\n"+ "- spa (single page application): a JavaScript front-end app that uses an API.\n"+ "- regular: Traditional web app using redirects.\n"+ "- m2m (machine to machine): CLIs, daemons or services running on your backend.") - cmd.Flags().StringVarP(&inputs.Description, "description", "d", "", "Description of the application. Max character count is 140.") + appDescription.RegisterStringU(cmd, &inputs.Description) cmd.Flags().StringSliceVarP(&inputs.Callbacks, "callbacks", "c", nil, "After the user authenticates we will only call back to any of these URLs. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol (https://) otherwise the callback may fail in some cases. With the exception of custom URI schemes for native apps, all callbacks should use protocol https://.") cmd.Flags().StringSliceVarP(&inputs.AllowedOrigins, "origins", "o", nil, "Comma-separated list of URLs allowed to make requests from JavaScript to Auth0 API (typically used with CORS). By default, all your callback URLs will be allowed. This field allows you to enter other origins if necessary. You can also use wildcards at the subdomain level (e.g., https://*.contoso.com). Query strings and hash information are not taken into account when validating these URLs.") cmd.Flags().StringSliceVarP(&inputs.AllowedWebOrigins, "web-origins", "w", nil, "Comma-separated list of allowed origins for use with Cross-Origin Authentication, Device Flow, and web message response mode.") cmd.Flags().StringSliceVarP(&inputs.AllowedLogoutURLs, "logout-urls", "l", nil, "Comma-separated list of URLs that are valid to redirect to after logout from Auth0. Wildcards are allowed for subdomains.") - cmd.Flags().StringVarP(&inputs.AuthMethod, "auth-method", "a", "", "Defines the requested authentication method for the token endpoint. Possible values are 'None' (public application without a client secret), 'Post' (application uses HTTP POST parameters) or 'Basic' (application uses HTTP Basic).") + appAuthMethod.RegisterStringU(cmd, &inputs.AuthMethod) cmd.Flags().StringSliceVarP(&inputs.Grants, "grants", "g", nil, "List of grant types supported for this application. Can include code, implicit, refresh-token, credentials, password, password-realm, mfa-oob, mfa-otp, mfa-recovery-code, and device-code.") return cmd diff --git a/internal/cli/flags.go b/internal/cli/flags.go new file mode 100644 index 000000000..42e98af8f --- /dev/null +++ b/internal/cli/flags.go @@ -0,0 +1,69 @@ +package cli + +import ( + "fmt" + + "github.com/auth0/auth0-cli/internal/prompt" + "github.com/spf13/cobra" +) + +type Flag struct { + Name string + LongForm string + ShortForm string + DefaultValue string + Help string + IsRequired bool +} + +func (f *Flag) Ask(cmd *cobra.Command, value interface{}) error { + return ask(cmd, f, value, false) +} + +func (f *Flag) AskU(cmd *cobra.Command, value interface{}) error { + return ask(cmd, f, value, true) +} + +func (f *Flag) RegisterString(cmd *cobra.Command, value *string) { + registerString(cmd, f, value, false) +} + +func (f *Flag) RegisterStringU(cmd *cobra.Command, value *string) { + registerString(cmd, f, value, true) +} + +func ask(cmd *cobra.Command, f *Flag, value interface{}, isUpdate bool) error { + var shouldAsk bool + + if isUpdate { + shouldAsk = shouldPromptWhenFlagless(cmd, f.LongForm) + } else { + shouldAsk = shouldPrompt(cmd, f.LongForm) + } + + if shouldAsk { + input := prompt.TextInput("", fmt.Sprintf("%s:", f.Name), f.Help, f.IsRequired) + + if err := prompt.AskOne(input, value); err != nil { + return fmt.Errorf("An unexpected error occurred: %w", err) + } + } + + return nil +} + +func registerString(cmd *cobra.Command, f *Flag, value *string, isUpdate bool) { + cmd.Flags().StringVarP(value, f.LongForm, f.ShortForm, f.DefaultValue, f.Help) + + if err := markFlagRequired(cmd, f, isUpdate); err != nil { + panic(fmt.Errorf("An unexpected error occurred: %w", err)) // TODO: Handle + } +} + +func markFlagRequired(cmd *cobra.Command, f *Flag, isUpdate bool) error { + if f.IsRequired && !isUpdate { + return cmd.MarkFlagRequired(f.LongForm) + } + + return nil +} diff --git a/internal/cli/login.go b/internal/cli/login.go index 3102e6c48..d84402c51 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -8,6 +8,7 @@ import ( "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth" "github.com/auth0/auth0-cli/internal/open" + "github.com/auth0/auth0-cli/internal/prompt" "github.com/spf13/cobra" ) @@ -62,7 +63,7 @@ func RunLogin(ctx context.Context, cli *cli, expired bool) error { cli.renderer.Infof("Successfully logged in.") cli.renderer.Infof("Tenant: %s\n", res.Tenant) - return cli.addTenant(tenant{ + err = cli.addTenant(tenant{ Name: res.Tenant, Domain: res.Domain, AccessToken: res.AccessToken, @@ -70,4 +71,20 @@ func RunLogin(ctx context.Context, cli *cli, expired bool) error { time.Duration(res.ExpiresIn) * time.Second, ), }) + if err != nil { + return fmt.Errorf("Unexpected error adding tenant to config: %w", err) + } + + if cli.config.DefaultTenant != res.Tenant { + promptText := fmt.Sprintf("Your default tenant is %s. Do you want to change it to %s?", cli.config.DefaultTenant, res.Tenant) + if confirmed := prompt.Confirm(promptText); !confirmed { + return nil + } + cli.config.DefaultTenant = res.Tenant + if err := cli.persistConfig(); err != nil { + return fmt.Errorf("An error occurred while setting the default tenant: %w", err) + } + } + + return nil }