Skip to content

Commit

Permalink
Handle the "Legacy Authentication" Setting
Browse files Browse the repository at this point in the history
iRacing will require a separate setting to turn off multi-factor
authentication after it is introduced, so that simple username and
password authentication can continue before they start enabling people
to register OAuth applications.
  • Loading branch information
AdrianJSClark committed Sep 29, 2024
1 parent 29575b4 commit 99ef652
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 6 deletions.
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"

0 comments on commit 99ef652

Please sign in to comment.