Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle the "Legacy Authentication" Setting #227

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,27 @@ public async Task GetSubSessionResultUnauthorizedThrowsErrorsAsync()
});
}

[Test(TestOf = typeof(DataClient))]
public async Task GetSubSessionResultUnauthorizedDueToLegacyAuthenticationSettingThrowsErrorsAsync()
{
await MessageHandler.QueueResponsesAsync("ResponseUnauthorizedLegacyRequired", false).ConfigureAwait(false);

Assert.Multiple(() =>
{
var loginFailedException = Assert.ThrowsAsync<iRacingLoginFailedException>(async () =>
{
var lapChartResponse = await sut.GetSubSessionResultAsync(12345, false).ConfigureAwait(false);
});

if (loginFailedException != null)
{
Assert.That(loginFailedException.LegacyAuthenticationRequired, Is.True);
}

Assert.That(sut.IsLoggedIn, Is.False);
});
}

[Test(TestOf = typeof(DataClient))]
public async Task GetSubsessionEventLogSuccessfulAsync()
{
Expand Down
4 changes: 3 additions & 1 deletion src/Aydsko.iRacingData.UnitTests/MockedHttpMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// This file is licensed to you under the MIT license.

using System.Net;
#if !NET6_0_OR_GREATER
using System.Net.Http;
using System.Net.Http.Json;
#endif
using System.Reflection;
using System.Text;
using System.Text.Json;
Expand Down Expand Up @@ -67,7 +69,7 @@ public async Task QueueResponsesAsync(string testName, bool prefixLoginResponse
{
var manifestResourceNames = (prefixLoginResponse ? SuccessfulLoginResponse : [])
.Concat(ResourceAssembly.GetManifestResourceNames()
.Where(mrn => mrn.StartsWith($"Aydsko.iRacingData.UnitTests.Responses.{testName}", StringComparison.InvariantCultureIgnoreCase)));
.Where(mrn => mrn.StartsWith($"Aydsko.iRacingData.UnitTests.Responses.{testName}.", StringComparison.InvariantCultureIgnoreCase)));

foreach (var manifestName in manifestResourceNames)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"statuscode": 401,
"headers": { },
"content": {
"error": "access_denied",
"error_description": "legacy authorization refused"
}
}
3 changes: 3 additions & 0 deletions src/Aydsko.iRacingData/Common/ErrorResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ public class ErrorResponse

[JsonPropertyName("message")]
public string? Message { get; set; }

[JsonPropertyName("error_description")]
public string? ErrorDescription { get; set; }
}
21 changes: 21 additions & 0 deletions src/Aydsko.iRacingData/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
<Right>lib/net6.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Exceptions.iRacingLoginFailedException.Create(System.String,System.Nullable{System.Boolean})</Target>
<Left>lib/net6.0/Aydsko.iRacingData.dll</Left>
<Right>lib/net6.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Hosted.Car.get_PowerAdjustPercent</Target>
Expand Down Expand Up @@ -84,6 +91,13 @@
<Right>lib/net8.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Exceptions.iRacingLoginFailedException.Create(System.String,System.Nullable{System.Boolean})</Target>
<Left>lib/net8.0/Aydsko.iRacingData.dll</Left>
<Right>lib/net8.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Hosted.Car.get_PowerAdjustPercent</Target>
Expand Down Expand Up @@ -199,6 +213,13 @@
<Right>lib/netstandard2.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Exceptions.iRacingLoginFailedException.Create(System.String,System.Nullable{System.Boolean})</Target>
<Left>lib/netstandard2.0/Aydsko.iRacingData.dll</Left>
<Right>lib/netstandard2.0/Aydsko.iRacingData.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Aydsko.iRacingData.Hosted.Car.get_PowerAdjustPercent</Target>
Expand Down
21 changes: 19 additions & 2 deletions src/Aydsko.iRacingData/DataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,23 @@ private async Task LoginInternalAsync(CancellationToken cancellationToken)
{
throw new iRacingInMaintenancePeriodException("Maintenance assumed because login returned HTTP Error 503 \"Service Unavailable\".");
}
else if (loginResponse.StatusCode == HttpStatusCode.Unauthorized)
{
#if NET6_0_OR_GREATER
var content = await loginResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var content = await loginResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
var errorResponse = JsonSerializer.Deserialize<ErrorResponse>(content);

if (errorResponse is not null && errorResponse.ErrorCode == "access_denied")
{
var errorDescription = errorResponse.ErrorDescription ?? errorResponse.Note ?? errorResponse.Message ?? string.Empty;
throw iRacingLoginFailedException.Create($"Access was denied with message \"{errorDescription}\"",
false,
errorDescription.Equals("legacy authorization refused", StringComparison.OrdinalIgnoreCase));
}
}
throw new iRacingLoginFailedException($"Login failed with HTTP response \"{loginResponse.StatusCode} {loginResponse.ReasonPhrase}\"");
}

