diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 0613dd058..3ff7c0588 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -2778,14 +2778,14 @@ public sealed class Bot : IAsyncDisposable, IDisposable { InitConnectionFailureTimer(); if (string.IsNullOrEmpty(RefreshToken)) { + BotCredentialsProvider botCredentialsProvider = new(this); + AuthPollResult pollResult; try { - using CancellationTokenSource authCancellationTokenSource = new(); - CredentialsAuthSession authSession = await SteamClient.Authentication.BeginAuthSessionViaCredentialsAsync( new AuthSessionDetails { - Authenticator = new BotCredentialsProvider(this, authCancellationTokenSource), + Authenticator = botCredentialsProvider, DeviceFriendlyName = machineName, GuardData = BotConfig.UseLoginKeys ? BotDatabase.SteamGuardData : null, IsPersistentSession = true, @@ -2794,10 +2794,12 @@ public sealed class Bot : IAsyncDisposable, IDisposable { } ).ConfigureAwait(false); - pollResult = await authSession.PollingWaitForResultAsync(authCancellationTokenSource.Token).ConfigureAwait(false); + pollResult = await authSession.PollingWaitForResultAsync().ConfigureAwait(false); } catch (AsyncJobFailedException e) { ArchiLogger.LogGenericWarningException(e); + LoginFailures += botCredentialsProvider.LoginFailures; + await HandleLoginResult(EResult.Timeout, EResult.Timeout).ConfigureAwait(false); ReconnectOnUserInitiated = true; @@ -2807,6 +2809,17 @@ public sealed class Bot : IAsyncDisposable, IDisposable { } catch (AuthenticationException e) { ArchiLogger.LogGenericWarningException(e); + LoginFailures += botCredentialsProvider.LoginFailures; + + await HandleLoginResult(e.Result, e.Result).ConfigureAwait(false); + + ReconnectOnUserInitiated = true; + SteamClient.Disconnect(); + + return; + } catch (BotAuthenticationException e) { + LoginFailures += botCredentialsProvider.LoginFailures; + await HandleLoginResult(e.Result, e.Result).ConfigureAwait(false); ReconnectOnUserInitiated = true; @@ -2814,10 +2827,18 @@ public sealed class Bot : IAsyncDisposable, IDisposable { return; } catch (OperationCanceledException) { - // This is okay, we already took care of that and can ignore it here + LoginFailures += botCredentialsProvider.LoginFailures; + + await HandleLoginResult(EResult.Timeout, EResult.Timeout).ConfigureAwait(false); + + ReconnectOnUserInitiated = true; + SteamClient.Disconnect(); + return; } + LoginFailures += botCredentialsProvider.LoginFailures; + if (!string.IsNullOrEmpty(pollResult.NewGuardData) && BotConfig.UseLoginKeys) { BotDatabase.SteamGuardData = pollResult.NewGuardData; } diff --git a/ArchiSteamFarm/Steam/Integration/BotCredentialsProvider.cs b/ArchiSteamFarm/Steam/Integration/BotCredentialsProvider.cs index a11c3f4bd..b46f650d6 100644 --- a/ArchiSteamFarm/Steam/Integration/BotCredentialsProvider.cs +++ b/ArchiSteamFarm/Steam/Integration/BotCredentialsProvider.cs @@ -23,10 +23,10 @@ using System; using System.ComponentModel; -using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Core; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Steam.Security; using ArchiSteamFarm.Storage; using SteamKit2; using SteamKit2.Authentication; @@ -34,19 +34,16 @@ using SteamKit2.Authentication; namespace ArchiSteamFarm.Steam.Integration; internal sealed class BotCredentialsProvider : IAuthenticator { - private const byte MaxLoginFailures = 5; + private const byte MaxLoginFailures = 3; private readonly Bot Bot; - private readonly CancellationTokenSource CancellationTokenSource; - private byte LoginFailures; + internal byte LoginFailures { get; private set; } - internal BotCredentialsProvider(Bot bot, CancellationTokenSource cancellationTokenSource) { + internal BotCredentialsProvider(Bot bot) { ArgumentNullException.ThrowIfNull(bot); - ArgumentNullException.ThrowIfNull(cancellationTokenSource); Bot = bot; - CancellationTokenSource = cancellationTokenSource; } public async Task AcceptDeviceConfirmationAsync() { @@ -75,24 +72,26 @@ internal sealed class BotCredentialsProvider : IAuthenticator { throw new InvalidEnumArgumentException(nameof(inputType), (int) inputType, typeof(ASF.EUserInputType)); } - if (previousCodeWasIncorrect && (++LoginFailures >= MaxLoginFailures)) { - EResult reason = inputType == ASF.EUserInputType.TwoFactorAuthentication ? EResult.TwoFactorCodeMismatch : EResult.InvalidLoginAuthCode; + EResult reason = inputType == ASF.EUserInputType.TwoFactorAuthentication ? EResult.TwoFactorCodeMismatch : EResult.InvalidLoginAuthCode; + if (previousCodeWasIncorrect) { Bot.ArchiLogger.LogGenericWarning(Strings.FormatBotUnableToLogin(reason, reason)); - await CancellationTokenSource.CancelAsync().ConfigureAwait(false); - - return ""; + if (++LoginFailures >= MaxLoginFailures) { + throw new BotAuthenticationException(reason); + } } - string? result = await Bot.RequestInput(inputType, previousCodeWasIncorrect).ConfigureAwait(false); + string? input = await Bot.RequestInput(inputType, previousCodeWasIncorrect).ConfigureAwait(false); - if (string.IsNullOrEmpty(result)) { - await CancellationTokenSource.CancelAsync().ConfigureAwait(false); + if (string.IsNullOrEmpty(input)) { + Bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorIsEmpty(nameof(input))); - return ""; + LoginFailures = MaxLoginFailures; + + throw new BotAuthenticationException(reason); } - return result; + return input; } } diff --git a/ArchiSteamFarm/Steam/Security/BotAuthenticationException.cs b/ArchiSteamFarm/Steam/Security/BotAuthenticationException.cs new file mode 100644 index 000000000..5568b3f9c --- /dev/null +++ b/ArchiSteamFarm/Steam/Security/BotAuthenticationException.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2025 Ɓukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; +using SteamKit2; + +namespace ArchiSteamFarm.Steam.Security; + +internal sealed class BotAuthenticationException : Exception { + internal readonly EResult Result; + + internal BotAuthenticationException(EResult result) { + if (!Enum.IsDefined(result)) { + throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); + } + + Result = result; + } +}