Skip to content

Commit 4eee2e2

Browse files
committed
Fix authentication flow when reaching max failures in credentials provider
This one is tricky, previously we've properly handled max failures, and told SK2 to abort the request with operation canceled exception, which we even handled back in bot flow, so it looked OK at first, but the bot didn't do anything with that, which resulted in forced TryAnotherCM disconnection after 1 minute of inactivity. Since we need to be informed what to do in such case, simple cancellation of flow is not enough, we require a custom exception to handle, which will tell us the precise reason for failure + possible count of them, and that will in result allow us to react accordingly in the bot flow, e.g. by stopping the bot if needed.
1 parent 90fe1a9 commit 4eee2e2

File tree

3 files changed

+82
-22
lines changed

3 files changed

+82
-22
lines changed

ArchiSteamFarm/Steam/Bot.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,14 +2778,14 @@ private async void OnConnected(SteamClient.ConnectedCallback callback) {
27782778
InitConnectionFailureTimer();
27792779

27802780
if (string.IsNullOrEmpty(RefreshToken)) {
2781+
BotCredentialsProvider botCredentialsProvider = new(this);
2782+
27812783
AuthPollResult pollResult;
27822784

27832785
try {
2784-
using CancellationTokenSource authCancellationTokenSource = new();
2785-
27862786
CredentialsAuthSession authSession = await SteamClient.Authentication.BeginAuthSessionViaCredentialsAsync(
27872787
new AuthSessionDetails {
2788-
Authenticator = new BotCredentialsProvider(this, authCancellationTokenSource),
2788+
Authenticator = botCredentialsProvider,
27892789
DeviceFriendlyName = machineName,
27902790
GuardData = BotConfig.UseLoginKeys ? BotDatabase.SteamGuardData : null,
27912791
IsPersistentSession = true,
@@ -2794,10 +2794,12 @@ private async void OnConnected(SteamClient.ConnectedCallback callback) {
27942794
}
27952795
).ConfigureAwait(false);
27962796

2797-
pollResult = await authSession.PollingWaitForResultAsync(authCancellationTokenSource.Token).ConfigureAwait(false);
2797+
pollResult = await authSession.PollingWaitForResultAsync().ConfigureAwait(false);
27982798
} catch (AsyncJobFailedException e) {
27992799
ArchiLogger.LogGenericWarningException(e);
28002800

2801+
LoginFailures += botCredentialsProvider.LoginFailures;
2802+
28012803
await HandleLoginResult(EResult.Timeout, EResult.Timeout).ConfigureAwait(false);
28022804

28032805
ReconnectOnUserInitiated = true;
@@ -2807,17 +2809,36 @@ private async void OnConnected(SteamClient.ConnectedCallback callback) {
28072809
} catch (AuthenticationException e) {
28082810
ArchiLogger.LogGenericWarningException(e);
28092811

2812+
LoginFailures += botCredentialsProvider.LoginFailures;
2813+
2814+
await HandleLoginResult(e.Result, e.Result).ConfigureAwait(false);
2815+
2816+
ReconnectOnUserInitiated = true;
2817+
SteamClient.Disconnect();
2818+
2819+
return;
2820+
} catch (BotAuthenticationException e) {
2821+
LoginFailures += botCredentialsProvider.LoginFailures;
2822+
28102823
await HandleLoginResult(e.Result, e.Result).ConfigureAwait(false);
28112824

28122825
ReconnectOnUserInitiated = true;
28132826
SteamClient.Disconnect();
28142827

28152828
return;
28162829
} catch (OperationCanceledException) {
2817-
// This is okay, we already took care of that and can ignore it here
2830+
LoginFailures += botCredentialsProvider.LoginFailures;
2831+
2832+
await HandleLoginResult(EResult.Timeout, EResult.Timeout).ConfigureAwait(false);
2833+
2834+
ReconnectOnUserInitiated = true;
2835+
SteamClient.Disconnect();
2836+
28182837
return;
28192838
}
28202839

2840+
LoginFailures += botCredentialsProvider.LoginFailures;
2841+
28212842
if (!string.IsNullOrEmpty(pollResult.NewGuardData) && BotConfig.UseLoginKeys) {
28222843
BotDatabase.SteamGuardData = pollResult.NewGuardData;
28232844
}

ArchiSteamFarm/Steam/Integration/BotCredentialsProvider.cs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,27 @@
2323

2424
using System;
2525
using System.ComponentModel;
26-
using System.Threading;
2726
using System.Threading.Tasks;
2827
using ArchiSteamFarm.Core;
2928
using ArchiSteamFarm.Localization;
29+
using ArchiSteamFarm.Steam.Security;
3030
using ArchiSteamFarm.Storage;
3131
using SteamKit2;
3232
using SteamKit2.Authentication;
3333

3434
namespace ArchiSteamFarm.Steam.Integration;
3535

3636
internal sealed class BotCredentialsProvider : IAuthenticator {
37-
private const byte MaxLoginFailures = 5;
37+
private const byte MaxLoginFailures = 3;
3838

3939
private readonly Bot Bot;
40-
private readonly CancellationTokenSource CancellationTokenSource;
4140

42-
private byte LoginFailures;
41+
internal byte LoginFailures { get; private set; }
4342

44-
internal BotCredentialsProvider(Bot bot, CancellationTokenSource cancellationTokenSource) {
43+
internal BotCredentialsProvider(Bot bot) {
4544
ArgumentNullException.ThrowIfNull(bot);
46-
ArgumentNullException.ThrowIfNull(cancellationTokenSource);
4745

4846
Bot = bot;
49-
CancellationTokenSource = cancellationTokenSource;
5047
}
5148

5249
public async Task<bool> AcceptDeviceConfirmationAsync() {
@@ -75,24 +72,26 @@ private async Task<string> ProvideInput(ASF.EUserInputType inputType, bool previ
7572
throw new InvalidEnumArgumentException(nameof(inputType), (int) inputType, typeof(ASF.EUserInputType));
7673
}
7774

78-
if (previousCodeWasIncorrect && (++LoginFailures >= MaxLoginFailures)) {
79-
EResult reason = inputType == ASF.EUserInputType.TwoFactorAuthentication ? EResult.TwoFactorCodeMismatch : EResult.InvalidLoginAuthCode;
75+
EResult reason = inputType == ASF.EUserInputType.TwoFactorAuthentication ? EResult.TwoFactorCodeMismatch : EResult.InvalidLoginAuthCode;
8076

77+
if (previousCodeWasIncorrect) {
8178
Bot.ArchiLogger.LogGenericWarning(Strings.FormatBotUnableToLogin(reason, reason));
8279

83-
await CancellationTokenSource.CancelAsync().ConfigureAwait(false);
84-
85-
return "";
80+
if (++LoginFailures >= MaxLoginFailures) {
81+
throw new BotAuthenticationException(reason);
82+
}
8683
}
8784

88-
string? result = await Bot.RequestInput(inputType, previousCodeWasIncorrect).ConfigureAwait(false);
85+
string? input = await Bot.RequestInput(inputType, previousCodeWasIncorrect).ConfigureAwait(false);
86+
87+
if (string.IsNullOrEmpty(input)) {
88+
Bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorIsEmpty(nameof(input)));
8989

90-
if (string.IsNullOrEmpty(result)) {
91-
await CancellationTokenSource.CancelAsync().ConfigureAwait(false);
90+
LoginFailures = MaxLoginFailures;
9291

93-
return "";
92+
throw new BotAuthenticationException(reason);
9493
}
9594

96-
return result;
95+
return input;
9796
}
9897
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// ----------------------------------------------------------------------------------------------
2+
// _ _ _ ____ _ _____
3+
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4+
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5+
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6+
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
7+
// ----------------------------------------------------------------------------------------------
8+
// |
9+
// Copyright 2015-2025 Łukasz "JustArchi" Domeradzki
10+
// Contact: [email protected]
11+
// |
12+
// Licensed under the Apache License, Version 2.0 (the "License");
13+
// you may not use this file except in compliance with the License.
14+
// You may obtain a copy of the License at
15+
// |
16+
// http://www.apache.org/licenses/LICENSE-2.0
17+
// |
18+
// Unless required by applicable law or agreed to in writing, software
19+
// distributed under the License is distributed on an "AS IS" BASIS,
20+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
// See the License for the specific language governing permissions and
22+
// limitations under the License.
23+
24+
using System;
25+
using System.ComponentModel;
26+
using SteamKit2;
27+
28+
namespace ArchiSteamFarm.Steam.Security;
29+
30+
internal sealed class BotAuthenticationException : Exception {
31+
internal readonly EResult Result;
32+
33+
internal BotAuthenticationException(EResult result) {
34+
if (!Enum.IsDefined(result)) {
35+
throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult));
36+
}
37+
38+
Result = result;
39+
}
40+
}

0 commit comments

Comments
 (0)