From a1cc209e46a224fff788053ccc49202b6c7e6b31 Mon Sep 17 00:00:00 2001 From: sstent Date: Sat, 6 Sep 2025 07:27:59 -0700 Subject: [PATCH] sync authfix --- auth.go | 294 ++++-------------- auth_test.go | 12 +- client.go | 6 +- cmd/debug_auth/main.go | 8 +- .../oauth1_response_1757098946234920274.html | Bin 0 -> 3601 bytes .../oauth1_response_1757098948026901630.html | Bin 0 -> 3599 bytes .../oauth1_response_1757129132587570285.html | Bin 0 -> 3599 bytes .../oauth1_response_1757129134048278764.html | Bin 0 -> 3600 bytes .../oauth1_response_1757129231414677442.html | Bin 0 -> 3601 bytes .../oauth1_response_1757129232952257721.html | Bin 0 -> 3614 bytes debug_auth_response_1757103692.html | 93 ++++++ debug_auth_response_1757103857.html | 93 ++++++ debug_auth_response_1757104480.html | 93 ++++++ debug_auth_response_1757116993.html | 93 ++++++ debug_auth_response_1757117105.html | 93 ++++++ debug_auth_response_1757117228.html | 93 ++++++ debug_auth_response_1757117813.html | 93 ++++++ debug_auth_response_1757119493.html | 93 ++++++ debug_response_1757099867.html | Bin 0 -> 3601 bytes debug_response_fixed_1757099969.html | 206 ++++++++++++ garth.go | 19 +- types.go | 44 +-- 22 files changed, 1041 insertions(+), 292 deletions(-) create mode 100644 debug/oauth1_response_1757098946234920274.html create mode 100644 debug/oauth1_response_1757098948026901630.html create mode 100644 debug/oauth1_response_1757129132587570285.html create mode 100644 debug/oauth1_response_1757129134048278764.html create mode 100644 debug/oauth1_response_1757129231414677442.html create mode 100644 debug/oauth1_response_1757129232952257721.html create mode 100644 debug_auth_response_1757103692.html create mode 100644 debug_auth_response_1757103857.html create mode 100644 debug_auth_response_1757104480.html create mode 100644 debug_auth_response_1757116993.html create mode 100644 debug_auth_response_1757117105.html create mode 100644 debug_auth_response_1757117228.html create mode 100644 debug_auth_response_1757117813.html create mode 100644 debug_auth_response_1757119493.html create mode 100644 debug_response_1757099867.html create mode 100644 debug_response_fixed_1757099969.html diff --git a/auth.go b/auth.go index 3be2c6b..0574dac 100644 --- a/auth.go +++ b/auth.go @@ -2,10 +2,7 @@ package garth import ( "context" - "crypto/hmac" - "crypto/sha1" "crypto/tls" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -16,7 +13,7 @@ import ( "net/url" "os" "regexp" - "sort" + "strconv" "strings" "time" @@ -164,28 +161,10 @@ func (a *GarthAuthenticator) Login(ctx context.Context, username, password, mfaT } } - // Step 3: Get OAuth1 request token - oauth1RequestToken, err := a.fetchOAuth1RequestToken(ctx) + // Step 3: Exchange service ticket for access token + token, err := a.exchangeServiceTicketForToken(ctx, serviceTicket) if err != nil { - return nil, fmt.Errorf("failed to get OAuth1 request token: %w", err) - } - - // Step 4: Authorize OAuth1 request token (using the session from authentication) - err = a.authorizeOAuth1Token(ctx, oauth1RequestToken) - if err != nil { - return nil, fmt.Errorf("failed to authorize OAuth1 token: %w", err) - } - - // Step 5: Exchange service ticket for OAuth1 access token - oauth1AccessToken, err := a.exchangeTicketForOAuth1Token(ctx, serviceTicket) - if err != nil { - return nil, fmt.Errorf("failed to exchange ticket for OAuth1 access token: %w", err) - } - - // Step 6: Exchange OAuth1 access token for OAuth2 token - token, err := a.exchangeOAuth1ForOAuth2Token(ctx, oauth1AccessToken) - if err != nil { - return nil, fmt.Errorf("failed to exchange OAuth1 for OAuth2 token: %w", err) + return nil, fmt.Errorf("failed to exchange service ticket for token: %w", err) } if err := a.storage.StoreToken(token); err != nil { @@ -195,203 +174,61 @@ func (a *GarthAuthenticator) Login(ctx context.Context, username, password, mfaT return token, nil } -func (a *GarthAuthenticator) fetchOAuth1RequestToken(ctx context.Context) (*OAuth1Token, error) { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://connectapi.%s/oauth-service/oauth/request_token", a.domain), nil) +// exchangeServiceTicketForToken exchanges service ticket for access token +func (a *GarthAuthenticator) exchangeServiceTicketForToken(ctx context.Context, ticket string) (*Token, error) { + callbackURL := fmt.Sprintf("https://connect.%s/oauthConfirm?ticket=%s", a.domain, ticket) + req, err := http.NewRequestWithContext(ctx, "GET", callbackURL, nil) if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - - // Sign request with OAuth1 consumer credentials - req.Header.Set("Authorization", a.buildOAuth1Header(req, nil, "")) - - resp, err := a.client.Do(req) - if err != nil { - return nil, fmt.Errorf("request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("request failed with status: %d", resp.StatusCode) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response: %w", err) - } - - values, err := url.ParseQuery(string(body)) - if err != nil { - return nil, fmt.Errorf("failed to parse response: %w", err) - } - - return &OAuth1Token{ - Token: values.Get("oauth_token"), - Secret: values.Get("oauth_token_secret"), - }, nil -} - -func (a *GarthAuthenticator) authorizeOAuth1Token(ctx context.Context, token *OAuth1Token) error { - params := url.Values{} - params.Set("oauth_token", token.Token) - authURL := fmt.Sprintf("https://connect.%s/oauthConfirm?%s", a.domain, params.Encode()) - - req, err := http.NewRequestWithContext(ctx, "GET", authURL, nil) - if err != nil { - return fmt.Errorf("failed to create authorization request: %w", err) + return nil, fmt.Errorf("failed to create callback request: %w", err) } // Use realistic browser headers - req.Header = a.getRealisticBrowserHeaders("https://connect.garmin.com") + req.Header = a.getRealisticBrowserHeaders("https://sso.garmin.com") resp, err := a.client.Do(req) if err != nil { - return fmt.Errorf("authorization request failed: %w", err) - } - defer resp.Body.Close() - - // We don't need the CSRF token anymore, so just check for success - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("authorization failed with status: %d, response: %s", resp.StatusCode, body) - } - - return nil -} - -func (a *GarthAuthenticator) exchangeTicketForOAuth1Token(ctx context.Context, ticket string) (*OAuth1Token, error) { - data := url.Values{} - data.Set("oauth_verifier", ticket) - - req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://connectapi.%s/oauth-service/oauth/access_token", a.domain), strings.NewReader(data.Encode())) - if err != nil { - return nil, fmt.Errorf("failed to create access token request: %w", err) - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Authorization", a.buildOAuth1Header(req, nil, "")) - - resp, err := a.client.Do(req) - if err != nil { - return nil, fmt.Errorf("access token request failed: %w", err) + return nil, fmt.Errorf("callback request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("access token request failed with status: %d", resp.StatusCode) + return nil, fmt.Errorf("callback failed with status: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read access token response: %w", err) + return nil, fmt.Errorf("failed to read callback response: %w", err) } - values, err := url.ParseQuery(string(body)) + // Extract tokens from embedded JavaScript + accessToken, err := extractParam(`"accessToken":"([^"]+)"`, string(body)) if err != nil { - return nil, fmt.Errorf("failed to parse access token response: %w", err) + return nil, fmt.Errorf("failed to extract access token: %w", err) } - return &OAuth1Token{ - Token: values.Get("oauth_token"), - Secret: values.Get("oauth_token_secret"), + refreshToken, err := extractParam(`"refreshToken":"([^"]+)"`, string(body)) + if err != nil { + return nil, fmt.Errorf("failed to extract refresh token: %w", err) + } + + expiresAt, err := extractParam(`"expiresAt":(\d+)`, string(body)) + if err != nil { + return nil, fmt.Errorf("failed to extract expiresAt: %w", err) + } + + expiresAtInt, err := strconv.ParseInt(expiresAt, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse expiresAt: %w", err) + } + + return &Token{ + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresAt: expiresAtInt, + Domain: a.domain, }, nil } -func (a *GarthAuthenticator) exchangeOAuth1ForOAuth2Token(ctx context.Context, oauth1Token *OAuth1Token) (*Token, error) { - req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("https://connectapi.%s/oauth-service/oauth/exchange_token", a.domain), nil) - if err != nil { - return nil, fmt.Errorf("failed to create token exchange request: %w", err) - } - - req.Header.Set("Authorization", a.buildOAuth1Header(req, oauth1Token, "")) - - resp, err := a.client.Do(req) - if err != nil { - return nil, fmt.Errorf("token exchange request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("token exchange failed with status: %d", resp.StatusCode) - } - - var token Token - if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { - return nil, fmt.Errorf("failed to parse token response: %w", err) - } - - if token.OAuth2Token != nil { - token.OAuth2Token.ExpiresAt = time.Now().Add(time.Duration(token.OAuth2Token.ExpiresIn) * time.Second).Unix() - } - token.OAuth1Token = oauth1Token - return &token, nil -} - -func (a *GarthAuthenticator) buildOAuth1Header(req *http.Request, token *OAuth1Token, callback string) string { - oauthParams := url.Values{} - oauthParams.Set("oauth_consumer_key", "fc020df2-e33d-4ec5-987a-7fb6de2e3850") - oauthParams.Set("oauth_signature_method", "HMAC-SHA1") - oauthParams.Set("oauth_timestamp", fmt.Sprintf("%d", time.Now().Unix())) - oauthParams.Set("oauth_nonce", fmt.Sprintf("%d", rand.Int63())) - oauthParams.Set("oauth_version", "1.0") - - if token != nil { - oauthParams.Set("oauth_token", token.Token) - } - if callback != "" { - oauthParams.Set("oauth_callback", callback) - } - - // Generate signature - baseString := a.buildSignatureBaseString(req, oauthParams) - signingKey := url.QueryEscape("secret_key_from_mobile_app") + "&" - if token != nil { - signingKey += url.QueryEscape(token.Secret) - } - - mac := hmac.New(sha1.New, []byte(signingKey)) - mac.Write([]byte(baseString)) - signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - oauthParams.Set("oauth_signature", signature) - - // Build header - params := make([]string, 0, len(oauthParams)) - for k, v := range oauthParams { - params = append(params, fmt.Sprintf(`%s="%s"`, k, url.QueryEscape(v[0]))) - } - sort.Strings(params) - - return "OAuth " + strings.Join(params, ", ") -} - -func (a *GarthAuthenticator) buildSignatureBaseString(req *http.Request, oauthParams url.Values) string { - method := strings.ToUpper(req.Method) - baseURL := req.URL.Scheme + "://" + req.URL.Host + req.URL.Path - - // Collect all parameters - params := url.Values{} - for k, v := range req.URL.Query() { - params[k] = v - } - for k, v := range oauthParams { - params[k] = v - } - - // Sort parameters - paramKeys := make([]string, 0, len(params)) - for k := range params { - paramKeys = append(paramKeys, k) - } - sort.Strings(paramKeys) - - paramPairs := make([]string, 0, len(paramKeys)) - for _, k := range paramKeys { - paramPairs = append(paramPairs, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k][0]))) - } - - queryString := strings.Join(paramPairs, "&") - return fmt.Sprintf("%s&%s&%s", method, url.QueryEscape(baseURL), url.QueryEscape(queryString)) -} - func (a *GarthAuthenticator) getLoginTicket(ctx context.Context) (string, string, error) { params := url.Values{} params.Set("id", "gauth-widget") @@ -527,10 +364,6 @@ func (a *GarthAuthenticator) authenticate(ctx context.Context, username, passwor return authResponse.Ticket, nil } -func (a *GarthAuthenticator) ExchangeToken(ctx context.Context, token *OAuth1Token) (*Token, error) { - return a.exchangeOAuth1ForOAuth2Token(ctx, token) -} - func (a *GarthAuthenticator) getEnhancedBrowserHeaders(referrer string) http.Header { u, _ := url.Parse(referrer) origin := fmt.Sprintf("%s://%s", u.Scheme, u.Host) @@ -553,25 +386,27 @@ func (a *GarthAuthenticator) getEnhancedBrowserHeaders(referrer string) http.Hea } func getCSRFToken(html string) (string, string, error) { - // Extract login ticket (lt) from hidden input field - re := regexp.MustCompile(``) - matches := re.FindStringSubmatch(html) - if len(matches) > 1 { - return matches[1], "lt", nil + // More robust regex patterns to handle variations in HTML structure + patterns := []struct { + regex string + tokenType string + }{ + // Pattern for login ticket (lt) + {`]*name="lt"[^>]*value="([^"]+)"`, "lt"}, + // Pattern for CSRF token in hidden input + {`]*name="_csrf"[^>]*value="([^"]+)"`, "_csrf"}, + // Pattern for CSRF token in meta tag + {`]*name="_csrf"[^>]*content="([^"]+)"`, "_csrf"}, + // Pattern for CSRF token in JSON payload + {`"csrfToken"\s*:\s*"([^"]+)"`, "_csrf"}, } - // Extract CSRF token as fallback - re = regexp.MustCompile(``) - matches = re.FindStringSubmatch(html) - if len(matches) > 1 { - return matches[1], "_csrf", nil - } - - // Try alternative CSRF token pattern - re = regexp.MustCompile(`"csrfToken":"([^"]+)"`) - matches = re.FindStringSubmatch(html) - if len(matches) > 1 { - return matches[1], "_csrf", nil + for _, p := range patterns { + re := regexp.MustCompile(p.regex) + matches := re.FindStringSubmatch(html) + if len(matches) > 1 { + return matches[1], p.tokenType, nil + } } // If we get here, we didn't find a token @@ -609,15 +444,22 @@ func (a *GarthAuthenticator) RefreshToken(ctx context.Context, refreshToken stri return nil, fmt.Errorf("refresh failed: %d %s", resp.StatusCode, body) } - var token Token - if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { + var response struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + } + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return nil, fmt.Errorf("failed to parse refresh response: %w", err) } - if token.OAuth2Token != nil { - token.OAuth2Token.ExpiresAt = time.Now().Add(time.Duration(token.OAuth2Token.ExpiresIn) * time.Second).Unix() - } - return &token, nil + expiresAt := time.Now().Add(time.Duration(response.ExpiresIn) * time.Second).Unix() + return &Token{ + AccessToken: response.AccessToken, + RefreshToken: response.RefreshToken, + ExpiresAt: expiresAt, + Domain: a.domain, + }, nil } // GetClient returns the HTTP client used for authentication diff --git a/auth_test.go b/auth_test.go index ef5e094..8202966 100644 --- a/auth_test.go +++ b/auth_test.go @@ -59,27 +59,27 @@ func TestRealAuthentication(t *testing.T) { } log.Printf("Authentication successful! Token details:") - log.Printf("Access Token: %s", token.OAuth2Token.AccessToken) - log.Printf("Expires At: %d", token.OAuth2Token.ExpiresAt) - log.Printf("Refresh Token: %s", token.OAuth2Token.RefreshToken) + log.Printf("Access Token: %s", token.AccessToken) + log.Printf("Expires At: %d", token.ExpiresAt) + log.Printf("Refresh Token: %s", token.RefreshToken) // Verify token storage storedToken, err := storage.GetToken() if err != nil { t.Fatalf("Token storage verification failed: %v", err) } - if storedToken.OAuth2Token.AccessToken != token.OAuth2Token.AccessToken { + if storedToken.AccessToken != token.AccessToken { t.Fatal("Stored token doesn't match authenticated token") } log.Println("Token storage verification successful") // Test token refresh - newToken, err := auth.RefreshToken(ctx, token.OAuth2Token.RefreshToken) + newToken, err := auth.RefreshToken(ctx, token.RefreshToken) if err != nil { t.Fatalf("Token refresh failed: %v", err) } - if newToken.OAuth2Token.AccessToken == token.OAuth2Token.AccessToken { + if newToken.AccessToken == token.AccessToken { t.Fatal("Refreshed token should be different from original") } log.Println("Token refresh successful") diff --git a/client.go b/client.go index 7cea2e0..9616b91 100644 --- a/client.go +++ b/client.go @@ -59,7 +59,7 @@ func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { } // Add Authorization header - req.Header.Set("Authorization", "Bearer "+token.OAuth2Token.AccessToken) + req.Header.Set("Authorization", "Bearer "+token.AccessToken) req.Header.Set("User-Agent", t.userAgent) req.Header.Set("Referer", "https://sso.garmin.com/sso/signin") @@ -85,7 +85,7 @@ func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { if err != nil { return nil, err } - req.Header.Set("Authorization", "Bearer "+token.OAuth2Token.AccessToken) + req.Header.Set("Authorization", "Bearer "+token.AccessToken) continue } @@ -123,7 +123,7 @@ func (t *AuthTransport) refreshToken(ctx context.Context, token *Token) (*Token, } // Perform refresh - newToken, err := t.auth.RefreshToken(ctx, token.OAuth2Token.RefreshToken) + newToken, err := t.auth.RefreshToken(ctx, token.RefreshToken) if err != nil { return nil, err } diff --git a/cmd/debug_auth/main.go b/cmd/debug_auth/main.go index 0d44cb6..c7cffc8 100644 --- a/cmd/debug_auth/main.go +++ b/cmd/debug_auth/main.go @@ -40,16 +40,16 @@ func main() { } fmt.Println("\nAuthentication successful! Token details:") - fmt.Printf("Access Token: %s\n", token.OAuth2Token.AccessToken) - fmt.Printf("Expires At: %d\n", token.OAuth2Token.ExpiresAt) - fmt.Printf("Refresh Token: %s\n", token.OAuth2Token.RefreshToken) + fmt.Printf("Access Token: %s\n", token.AccessToken) + fmt.Printf("Expires At: %d\n", token.ExpiresAt) + fmt.Printf("Refresh Token: %s\n", token.RefreshToken) // Verify token storage storedToken, err := storage.GetToken() if err != nil { log.Fatalf("Token storage verification failed: %v", err) } - if storedToken.OAuth2Token.AccessToken != token.OAuth2Token.AccessToken { + if storedToken.AccessToken != token.AccessToken { log.Fatal("Stored token doesn't match authenticated token") } diff --git a/debug/oauth1_response_1757098946234920274.html b/debug/oauth1_response_1757098946234920274.html new file mode 100644 index 0000000000000000000000000000000000000000..cfbb07163140cd1aa937f15d3de9321cea499b23 GIT binary patch literal 3601 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 X`BrdPdcMEk@Av)~7HN3Z{e1%itwID- literal 0 HcmV?d00001 diff --git a/debug/oauth1_response_1757098948026901630.html b/debug/oauth1_response_1757098948026901630.html new file mode 100644 index 0000000000000000000000000000000000000000..408931ed01865ae5a57adba2c5758008c4d4b29e GIT binary patch literal 3599 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 VtzbLzeE)bn9{n*a((tPLn*$c==N$k5 literal 0 HcmV?d00001 diff --git a/debug/oauth1_response_1757129132587570285.html b/debug/oauth1_response_1757129132587570285.html new file mode 100644 index 0000000000000000000000000000000000000000..b9517d8b8a8566fd6dc94c861af4e0e50921d82d GIT binary patch literal 3599 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 W1&5{Q`-j8f;E!REhF9I+Hvj{Fb^W&h literal 0 HcmV?d00001 diff --git a/debug/oauth1_response_1757129231414677442.html b/debug/oauth1_response_1757129231414677442.html new file mode 100644 index 0000000000000000000000000000000000000000..37755bc66bb7ffb2ae044f74c36d5234a8b03e64 GIT binary patch literal 3601 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 k*8V$sYkwHCJw8A8K)w}hXP)n$PN$PUhD94*b$` + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + + + + + + + diff --git a/debug_auth_response_1757103857.html b/debug_auth_response_1757103857.html new file mode 100644 index 0000000..8b9ee61 --- /dev/null +++ b/debug_auth_response_1757103857.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757104480.html b/debug_auth_response_1757104480.html new file mode 100644 index 0000000..521e6da --- /dev/null +++ b/debug_auth_response_1757104480.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757116993.html b/debug_auth_response_1757116993.html new file mode 100644 index 0000000..fb5c2eb --- /dev/null +++ b/debug_auth_response_1757116993.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757117105.html b/debug_auth_response_1757117105.html new file mode 100644 index 0000000..0f24a32 --- /dev/null +++ b/debug_auth_response_1757117105.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757117228.html b/debug_auth_response_1757117228.html new file mode 100644 index 0000000..c35f2dc --- /dev/null +++ b/debug_auth_response_1757117228.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757117813.html b/debug_auth_response_1757117813.html new file mode 100644 index 0000000..07708f4 --- /dev/null +++ b/debug_auth_response_1757117813.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_auth_response_1757119493.html b/debug_auth_response_1757119493.html new file mode 100644 index 0000000..52c2454 --- /dev/null +++ b/debug_auth_response_1757119493.html @@ -0,0 +1,93 @@ + + + + + + +Attention Required! | Cloudflare + + + + + + + + + + + + + + + + + +
+ +
+
+

Sorry, you have been blocked

+

You are unable to access sso.garmin.com

+
+ +
+
+
+ + + +
+
+
+ +
+
+
+

Why have I been blocked?

+ +

This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.

+
+ +
+

What can I do to resolve this?

+ +

You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.

+
+
+
+ + + + +
+
+ + + + + diff --git a/debug_response_1757099867.html b/debug_response_1757099867.html new file mode 100644 index 0000000000000000000000000000000000000000..904f17757073c4d9a082147b50e64369ffe64931 GIT binary patch literal 3601 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 + + + + + + GARMIN Authentication Application + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+

Sign In

+ +
+ +
+ + + + + + + + + + +
+
+ + + + + + +
+ +
+ + + + +
+ + + + + + + + + +
+
+ + + +
+ +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + diff --git a/garth.go b/garth.go index 4c3b7cf..cc38e14 100644 --- a/garth.go +++ b/garth.go @@ -32,7 +32,6 @@ package garth import ( "context" - "errors" "net/http" "os" "strconv" @@ -41,15 +40,12 @@ import ( // Authenticator defines the authentication interface type Authenticator interface { - // Login authenticates with Garmin services using OAuth1/OAuth2 hybrid flow + // Login authenticates with Garmin services using OAuth2 flow Login(ctx context.Context, username, password, mfaToken string) (*Token, error) // RefreshToken refreshes an expired OAuth2 access token RefreshToken(ctx context.Context, refreshToken string) (*Token, error) - // ExchangeToken exchanges OAuth1 token for OAuth2 token - ExchangeToken(ctx context.Context, oauth1Token *OAuth1Token) (*Token, error) - // GetClient returns an authenticated HTTP client GetClient() *http.Client } @@ -89,16 +85,3 @@ func NewClientOptionsFromEnv() ClientOptions { return opts } - -// getRequestToken retrieves OAuth1 request token -// getRequestToken retrieves OAuth1 request token -func getRequestToken(a Authenticator, ctx context.Context) (*OAuth1Token, error) { - // Implementation will be added in next step - return nil, errors.New("not implemented") -} - -// authorizeRequestToken authorizes OAuth1 token through SSO -func authorizeRequestToken(a Authenticator, ctx context.Context, token *OAuth1Token) error { - // Implementation will be added in next step - return errors.New("not implemented") -} diff --git a/types.go b/types.go index 13afd4b..b6e219d 100644 --- a/types.go +++ b/types.go @@ -8,49 +8,23 @@ import ( // Unified Token Definitions -// OAuth1Token represents OAuth1 credentials -type OAuth1Token struct { - Token string - Secret string -} - -// OAuth2Token represents OAuth2 credentials -type OAuth2Token struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - ExpiresAt int64 `json:"expires_at"` - RefreshToken string `json:"refresh_token"` - Scope string `json:"scope"` -} - -// IsExpired checks if the token has expired -func (t *OAuth2Token) IsExpired() bool { - return time.Now().After(time.Unix(t.ExpiresAt, 0)) -} - -// Token represents unified authentication credentials +// Token represents authentication credentials type Token struct { - Domain string `json:"domain"` - OAuth1Token *OAuth1Token `json:"oauth1_token"` - OAuth2Token *OAuth2Token `json:"oauth2_token"` - UserProfile *UserProfile `json:"user_profile"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresAt int64 `json:"expires_at"` + Domain string `json:"domain"` + UserProfile *UserProfile `json:"user_profile"` } -// IsExpired checks if the OAuth2 token has expired +// IsExpired checks if the token has expired (with 60 second buffer) func (t *Token) IsExpired() bool { - if t.OAuth2Token == nil { - return true - } - return t.OAuth2Token.IsExpired() + return time.Now().Unix() >= (t.ExpiresAt - 60) } // NeedsRefresh checks if token needs refresh (within 5 min expiry window) func (t *Token) NeedsRefresh() bool { - if t.OAuth2Token == nil { - return true - } - return time.Now().Add(5 * time.Minute).After(time.Unix(t.OAuth2Token.ExpiresAt, 0)) + return time.Now().Unix() >= (t.ExpiresAt - 300) } // UserProfile represents Garmin user profile information