diff --git a/.github/actions/publish-ui-dist/action.yaml b/.github/actions/publish-ui-dist/action.yaml
index 35f1d4233..3c7a18656 100644
--- a/.github/actions/publish-ui-dist/action.yaml
+++ b/.github/actions/publish-ui-dist/action.yaml
@@ -22,6 +22,10 @@ runs:
with:
dotnet-version: ${{ inputs.dotnet-version }}
+ - name: Restore MAUI Workloads
+ run: dotnet workload restore
+ shell: pwsh
+
- name: List MAUI Workloads
run: dotnet workload list
shell: pwsh
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 5c70a54d3..4318e42c2 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -12,6 +12,7 @@ jobs:
matrix:
dotnet: ['7.0']
os: [windows-latest]
+ framework: ['net7.0-windows10.0.19041.0']
runs-on: ${{ matrix.os }}
@@ -23,6 +24,17 @@ jobs:
with:
dotnet-version: ${{ matrix.dotnet }}
+ - name: List SDKs
+ run: dotnet --list-sdks
+
+ - name: Restore MAUI Workloads
+ run: dotnet workload restore
+ shell: pwsh
+
+ - name: List MAUI Workloads
+ run: dotnet workload list
+ shell: pwsh
+
- name: Clean
run: dotnet clean --configuration Debug && dotnet nuget locals all --clear
@@ -41,7 +53,7 @@ jobs:
needs: build-and-test
strategy:
matrix:
- dotnet: [ '7.0.401' ]
+ dotnet: [ '7.0.410' ]
framework: ['net7.0-windows10.0.19041.0']
os: [ 'win10-x64' ]
@@ -80,4 +92,4 @@ jobs:
tag: ${{ matrix.tag }}
secret_docker_username: ${{ secrets.DOCKER_USERNAME }}
secret_docker_password: ${{ secrets.DOCKER_PASSWORD }}
- secret_github_package: ${{ secrets.GH_PACKAGE_SECRET}}
\ No newline at end of file
+ secret_github_package: ${{ secrets.GH_PACKAGE_SECRET}}
diff --git a/.github/workflows/publish-latest.yaml b/.github/workflows/publish-latest.yaml
index 4d0a4b45b..e49ca6298 100644
--- a/.github/workflows/publish-latest.yaml
+++ b/.github/workflows/publish-latest.yaml
@@ -44,7 +44,7 @@ jobs:
runs-on: 'windows-latest'
strategy:
matrix:
- dotnet: [ '7.0.400' ]
+ dotnet: [ '7.0.410' ]
framework: ['net7.0-windows10.0.19041.0']
os: [ 'win10-x64' ]
diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml
index c7b3724a8..7d8d85d8a 100644
--- a/.github/workflows/publish-release.yaml
+++ b/.github/workflows/publish-release.yaml
@@ -52,7 +52,7 @@ jobs:
artifact_name: ${{ steps.win-ui-create-artifact.outputs.artifact_name }}
strategy:
matrix:
- dotnet: [ '7.0.400' ]
+ dotnet: [ '7.0.410' ]
framework: ['net7.0-windows10.0.19041.0']
os: [ 'win10-x64' ]
diff --git a/PelotonToGarmin.sln b/PelotonToGarmin.sln
index fdb80f071..9812132fa 100644
--- a/PelotonToGarmin.sln
+++ b/PelotonToGarmin.sln
@@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solution", ".solution", "{
.gitignore = .gitignore
configuration.example.json = configuration.example.json
deviceInfo.sample.xml = deviceInfo.sample.xml
+ global.json = global.json
README.md = README.md
vNextReleaseNotes.md = vNextReleaseNotes.md
EndProjectSection
diff --git a/global.json b/global.json
new file mode 100644
index 000000000..796f298d4
--- /dev/null
+++ b/global.json
@@ -0,0 +1,5 @@
+{
+ "sdk": {
+ "version": "7.0.410"
+ }
+}
\ No newline at end of file
diff --git a/mkdocs/docs/configuration/garmin.md b/mkdocs/docs/configuration/garmin.md
index f50af2111..d0d8b164c 100644
--- a/mkdocs/docs/configuration/garmin.md
+++ b/mkdocs/docs/configuration/garmin.md
@@ -19,7 +19,21 @@ This Garmin Settings provide settings related to uploading workouts to Garmin.
"Password": "garmin",
"TwoStepVerificationEnabled": false,
"Upload": false,
- "FormatToUpload": "fit"
+ "FormatToUpload": "fit",
+ "api": {
+ "ssoSignInUrl": "https://sso.garmin.com/sso/signin",
+ "ssoEmbedUrl": "https://sso.garmin.com/sso/embed",
+ "ssoMfaCodeUrl": "https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode",
+ "ssoUserAgent": "GCM-iOS-5.7.2.1",
+ "oAuth1TokenUrl": "https://connectapi.garmin.com/oauth-service/oauth/preauthorized",
+ "oAuth1LoginUrlParam": "https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true",
+ "oAuth2RequestUrl": "https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0",
+ "uploadActivityUrl": "https://connectapi.garmin.com/upload-service/upload",
+ "uploadActivityUserAgent": "GCM-iOS-5.7.2.1",
+ "uplaodActivityNkHeader": "NT",
+ "origin": "https://sso.garmin.com",
+ "referer": "https://sso.garmin.com/sso/signin"
+ }
}
```
@@ -37,4 +51,5 @@ This Garmin Settings provide settings related to uploading workouts to Garmin.
| Password | **yes - if Upload=true** | `null` | `Garmin Tab` | Your Garmin password used to sign in. **Note: Does not support `\` character in password** |
| TwoStepVerificationEnabled | no | `false` | `Garmin Tab` | Whether or not your Garmin account is protected by Two Step Verification |
| Upload | no | `false` | `Garmin Tab` | `true` indicates you wish downloaded Peloton workouts to be uploaded to Garmin Connect. |
-| FormatToUpload | no | `fit` | `Garmin Tab > Advanced` | Valid values are `fit` or `tcx`. Ensure the format you specify here is also enabled in your [Format config](format.md) |
\ No newline at end of file
+| FormatToUpload | no | `fit` | `Garmin Tab > Advanced` | Valid values are `fit` or `tcx`. Ensure the format you specify here is also enabled in your [Format config](format.md) |
+| Api | no | See sample above | `Garmin Tab > Advanced > Garmin Api Settings` | Configures how P2G communicates with the Garmin Api. **Do not modify unless told to do so** |
diff --git a/src/Api.Contract/SettingsContracts.cs b/src/Api.Contract/SettingsContracts.cs
index 01dc094fe..64f613d07 100644
--- a/src/Api.Contract/SettingsContracts.cs
+++ b/src/Api.Contract/SettingsContracts.cs
@@ -34,7 +34,8 @@ public SettingsGetResponse(Settings settings)
TwoStepVerificationEnabled = settings.Garmin.TwoStepVerificationEnabled,
FormatToUpload = settings.Garmin.FormatToUpload,
Upload = settings.Garmin.Upload,
- IsPasswordSet = !string.IsNullOrEmpty(settings.Garmin.Password)
+ IsPasswordSet = !string.IsNullOrEmpty(settings.Garmin.Password),
+ Api = settings.Garmin.Api ?? new GarminApiSettings()
};
}
@@ -52,6 +53,7 @@ public class SettingsGarminGetResponse
public bool TwoStepVerificationEnabled { get; set; }
public bool Upload { get; set; }
public FileFormat FormatToUpload { get; set; }
+ public GarminApiSettings Api { get; set; } = new GarminApiSettings();
}
public class SettingsGarminPostRequest
@@ -61,6 +63,7 @@ public class SettingsGarminPostRequest
public bool TwoStepVerificationEnabled { get; set; }
public bool Upload { get; set; }
public FileFormat FormatToUpload { get; set; }
+ public GarminApiSettings Api { get; set; }
}
public class SettingsPelotonGetResponse
@@ -118,6 +121,7 @@ public static SettingsGarminPostRequest Map(this SettingsGarminGetResponse respo
TwoStepVerificationEnabled = response.TwoStepVerificationEnabled,
FormatToUpload = response.FormatToUpload,
Upload = response.Upload,
+ Api = response.Api,
};
}
@@ -130,6 +134,7 @@ public static GarminSettings Map(this SettingsGarminPostRequest request)
TwoStepVerificationEnabled = request.TwoStepVerificationEnabled,
FormatToUpload = request.FormatToUpload,
Upload = request.Upload,
+ Api = request.Api,
};
}
}
\ No newline at end of file
diff --git a/src/ClientUI/ClientUI.csproj b/src/ClientUI/ClientUI.csproj
index a297e2a1d..51bafb057 100644
--- a/src/ClientUI/ClientUI.csproj
+++ b/src/ClientUI/ClientUI.csproj
@@ -38,6 +38,10 @@
P2G ClientUI
+
+
@@ -60,6 +64,11 @@
+
+
+
+
+
diff --git a/src/Common/Constants.cs b/src/Common/Constants.cs
index cd5e7cffd..278aec717 100644
--- a/src/Common/Constants.cs
+++ b/src/Common/Constants.cs
@@ -9,6 +9,6 @@ public static class Constants
public const string WebUIName = "p2g_webui";
public const string ClientUIName = "p2g_clientui";
- public const string AppVersion = "4.3.0";
+ public const string AppVersion = "4.3.1-rc";
}
}
diff --git a/src/Common/Dto/Settings.cs b/src/Common/Dto/Settings.cs
index 0309f6ae7..6d2beffb2 100644
--- a/src/Common/Dto/Settings.cs
+++ b/src/Common/Dto/Settings.cs
@@ -140,7 +140,30 @@ public class GarminSettings : ICredentials
public string Password { get; set; }
public bool TwoStepVerificationEnabled { get; set; }
public bool Upload { get; set; }
- public FileFormat FormatToUpload { get; set; }
+ public FileFormat FormatToUpload { get; set; }
+ public GarminApiSettings Api { get; set; } = new GarminApiSettings();
+}
+
+public class GarminApiSettings
+{
+ public string SsoSignInUrl { get; set; } = "https://sso.garmin.com/sso/signin";
+ public string SsoEmbedUrl { get; set; } = "https://sso.garmin.com/sso/embed";
+ public string SsoMfaCodeUrl { get; set; } = "https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode";
+ public string SsoUserAgent { get; set; } = "GCM-iOS-5.7.2.1";
+
+ public string OAuth1TokenUrl { get; set; } = "https://connectapi.garmin.com/oauth-service/oauth/preauthorized";
+ public string OAuth1LoginUrlParam { get; set; } = "https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true";
+
+ public string OAuth2RequestUrl { get; set; } = "https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0";
+
+ public string UploadActivityUrl { get; set; } = "https://connectapi.garmin.com/upload-service/upload";
+ public string UploadActivityUserAgent { get; set; } = "GCM-iOS-5.7.2.1";
+ public string UplaodActivityNkHeader { get; set; } = "NT";
+
+ public string Origin { get; set; } = "https://sso.garmin.com";
+ public string Referer { get; set; } = "https://sso.garmin.com/sso/signin";
+
+
}
public enum FileFormat : byte
diff --git a/src/Garmin/ApiClient.cs b/src/Garmin/ApiClient.cs
index 7f9879d28..c02fafff5 100644
--- a/src/Garmin/ApiClient.cs
+++ b/src/Garmin/ApiClient.cs
@@ -1,5 +1,6 @@
using Common.Http;
using Common.Observe;
+using Common.Service;
using Flurl.Http;
using Garmin.Auth;
using Garmin.Dto;
@@ -13,51 +14,56 @@ namespace Garmin
{
public interface IGarminApiClient
{
- Task InitCookieJarAsync(object queryParams, string userAgent, out CookieJar jar);
- Task GetCsrfTokenAsync(object queryParams, CookieJar jar, string userAgent);
- Task SendCredentialsAsync(string email, string password, object queryParams, object loginData, string userAgent, CookieJar jar);
- Task SendMfaCodeAsync(string userAgent, object queryParams, object mfaData, CookieJar jar);
- Task GetOAuth1TokenAsync(ConsumerCredentials credentials, string ticket, string userAgent);
- Task GetOAuth2TokenAsync(OAuth1Token oAuth1Token, ConsumerCredentials credentials, string userAgent);
+ Task InitCookieJarAsync(object queryParams);
+ Task GetCsrfTokenAsync(object queryParams, CookieJar jar);
+ Task SendCredentialsAsync(string email, string password, object queryParams, object loginData, CookieJar jar);
+ Task SendMfaCodeAsync(object queryParams, object mfaData, CookieJar jar);
+ Task GetOAuth1TokenAsync(ConsumerCredentials credentials, string ticket);
+ Task GetOAuth2TokenAsync(OAuth1Token oAuth1Token, ConsumerCredentials credentials);
Task GetConsumerCredentialsAsync();
- Task UploadActivity(string filePath, string format, GarminApiAuthentication auth, string userAgent);
+ Task UploadActivity(string filePath, string format, GarminApiAuthentication auth);
}
public class ApiClient : IGarminApiClient
{
- 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 = $"https://connectapi.garmin.com/upload-service/upload";
-
- private const string ORIGIN = "https://sso.garmin.com";
- private const string REFERER = "https://sso.garmin.com/sso/signin";
+ private ISettingsService _settingsService;
private static readonly ILogger _logger = LogContext.ForClass();
+ public ApiClient(ISettingsService settingsService)
+ {
+ _settingsService = settingsService;
+ }
+
public Task GetConsumerCredentialsAsync()
{
return "https://thegarth.s3.amazonaws.com/oauth_consumer.json"
.GetJsonAsync();
}
- public Task InitCookieJarAsync(object queryParams, string userAgent, out CookieJar jar)
+ public async Task InitCookieJarAsync(object queryParams)
{
- return SSO_EMBED_URL
- .WithHeader("User-Agent", userAgent)
- .WithHeader("origin", ORIGIN)
+ var setttings = await _settingsService.GetSettingsAsync();
+
+ await setttings.Garmin.Api.SsoEmbedUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
+ .WithHeader("origin", setttings.Garmin.Api.Origin)
.SetQueryParams(queryParams)
- .WithCookies(out jar)
+ .WithCookies(out var jar)
.GetStringAsync();
+
+ return jar;
}
- public async Task SendCredentialsAsync(string email, string password, object queryParams, object loginData, string userAgent, CookieJar jar)
+ public async Task SendCredentialsAsync(string email, string password, object queryParams, object loginData, CookieJar jar)
{
+ var setttings = await _settingsService.GetSettingsAsync();
+
var result = new SendCredentialsResult();
- result.RawResponseBody = await SSO_SIGNIN_URL
- .WithHeader("User-Agent", userAgent)
- .WithHeader("origin", ORIGIN)
- .WithHeader("referer", REFERER)
+ result.RawResponseBody = await setttings.Garmin.Api.SsoSignInUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
+ .WithHeader("origin", setttings.Garmin.Api.Origin)
+ .WithHeader("referer", setttings.Garmin.Api.Referer)
.WithHeader("NK", "NT")
.SetQueryParams(queryParams)
.WithCookies(jar)
@@ -69,12 +75,14 @@ public async Task SendCredentialsAsync(string email, stri
return result;
}
- public async Task GetCsrfTokenAsync(object queryParams, CookieJar jar, string userAgent)
+ public async Task GetCsrfTokenAsync(object queryParams, CookieJar jar)
{
+ var setttings = await _settingsService.GetSettingsAsync();
+
var result = new GarminResult();
- result.RawResponseBody = await SSO_SIGNIN_URL
- .WithHeader("User-Agent", userAgent)
- .WithHeader("origin", ORIGIN)
+ result.RawResponseBody = await setttings.Garmin.Api.SsoSignInUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
+ .WithHeader("origin", setttings.Garmin.Api.Origin)
.SetQueryParams(queryParams)
.WithCookies(jar)
.GetAsync()
@@ -83,11 +91,13 @@ public async Task GetCsrfTokenAsync(object queryParams, CookieJar
return result;
}
- public Task SendMfaCodeAsync(string userAgent, object queryParams, object mfaData, CookieJar jar)
+ public async Task SendMfaCodeAsync(object queryParams, object mfaData, CookieJar jar)
{
- return "https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode"
- .WithHeader("User-Agent", userAgent)
- .WithHeader("origin", ORIGIN)
+ var setttings = await _settingsService.GetSettingsAsync();
+
+ return await setttings.Garmin.Api.SsoMfaCodeUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
+ .WithHeader("origin", setttings.Garmin.Api.Origin)
.SetQueryParams(queryParams)
.WithCookies(jar)
.OnRedirect(redir => redir.Request.WithCookies(jar))
@@ -95,37 +105,43 @@ public Task SendMfaCodeAsync(string userAgent, object queryParams, objec
.ReceiveString();
}
- public Task GetOAuth1TokenAsync(ConsumerCredentials credentials, string ticket, string userAgent)
+ public async Task GetOAuth1TokenAsync(ConsumerCredentials credentials, string ticket)
{
+ var setttings = await _settingsService.GetSettingsAsync();
+
OAuthRequest oauthClient = OAuthRequest.ForRequestToken(credentials.Consumer_Key, credentials.Consumer_Secret);
- oauthClient.RequestUrl = $"https://connectapi.garmin.com/oauth-service/oauth/preauthorized?ticket={ticket}&login-url=https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true";
+ oauthClient.RequestUrl = $"{setttings.Garmin.Api.OAuth1TokenUrl}?ticket={ticket}&login-url={setttings.Garmin.Api.OAuth1LoginUrlParam}";
- return oauthClient.RequestUrl
- .WithHeader("User-Agent", userAgent)
+ return await oauthClient.RequestUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
.WithHeader("Authorization", oauthClient.GetAuthorizationHeader())
.GetStringAsync();
}
- public Task GetOAuth2TokenAsync(OAuth1Token oAuth1Token, ConsumerCredentials credentials, string userAgent)
+ public async Task GetOAuth2TokenAsync(OAuth1Token oAuth1Token, ConsumerCredentials credentials)
{
- OAuthRequest oauthClient2 = OAuthRequest.ForProtectedResource("POST", credentials.Consumer_Key, credentials.Consumer_Secret, oAuth1Token.Token, oAuth1Token.TokenSecret);
- oauthClient2.RequestUrl = "https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0";
+ var setttings = await _settingsService.GetSettingsAsync();
- return oauthClient2.RequestUrl
- .WithHeader("User-Agent", userAgent)
+ OAuthRequest oauthClient2 = OAuthRequest.ForProtectedResource("POST", credentials.Consumer_Key, credentials.Consumer_Secret, oAuth1Token.Token, oAuth1Token.TokenSecret);
+ oauthClient2.RequestUrl = setttings.Garmin.Api.OAuth2RequestUrl;
+
+ return await oauthClient2.RequestUrl
+ .WithHeader("User-Agent", setttings.Garmin.Api.SsoUserAgent)
.WithHeader("Authorization", oauthClient2.GetAuthorizationHeader())
.WithHeader("Content-Type", "application/x-www-form-urlencoded") // this header is required, without it you get a 500
.PostUrlEncodedAsync(new object()) // hack: PostAsync() will drop the content-type header, by posting empty object we trick flurl into leaving the header
.ReceiveJson();
}
- public async Task UploadActivity(string filePath, string format, GarminApiAuthentication auth, string userAgent)
+ public async Task UploadActivity(string filePath, string format, GarminApiAuthentication auth)
{
+ var settings = await _settingsService.GetSettingsAsync();
+
var fileName = Path.GetFileName(filePath);
- var response = await $"{UPLOAD_URL}/{format}"
+ var response = await $"{settings.Garmin.Api.UploadActivityUrl}/{format}"
.WithOAuthBearerToken(auth.OAuth2Token.Access_Token)
- .WithHeader("NK", "NT")
- .WithHeader("origin", ORIGIN)
- .WithHeader("User-Agent", userAgent)
+ .WithHeader("NK", settings.Garmin.Api.UplaodActivityNkHeader)
+ .WithHeader("origin", settings.Garmin.Api.Origin)
+ .WithHeader("User-Agent", settings.Garmin.Api.UploadActivityUserAgent)
.AllowHttpStatus("2xx,409")
.PostMultipartAsync((data) =>
{
diff --git a/src/Garmin/Auth/GarminAuthenticationService.cs b/src/Garmin/Auth/GarminAuthenticationService.cs
index bfffd1a08..d5c0956b1 100644
--- a/src/Garmin/Auth/GarminAuthenticationService.cs
+++ b/src/Garmin/Auth/GarminAuthenticationService.cs
@@ -75,11 +75,8 @@ public async Task GetGarminAuthenticationAsync()
{
var consumerCredentials = await _apiClient.GetConsumerCredentialsAsync();
var appConfig = await _settingsService.GetAppConfigurationAsync();
- var userAgent = Defaults.DefaultUserAgent;
- if (!string.IsNullOrEmpty(appConfig.Developer.UserAgent))
- userAgent = appConfig.Developer.UserAgent;
- return await ExchangeOAuth1ForOAuth2Async(oAuth1Token, consumerCredentials, userAgent);
+ return await ExchangeOAuth1ForOAuth2Async(oAuth1Token, consumerCredentials);
} catch (Exception ex)
{
@@ -98,18 +95,15 @@ public async Task SignInAsync()
await SignOutAsync();
CookieJar jar = null;
- var userAgent = Defaults.DefaultUserAgent;
var appConfig = await _settingsService.GetAppConfigurationAsync();
- if (!string.IsNullOrEmpty(appConfig.Developer.UserAgent))
- userAgent = appConfig.Developer.UserAgent;
/////////////////////////////////
// Init Auth Flow
////////////////////////////////
try
{
- await _apiClient.InitCookieJarAsync(CommonQueryParams, userAgent, out jar);
+ jar = await _apiClient.InitCookieJarAsync(CommonQueryParams);
}
catch (FlurlHttpException e)
{
@@ -123,17 +117,17 @@ public async Task SignInAsync()
{
id = "gauth-widget",
embedWidget = "true",
- gauthHost = "https://sso.garmin.com/sso/embed",
- 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",
+ gauthHost = settings.Garmin.Api.SsoEmbedUrl,
+ service = settings.Garmin.Api.SsoEmbedUrl,
+ source = settings.Garmin.Api.SsoEmbedUrl,
+ redirectAfterAccountLoginUrl = settings.Garmin.Api.SsoEmbedUrl,
+ redirectAfterAccountCreationUrl = settings.Garmin.Api.SsoEmbedUrl,
};
var csrfToken = string.Empty;
try
{
- var tokenResult = await _apiClient.GetCsrfTokenAsync(csrfRequest, jar, userAgent);
+ var tokenResult = await _apiClient.GetCsrfTokenAsync(csrfRequest, jar);
csrfToken = FindCsrfToken(tokenResult.RawResponseBody, failureStepCode: Code.FailedPriorToCredentialsUsed);
}
catch (FlurlHttpException e)
@@ -154,7 +148,7 @@ public async Task SignInAsync()
SendCredentialsResult sendCredentialsResult = null;
try
{
- sendCredentialsResult = await _apiClient.SendCredentialsAsync(settings.Garmin.Email, settings.Garmin.Password, csrfRequest, sendCredentialsRequest, userAgent, jar);
+ sendCredentialsResult = await _apiClient.SendCredentialsAsync(settings.Garmin.Email, settings.Garmin.Password, csrfRequest, sendCredentialsRequest, jar);
}
catch (FlurlHttpException e) when (e.StatusCode is (int)HttpStatusCode.Forbidden)
{
@@ -172,7 +166,7 @@ public async Task SignInAsync()
if (sendCredentialsResult.WasRedirected && sendCredentialsResult.RedirectedTo.Contains("https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode"))
{
if (!settings.Garmin.TwoStepVerificationEnabled)
- throw new GarminAuthenticationError("Detected Garmin TwoFactorAuthentication but TwoFactorAuthenctication is not enabled in P2G settings. Please enable TwoFactorAuthentication in your P2G Garmin settings.") { Code = Code.UnexpectedMfa };
+ throw new GarminAuthenticationError("Detected Garmin TwoFactorAuthentication but TwoFactorAuthentication is not enabled in P2G settings. Please enable TwoFactorAuthentication in your P2G Garmin settings.") { Code = Code.UnexpectedMfa };
var mfaCsrfToken = FindCsrfToken(sendCredentialsResult.RawResponseBody, failureStepCode: Code.FailedPriorToMfaUsed);
@@ -182,7 +176,6 @@ public async Task SignInAsync()
AuthStage = AuthStage.NeedMfaToken,
MFACsrfToken = mfaCsrfToken,
CookieJarString = jar.ToString(),
- UserAgent = userAgent,
};
await _garminDb.UpsertPartialGarminAuthenticationAsync(1, partialAuthentication);
@@ -190,10 +183,10 @@ public async Task SignInAsync()
}
var loginResult = sendCredentialsResult?.RawResponseBody;
- return await CompleteGarminAuthenticationAsync(loginResult, userAgent);
+ return await CompleteGarminAuthenticationAsync(loginResult);
}
- private async Task CompleteGarminAuthenticationAsync(string loginResult, string userAgent)
+ private async Task CompleteGarminAuthenticationAsync(string loginResult)
{
// Try to find the full post login ServiceTicket
var ticketRegex = new Regex("embed\\?ticket=(?[^\"]+)\"");
@@ -211,13 +204,13 @@ private async Task CompleteGarminAuthenticationAsync(st
// Get OAuth1 Tokens
///////////////////////////////////////////
var consumerCredentials = await _apiClient.GetConsumerCredentialsAsync();
- var oAuth1Token = await GetOAuth1Async(ticket, consumerCredentials, userAgent);
+ var oAuth1Token = await GetOAuth1Async(ticket, consumerCredentials);
await _garminDb.UpsertGarminOAuth1TokenAsync(1, oAuth1Token);
////////////////////////////////////////////
// Exchange for OAuth2 Token
///////////////////////////////////////////
- var result = await ExchangeOAuth1ForOAuth2Async(oAuth1Token, consumerCredentials, userAgent);
+ var result = await ExchangeOAuth1ForOAuth2Async(oAuth1Token, consumerCredentials);
// Clear partial data
await _garminDb.UpsertPartialGarminAuthenticationAsync(1, null);
@@ -225,12 +218,12 @@ private async Task CompleteGarminAuthenticationAsync(st
return result;
}
- private async Task ExchangeOAuth1ForOAuth2Async(OAuth1Token oAuth1Token, ConsumerCredentials consumerCredentials, string userAgent)
+ private async Task ExchangeOAuth1ForOAuth2Async(OAuth1Token oAuth1Token, ConsumerCredentials consumerCredentials)
{
OAuth2Token oAuth2Token = null;
try
{
- oAuth2Token = await _apiClient.GetOAuth2TokenAsync(oAuth1Token, consumerCredentials, userAgent);
+ oAuth2Token = await _apiClient.GetOAuth2TokenAsync(oAuth1Token, consumerCredentials);
oAuth2Token.ExpiresAt = DateTime.Now.AddSeconds(oAuth2Token.Expires_In);
await _garminDb.UpsertGarminOAuth2TokenAsync(1, oAuth2Token);
@@ -260,9 +253,6 @@ public async Task CompleteMFAAuthAsync(string mfaCode)
if (partialAuth.AuthStage != AuthStage.NeedMfaToken)
throw new ArgumentException($"We're in the wrong GarminAuthStage, expected NeedMfaToken but found {partialAuth.AuthStage}");
- if (string.IsNullOrEmpty(partialAuth.UserAgent))
- partialAuth.UserAgent = Defaults.DefaultUserAgent;
-
var mfaData = new List>()
{
new KeyValuePair("embed", "true"),
@@ -278,8 +268,8 @@ public async Task CompleteMFAAuthAsync(string mfaCode)
{
SendMFAResult mfaResponse = new();
var jar = CookieJar.LoadFromString(partialAuth.CookieJarString);
- mfaResponse.RawResponseBody = await _apiClient.SendMfaCodeAsync(partialAuth.UserAgent, CommonQueryParams, mfaData, jar);
- return await CompleteGarminAuthenticationAsync(mfaResponse.RawResponseBody, partialAuth.UserAgent);
+ mfaResponse.RawResponseBody = await _apiClient.SendMfaCodeAsync(CommonQueryParams, mfaData, jar);
+ return await CompleteGarminAuthenticationAsync(mfaResponse.RawResponseBody);
}
catch (FlurlHttpException e) when (e.StatusCode is (int)HttpStatusCode.Forbidden)
{
@@ -314,12 +304,12 @@ private string FindCsrfToken(string rawResponseBody, Code failureStepCode)
}
}
- private async Task GetOAuth1Async(string ticket, ConsumerCredentials credentials, string userAgent)
+ private async Task GetOAuth1Async(string ticket, ConsumerCredentials credentials)
{
string oauth1Response = null;
try
{
- oauth1Response = await _apiClient.GetOAuth1TokenAsync(credentials, ticket, userAgent);
+ oauth1Response = await _apiClient.GetOAuth1TokenAsync(credentials, ticket);
} catch (Exception e)
{
throw new GarminAuthenticationError("Auth appeared successful but failed to get the OAuth1 token.", e) { Code = Code.AuthAppearedSuccessful };
diff --git a/src/Garmin/GarminUploader.cs b/src/Garmin/GarminUploader.cs
index fd30b72ef..2dfe7009d 100644
--- a/src/Garmin/GarminUploader.cs
+++ b/src/Garmin/GarminUploader.cs
@@ -89,17 +89,12 @@ private async Task UploadAsync(string[] files, Settings settings)
if (auth.AuthStage == Dto.AuthStage.None)
throw new GarminUploadException("Expected user to be authenticated with Garmin at this point, but they are not. AuthStage: None.", -3);
- var userAgent = Defaults.DefaultUserAgent;
- var appConfig = await _settingsService.GetAppConfigurationAsync();
- if (!string.IsNullOrEmpty(appConfig.Developer.UserAgent))
- userAgent = appConfig.Developer.UserAgent;
-
foreach (var file in files)
{
try
{
_logger.Information("Uploading to Garmin: {@file}", file);
- await _api.UploadActivity(file, settings.Format.Fit ? ".fit" : ".tcx", auth, userAgent);
+ await _api.UploadActivity(file, settings.Format.Fit ? ".fit" : ".tcx", auth);
await RateLimit();
} catch (Exception e)
{
diff --git a/src/SharedUI/Shared/GarminSettingsForm.razor b/src/SharedUI/Shared/GarminSettingsForm.razor
index f01176448..3c864f906 100644
--- a/src/SharedUI/Shared/GarminSettingsForm.razor
+++ b/src/SharedUI/Shared/GarminSettingsForm.razor
@@ -5,9 +5,12 @@
-
+
+
+
-
+
+
Auth
@@ -39,37 +42,113 @@
-
-
+
+
+
+
Advanced
-
-
+
+
+ Most users should not need to modify these settings. Please be sure you've read the documentation before changing.
+
+
+
+
+ Upload Format Settings
+
+ ?
+
+
+
-
(click the ? to pin this window)";
}
diff --git a/src/SharedUI/SharedUI.csproj b/src/SharedUI/SharedUI.csproj
index 8c9464f87..32d557090 100644
--- a/src/SharedUI/SharedUI.csproj
+++ b/src/SharedUI/SharedUI.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/WebUI/WebUI.csproj b/src/WebUI/WebUI.csproj
index 77e69bb1c..5886a2fa4 100644
--- a/src/WebUI/WebUI.csproj
+++ b/src/WebUI/WebUI.csproj
@@ -26,6 +26,7 @@
+
diff --git a/vNextReleaseNotes.md b/vNextReleaseNotes.md
index dad5dc10f..970f4fde8 100644
--- a/vNextReleaseNotes.md
+++ b/vNextReleaseNotes.md
@@ -1,30 +1,28 @@
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/philosowaffle)
---
-## Features
+## Fixes
-- [#585]
- - Garmin Authentication now saves and refreshes tokens. Users using MFA will now only need provide their MFA code once.
- - For those running via Docker, automatic syncing now works for MFA users after you have entered your code the first time.
+- [#683] Initial fix for Garmin Upload error. Additionally introduces new settings for configuring Garmin Api.
-## Misc
-
-- [#587] Dependency updates + switched to the official Garmin FIT SDK nuget package
+> [!CAUTION]
+> **Windows App Users**
+> When editing settings, you may encounter an issue where your mouse stops working within the P2G app. Keyboard navigation continues to work. If this happens, quit P2G and restart. I will be investigating how to get a proper fix for this on a future release.
## Docker Tags
- Console
- `console-stable`
- `console-latest`
- - `console-v4.3.0`
+ - `console-v4.3.1`
- `console-v4`
- Api
- `api-stable`
- `api-latest`
- - `api-v4.3.0`
+ - `api-v4.3.1`
- `api-v4`
- WebUI
- `webui-stable`
- `webui-latest`
- - `webui-v4.3.0`
+ - `webui-v4.3.1`
- `webui-v4`