package oauth import ( "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" "go-garth/internal/models/types" "go-garth/internal/utils" ) // GetOAuth1Token retrieves an OAuth1 token using the provided ticket func GetOAuth1Token(domain, ticket string) (*types.OAuth1Token, error) { scheme := "https" if strings.HasPrefix(domain, "127.0.0.1") { scheme = "http" } consumer, err := utils.LoadOAuthConsumer() if err != nil { return nil, fmt.Errorf("failed to load OAuth consumer: %w", err) } baseURL := fmt.Sprintf("%s://connectapi.%s/oauth-service/oauth/", scheme, domain) loginURL := fmt.Sprintf("%s://sso.%s/sso/embed", scheme, domain) tokenURL := fmt.Sprintf("%spreauthorized?ticket=%s&login-url=%s&accepts-mfa-tokens=true", baseURL, ticket, url.QueryEscape(loginURL)) // Parse URL to extract query parameters for signing parsedURL, err := url.Parse(tokenURL) if err != nil { return nil, err } // Extract query parameters queryParams := make(map[string]string) for key, values := range parsedURL.Query() { if len(values) > 0 { queryParams[key] = values[0] } } // Create OAuth1 signed request baseURLForSigning := parsedURL.Scheme + "://" + parsedURL.Host + parsedURL.Path authHeader := utils.CreateOAuth1AuthorizationHeader("GET", baseURLForSigning, queryParams, consumer.ConsumerKey, consumer.ConsumerSecret, "", "") req, err := http.NewRequest("GET", tokenURL, nil) if err != nil { return nil, err } req.Header.Set("Authorization", authHeader) req.Header.Set("User-Agent", "com.garmin.android.apps.connectmobile") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } bodyStr := string(body) if resp.StatusCode != 200 { return nil, fmt.Errorf("OAuth1 request failed with status %d: %s", resp.StatusCode, bodyStr) } // Parse query string response - handle both & and ; separators bodyStr = strings.ReplaceAll(bodyStr, ";", "&") values, err := url.ParseQuery(bodyStr) if err != nil { return nil, fmt.Errorf("failed to parse OAuth1 response: %w", err) } oauthToken := values.Get("oauth_token") oauthTokenSecret := values.Get("oauth_token_secret") if oauthToken == "" || oauthTokenSecret == "" { return nil, fmt.Errorf("missing oauth_token or oauth_token_secret in response") } return &types.OAuth1Token{ OAuthToken: oauthToken, OAuthTokenSecret: oauthTokenSecret, MFAToken: values.Get("mfa_token"), Domain: domain, }, nil } // ExchangeToken exchanges an OAuth1 token for an OAuth2 token func ExchangeToken(oauth1Token *types.OAuth1Token) (*types.OAuth2Token, error) { scheme := "https" if strings.HasPrefix(oauth1Token.Domain, "127.0.0.1") { scheme = "http" } consumer, err := utils.LoadOAuthConsumer() if err != nil { return nil, fmt.Errorf("failed to load OAuth consumer: %w", err) } exchangeURL := fmt.Sprintf("%s://connectapi.%s/oauth-service/oauth/exchange/user/2.0", scheme, oauth1Token.Domain) // Prepare form data formData := url.Values{} if oauth1Token.MFAToken != "" { formData.Set("mfa_token", oauth1Token.MFAToken) } // Convert form data to map for OAuth signing formParams := make(map[string]string) for key, values := range formData { if len(values) > 0 { formParams[key] = values[0] } } // Create OAuth1 signed request authHeader := utils.CreateOAuth1AuthorizationHeader("POST", exchangeURL, formParams, consumer.ConsumerKey, consumer.ConsumerSecret, oauth1Token.OAuthToken, oauth1Token.OAuthTokenSecret) req, err := http.NewRequest("POST", exchangeURL, strings.NewReader(formData.Encode())) if err != nil { return nil, err } req.Header.Set("Authorization", authHeader) req.Header.Set("User-Agent", "com.garmin.android.apps.connectmobile") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != 200 { return nil, fmt.Errorf("OAuth2 exchange failed with status %d: %s", resp.StatusCode, string(body)) } var oauth2Token types.OAuth2Token if err := json.Unmarshal(body, &oauth2Token); err != nil { return nil, fmt.Errorf("failed to decode OAuth2 token: %w", err) } // Set expiration time if oauth2Token.ExpiresIn > 0 { oauth2Token.ExpiresAt = time.Now().Add(time.Duration(oauth2Token.ExpiresIn) * time.Second) } return &oauth2Token, nil }