matin / garth

Garmin SSO auth + Connect Python client
MIT License
305 stars 22 forks source link

when expired the refresh do not work #21

Closed yihong0618 closed 1 year ago

yihong0618 commented 1 year ago

the issue comes about 1 day. image

I printed the error text its a html

<!doctype html>
<html lang="en">
  <head>
    <title>HTTP Status 401 – Unauthorized</title>
    <style type="text/css">
      body {
        font-family: Tahoma, Arial, sans-serif;
      }
      h1,
      h2,
      h3,
      b {
        color: white;
        background-color: #525d76;
      }
      h1 {
        font-size: 22px;
      }
      h2 {
        font-size: 16px;
      }
      h3 {
        font-size: 14px;
      }
      p {
        font-size: 12px;
      }
      a {
        color: bl ack;
      }
      .line {
        height: 1px;
        background-color: #525d76;
        border: none;
      }
    </style>
  </head>
  <body>
    <h1>HTTP Status 401 – Unauthorized</h1>
    <hr class="line" />
    <p><b>Type</b> Status Report</p>
    <p><b>Message</b> OAuthToken is invalid</p>
    <p>
      <b>Description</b> The request has not been applied to the target resource
      because it lacks valid authentication credentials for that resource.
    </p>
    <hr class="line" />
    <h3>Garmin Connect API Server</h3>
    <script
      defer
      src="https://static.cloudflareinsights.com/beacon.min.js/v8b253dfea2ab4077af8c6f58422dfbfd1689876627854"
      integrity="sha512-bjgnUKX4azu3dLTVtie9u6TKqgx29RBwfj3QXYt5EKfWM/9hPSAI/4qcV5NACjwAo8UtTeWefx6Zq5PHcMm7Tg=="
      data-cf-beacon='{"rayId":"8109fdc18c3a15a0","version":"2023.8.0","b":1,"token":"dfcba71ff1d44ca3956104d931b99217","si":100}'
      crossorigin="anonymous"
    ></script>
  </body>
</html>
app4g commented 1 year ago

will a re-login / re-authenticate remove the error?

yihong0618 commented 1 year ago

yes, but the refresh token is for refresh, I wonder it can be refresh like web browser

app4g commented 1 year ago

You are right of course. I asked because I was wondering if there was any other underlying issue which could be tied to the login/token/authentication.

Given that garth is based on oAuth tokens, the expiry dates of the tokens would be such that it's a "long" time. Does it show that these tokens have long expiry time or do they expiry > 1 day? (from your PR https://github.com/matin/garth/pull/5#issue-1913538314). It technically shouldn't need a refresh so quickly

yihong0618 commented 1 year ago

You are right of course. I asked because I was wondering if there was any other underlying issue which could be tied to the login/token/authentication.

Given that garth is based on oAuth tokens, the expiry dates of the tokens would be such that it's a "long" time. Does it show that these tokens have long expiry time or do they expiry > 1 day? (from your PR #5 (comment)). It technically shouldn't need a refresh so quickly

yes, but for me and other user, it expiry the other day .... image

yihong0618 commented 1 year ago

@app4g https://github.com/yihong0618/running_page/issues/504#issuecomment-1746016970 sorry its Chinese hope google translate helps

app4g commented 1 year ago

I didn't check it before, but checking now, this is what I'm getting (using the example from the garth install/readme)

