From c5027b6c09369881d33efac2f90e4e4ed17e1581 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 14:38:46 -0500 Subject: [PATCH 01/11] Storing and refreshing access token for client credentials --- internal/auth/auth.go | 45 ++++++++++++++++++++++-- internal/cli/cli.go | 78 +++++++++++++++++++++++++---------------- internal/cli/login.go | 7 ++-- internal/cli/tenants.go | 22 ++++++++++++ 4 files changed, 114 insertions(+), 38 deletions(-) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 426e448ef..18f3be8b8 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -14,6 +14,7 @@ import ( "time" "github.com/joeshaw/envdecode" + "golang.org/x/oauth2/clientcredentials" ) const ( @@ -65,7 +66,7 @@ type Result struct { Domain string RefreshToken string AccessToken string - ExpiresIn int64 + ExpiresAt time.Time } type State struct { @@ -167,10 +168,14 @@ func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) { return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err) } + expiresAt := time.Now().Add( + time.Duration(res.ExpiresIn) * time.Second, + ) + return Result{ RefreshToken: res.RefreshToken, AccessToken: res.AccessToken, - ExpiresIn: res.ExpiresIn, + ExpiresAt: expiresAt, Tenant: ten, Domain: domain, }, nil @@ -249,3 +254,39 @@ func parseTenant(accessToken string) (tenant, domain string, err error) { } return "", "", fmt.Errorf("audience not found for %s", audiencePath) } + +// ClientCredentials encapsulates all data to facilitate access token creation with client credentials (client ID and client secret) +type ClientCredentials struct { + ClientID string + ClientSecret string + Domain string +} + +// GetAccessTokenFromClientCreds generates an access token from client credentials +func GetAccessTokenFromClientCreds(args ClientCredentials) (Result, error) { + u, err := url.Parse("https://" + args.Domain) + if err != nil { + return Result{}, err + } + + credsConfig := &clientcredentials.Config{ + ClientID: args.ClientID, + ClientSecret: args.ClientSecret, + TokenURL: u.String() + "/oauth/token", + EndpointParams: url.Values{ + "client_id": {args.ClientID}, + "scope": {strings.Join(RequiredScopesMin(), " ")}, + "audience": {u.String() + "/api/v2/"}, + }, + } + + resp, err := credsConfig.Token(context.Background()) + if err != nil { + return Result{}, err + } + + return Result{ + AccessToken: resp.AccessToken, + ExpiresAt: resp.Expiry, + }, nil +} diff --git a/internal/cli/cli.go b/internal/cli/cli.go index e082f80ad..829eb0c71 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -138,17 +138,10 @@ func (c *cli) setup(ctx context.Context) error { ua = fmt.Sprintf("%v/%v", userAgent, strings.TrimPrefix(buildinfo.Version, "v")) ) - if t.ClientID != "" && t.ClientSecret != "" { - m, err = management.New(t.Domain, - management.WithClientCredentials(t.ClientID, t.ClientSecret), - management.WithUserAgent(ua), - ) - } else { - m, err = management.New(t.Domain, - management.WithStaticToken(t.AccessToken), - management.WithUserAgent(ua), - ) - } + m, err = management.New(t.Domain, + management.WithStaticToken(t.AccessToken), + management.WithUserAgent(ua), + ) if err != nil { return err @@ -168,27 +161,48 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { return Tenant{}, err } - if t.ClientID != "" && t.ClientSecret != "" { - return t, nil - } - if t.AccessToken == "" || scopesChanged(t) { t, err = RunLogin(ctx, c, true) if err != nil { return Tenant{}, err } - } else if isExpired(t.ExpiresAt, accessTokenExpThreshold) { - // check if the stored access token is expired: - // use the refresh token to get a new access token: + return t, nil + } + + if isExpired(t.ExpiresAt, accessTokenExpThreshold) { + // regenerate access token for client credential auth'd tenants + if t.ClientID != "" && t.ClientSecret != "" { + token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ + ClientID: t.ClientID, + ClientSecret: t.ClientSecret, + Domain: t.Domain, + }) + + if err != nil { + return t, err + } + + t := Tenant{ + Name: t.Domain, + Domain: t.Domain, + AccessToken: token.AccessToken, + ExpiresAt: token.ExpiresAt, + Scopes: auth.RequiredScopes(), + } + + if err := c.addTenant(t); err != nil { + return t, fmt.Errorf("unexpected error adding tenant to config: %w", err) + } + return t, nil + } + + // regenerate access token for device auth'd tenants tr := &auth.TokenRetriever{ Authenticator: c.authenticator, Secrets: &auth.Keyring{}, Client: http.DefaultClient, } - // NOTE(cyx): this code will have to be adapted to instead - // maybe take the clientID/secret as additional params, or - // something similar. res, err := tr.Refresh(ctx, t.Domain) if err != nil { // ask and guide the user through the login process: @@ -197,18 +211,20 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { if err != nil { return Tenant{}, err } - } else { - // persist the updated tenant with renewed access token - t.AccessToken = res.AccessToken - t.ExpiresAt = time.Now().Add( - time.Duration(res.ExpiresIn) * time.Second, - ) + return t, nil + } - err = c.addTenant(t) - if err != nil { - return Tenant{}, err - } + // persist the updated tenant with renewed access token + t.AccessToken = res.AccessToken + t.ExpiresAt = time.Now().Add( + time.Duration(res.ExpiresIn) * time.Second, + ) + + err = c.addTenant(t) + if err != nil { + return Tenant{}, err } + } return t, nil diff --git a/internal/cli/login.go b/internal/cli/login.go index c5f7d598a..2bdac5460 100644 --- a/internal/cli/login.go +++ b/internal/cli/login.go @@ -3,7 +3,6 @@ package cli import ( "context" "fmt" - "time" "github.com/pkg/browser" "github.com/spf13/cobra" @@ -109,10 +108,8 @@ func RunLogin(ctx context.Context, cli *cli, expired bool) (Tenant, error) { Name: result.Tenant, Domain: result.Domain, AccessToken: result.AccessToken, - ExpiresAt: time.Now().Add( - time.Duration(result.ExpiresIn) * time.Second, - ), - Scopes: auth.RequiredScopes(), + ExpiresAt: result.ExpiresAt, + Scopes: auth.RequiredScopes(), } err = cli.addTenant(tenant) diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index f199d62c9..9bfe1629a 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" + "github.com/auth0/auth0-cli/internal/auth" "github.com/auth0/auth0-cli/internal/prompt" ) @@ -188,12 +189,30 @@ func addTenantCmd(cli *cli) *cobra.Command { return err } + token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ + ClientID: inputs.ClientID, + ClientSecret: inputs.ClientSecret, + Domain: inputs.Domain, + }) + + if err != nil { + return err + } + t := Tenant{ + Name: inputs.Domain, Domain: inputs.Domain, + AccessToken: token.AccessToken, + ExpiresAt: token.ExpiresAt, + Scopes: auth.RequiredScopes(), 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) + } + if err := cli.addTenant(t); err != nil { return err } @@ -203,6 +222,9 @@ func addTenantCmd(cli *cli) *cobra.Command { }, } + tenantClientID.RegisterString(cmd, &inputs.ClientID, "") + tenantClientSecret.RegisterString(cmd, &inputs.ClientSecret, "") + return cmd } From 5dd06884223ded8a02a0bdd9357f091691c30519 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 14:40:29 -0500 Subject: [PATCH 02/11] Removing unnecessary comment --- internal/cli/cli.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 829eb0c71..4ca2f62bf 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -214,7 +214,6 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { return t, nil } - // persist the updated tenant with renewed access token t.AccessToken = res.AccessToken t.ExpiresAt = time.Now().Add( time.Duration(res.ExpiresIn) * time.Second, From 972c1913cce87d9a34f709dfc3841e6e06e0cf88 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 14:50:47 -0500 Subject: [PATCH 03/11] Removing tenant name from being stored, removing flag declarations --- internal/cli/cli.go | 1 - internal/cli/tenants.go | 3 --- 2 files changed, 4 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 4ca2f62bf..b5a19f4b5 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -183,7 +183,6 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { } t := Tenant{ - Name: t.Domain, Domain: t.Domain, AccessToken: token.AccessToken, ExpiresAt: token.ExpiresAt, diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 9bfe1629a..287479bce 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -222,9 +222,6 @@ func addTenantCmd(cli *cli) *cobra.Command { }, } - tenantClientID.RegisterString(cmd, &inputs.ClientID, "") - tenantClientSecret.RegisterString(cmd, &inputs.ClientSecret, "") - return cmd } From 0b57340f212e5729748a34637d368a43f4ba25ae Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 15:13:25 -0500 Subject: [PATCH 04/11] Removing tenant name from being stored --- internal/cli/tenants.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 287479bce..3d5abf8cc 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -178,7 +178,7 @@ func addTenantCmd(cli *cli) *cobra.Command { } } } else { - inputs.Domain = args[0] + = args[0] } if err := tenantClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { @@ -200,7 +200,6 @@ func addTenantCmd(cli *cli) *cobra.Command { } t := Tenant{ - Name: inputs.Domain, Domain: inputs.Domain, AccessToken: token.AccessToken, ExpiresAt: token.ExpiresAt, From 2247df1de209661ed3691c2e277b2894805233a4 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 15:14:36 -0500 Subject: [PATCH 05/11] Fixing erroneous delete --- internal/cli/tenants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 3d5abf8cc..38dbb172d 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -178,7 +178,7 @@ func addTenantCmd(cli *cli) *cobra.Command { } } } else { - = args[0] + inputs.Domain = args[0] } if err := tenantClientID.Ask(cmd, &inputs.ClientID, nil); err != nil { From 6219b1379a2f3a2da25b5b736534cea16f83db2c Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Thu, 1 Dec 2022 15:15:39 -0500 Subject: [PATCH 06/11] Simplifying ExpiresAt assignment --- internal/auth/auth.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 18f3be8b8..1168cdb0c 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -168,16 +168,14 @@ func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) { return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err) } - expiresAt := time.Now().Add( - time.Duration(res.ExpiresIn) * time.Second, - ) - return Result{ RefreshToken: res.RefreshToken, AccessToken: res.AccessToken, - ExpiresAt: expiresAt, - Tenant: ten, - Domain: domain, + ExpiresAt: time.Now().Add( + time.Duration(res.ExpiresIn) * time.Second, + ), + Tenant: ten, + Domain: domain, }, nil } } From 1a0d1b2e5a66a76f9d33784f37368bf2a1ec071e Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea Date: Fri, 2 Dec 2022 15:42:50 +0100 Subject: [PATCH 07/11] Remove duplicate addTenant in tenants add command --- internal/cli/tenants.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 38dbb172d..154d73314 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -194,7 +194,6 @@ func addTenantCmd(cli *cli) *cobra.Command { ClientSecret: inputs.ClientSecret, Domain: inputs.Domain, }) - if err != nil { return err } @@ -209,11 +208,7 @@ func addTenantCmd(cli *cli) *cobra.Command { } if err := cli.addTenant(t); err != nil { - return fmt.Errorf("unexpected error when attempting to save tenant data: %w. ", err) - } - - if err := cli.addTenant(t); err != nil { - return err + return fmt.Errorf("unexpected error when attempting to save tenant data: %w", err) } cli.renderer.Infof("Tenant added successfully: %s", t.Domain) From 9fa07dbaa7626fedb3ba201a8a735abfd702800a Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea Date: Fri, 2 Dec 2022 15:48:25 +0100 Subject: [PATCH 08/11] Remove setting scopes on tenant when using client credentials --- internal/cli/cli.go | 18 +++++++++++------- internal/cli/tenants.go | 1 - 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index b5a19f4b5..e01fe3474 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -94,6 +94,14 @@ type cli struct { config config } +func (t *Tenant) AuthenticatedWithClientCredentials() bool { + return t.ClientID != "" && t.ClientSecret != "" +} + +func (t *Tenant) AuthenticatedWithDeviceCodeFlow() bool { + return t.ClientID == "" && t.ClientSecret == "" +} + // isLoggedIn encodes the domain logic for determining whether or not we're // logged in. This might check our config storage, or just in memory. func (c *cli) isLoggedIn() bool { @@ -161,17 +169,13 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { return Tenant{}, err } - if t.AccessToken == "" || scopesChanged(t) { - t, err = RunLogin(ctx, c, true) - if err != nil { - return Tenant{}, err - } - return t, nil + if t.AccessToken == "" || (scopesChanged(t) && t.AuthenticatedWithDeviceCodeFlow()) { + return RunLogin(ctx, c, true) } if isExpired(t.ExpiresAt, accessTokenExpThreshold) { // regenerate access token for client credential auth'd tenants - if t.ClientID != "" && t.ClientSecret != "" { + if t.AuthenticatedWithClientCredentials() { token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ ClientID: t.ClientID, ClientSecret: t.ClientSecret, diff --git a/internal/cli/tenants.go b/internal/cli/tenants.go index 154d73314..b028da9b0 100644 --- a/internal/cli/tenants.go +++ b/internal/cli/tenants.go @@ -202,7 +202,6 @@ func addTenantCmd(cli *cli) *cobra.Command { Domain: inputs.Domain, AccessToken: token.AccessToken, ExpiresAt: token.ExpiresAt, - Scopes: auth.RequiredScopes(), ClientID: inputs.ClientID, ClientSecret: inputs.ClientSecret, } From cd306af17b7bba930a3dc366ab6fddb073669a58 Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea Date: Fri, 2 Dec 2022 16:03:29 +0100 Subject: [PATCH 09/11] Refactor how we check for token expiration while preparing the tenant --- internal/cli/cli.go | 91 ++++++++++++++++++++-------------------- internal/cli/cli_test.go | 50 ++++++++++++---------- 2 files changed, 74 insertions(+), 67 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index e01fe3474..98c68c7d9 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -102,6 +102,10 @@ func (t *Tenant) AuthenticatedWithDeviceCodeFlow() bool { return t.ClientID == "" && t.ClientSecret == "" } +func (t *Tenant) HasExpiredToken() bool { + return time.Now().Add(accessTokenExpThreshold).After(t.ExpiresAt) +} + // isLoggedIn encodes the domain logic for determining whether or not we're // logged in. This might check our config storage, or just in memory. func (c *cli) isLoggedIn() bool { @@ -173,70 +177,65 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { return RunLogin(ctx, c, true) } - if isExpired(t.ExpiresAt, accessTokenExpThreshold) { - // regenerate access token for client credential auth'd tenants - if t.AuthenticatedWithClientCredentials() { - token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ - ClientID: t.ClientID, - ClientSecret: t.ClientSecret, - Domain: t.Domain, - }) - - if err != nil { - return t, err - } + if !t.HasExpiredToken() { + return t, nil + } - t := Tenant{ - Domain: t.Domain, - AccessToken: token.AccessToken, - ExpiresAt: token.ExpiresAt, - Scopes: auth.RequiredScopes(), - } + // regenerate access token for client credential auth'd tenants + if t.AuthenticatedWithClientCredentials() { + token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ + ClientID: t.ClientID, + ClientSecret: t.ClientSecret, + Domain: t.Domain, + }) - if err := c.addTenant(t); err != nil { - return t, fmt.Errorf("unexpected error adding tenant to config: %w", err) - } - return t, nil + if err != nil { + return t, err } - // regenerate access token for device auth'd tenants - tr := &auth.TokenRetriever{ - Authenticator: c.authenticator, - Secrets: &auth.Keyring{}, - Client: http.DefaultClient, + t := Tenant{ + Domain: t.Domain, + AccessToken: token.AccessToken, + ExpiresAt: token.ExpiresAt, + Scopes: auth.RequiredScopes(), } - res, err := tr.Refresh(ctx, t.Domain) - if err != nil { - // ask and guide the user through the login process: - c.renderer.Errorf("failed to renew access token, %s", err) - t, err = RunLogin(ctx, c, true) - if err != nil { - return Tenant{}, err - } - return t, nil + if err := c.addTenant(t); err != nil { + return t, fmt.Errorf("unexpected error adding tenant to config: %w", err) } + return t, nil + } - t.AccessToken = res.AccessToken - t.ExpiresAt = time.Now().Add( - time.Duration(res.ExpiresIn) * time.Second, - ) + // regenerate access token for device auth'd tenants + tr := &auth.TokenRetriever{ + Authenticator: c.authenticator, + Secrets: &auth.Keyring{}, + Client: http.DefaultClient, + } - err = c.addTenant(t) + res, err := tr.Refresh(ctx, t.Domain) + if err != nil { + // ask and guide the user through the login process: + c.renderer.Errorf("failed to renew access token, %s", err) + t, err = RunLogin(ctx, c, true) if err != nil { return Tenant{}, err } + return t, nil + } + t.AccessToken = res.AccessToken + t.ExpiresAt = time.Now().Add( + time.Duration(res.ExpiresIn) * time.Second, + ) + + if err = c.addTenant(t); err != nil { + return Tenant{}, err } return t, nil } -// isExpired is true if now() + a threshold is after the given date. -func isExpired(t time.Time, threshold time.Duration) bool { - return time.Now().Add(threshold).After(t) -} - // scopesChanged compare the tenant scopes // with the currently required scopes. func scopesChanged(t Tenant) bool { diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 290217f0b..b967dc46a 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -16,27 +16,35 @@ import ( "github.com/auth0/auth0-cli/internal/display" ) -func TestIsExpired(t *testing.T) { - t.Run("is expired", func(t *testing.T) { - d := time.Date(2021, 01, 01, 10, 30, 30, 0, time.UTC) - if want, got := true, isExpired(d, 1*time.Minute); want != got { - t.Fatalf("wanted: %v, got %v", want, got) - } - }) - - t.Run("expired because of the threshold", func(t *testing.T) { - d := time.Now().Add(-2 * time.Minute) - if want, got := true, isExpired(d, 5*time.Minute); want != got { - t.Fatalf("wanted: %v, got %v", want, got) - } - }) - - t.Run("is not expired", func(t *testing.T) { - d := time.Now().Add(10 * time.Minute) - if want, got := false, isExpired(d, 5*time.Minute); want != got { - t.Fatalf("wanted: %v, got %v", want, got) - } - }) +func TestTenant_HasExpiredToken(t *testing.T) { + var testCases = []struct { + name string + givenTime time.Time + expectedTokenToBeExpired bool + }{ + { + name: "is expired", + givenTime: time.Date(2021, 01, 01, 10, 30, 30, 0, time.UTC), + expectedTokenToBeExpired: true, + }, + { + name: "expired because of the threshold", + givenTime: time.Now().Add(-2 * time.Minute), + expectedTokenToBeExpired: true, + }, + { + name: "is not expired", + givenTime: time.Now().Add(10 * time.Minute), + expectedTokenToBeExpired: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + tenant := Tenant{ExpiresAt: testCase.givenTime} + assert.Equal(t, testCase.expectedTokenToBeExpired, tenant.HasExpiredToken()) + }) + } } // TODO(cyx): think about whether we should extract this function in the From 5abe26a7c80feafdcad851f72ddef3d8417f7f1c Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea Date: Fri, 2 Dec 2022 16:28:37 +0100 Subject: [PATCH 10/11] Refactor cli.prepareTenant func --- internal/cli/cli.go | 98 +++++++++++++++++++--------------------- internal/cli/cli_test.go | 2 +- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 98c68c7d9..971a645cd 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -94,18 +94,54 @@ type cli struct { config config } -func (t *Tenant) AuthenticatedWithClientCredentials() bool { +func (t *Tenant) authenticatedWithClientCredentials() bool { return t.ClientID != "" && t.ClientSecret != "" } -func (t *Tenant) AuthenticatedWithDeviceCodeFlow() bool { +func (t *Tenant) authenticatedWithDeviceCodeFlow() bool { return t.ClientID == "" && t.ClientSecret == "" } -func (t *Tenant) HasExpiredToken() bool { +func (t *Tenant) hasExpiredToken() bool { return time.Now().Add(accessTokenExpThreshold).After(t.ExpiresAt) } +func (t *Tenant) regenerateAccessToken(ctx context.Context, c *cli) error { + if t.authenticatedWithClientCredentials() { + token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ + ClientID: t.ClientID, + ClientSecret: t.ClientSecret, + Domain: t.Domain, + }) + if err != nil { + return err + } + + t.AccessToken = token.AccessToken + t.ExpiresAt = token.ExpiresAt + } + + if t.authenticatedWithDeviceCodeFlow() { + tokenRetriever := &auth.TokenRetriever{ + Authenticator: c.authenticator, + Secrets: &auth.Keyring{}, + Client: http.DefaultClient, + } + + tokenResponse, err := tokenRetriever.Refresh(ctx, t.Domain) + if err != nil { + return err + } + + t.AccessToken = tokenResponse.AccessToken + t.ExpiresAt = time.Now().Add( + time.Duration(tokenResponse.ExpiresIn) * time.Second, + ) + } + + return nil +} + // isLoggedIn encodes the domain logic for determining whether or not we're // logged in. This might check our config storage, or just in memory. func (c *cli) isLoggedIn() bool { @@ -173,64 +209,22 @@ func (c *cli) prepareTenant(ctx context.Context) (Tenant, error) { return Tenant{}, err } - if t.AccessToken == "" || (scopesChanged(t) && t.AuthenticatedWithDeviceCodeFlow()) { + if t.AccessToken == "" || (scopesChanged(t) && t.authenticatedWithDeviceCodeFlow()) { return RunLogin(ctx, c, true) } - if !t.HasExpiredToken() { + if !t.hasExpiredToken() { return t, nil } - // regenerate access token for client credential auth'd tenants - if t.AuthenticatedWithClientCredentials() { - token, err := auth.GetAccessTokenFromClientCreds(auth.ClientCredentials{ - ClientID: t.ClientID, - ClientSecret: t.ClientSecret, - Domain: t.Domain, - }) - - if err != nil { - return t, err - } - - t := Tenant{ - Domain: t.Domain, - AccessToken: token.AccessToken, - ExpiresAt: token.ExpiresAt, - Scopes: auth.RequiredScopes(), - } - - if err := c.addTenant(t); err != nil { - return t, fmt.Errorf("unexpected error adding tenant to config: %w", err) - } - return t, nil - } - - // regenerate access token for device auth'd tenants - tr := &auth.TokenRetriever{ - Authenticator: c.authenticator, - Secrets: &auth.Keyring{}, - Client: http.DefaultClient, - } - - res, err := tr.Refresh(ctx, t.Domain) - if err != nil { - // ask and guide the user through the login process: + 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) - t, err = RunLogin(ctx, c, true) - if err != nil { - return Tenant{}, err - } - return t, nil + return RunLogin(ctx, c, true) } - t.AccessToken = res.AccessToken - t.ExpiresAt = time.Now().Add( - time.Duration(res.ExpiresIn) * time.Second, - ) - - if err = c.addTenant(t); err != nil { - return Tenant{}, err + if err := c.addTenant(t); err != nil { + return Tenant{}, fmt.Errorf("unexpected error adding tenant to config: %w", err) } return t, nil diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index b967dc46a..e8bbae59c 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -42,7 +42,7 @@ func TestTenant_HasExpiredToken(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { tenant := Tenant{ExpiresAt: testCase.givenTime} - assert.Equal(t, testCase.expectedTokenToBeExpired, tenant.HasExpiredToken()) + assert.Equal(t, testCase.expectedTokenToBeExpired, tenant.hasExpiredToken()) }) } } From 4e611267ebba6f11c7594583bb340d5b44ecd3d2 Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea Date: Fri, 2 Dec 2022 16:31:42 +0100 Subject: [PATCH 11/11] Refactor cli.setup func --- internal/cli/cli.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 971a645cd..e30837d9d 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -181,21 +181,18 @@ func (c *cli) setup(ctx context.Context) error { return err } - var ( - m *management.Management - ua = fmt.Sprintf("%v/%v", userAgent, strings.TrimPrefix(buildinfo.Version, "v")) - ) + userAgent := fmt.Sprintf("%v/%v", userAgent, strings.TrimPrefix(buildinfo.Version, "v")) - m, err = management.New(t.Domain, + api, err := management.New( + t.Domain, management.WithStaticToken(t.AccessToken), - management.WithUserAgent(ua), + management.WithUserAgent(userAgent), ) - if err != nil { return err } - c.api = auth0.NewAPI(m) + c.api = auth0.NewAPI(api) return nil }