From 57b23b4ba7eae453a0830cc1bf4c3a5ad9ccfc61 Mon Sep 17 00:00:00 2001 From: Cyril David Date: Fri, 5 Mar 2021 17:09:56 -0800 Subject: [PATCH 1/4] add logout command Context: we'll want to give our customers the ability to clear out their sessions and prune out any access tokens. --- internal/cli/cli.go | 32 ++++++++++++++++++++++++++++++++ internal/cli/root.go | 7 +++++++ 2 files changed, 39 insertions(+) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 5705fcefb..1f3305813 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -229,6 +229,38 @@ func (c *cli) addTenant(ten tenant) error { return nil } +func (c *cli) removeTenant(ten string) error { + // init will fail here with a `no tenant found` error if we're logging + // in for the first time and that's expected. + _ = c.init() + + // If we're dealing with an empty file, we'll need to initialize this + // map. + if c.config.Tenants == nil { + c.config.Tenants = map[string]tenant{} + } + + // If the default tenant is being removed, we'll pick the first tenant + // that's not the one being removed, and make that the new default. + if c.config.DefaultTenant == ten { + Loop: + for t := range c.config.Tenants { + if t != ten { + c.config.DefaultTenant = t + break Loop + } + } + } + + delete(c.config.Tenants, ten) + + if err := c.persistConfig(); err != nil { + return fmt.Errorf("persisting config: %w", err) + } + + return nil +} + func (c *cli) persistConfig() error { dir := filepath.Dir(c.path) if _, err := os.Stat(dir); os.IsNotExist(err) { diff --git a/internal/cli/root.go b/internal/cli/root.go index e18bceb75..9c3292e17 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -34,6 +34,12 @@ func Execute() { return nil } + // If the user is trying to logout, session information + // isn't important as well. + if cmd.Use == "logout" && cmd.Parent().Use == "auth0" { + return nil + } + // Selecting tenants shouldn't really trigger a login. if cmd.Use == "use" && cmd.Parent().Use == "tenants" { return nil @@ -72,6 +78,7 @@ func Execute() { rootCmd.AddCommand(testCmd(cli)) rootCmd.AddCommand(logsCmd(cli)) rootCmd.AddCommand(actionsCmd(cli)) + rootCmd.AddCommand(logoutCmd(cli)) // keep completion at the bottom: rootCmd.AddCommand(completionCmd(cli)) From 94c904bd3e9385e0e02358c30654417ec3e5619a Mon Sep 17 00:00:00 2001 From: Cyril David Date: Fri, 5 Mar 2021 17:12:30 -0800 Subject: [PATCH 2/4] Add logoutCmd --- internal/cli/logout.go | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 internal/cli/logout.go diff --git a/internal/cli/logout.go b/internal/cli/logout.go new file mode 100644 index 000000000..4f7d7c1fb --- /dev/null +++ b/internal/cli/logout.go @@ -0,0 +1,53 @@ +package cli + +import ( + "fmt" + + "github.com/auth0/auth0-cli/internal/prompt" + "github.com/spf13/cobra" +) + +func logoutCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "logout", + Short: "Logout of a tenant's session", + Long: `auth0 logout `, + RunE: func(cmd *cobra.Command, args []string) error { + // NOTE(cyx): This was mostly copy/pasted from tenants + // use command. Consider refactoring. + var selectedTenant string + if len(args) == 0 { + tens, err := cli.listTenants() + if err != nil { + return fmt.Errorf("Unable to load tenants due to an unexpected error: %w", err) + } + + tenNames := make([]string, len(tens)) + for i, t := range tens { + tenNames[i] = t.Name + } + + 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) + } + } else { + requestedTenant := args[0] + t, ok := cli.config.Tenants[requestedTenant] + if !ok { + return fmt.Errorf("Unable to find tenant %s; run `auth0 tenants use` to see your configured tenants or run `auth0 login` to configure a new tenant", requestedTenant) + } + selectedTenant = t.Name + } + + if err := cli.removeTenant(selectedTenant); err != nil { + return fmt.Errorf("Unexpected error logging out tenant: %s: %v", selectedTenant, err) + } + + cli.renderer.Infof("Successfully logged out tenant: %s", selectedTenant) + return nil + }, + } + + return cmd +} From 2ad756ee02ef852181ee471dc037d6396b553b0d Mon Sep 17 00:00:00 2001 From: Cyril David Date: Fri, 5 Mar 2021 17:30:36 -0800 Subject: [PATCH 3/4] Handle case for when tenants is empty after removing --- internal/cli/cli.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 1f3305813..9ecb0a41a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -240,20 +240,24 @@ func (c *cli) removeTenant(ten string) error { c.config.Tenants = map[string]tenant{} } + delete(c.config.Tenants, ten) + // If the default tenant is being removed, we'll pick the first tenant // that's not the one being removed, and make that the new default. if c.config.DefaultTenant == ten { - Loop: - for t := range c.config.Tenants { - if t != ten { - c.config.DefaultTenant = t - break Loop + if len(c.config.Tenants) == 0 { + c.config.DefaultTenant = "" + } else { + Loop: + for t := range c.config.Tenants { + if t != ten { + c.config.DefaultTenant = t + break Loop + } } } } - delete(c.config.Tenants, ten) - if err := c.persistConfig(); err != nil { return fmt.Errorf("persisting config: %w", err) } From d46f61d3dff783bb5753a61700362c910f906a00 Mon Sep 17 00:00:00 2001 From: Cyril David Date: Fri, 5 Mar 2021 18:03:48 -0800 Subject: [PATCH 4/4] Remove refresh token as well on logout --- internal/auth/auth.go | 2 ++ internal/auth/secrets.go | 5 +++++ internal/auth/token.go | 5 +++++ internal/cli/cli.go | 7 ++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 4877c550a..878c6f57b 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -36,6 +36,8 @@ type SecretStore interface { Set(namespace, key, value string) error // Get gets the secret Get(namespace, key string) (string, error) + // Delete removes the secret + Delete(namespace, key string) error } type Authenticator struct { diff --git a/internal/auth/secrets.go b/internal/auth/secrets.go index 06f76820e..d3967107b 100644 --- a/internal/auth/secrets.go +++ b/internal/auth/secrets.go @@ -13,3 +13,8 @@ func (k *Keyring) Set(namespace, key, value string) error { func (k *Keyring) Get(namespace, key string) (string, error) { return keyring.Get(namespace, key) } + +// Delete deletes a value for the given namespace and key. +func (k *Keyring) Delete(namespace, key string) error { + return keyring.Delete(namespace, key) +} diff --git a/internal/auth/token.go b/internal/auth/token.go index 7fee8d290..3395810d9 100644 --- a/internal/auth/token.go +++ b/internal/auth/token.go @@ -22,6 +22,11 @@ type TokenRetriever struct { Client *http.Client } +// Delete deletes the given tenant from the secrets storage. +func (t *TokenRetriever) Delete(tenant string) error { + return t.Secrets.Delete(secretsNamespace, tenant) +} + // Refresh gets a new access token from the provided refresh token, // The request is used the default client_id and endpoint for device authentication. func (t *TokenRetriever) Refresh(ctx context.Context, tenant string) (TokenResponse, error) { diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 9ecb0a41a..8ef322dc7 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -259,7 +259,12 @@ func (c *cli) removeTenant(ten string) error { } if err := c.persistConfig(); err != nil { - return fmt.Errorf("persisting config: %w", err) + return fmt.Errorf("Unexpected error persisting config: %w", err) + } + + tr := &auth.TokenRetriever{Secrets: &auth.Keyring{}} + if err := tr.Delete(ten); err != nil { + return fmt.Errorf("Unexpected error clearing tenant information: %w", err) } return nil