philosowaffle / peloton-to-garmin

Convert workout data from Peloton into JSON/TCX/FIT files and automatically upload to Garmin Connect
https://philosowaffle.github.io/peloton-to-garmin/
GNU General Public License v3.0
278 stars 374 forks source link

[Feature] Translate Garth to C# and use in P2G #524

Closed philosowaffle closed 1 year ago

philosowaffle commented 1 year ago

https://github.com/matin/garth

Seems someone has figured out how to authenticate with Garmin using proper OAuth tokens instead of storing u/p. See if I can implement this in C# and use for P2G.

philosowaffle commented 1 year ago

Will use oauth library: https://github.com/DuendeSoftware/Duende.AccessTokenManagement or IdentityModel? These may not support OAuth1 which could be deal breaker.

Copy rest of impl from garth: https://github.com/matin/garth/blob/main/garth/sso.py

philosowaffle commented 1 year ago

ref: https://github.com/lswiderski/mi-scale-exporter/issues/20

lswiderski commented 1 year ago

Maybe you will be interested. It's dirty code but it's works. However, without MFA support yet, (But I want to update my asap so it was not priority for me)

https://github.com/lswiderski/GarminWeightScaleUploader/blob/master/Libs/garmin-connect-client/GarminConnectClient.Lib/Services/Client.cs

OAuth.DotNetCore used as OAuth1 Client

philosowaffle commented 1 year ago

Awesome thanks I'll take a look! I've got most of the flow stubbed out on my end, just about to dig into seeing if it actually works + figuring out the OAuth portion. I was a bit worried about OAuth.DotNetCore no longer being actively maintained, but if its still the best option I'll use it.

lswiderski commented 1 year ago

@philosowaffle First I tried with Duende.AccessTokenManagement but without success and and after tried few more. It was the first that worked. Garmin still use OAuth1 and not so many libraries of this standard are still maintaned :/

philosowaffle commented 1 year ago

Ah perfect, sounds like you have saved me a lot of headache then :)

philosowaffle commented 1 year ago

@lswiderski I appear to have everything working right up until the final step, exchanging the oauth1 for an oauth2 token. I'm getting a 500 response from POST https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0. Did you happen to run into a similar error while you were implementing? Trying to figure out what I may have messed up on my request.

lswiderski commented 1 year ago

@philosowaffle Did you set up Content-type to 'application/x-www-form-urlencoded'? Without that I got 500 too. Other cookies are not important for that exchange. It works well in Postman too with authorization type OAuth 1.0

This snippet works for me:

 public async Task<OAuth2Token> GetOAuth2Token(string accessToken, string tokenSecret)
 {

     OAuthRequest oauthClient2 = OAuthRequest.ForProtectedResource("POST", CONSUMER_KEY, CONSUMER_SECRET, accessToken, tokenSecret);

     oauthClient2.RequestUrl = $"https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0";
     string auth2 = oauthClient2.GetAuthorizationHeader();

     HttpWebRequest request2 = (HttpWebRequest)WebRequest.Create(oauthClient2.RequestUrl);

     request2.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
     request2.Headers.Add("Authorization", auth2);
     request2.Headers.Add("User-Agent", USER_AGENT);
     request2.Method = "POST";

     var oauthContent2 = "";
     OAuth2Token token = null;
     using (var oAuthResponse = (HttpWebResponse)request2.GetResponse())
     {

         using (var responseStream = oAuthResponse.GetResponseStream())
         using (var reader = new StreamReader(responseStream))
             oauthContent2 = reader.ReadToEnd();
         token = DeserializeData<OAuth2Token>(oauthContent2);

     }

     return token;
 }
philosowaffle commented 1 year ago

Yup, I think you're right, I'm missing that header. Thank you!

matin commented 1 year ago

@philosowaffle @lswiderski feel free to ping me if you need clarifications on the SSO logic.

matin commented 1 year ago

@lswiderski fwiw, I recommend fetching the keys from https://thegarth.s3.amazonaws.com/oauth_consumer.json vs storing them in your code like this: https://github.com/lswiderski/GarminWeightScaleUploader/blob/master/Libs/garmin-connect-client/GarminConnectClient.Lib/Services/Client.cs#L67-L68