From 70d83063d020c772c81bee9f393815f5bac60bbb Mon Sep 17 00:00:00 2001 From: lazydiv Date: Sat, 4 Jul 2026 15:16:40 +0300 Subject: [PATCH] fix: always prompt for token in `doctl auth init` Previously, `auth init` would skip the token prompt if an existing token was found in the config, and instead immediately validate the old token. If that token was expired or revoked, the user received a 401 error with no opportunity to enter a new one. Now `auth init` always prompts for a new token (its intended purpose), unless a token is explicitly provided via the `--access-token` flag. --- commands/auth.go | 11 +++++++---- commands/auth_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/commands/auth.go b/commands/auth.go index fa2d3cd70..120605c5c 100644 --- a/commands/auth.go +++ b/commands/auth.go @@ -158,20 +158,23 @@ To create new contexts, see the help for `+"`"+`doctl auth init`+"`"+`.`, Writer // XDG_CONFIG_HOME is not set, use $HOME/.config. On Windows use %APPDATA%/doctl/config. func RunAuthInit(retrieveUserTokenFunc func() (string, error)) func(c *CmdConfig) error { return func(c *CmdConfig) error { - token := c.getContextAccessToken() context := strings.ToLower(Context) if context == "" { context = strings.ToLower(viper.GetString("context")) } - if token == "" { + var token string + if Token != "" { + // Use the token provided via --access-token flag. + token = Token + } else { + // Always prompt for a new token, even if one exists in the config. + // This ensures the user can replace an expired or revoked token. in, err := retrieveUserTokenFunc() if err != nil { return fmt.Errorf("Unable to read DigitalOcean access token: %s", err) } token = strings.TrimSpace(in) - } else { - template.Render(c.Out, `Using token for context {{highlight .}}{{nl}}`, context) } c.setContextAccessToken(token) diff --git a/commands/auth_test.go b/commands/auth_test.go index 72c5d457b..e63ff8024 100644 --- a/commands/auth_test.go +++ b/commands/auth_test.go @@ -103,9 +103,12 @@ func TestAuthInitConfig(t *testing.T) { func TestAuthInitWithProvidedToken(t *testing.T) { cfw := cfgFileWriter + origToken := Token + Token = "valid-token" viper.Set(doctl.ArgAccessToken, "valid-token") defer func() { cfgFileWriter = cfw + Token = origToken viper.Set(doctl.ArgAccessToken, nil) }() @@ -123,11 +126,37 @@ func TestAuthInitWithProvidedToken(t *testing.T) { }) } +func TestAuthInitPromptsWhenTokenExists(t *testing.T) { + cfw := cfgFileWriter + // Simulate an existing (possibly expired) token in the config. + viper.Set(doctl.ArgAccessToken, "expired-token") + defer func() { + cfgFileWriter = cfw + viper.Set(doctl.ArgAccessToken, nil) + }() + + retrieveUserTokenFunc := func() (string, error) { + return "new-valid-token", nil + } + + cfgFileWriter = func() (io.WriteCloser, error) { return &nopWriteCloser{Writer: io.Discard}, nil } + + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.oauth.EXPECT().TokenInfo(gomock.Any()).Return(&do.OAuthTokenInfo{}, nil) + + err := RunAuthInit(retrieveUserTokenFunc)(config) + assert.NoError(t, err) + }) +} + func TestAuthForcesLowercase(t *testing.T) { cfw := cfgFileWriter + origToken := Token + Token = "valid-token" viper.Set(doctl.ArgAccessToken, "valid-token") defer func() { cfgFileWriter = cfw + Token = origToken viper.Set(doctl.ArgAccessToken, nil) }()