Expand Down Expand Up @@ -2125,13 +2142,13 @@ protected virtual void HandleUnsuccessfulResponse(HttpResponseMessage httpRespon
else
{
var errorResponse = JsonSerializer.Deserialize<ErrorResponse>(content);
errorDescription = errorResponse?.Note ?? errorResponse?.Message ?? "An error occurred.";
errorDescription = errorResponse?.Note ?? errorResponse?.Message ?? errorResponse?.ErrorDescription ?? "An error occurred.";

exception = errorResponse switch
{
{ ErrorCode: "Site Maintenance" } => new iRacingInMaintenancePeriodException(errorResponse.Note ?? "iRacing services are down for maintenance."),
{ ErrorCode: "Forbidden" } => iRacingForbiddenResponseException.Create(),
{ ErrorCode: "Unauthorized" } => iRacingUnauthorizedResponseException.Create(errorResponse.Message),
{ ErrorCode: "Unauthorized" } or { ErrorCode: "access_denied" } => iRacingUnauthorizedResponseException.Create(errorResponse.Message),
_ => null
};
}
Expand Down
16 changes: 13 additions & 3 deletions src/Aydsko.iRacingData/Exceptions/iRacingLoginFailedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ namespace Aydsko.iRacingData.Exceptions;
[Serializable]
public class iRacingLoginFailedException : iRacingDataClientException
{
/// <summary>Indicates the account requires the user to authenticate via a browser to complete a CAPTCHA or contact iRacing Support.</summary>
public bool? VerificationRequired { get; private set; }
/// <summary>If set to <see langword="true"/> the user account must be configured for &quot;Legacy Authentication&quot; to bypass multi-factor authentication.</summary>
public bool? LegacyAuthenticationRequired { get; private set; }

public static iRacingLoginFailedException Create(string? message, bool? verificationRequired = null)
public static iRacingLoginFailedException Create(string? message, bool? verificationRequired = null, bool? legacyAuthenticationRequired = null)
{
var exceptionMessage = message ?? "Login to iRacing failed.";

return verificationRequired is null
return verificationRequired is null && legacyAuthenticationRequired is null
? new iRacingLoginFailedException(exceptionMessage)
: new iRacingLoginFailedException(exceptionMessage, verificationRequired.Value);
: new iRacingLoginFailedException(exceptionMessage, verificationRequired ?? false, legacyAuthenticationRequired ?? false);
}

public static iRacingLoginFailedException Create(Exception ex)
Expand All @@ -36,6 +39,13 @@ public iRacingLoginFailedException(string message, bool verificationRequired)
VerificationRequired = verificationRequired;
}

public iRacingLoginFailedException(string message, bool verificationRequired, bool legacyAuthenticationRequired)
: base(message)
{
VerificationRequired = verificationRequired;
LegacyAuthenticationRequired = legacyAuthenticationRequired;
}

public iRacingLoginFailedException(string message, Exception inner)
: base(message, inner)
{ }
Expand Down
11 changes: 11 additions & 0 deletions src/Aydsko.iRacingData/Package Release Notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@ Fixes / Changes:
- Season Driver Standings Deserialization Failure (Issue #224)
Fixed an issue where the "SeasonDriverStandings" deserialization would fail
due to the "avg_start_position" property containing non-integer data.

- Support "Legacy Authentication" Setting Failure Response (Issue #223)
Added support for the "Legacy Authentication" setting in the iRacing
account settings. This setting will be required to login to the
"/data API" after iRacing enables multi-factor authentication (aka "2FA").

If the setting is not enabled, an "iRacingLoginFailedException" will be
thrown when attempting to login with the new "LegacyAuthenticationRequired"
property set to "true" and the following message:

Access was denied with message \"legacy authorization refused"
Loading