(.venv) ➜  .garth cat oauth1_token.json    
{
    "oauth_token": "XX",
    "oauth_token_secret": "XX",
    "mfa_token": null,
    "mfa_expiration_timestamp": null
}%                                                                             

 (.venv) ➜  .garth cat oauth2_token.json
{
    "scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ GCOFFER_READ CONNECT_READ ATP_WRITE",
    "jti": "XXX",
    "token_type": "Bearer",
    "access_token": "XX",
    "refresh_token": "XX",
    "expires_in": 74452,
    "expires_at": 1696472443,
    "refresh_token_expires_in": 2591999,
    "refresh_token_expires_at": 1698989990
Screenshot 2023-10-04 at 1 42 08 PM
yihong0618 commented 1 year ago

I didn't check it before, but checking now, this is what I'm getting (using the example from the garth install/readme)

(.venv) ➜  .garth cat oauth1_token.json    
{
    "oauth_token": "XX",
    "oauth_token_secret": "XX",
    "mfa_token": null,
    "mfa_expiration_timestamp": null
}%                                                                             

 (.venv) ➜  .garth cat oauth2_token.json
{
    "scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ GCOFFER_READ CONNECT_READ ATP_WRITE",
    "jti": "XXX",
    "token_type": "Bearer",
    "access_token": "XX",
    "refresh_token": "XX",
    "expires_in": 74452,
    "expires_at": 1696472443,
    "refresh_token_expires_in": 2591999,
    "refresh_token_expires_at": 1698989990
Screenshot 2023-10-04 at 1 42 08 PM

yep it will expire the other day...

matin commented 1 year ago

@yihong0618 @app4g To clarify, "refreshing" the OAuth2 token simply obtains a new one:

    def refresh_oauth2(self):
        assert self.oauth1_token
        # There is a way to perform a refresh of an OAuth2 token, but it
        # appears even Garmin uses this approach when the OAuth2 is expired
        self.oauth2_token = sso.exchange(self.oauth1_token, self)

sso.exchange() uses a valid OAuth1 token to obtain an OAuth2 token. I mention in the README that the OAuth1 token is valid for a year, but it's only the MFA token that expires. The OAuth1 token (without MFA) never expires. The refresh_token is not relevant.

The only way to invalidate an OAuth1 token for Garmin is to change your password and choose to log out of all existing sessions.

It appears in the following, the error is occurring during username / password authentication and not during an attempt to refresh the session. Am I understanding that correctly?

image

matin commented 1 year ago

@yihong0618 Could you try getting rid of these lines?

        if garth.client.oauth2_token.expired:
            garth.client.refresh_oauth2()

This is handled automatically in Garth.

yihong0618 commented 1 year ago

@yihong0618 Could you try getting rid of these lines?

        if garth.client.oauth2_token.expired:
            garth.client.refresh_oauth2()

This is handled automatically in Garth.

will drop it, but seems not these problem.

matin commented 1 year ago

The only thing I can think of is the usage of the garmin.com vs garmin.cn domain.

Are you also using --is-cn for the GH action? Is the GH action running from a server in the US or China?

yihong0618 commented 1 year ago

@matin aha, seems my bug, will fix it and test tomorrow.

yihong0618 commented 1 year ago

I think I should make it to cn configure before this line https://github.com/yihong0618/running_page/blob/320e8532b932618e3c0fdd94279a1438d50b6505/run_page/garmin_sync.py#L65

yihong0618 commented 1 year ago

@matin seems my fault so closing this

and I think we can add domain to the token json too?

matin commented 1 year ago

Sure. We can add it to the OAuth1 token. That's a fairly easy change, and I see the value.

app4g commented 1 year ago

To clarify, "refreshing" the OAuth2 token simply obtains a new one:

3 Weeks later and I revisit this and I get what is the difference that is being mentioned in the README and reading the code in your comment above..

Many Thanks.

BTW, @matin the oAuth2Token are not really oAuth version 2 right? I see google's cache of Garmin's Health API all states its using oAuth 1.0

tx

matin commented 1 year ago

3 Weeks later and I revisit this and I get what is the difference that is being mentioned in the README and reading the code in your comment above..

Many Thanks.

I'm happy to hear it's working for you!

BTW, @matin the oAuth2Token are not really oAuth version 2 right? I see google's cache of Garmin's Health API all states its using oAuth 1.0

The method in which you obtain the OAuth2 token doesn't follow the spec, but the format of the token itself is consistent with RFC 6749 Section 5.1.