diff --git a/Makefile b/Makefile index e1f267425..8c703574f 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,7 @@ test-unit: ## Run unit tests test-integration: $(GO_BIN)/commander ## Run integration tests. To run a specific test pass the FILTER var. Usage: `make test-integration FILTER="attack protection"` ${call print, "Running integration tests"} @$(MAKE) install # ensure fresh install prior to running test - auth0 tenants add ${AUTH0_CLI_CLIENT_DOMAIN} --client-id ${AUTH0_CLI_CLIENT_ID} --client-secret ${AUTH0_CLI_CLIENT_SECRET} && commander test ./test/integration/test-cases.yaml --filter "$(FILTER)"; \ + auth0 login --domain ${AUTH0_CLI_CLIENT_DOMAIN} --client-id ${AUTH0_CLI_CLIENT_ID} --client-secret ${AUTH0_CLI_CLIENT_SECRET} && commander test ./test/integration/test-cases.yaml --filter "$(FILTER)"; \ exit_code=$$?; \ bash ./test/integration/scripts/test-cleanup.sh; \ exit $$exit_code diff --git a/docs/auth0_login.md b/docs/auth0_login.md index 384b018bf..e22cb0f2e 100644 --- a/docs/auth0_login.md +++ b/docs/auth0_login.md @@ -7,16 +7,26 @@ Authenticate the Auth0 CLI ### Synopsis -Sign in to your Auth0 account and authorize the CLI to access the Management API. +Authenticates the Auth0 CLI either as a user using personal credentials or as a machine using client credentials. ``` auth0 login [flags] ``` +### Examples + +``` +auth0 login +auth0 login --domain --client-id --client-secret +``` + ### Options ``` - -h, --help help for login + --client-id string Client ID of the application when authenticating via client credentials. + --client-secret string Client secret of the application when authenticating via client credentials. + --domain string Tenant domain of the application when authenticating via client credentials. + -h, --help help for login ``` ### Options inherited from parent commands diff --git a/docs/auth0_tenants.md b/docs/auth0_tenants.md index 56be1080a..34b51b17b 100644 --- a/docs/auth0_tenants.md +++ b/docs/auth0_tenants.md @@ -28,7 +28,6 @@ Manage configured tenants. ### SEE ALSO * [auth0](/auth0-cli/) - Supercharge your development workflow. -* [auth0 tenants add](auth0_tenants_add.md) - Add a tenant with client credentials * [auth0 tenants list](auth0_tenants_list.md) - List your tenants * [auth0 tenants open](auth0_tenants_open.md) - Open tenant settings page in the Auth0 Dashboard * [auth0 tenants use](auth0_tenants_use.md) - Set the active tenant diff --git a/docs/auth0_tenants_add.md b/docs/auth0_tenants_add.md deleted file mode 100644 index a8d7ffe68..000000000 --- a/docs/auth0_tenants_add.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: default ---- -## auth0 tenants add - -Add a tenant with client credentials - -### Synopsis - -Add a tenant with client credentials. - -``` -auth0 tenants add [flags] -``` - -### Examples - -``` -auth0 tenants add --client-id --client-secret -``` - -### Options - -``` - -h, --help help for add -``` - -### Options inherited from parent commands - -``` - --debug Enable debug mode. - --json Output in json format. - --no-color Disable colors. - --no-input Disable interactivity. - --tenant string Specific tenant to use. -``` - -### SEE ALSO - -* [auth0 tenants](auth0_tenants.md) - Manage configured tenants - diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 116688ecb..927d7223c 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -208,7 +208,7 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { } if t.AccessToken == "" || (scopesChanged(t) && t.authenticatedWithDeviceCodeFlow()) { - return RunLogin(ctx, c, true) + return RunLoginAsUser(ctx, c, true) } if !t.hasExpiredToken() { @@ -218,7 +218,7 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { if err := t.regenerateAccessToken(ctx, c); err != nil { // Ask and guide the user through the login process. c.renderer.Errorf("failed to renew access token, %s", err) - return RunLogin(ctx, c, true) + return RunLoginAsUser(ctx, c, true) } if err := c.addTenant(t); err != nil { diff --git a/internal/cli/login.go b/internal/cli/login.go index 4ddd967f6..23c5693bf 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -12,16 +12,63 @@ import ( "github.com/auth0/auth0-cli/internal/prompt" ) +var ( + loginTenantDomain = Flag{ + Name: "Tenant Domain", + LongForm: "domain", + Help: "Tenant domain of the application when authenticating via client credentials.", + IsRequired: false, + AlwaysPrompt: false, + } + + loginClientID = Flag{ + Name: "Client ID", + LongForm: "client-id", + Help: "Client ID of the application when authenticating via client credentials.", + IsRequired: false, + AlwaysPrompt: false, + } + + loginClientSecret = Flag{ + Name: "Client Secret", + LongForm: "client-secret", + Help: "Client secret of the application when authenticating via client credentials.", + IsRequired: false, + AlwaysPrompt: false, + } +) + +type LoginInputs struct { + Domain string + ClientID string + ClientSecret string +} + +func (i *LoginInputs) shouldLoginAsMachine() bool { + return i.ClientID != "" || i.ClientSecret != "" || i.Domain != "" +} + func loginCmd(cli *cli) *cobra.Command { + var inputs LoginInputs + cmd := &cobra.Command{ Use: "login", Args: cobra.NoArgs, Short: "Authenticate the Auth0 CLI", - Long: "Sign in to your Auth0 account and authorize the CLI to access the Management API.", + Long: "Authenticates the Auth0 CLI either as a user using personal credentials or as a machine using client credentials.", + Example: `auth0 login +auth0 login --domain --client-id --client-secret `, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - if _, err := RunLogin(ctx, cli, false); err != nil { - return err + + if inputs.shouldLoginAsMachine() { + if err := RunLoginAsMachine(ctx, inputs, cli, cmd); err != nil { + return err + } + } else { + if _, err := RunLoginAsUser(ctx, cli, false); err != nil { + return err + } } cli.tracker.TrackCommandRun(cmd, cli.config.InstallID) @@ -30,20 +77,26 @@ func loginCmd(cli *cli) *cobra.Command { }, } + loginTenantDomain.RegisterString(cmd, &inputs.Domain, "") + loginClientID.RegisterString(cmd, &inputs.ClientID, "") + loginClientSecret.RegisterString(cmd, &inputs.ClientSecret, "") + cmd.MarkFlagsRequiredTogether("client-id", "client-secret", "domain") + cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { _ = cmd.Flags().MarkHidden("tenant") _ = cmd.Flags().MarkHidden("json") + _ = cmd.Flags().MarkHidden("no-input") cmd.Parent().HelpFunc()(cmd, args) }) return cmd } -// RunLogin runs the login flow guiding the user through the process +// RunLoginAsUser runs the login flow guiding the user through the process // by showing the login instructions, opening the browser. // Use `expired` to run the login from other commands setup: // this will only affect the messages. -func RunLogin(ctx context.Context, cli *cli, expired bool) (Tenant, error) { +func RunLoginAsUser(ctx context.Context, cli *cli, expired bool) (Tenant, error) { message := fmt.Sprintf( "%s\n\n%s\n\n", "✪ Welcome to the Auth0 CLI 🎊", @@ -141,3 +194,49 @@ func RunLogin(ctx context.Context, cli *cli, expired bool) (Tenant, error) { return tenant, nil } + +// RunLoginAsMachine facilitates the authentication process using client credentials (client ID, client secret) +func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *cobra.Command) error { + if err := loginTenantDomain.Ask(cmd, &inputs.Domain, nil); err != nil { + return err + } + + if err := loginClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { + return err + } + + if err := loginClientSecret.AskPassword(cmd, &inputs.ClientSecret, nil); err != nil { + return err + } + + token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ + ClientID: inputs.ClientID, + ClientSecret: inputs.ClientSecret, + Domain: inputs.Domain, + }) + if err != nil { + return err + } + + t := Tenant{ + Domain: inputs.Domain, + AccessToken: token.AccessToken, + ExpiresAt: token.ExpiresAt, + ClientID: inputs.ClientID, + ClientSecret: inputs.ClientSecret, + } + + if err := cli.addTenant(t); err != nil { + return fmt.Errorf("unexpected error when attempting to save tenant data: %w", err) + } + + cli.renderer.Newline() + cli.renderer.Infof("Successfully logged in.") + cli.renderer.Infof("Tenant: %s", inputs.Domain) + + if err := checkInstallID(cli); err != nil { + return fmt.Errorf("failed to update the config: %w", err) + } + + return nil +} diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 7d178905c..c11859adc 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -1,38 +1,13 @@ package cli import ( - "errors" "fmt" "github.com/spf13/cobra" - "github.com/auth0/auth0-cli/internal/auth" "github.com/auth0/auth0-cli/internal/prompt" ) -var ( - tenantDomain = Argument{ - Name: "Tenant", - Help: "Tenant to select", - } - - tenantClientID = Flag{ - Name: "Client ID", - LongForm: "client-id", - ShortForm: "i", - Help: "Client ID of the application.", - IsRequired: true, - } - - tenantClientSecret = Flag{ - Name: "Client Secret", - LongForm: "client-secret", - ShortForm: "s", - Help: "Client Secret of the application.", - IsRequired: true, - } -) - func tenantsCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "tenants", @@ -44,7 +19,6 @@ func tenantsCmd(cli *cli) *cobra.Command { cmd.AddCommand(useTenantCmd(cli)) cmd.AddCommand(listTenantCmd(cli)) cmd.AddCommand(openTenantCmd(cli)) - cmd.AddCommand(addTenantCmd(cli)) return cmd } @@ -129,6 +103,11 @@ func openTenantCmd(cli *cli) *cobra.Command { Domain string } + var tenantDomain = Argument{ + Name: "Tenant", + Help: "Tenant to select", + } + cmd := &cobra.Command{ Use: "open", Args: cobra.MaximumNArgs(1), @@ -162,75 +141,6 @@ func openTenantCmd(cli *cli) *cobra.Command { return cmd } -func addTenantCmd(cli *cli) *cobra.Command { - var inputs struct { - Domain string - ClientID string - ClientSecret string - } - - cmd := &cobra.Command{ - Use: "add", - Args: cobra.MaximumNArgs(1), - Short: "Add a tenant with client credentials", - Long: "Add a tenant with client credentials.", - Example: "auth0 tenants add --client-id --client-secret ", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - err := tenantDomain.Pick(cmd, &inputs.Domain, cli.tenantPickerOptions) - if err != nil { - if !errors.Is(err, errUnauthenticated) { - return err - } - - if err := tenantDomain.Ask(cmd, &inputs.Domain); err != nil { - return err - } - } - } else { - inputs.Domain = args[0] - } - - if err := tenantClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { - return err - } - - if err := tenantClientSecret.Ask(cmd, &inputs.ClientSecret, nil); err != nil { - return err - } - - token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ - ClientID: inputs.ClientID, - ClientSecret: inputs.ClientSecret, - Domain: inputs.Domain, - }) - if err != nil { - return err - } - - t := Tenant{ - Domain: inputs.Domain, - AccessToken: token.AccessToken, - ExpiresAt: token.ExpiresAt, - ClientID: inputs.ClientID, - ClientSecret: inputs.ClientSecret, - } - - if err := cli.addTenant(t); err != nil { - return fmt.Errorf("unexpected error when attempting to save tenant data: %w", err) - } - - cli.renderer.Infof("Tenant added successfully: %s", t.Domain) - return nil - }, - } - - tenantClientID.RegisterString(cmd, &inputs.ClientID, "") - tenantClientSecret.RegisterString(cmd, &inputs.ClientSecret, "") - - return cmd -} - func (c *cli) tenantPickerOptions() (pickerOptions, error) { tens, err := c.listTenants() if err != nil { diff --git a/test/integration/test-cases.yaml b/test/integration/test-cases.yaml index c3b071bdc..8457936a6 100644 --- a/test/integration/test-cases.yaml +++ b/test/integration/test-cases.yaml @@ -37,7 +37,7 @@ tests: auth0 completion bash: exit-code: 0 - + # Test 'apps create' --type flag apps create type native and check data: command: auth0 apps create --name integration-test-app-nativeapp1 --type native --description NativeApp1 --json @@ -109,7 +109,7 @@ tests: json: token_endpoint_auth_method: client_secret_basic exit-code: 0 - + # Test 'apps create' --callbacks flag apps create type m2m callbacks: command: auth0 apps create --name integration-test-app-m2mapp3 --type m2m --description M2mApp3 --callbacks https://example.com @@ -124,7 +124,7 @@ tests: json: callbacks: "[https://example.com https://google.com]" exit-code: 0 - + # Test 'apps create' --grants flag apps create type regular grants: command: auth0 apps create --name integration-test-app-regapp5 --type regular --description RegApp4 --grants credentials,password @@ -212,7 +212,7 @@ tests: json: callbacks: "[https://example.com]" exit-code: 0 - + apps update description: command: auth0 apps update $(cat ./test/integration/identifiers/app-id) --description "A better description" --json stdout: @@ -350,10 +350,10 @@ tests: command: auth0 apis show $(cat ./test/integration/identifiers/api-id) # depends on "apis create test app" test stdout: contains: - - NAME integration-test-api-newapi - - IDENTIFIER http://integration-test-api-newapi - - SCOPES read:todos - - TOKEN LIFETIME 86400 + - NAME integration-test-api-newapi + - IDENTIFIER http://integration-test-api-newapi + - SCOPES read:todos + - TOKEN LIFETIME 86400 - ALLOW OFFLINE ACCESS ✗ exit-code: 0 @@ -403,7 +403,7 @@ tests: exit-code: 0 stdout: json: - email: "testuser@example.com" + email: "testuser@example.com" connection: "Username-Password-Authentication" users create and check output: @@ -423,7 +423,7 @@ tests: command: auth0 users show $(cat ./test/integration/identifiers/user-id) --json stdout: json: - email: "newuser@example.com" + email: "newuser@example.com" connection: "Username-Password-Authentication" exit-code: 0 @@ -456,8 +456,8 @@ tests: exit-code: 0 stdout: json: - name: integration-test-role-new1 - description: testRole + name: integration-test-role-new1 + description: testRole roles create and check output: command: auth0 roles create --name integration-test-role-new2 --description testRole2 --no-input @@ -476,7 +476,7 @@ tests: command: auth0 roles show $(cat ./test/integration/identifiers/role-id) --json stdout: json: - name: integration-test-role-newRole + name: integration-test-role-newRole description: integration-test-role exit-code: 0 @@ -521,7 +521,7 @@ tests: - NAME integration-test-rule-new2 - ENABLED ✗ - ORDER 2 - - SCRIPT function(user, context, cb) { + - SCRIPT function(user, context, cb) { exit-code: 0 # Test 'rules show'