diff --git a/go.sum b/go.sum index 193145d30..a6df28f61 100644 --- a/go.sum +++ b/go.sum @@ -31,11 +31,11 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ= github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig= github.com/AlecAivazis/survey/v2 v2.2.7/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/rehttp v1.0.0 h1:aJ7A7YI2lIvOxcJVeUZY4P6R7kKZtLeONjgyKGwOIu8= @@ -152,6 +152,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -166,8 +167,10 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -502,6 +505,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/internal/cli/clients.go b/internal/cli/clients.go index 4ba5397b0..67b3e0628 100644 --- a/internal/cli/clients.go +++ b/internal/cli/clients.go @@ -3,6 +3,7 @@ package cli import ( "strings" + "github.com/AlecAivazis/survey/v2" "github.com/auth0/auth0-cli/internal/ansi" "github.com/auth0/auth0-cli/internal/auth0" "github.com/spf13/cobra" @@ -53,11 +54,11 @@ Lists your existing clients. To create one try: func clientsCreateCmd(cli *cli) *cobra.Command { var flags struct { - name string - appType string - description string - reveal bool - callbacks []string + Name string + AppType string + Description string + Callbacks []string + TokenEndpointAuthMethod string } cmd := &cobra.Command{ Use: "create", @@ -73,13 +74,67 @@ auth0 clients create --name myapp --type [native|spa|regular|m2m] - m2m (machine to machine): CLIs, daemons or services running on your backend. `, RunE: func(cmd *cobra.Command, args []string) error { - // TODO(jfatta): depending on the app type, other client properties might be mandatory - // check: create app dashboard + // todo(jfatta) on non-interactive the cmd should fail on missing mandatory args (name, type) + if !cmd.Flags().Changed("name") { + qs := []*survey.Question{ + { + Name: "Name", + Prompt: &survey.Input{ + Message: "Name:", + Default: "My App", + Help: "Name of the client (also known as application). You can change the application name later in the application settings.", + }, + }, + } + + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + if !cmd.Flags().Changed("type") { + qs := []*survey.Question{ + { + Name: "AppType", + Prompt: &survey.Select{ + Message: "Type:", + Help: "\n- Native: Mobile, desktop, CLI and smart device apps running natively." + + "\n- Single Page Web Application: A JavaScript front-end app that uses an API." + + "\n- Regular Web Application: Traditional web app using redirects." + + "\n- Machine To Machine: CLIs, daemons or services running on your backend.", + Options: []string{"Native", "Single Page Web Application", "Regular Web Application", "Machine to Machine"}, + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + if !cmd.Flags().Changed("description") { + qs := []*survey.Question{ + { + Name: "Description", + Prompt: &survey.Input{ + Message: "Description:", + Help: "A free text description of the application.", + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + c := &management.Client{ - Name: &flags.name, - Description: &flags.description, - AppType: auth0.String(apiAppTypeFor(flags.appType)), - Callbacks: apiCallbacksFor(flags.callbacks), + Name: &flags.Name, + Description: &flags.Description, + AppType: auth0.String(apiAppTypeFor(flags.AppType)), + Callbacks: apiCallbacksFor(flags.Callbacks), + TokenEndpointAuthMethod: apiTokenEndpointAuthMethodFor(flags.TokenEndpointAuthMethod), } err := ansi.Spinner("Creating client", func() error { @@ -91,17 +146,17 @@ auth0 clients create --name myapp --type [native|spa|regular|m2m] } // note: c is populated with the rest of the client fields by the API during creation. - cli.renderer.ClientCreate(c, flags.reveal) + revealClientSecret := auth0.StringValue(c.AppType) != "native" && auth0.StringValue(c.AppType) != "spa" + cli.renderer.ClientCreate(c, revealClientSecret) return nil }, } - cmd.Flags().StringVarP(&flags.name, "name", "n", "", "Name of the client.") - cmd.Flags().StringVarP(&flags.appType, "type", "t", "", "Type of the client: native, spa, regular, or m2m.") - cmd.Flags().StringVarP(&flags.description, "description", "d", "", "A free text description of the application. Max character count is 140.") - cmd.Flags().BoolVarP(&flags.reveal, "reveal", "r", false, "⚠️ Reveal the SECRET of the created client.") - 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 clients, all callbacks should use protocol https://.") + cmd.Flags().StringVarP(&flags.Name, "name", "n", "", "Name of the client.") + cmd.Flags().StringVarP(&flags.AppType, "type", "t", "", "Type of the client: native, spa, regular, or m2m.") + cmd.Flags().StringVarP(&flags.Description, "description", "d", "", "A free text description of the application. Max character count is 140.") + 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 clients, all callbacks should use protocol https://.") - mustRequireFlags(cmd, "name", "type") + cmd.Flags().StringVar(&flags.TokenEndpointAuthMethod, "auth-method", "", "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).") return cmd } @@ -110,11 +165,11 @@ func apiAppTypeFor(v string) string { switch strings.ToLower(v) { case "native": return "native" - case "spa": + case "spa", "single page web application": return "spa" - case "regular": + case "regular", "regular web application": return "regular_web" - case "m2m": + case "m2m", "machine to machine": return "non_interactive" default: @@ -130,3 +185,16 @@ func apiCallbacksFor(s []string) []interface{} { return res } + +func apiTokenEndpointAuthMethodFor(v string) *string { + switch strings.ToLower(v) { + case "none": + return auth0.String("none") + case "post": + return auth0.String("client_secret_post") + case "basic": + return auth0.String("client_secret_basic") + default: + return nil + } +} diff --git a/internal/display/clients.go b/internal/display/clients.go index cbf32ca7e..25391fcb1 100644 --- a/internal/display/clients.go +++ b/internal/display/clients.go @@ -9,6 +9,14 @@ import ( "gopkg.in/auth0.v5/management" ) +const ( + quickstartsNative = "https://auth0.com/docs/quickstart/native" + quickstartsSPA = "https://auth0.com/docs/quickstart/spa" + quickstartsRegularWeb = "https://auth0.com/docs/quickstart/webapp" + quickstartsM2M = "https://auth0.com/docs/quickstart/backend" + quickstartsGeneric = "https://auth0.com/docs/quickstarts" +) + type clientView struct { Name string Type string @@ -79,6 +87,8 @@ func (r *Renderer) ClientCreate(client *management.Client, revealSecrets bool) { } r.Results([]View{v}) + + r.Infof("\nQuickstarts: %s", quickstartsURIFor(client.AppType)) } // TODO(cyx): determine if there's a better way to filter this out. @@ -106,6 +116,21 @@ func appTypeFor(v *string) string { } } +func quickstartsURIFor(v *string) string { + switch { + case *v == "native": + return quickstartsNative + case *v == "spa": + return quickstartsSPA + case *v == "regular_web": + return quickstartsRegularWeb + case *v == "non_interactive": + return quickstartsM2M + default: + return quickstartsGeneric + } +} + func callbacksFor(s []interface{}) []string { res := make([]string, len(s)) for i, v := range s {