From 734443a147a65fde69d428322bd03118cf43d6be Mon Sep 17 00:00:00 2001 From: Cyril David Date: Mon, 29 Mar 2021 10:02:34 -0700 Subject: [PATCH] apps picker: allow configurability of default app (#205) * apps picker: allow configurability of default app Closes #190 * Show hint with the client id * Update internal/cli/apps.go Co-authored-by: Rita Zerrizuela Co-authored-by: Rita Zerrizuela --- internal/cli/apps.go | 104 ++++++++++++++++++++++++++++++++++++++----- internal/cli/cli.go | 28 +++++++++--- 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 5c1b68fc1..0d5b8c668 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -32,6 +32,13 @@ var ( Help: "Name of the application.", IsRequired: true, } + appNone = Flag{ + Name: "None", + LongForm: "none", + ShortForm: "n", + Help: "Specify none of your apps", + } + appType = Flag{ Name: "Type", LongForm: "type", @@ -65,11 +72,11 @@ var ( AlwaysPrompt: true, } appOrigins = Flag{ - Name: "Allowed Origin URLs", - LongForm: "origins", - ShortForm: "o", - Help: "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.", - IsRequired: false, + Name: "Allowed Origin URLs", + LongForm: "origins", + ShortForm: "o", + Help: "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.", + IsRequired: false, AlwaysPrompt: true, } appWebOrigins = Flag{ @@ -117,10 +124,60 @@ func appsCmd(cli *cli) *cobra.Command { cmd.AddCommand(showAppCmd(cli)) cmd.AddCommand(updateAppCmd(cli)) cmd.AddCommand(deleteAppCmd(cli)) + cmd.AddCommand(useAppCmd(cli)) return cmd } +func useAppCmd(cli *cli) *cobra.Command { + var inputs struct { + ID string + None bool + } + + cmd := &cobra.Command{ + Use: "use", + Short: "Choose a default application", + Long: `auth0 apps use +Specify your preferred application for interaction with the Auth0 CLI +`, + PreRun: func(cmd *cobra.Command, args []string) { + prepareInteractivity(cmd) + }, + + RunE: func(cmd *cobra.Command, args []string) error { + if inputs.None { + inputs.ID = "" + } else { + if len(args) == 0 { + err := appID.Pick(cmd, &inputs.ID, cli.appPickerOptions) + if err != nil { + return err + } + } else { + inputs.ID = args[0] + } + } + + if err := cli.setDefaultAppID(inputs.ID); err != nil { + return err + } + + if inputs.ID == "" { + cli.renderer.Infof("Successfully removed the default application") + } else { + cli.renderer.Infof("Successfully set the default application to %s", ansi.Faint(inputs.ID)) + cli.renderer.Infof("%s You might wanna try 'auth0 quickstarts download %s'", ansi.Faint("Hint:"), inputs.ID) + } + + return nil + }, + } + + appNone.RegisterBool(cmd, &inputs.None, false) + return cmd +} + func listAppsCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "list", @@ -352,6 +409,10 @@ auth0 apps create --name myapp --type [native|spa|regular|m2m] return fmt.Errorf("Unable to create application: %w", err) } + if err := cli.setDefaultAppID(a.GetClientID()); err != nil { + return 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" @@ -713,19 +774,40 @@ func (c *cli) appPickerOptions() (pickerOptions, error) { return nil, err } - // NOTE: because client names are not unique, we'll just number these - // labels. - var opts pickerOptions + tenant, err := c.getTenant() + if err != nil { + return nil, err + } + + // NOTE(cyx): To keep the contract for this simple, we'll rely on the + // implicit knowledge that the default value for the picker is the + // first option. With that in mind, we'll use the state in + // tenant.DefaultAppID to determine which should be chosen as the + // default. + var ( + priorityOpts, opts pickerOptions + ) for _, c := range list.Clients { + // empty type means the default client that we shouldn't display. + if c.GetAppType() == "" { + continue + } + value := c.GetClientID() label := fmt.Sprintf("%s %s", c.GetName(), ansi.Faint("("+value+")")) + opt := pickerOption{value: value, label: label} - opts = append(opts, pickerOption{value: value, label: label}) + // check if this is currently the default application. + if tenant.DefaultAppID == c.GetClientID() { + priorityOpts = append(priorityOpts, opt) + } else { + opts = append(opts, opt) + } } - if len(opts) == 0 { + if len(opts)+len(priorityOpts) == 0 { return nil, errors.New("There are currently no applications.") } - return opts, nil + return append(priorityOpts, opts...), nil } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 67cefc7ef..363b1e199 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -40,11 +40,12 @@ type config struct { // tenant is the cli's concept of an auth0 tenant. The fields are tailor fit // specifically for interacting with the management API. type tenant struct { - Name string `json:"name"` - Domain string `json:"domain"` - AccessToken string `json:"access_token,omitempty"` - ExpiresAt time.Time `json:"expires_at"` - Apps map[string]app `json:"apps,omitempty"` + Name string `json:"name"` + Domain string `json:"domain"` + AccessToken string `json:"access_token,omitempty"` + ExpiresAt time.Time `json:"expires_at"` + Apps map[string]app `json:"apps,omitempty"` + DefaultAppID string `json:"default_app_id,omitempty"` } type app struct { @@ -296,9 +297,24 @@ func (c *cli) isFirstCommandRun(clientID string, command string) (bool, error) { return true, nil } -func (c *cli) setFirstCommandRun(clientID string, command string) error { +func (c *cli) setDefaultAppID(id string) error { tenant, err := c.getTenant() + if err != nil { + return err + } + tenant.DefaultAppID = id + + c.config.Tenants[tenant.Name] = tenant + if err := c.persistConfig(); err != nil { + return fmt.Errorf("Unexpected error persisting config: %w", err) + } + + return nil +} + +func (c *cli) setFirstCommandRun(clientID string, command string) error { + tenant, err := c.getTenant() if err != nil { return err }