Skip to content

Commit

Permalink
stubbing out more of the flow
Browse files Browse the repository at this point in the history
  • Loading branch information
philosowaffle committed Sep 30, 2023
1 parent 9db212d commit 09a823f
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 19 deletions.
24 changes: 20 additions & 4 deletions src/Garmin/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace Garmin
public interface IGarminApiClient
{
Task InitSigninFlowAsync(object queryParams, string userAgent, out CookieJar jar);
Task InitCookieJarAsync(object queryParams, string userAgent, out CookieJar jar)
Task InitCookieJarAsync(object queryParams, string userAgent, out CookieJar jar);
Task<GarminResult> GetCsrfTokenAsync(GarminApiAuthentication auth, object queryParams, CookieJar jar);
Task<SendCredentialsResult> SendCredentialsAsync(GarminApiAuthentication auth, object queryParams, object loginData, CookieJar jar);
Task<string> SendMfaCodeAsync(string userAgent, object queryParams, object mfaData, CookieJar jar);
Task<IFlurlResponse> SendServiceTicketAsync(string userAgent, string serviceTicket, CookieJar jar);
Expand All @@ -25,7 +26,7 @@ public class ApiClient : IGarminApiClient
{
private const string BASE_URL = "https://connect.garmin.com";
private const string SSO_URL = "https://sso.garmin.com";
private const string SIGNIN_URL = "https://sso.garmin.com/sso/signin";
private const string SSO_SIGNIN_URL = "https://sso.garmin.com/sso/signin";
private const string SSO_EMBED_URL = "https://sso.garmin.com/sso/embed";

private static string UPLOAD_URL = $"{BASE_URL}/modern/proxy/upload-service/upload";
Expand All @@ -36,7 +37,7 @@ public class ApiClient : IGarminApiClient

public Task InitSigninFlowAsync(object queryParams, string userAgent, out CookieJar jar)
{
return SIGNIN_URL
return SSO_SIGNIN_URL
.WithHeader("User-Agent", userAgent)
.WithHeader("origin", ORIGIN)
.SetQueryParams(queryParams)
Expand All @@ -57,7 +58,7 @@ public Task InitCookieJarAsync(object queryParams, string userAgent, out CookieJ
public async Task<SendCredentialsResult> SendCredentialsAsync(GarminApiAuthentication auth, object queryParams, object loginData, CookieJar jar)
{
var result = new SendCredentialsResult();
result.RawResponseBody = await SIGNIN_URL
result.RawResponseBody = await SSO_SIGNIN_URL
.WithHeader("User-Agent", auth.UserAgent)
.WithHeader("origin", ORIGIN)
.SetQueryParams(queryParams)
Expand All @@ -70,6 +71,21 @@ public async Task<SendCredentialsResult> SendCredentialsAsync(GarminApiAuthentic
return result;
}

public async Task<GarminResult> GetCsrfTokenAsync(GarminApiAuthentication auth, object queryParams, CookieJar jar)
{
var result = new GarminResult();
result.RawResponseBody = await SSO_SIGNIN_URL
.WithHeader("User-Agent", auth.UserAgent)
.WithHeader("origin", ORIGIN)
.SetQueryParams(queryParams)
.WithCookies(jar)
.StripSensitiveDataFromLogging(auth.Email, auth.Password)
.GetAsync()
.ReceiveString();

return result;
}

public Task<string> SendMfaCodeAsync(string userAgent, object queryParams, object mfaData, CookieJar jar)
{
return "https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode"
Expand Down
6 changes: 6 additions & 0 deletions src/Garmin/Auth/GarminAuthContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ public interface GarminResultWrapper
string RawResponseBody { get; set; }
}

public class GarminResult : GarminResultWrapper
{
public string RawResponseBody { get; set; }
}


public class SendCredentialsResult : GarminResultWrapper
{
public bool WasRedirected { get; set; }
Expand Down
77 changes: 62 additions & 15 deletions src/Garmin/Auth/GarminOAuthService.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using Common.Service;
using Common.Stateful;
using Flurl;
using Flurl.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Intrinsics.X86;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using IdentityModel.Client;

namespace Garmin.Auth;

Expand Down Expand Up @@ -54,18 +51,68 @@ private async Task GetAuthTokenAsync()
throw new GarminAuthenticationError("Failed to initialize sign in flow.", e) { Code = Code.FailedPriorToCredentialsUsed };
}

object loginData = new
{
embed = "true",
username = auth.Email,
password = auth.Password,
lt = "e1s1",
_eventId = "submit",
displayNameRequired = "false",
/////////////////////////////////
// Get CSRF token
////////////////////////////////
object csrfRequest = new
{
id = "gauth-widget",
embed = "true",
gauthHost = "https://sso.garmin.com/sso",
service = "https://sso.garmin.com/sso/embed",
source = "https://sso.garmin.com/sso/embed",
redirectAfterAccountLoginUrl = "https://sso.garmin.com/sso/embed",
redirectAfterAccountCreationUrl = "https://sso.garmin.com/sso/embed",
};

var tokenResult = await _apiClient.GetCsrfTokenAsync(auth, csrfRequest, jar);
var tokenRegex = new Regex("name=\"_csrf\"\\s+value=\"(.+?)\"");
var match = tokenRegex.Match(tokenResult.RawResponseBody);
if (!match.Success)
throw new Exception("Failed to regex match token");

var csrfToken = match.Groups.Values.First();

/////////////////////////////////
// Get CSRF token
// Submit login form
////////////////////////////////
var loginData = new
{
username = "email",
passowrd = "password",
embed = "true",
_csrf = csrfToken
};
var signInResult = await _apiClient.SendCredentialsAsync(auth, csrfRequest, loginData, jar);

if (signInResult.WasRedirected && signInResult.RedirectedTo.Contains("https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode"))
{
// todo: handle mfa flow
throw new NotImplementedException("handle mfa");
}

var ticketRegex = new Regex("embed\\?ticket=([^\"]+)\"");
var ticketMatch = ticketRegex.Match(signInResult.RawResponseBody);
if (!ticketMatch.Success)
throw new Exception("Filed to find post signin ticket.");

var ticket = ticketMatch.Groups.Values.First();

/////////////////////////////////
// Get OAuth Tokens
////////////////////////////////

// TODO: fetch id and secret from garth hosted file
var c = new FlurlClient();
var result = await c
.WithHeader("User-Agent", auth.UserAgent)
.HttpClient
.RequestTokenAsync(new TokenRequest()
{
Address = $"https://connectapi.garmin.com/oauth-service/oauth/preauthorized?ticket={ticket}&login-url=https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true",
ClientId = "fc3e99d2-118c-44b8-8ae3-03370dde24c0",
ClientSecret = "E08WAR897WEy2knn7aFBrvegVAf0AFdWBBF"
});

}
}

0 comments on commit 09a823f

Please sign in to comment.