Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/cli/auth-token-error-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Add an `INVALID_REFRESH_TOKEN` error code to `databricks auth token --output json` failures. ([#6681](https://github.com/databricks/cli/pull/6681))
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
Error: A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:
$ databricks auth login --profile test-profile
{
"error_code": "INVALID_REFRESH_TOKEN",
"message": "A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:\n $ databricks auth login --profile test-profile"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ setup_test_profile
setup_test_token_cache

musterr $CLI auth token --profile test-profile --force-refresh
musterr $CLI auth token --profile test-profile --force-refresh --output json
24 changes: 24 additions & 0 deletions cmd/auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import (
"golang.org/x/oauth2"
)

const invalidRefreshTokenErrorCode = "INVALID_REFRESH_TOKEN"

type tokenErrorOutput struct {
ErrorCode string `json:"error_code"`
Message string `json:"message"`
}

func helpfulError(ctx context.Context, profile string, persistentAuth u2m.OAuthArgument) string {
loginMsg := auth.BuildLoginCommand(ctx, profile, persistentAuth)
return fmt.Sprintf("Try logging in again with `%s` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new", loginMsg)
Expand Down Expand Up @@ -72,6 +79,14 @@ and secret is not supported.`,
persistentAuthOpts: nil,
})
if err != nil {
if cmd.Flag("output").Changed && root.OutputType(cmd) == flags.OutputJSON {
if _, ok := errors.AsType[*u2m.InvalidRefreshTokenError](err); ok {
if outputErr := writeTokenErrorOutput(cmd.OutOrStdout(), err); outputErr != nil {
return outputErr
}
return root.ErrAlreadyPrinted
}
}
return err
}
// Only honor the explicit --output text flag, not implicit text mode
Expand All @@ -98,6 +113,15 @@ func writeTokenOutput(w io.Writer, t *oauth2.Token, textMode bool) error {
return err
}

func writeTokenErrorOutput(w io.Writer, err error) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
return encoder.Encode(tokenErrorOutput{
ErrorCode: invalidRefreshTokenErrorCode,
Message: err.Error(),
})
}

type loadTokenArgs struct {
// authArguments is the parsed auth arguments, including the host and optionally the account ID.
authArguments *auth.AuthArguments
Expand Down
27 changes: 22 additions & 5 deletions cmd/auth/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ func TestToken_loadToken(t *testing.T) {
}

cases := []struct {
name string
setupCtx func(context.Context) context.Context
args loadTokenArgs
validateToken func(*oauth2.Token)
wantErr string
name string
setupCtx func(context.Context) context.Context
args loadTokenArgs
validateToken func(*oauth2.Token)
wantErr string
wantInvalidRefreshToken bool
}{
{
name: "prints helpful login message on refresh failure when profile is specified",
Expand All @@ -260,6 +261,7 @@ func TestToken_loadToken(t *testing.T) {
},
wantErr: `A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:
$ databricks auth login --profile expired`,
wantInvalidRefreshToken: true,
},
{
name: "prints helpful login message on refresh failure when host is specified",
Expand All @@ -281,6 +283,7 @@ func TestToken_loadToken(t *testing.T) {
},
wantErr: `A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:
$ databricks auth login --profile expired`,
wantInvalidRefreshToken: true,
},
{
name: "prints helpful login message on invalid response",
Expand Down Expand Up @@ -893,6 +896,7 @@ func TestToken_loadToken(t *testing.T) {
},
wantErr: `A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:
$ databricks auth login --profile valid-token`,
wantInvalidRefreshToken: true,
},
}
for _, c := range cases {
Expand All @@ -904,6 +908,8 @@ func TestToken_loadToken(t *testing.T) {
got, err := loadToken(ctx, c.args)
if c.wantErr != "" {
assert.Equal(t, c.wantErr, err.Error())
_, isInvalidRefreshToken := errors.AsType[*u2m.InvalidRefreshTokenError](err)
assert.Equal(t, c.wantInvalidRefreshToken, isInvalidRefreshToken)
} else {
assert.NoError(t, err)
c.validateToken(got)
Expand Down Expand Up @@ -948,3 +954,14 @@ func TestWriteTokenOutput(t *testing.T) {
assert.Equal(t, "my-access-token\n", buf.String())
})
}

func TestWriteTokenErrorOutput(t *testing.T) {
var buf bytes.Buffer
err := writeTokenErrorOutput(&buf, errors.New("refresh token is invalid"))
assert.NoError(t, err)

var got tokenErrorOutput
assert.NoError(t, json.Unmarshal(buf.Bytes(), &got))
assert.Equal(t, invalidRefreshTokenErrorCode, got.ErrorCode)
assert.Equal(t, "refresh token is invalid", got.Message)
}
21 changes: 17 additions & 4 deletions libs/auth/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,33 @@ func AuthTypeDisplayName(authType string) string {
return authType
}

type rewrittenAuthError struct {
message string
cause error
}

func (e *rewrittenAuthError) Error() string {
return e.message
}

func (e *rewrittenAuthError) Unwrap() error {
return e.cause
}

// RewriteAuthError rewrites the error message for invalid refresh token error.
// It returns whether the error was rewritten and the rewritten error.
func RewriteAuthError(ctx context.Context, host, accountId, profile string, err error) (bool, error) {
if _, ok := errors.AsType[*u2m.InvalidRefreshTokenError](err); ok {
oauthArgument, err := AuthArguments{
oauthArgument, argErr := AuthArguments{
Host: host,
AccountID: accountId,
}.ToOAuthArgument()
if err != nil {
return false, err
if argErr != nil {
return false, argErr
}
msg := `A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command:
$ ` + BuildLoginCommand(ctx, profile, oauthArgument)
return true, errors.New(msg)
return true, &rewrittenAuthError{message: msg, cause: err}
}
return false, err
}
Expand Down
Loading