From d66f5e8db07f4b75479c68aeb633bce3959ad413 Mon Sep 17 00:00:00 2001 From: Alex Chen Date: Thu, 12 Jan 2023 00:50:14 +0800 Subject: [PATCH] lib/oauthutil: handle fatal errors better PR #6678 --- lib/oauthutil/oauthutil.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go index cd9cb74d3..5f3e62f23 100644 --- a/lib/oauthutil/oauthutil.go +++ b/lib/oauthutil/oauthutil.go @@ -204,6 +204,37 @@ func (ts *TokenSource) reReadToken() (changed bool) { return changed } +type retrieveErrResponse struct { + Error string `json:"error"` +} + +// If err is nil or an error other than fatal OAuth errors, returns err itself. +// Otherwise returns a more user-friendly error. +func maybeWrapOAuthError(err error, remoteName string) (newErr error) { + newErr = err + if rErr, ok := err.(*oauth2.RetrieveError); ok { + if rErr.Response.StatusCode == 400 || rErr.Response.StatusCode == 401 { + fs.Debugf(remoteName, "got fatal oauth error: %v", rErr) + var resp retrieveErrResponse + if err = json.Unmarshal(rErr.Body, &resp); err != nil { + newErr = fmt.Errorf("(can't decode error info) - try refreshing token with \"rclone config reconnect %s:\"", remoteName) + return + } + var suggestion string + switch resp.Error { + case "invalid_client", "unauthorized_client", "unsupported_grant_type", "invalid_scope": + suggestion = fmt.Sprintf("if you're using your own client id/secret, make sure they're properly set up following the docs") + case "invalid_grant": + fallthrough + default: + suggestion = fmt.Sprintf("maybe token expired? - try refreshing with \"rclone config reconnect %s:\"", remoteName) + } + newErr = fmt.Errorf("%s: %s", resp.Error, suggestion) + } + } + return +} + // Token returns a token or an error. // Token must be safe for concurrent use by multiple goroutines. // The returned Token must not be modified. @@ -242,11 +273,15 @@ func (ts *TokenSource) Token() (*oauth2.Token, error) { if err == nil { break } + if newErr := maybeWrapOAuthError(err, ts.name); newErr != err { + err = newErr // Fatal OAuth error + break + } fs.Debugf(ts.name, "Token refresh failed try %d/%d: %v", i, maxTries, err) time.Sleep(1 * time.Second) } if err != nil { - return nil, fmt.Errorf("couldn't fetch token - maybe it has expired? - refresh with \"rclone config reconnect %s:\": %w", ts.name, err) + return nil, fmt.Errorf("couldn't fetch token: %w", err) } changed = changed || (*token != *ts.token) ts.token = token