From 51b2be55f10c8ee3e7b24d08b7fb68d346348614 Mon Sep 17 00:00:00 2001 From: Andrii Lavrenko Date: Mon, 30 Oct 2023 22:08:32 +0200 Subject: [PATCH] Fix Steam October 2023 breaking change; 3.0.0 Release --- .../SteamAccountDataFetcher.csproj | 4 +- .../SteamDataClient/AccountInfo.cs | 1 + ...rator.cs => AutoTwoFactorAuthenticator.cs} | 20 +++-- .../SteamDataClient/Client.cs | 70 +++++++-------- .../SteamDataClient/SteamWebClient.cs | 87 +++---------------- 5 files changed, 63 insertions(+), 119 deletions(-) rename SteamAccountDataFetcher/SteamDataClient/{SteamTwoFactorGenerator.cs => AutoTwoFactorAuthenticator.cs} (79%) diff --git a/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj b/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj index 8a0e11e..25e52de 100644 --- a/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj +++ b/SteamAccountDataFetcher/SteamAccountDataFetcher.csproj @@ -9,11 +9,11 @@ Andrii Lavrenko Utility for collecting Steam account information Andrii Lavrenko - 2.0.0 + 3.0.0 - + diff --git a/SteamAccountDataFetcher/SteamDataClient/AccountInfo.cs b/SteamAccountDataFetcher/SteamDataClient/AccountInfo.cs index ff6821c..8ffa3ed 100644 --- a/SteamAccountDataFetcher/SteamDataClient/AccountInfo.cs +++ b/SteamAccountDataFetcher/SteamDataClient/AccountInfo.cs @@ -10,6 +10,7 @@ public class PackageInfo public string Username { get; set; } = string.Empty; public ulong SteamId { get; set; } = 0; public List Packages { get; set; } = new(); + public bool IsLocked { get; set; } public bool IsLimited { get; set; } public bool IsBanned { get; set; } public string ApiKey { get; set; } = string.Empty; diff --git a/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs b/SteamAccountDataFetcher/SteamDataClient/AutoTwoFactorAuthenticator.cs similarity index 79% rename from SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs rename to SteamAccountDataFetcher/SteamDataClient/AutoTwoFactorAuthenticator.cs index 6cda007..5074318 100644 --- a/SteamAccountDataFetcher/SteamDataClient/SteamTwoFactorGenerator.cs +++ b/SteamAccountDataFetcher/SteamDataClient/AutoTwoFactorAuthenticator.cs @@ -1,11 +1,12 @@ using SteamKit2; +using SteamKit2.Authentication; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; namespace SteamAccountDataFetcher.SteamDataClient; -internal class SteamTwoFactorGenerator +public class AutoTwoFactorAuthenticator: IAuthenticator { private const string STEAM_TWO_FACTOR_SERVICE_INTERFACE = "ITwoFactorService"; private const string STEAM_QUERY_TIME_METHOD = "QueryTime"; @@ -16,20 +17,19 @@ internal class SteamTwoFactorGenerator private static bool _aligned = false; private Client _steamClient; + private string _sharedSecret; - internal SteamTwoFactorGenerator(Client steamClient) + public AutoTwoFactorAuthenticator(Client steamClient, string sharedSecret) { _steamClient = steamClient; + _sharedSecret = sharedSecret; } - internal async Task GenerateSteamGuardCodeAsync(string sharedSecret) + public async Task GenerateSteamGuardCodeAsync() { - if (string.IsNullOrWhiteSpace(sharedSecret)) - return string.Empty; - long time = await GetSteamTimeAsync(); - string sharedSecretUnescaped = Regex.Unescape(sharedSecret); + string sharedSecretUnescaped = Regex.Unescape(_sharedSecret); byte[] sharedSecretArray = Convert.FromBase64String(sharedSecretUnescaped); byte[] timeArray = new byte[8]; @@ -88,4 +88,10 @@ private async Task AlignTimeAsync() _aligned = true; return; } + + public Task GetDeviceCodeAsync(bool previousCodeWasIncorrect) => GenerateSteamGuardCodeAsync(); + + public Task GetEmailCodeAsync(string email, bool previousCodeWasIncorrect) => throw new NotImplementedException(); + + public Task AcceptDeviceConfirmationAsync() => throw new NotImplementedException(); } diff --git a/SteamAccountDataFetcher/SteamDataClient/Client.cs b/SteamAccountDataFetcher/SteamDataClient/Client.cs index 3155371..03aeae9 100644 --- a/SteamAccountDataFetcher/SteamDataClient/Client.cs +++ b/SteamAccountDataFetcher/SteamDataClient/Client.cs @@ -1,12 +1,13 @@ using System.Runtime.CompilerServices; using SteamKit2; +using SteamKit2.Authentication; namespace SteamAccountDataFetcher.SteamDataClient; -internal class Client : IDisposable +public class Client : IDisposable { private SteamClient _steamClient; - private SteamTwoFactorGenerator _steamTwoFactorGenerator; + private AutoTwoFactorAuthenticator _autoTwoFactorAuthenticator; private SteamWebClient _steamWebClient; private CallbackManager _callbackManager; private SteamUser _steamUser; @@ -26,7 +27,8 @@ internal SteamID? SteamID } internal string Username { get; private set; } internal string Password { get; private set; } - private string SharedSecret { get; set; } + internal string AccessToken { get; private set; } = string.Empty; + internal string RefreshToken { get; private set; } = string.Empty; private static uint _instance = 0; private static DateTime _lastConnectionTime = DateTime.MinValue; @@ -43,7 +45,6 @@ internal Client(string username, string password, string sharedSecret) Username = username; Password = password; - SharedSecret = sharedSecret; _responseAccountInfo = new() { @@ -74,13 +75,13 @@ internal Client(string username, string password, string sharedSecret) _callbackManager.Subscribe(OnConnectedAsync); _callbackManager.Subscribe(OnDisconnectedAsync); - _callbackManager.Subscribe(OnLoggedOnAsync); + _callbackManager.Subscribe(OnLoggedOn); _callbackManager.Subscribe(OnLoggedOffAsync); _callbackManager.Subscribe(OnLicenseListAsync); _callbackManager.Subscribe(OnIsLimitedAccount); - _steamTwoFactorGenerator = new(this); + _autoTwoFactorAuthenticator = new(this, sharedSecret); _steamWebClient = new(this); } @@ -117,12 +118,32 @@ internal async Task RunAsync() private async Task LoginAsync() { - string twoFactorCode = await _steamTwoFactorGenerator.GenerateSteamGuardCodeAsync(SharedSecret); - _steamUser.LogOn(new() + CredentialsAuthSession authSession = await _steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new() { Username = Username, Password = Password, - TwoFactorCode = twoFactorCode + Authenticator = _autoTwoFactorAuthenticator + }); + + AuthPollResult authPollResult; + try + { + authPollResult = await authSession.PollingWaitForResultAsync(); + } + catch (AuthenticationException e) + { + Log($"Unable to authenticate user to Steam Client with error {e.Message}.", Logger.Level.Error); + _steamClient.Disconnect(); + return; + } + + AccessToken = authPollResult.AccessToken; + RefreshToken = authPollResult.RefreshToken; + + _steamUser.LogOn(new() + { + Username = Username, + AccessToken = RefreshToken }); } @@ -170,7 +191,7 @@ private async void OnDisconnectedAsync(SteamClient.DisconnectedCallback callback _steamClient.Connect(); } - private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback) + private void OnLoggedOn(SteamUser.LoggedOnCallback callback) { if (callback.Result != EResult.OK) { @@ -179,13 +200,6 @@ private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback) return; } - if (string.IsNullOrEmpty(callback.WebAPIUserNonce)) - { - Log($"{nameof(callback.WebAPIUserNonce)} is empty.", Logger.Level.Error); - _steamClient.Disconnect(); - return; - } - if (callback.ClientSteamID == null) { Log($"{nameof(callback.ClientSteamID)} is empty.", Logger.Level.Error); @@ -193,30 +207,18 @@ private async void OnLoggedOnAsync(SteamUser.LoggedOnCallback callback) return; } _responseAccountInfo.SteamId = callback.ClientSteamID.ConvertToUInt64(); - - Log("Logged into Steam. Log-in into Web Api."); - var webLoginResult = await _steamWebClient.InitAsync(callback.WebAPIUserNonce); - if (!webLoginResult) - { - Log("Unable to log-in into Web Api.", Logger.Level.Error); - _steamClient.Disconnect(); - return; - } - Log("Logged into Web Api."); + _steamWebClient.InitAsync(); } private async void OnIsLimitedAccount(DataFetcher.IsLimitedAccountCallback callback) { - if (callback.Locked) - { - Log("Account is locked.", Logger.Level.Error); - _steamClient.Disconnect(); - return; - } - + _responseAccountInfo.IsLocked = callback.Locked; _responseAccountInfo.IsBanned = callback.CommunityBanned; _responseAccountInfo.IsLimited = callback.Limited; + if (callback.Locked) + Log("Account is locked.", Logger.Level.Warning); + if (callback.CommunityBanned) Log("Account is banned.", Logger.Level.Warning); diff --git a/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs b/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs index deb843e..d82fec4 100644 --- a/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs +++ b/SteamAccountDataFetcher/SteamDataClient/SteamWebClient.cs @@ -1,18 +1,12 @@ using System.Net; using System.Text; using System.Text.RegularExpressions; -using SteamKit2; +using System.Web; namespace SteamAccountDataFetcher.SteamDataClient; internal class SteamWebClient: IDisposable { - private const string STEAM_USER_AUTH_INTERFACE = "ISteamUserAuth"; - private const string STEAM_COMMUNITY_SERVICE_INTERFACE = "ICommunityService"; - - private const string STEAM_AUTH_USER_METHOD = "AuthenticateUser"; - private const string STEAM_GET_APPS_METHOD = "GetApps"; - private static readonly Uri API_KEY_URI = new("https://steamcommunity.com/dev/apikey"); private static readonly Uri REGISTER_API_KEY_URI = new("https://steamcommunity.com/dev/registerkey"); private const string API_GROUP_KEY = "apiKey"; @@ -179,78 +173,23 @@ private string MatchApiKey(string html) return apiKey; } - internal async Task InitAsync(string webApiUserNonce) + internal void InitAsync() { if (_steamClient.SteamID == null || !_steamClient.SteamID.IsValid || !_steamClient.SteamID.IsIndividualAccount) { - _steamClient.Log($"{nameof(_steamClient.SteamID)} is invalid.", Logger.Level.Error); - return false; + var msg = $"{nameof(_steamClient.SteamID)} is invalid."; + _steamClient.Log(msg, Logger.Level.Error); + throw new InvalidOperationException(msg); } - if (string.IsNullOrEmpty(webApiUserNonce)) - { - _steamClient.Log($"{nameof(webApiUserNonce)} is empty.", Logger.Level.Error); - return false; - } - - await RunOrSleep(); - - var publicKey = KeyDictionary.GetPublicKey(EUniverse.Public); - if (publicKey == null || publicKey.Length == 0) + if (string.IsNullOrEmpty(_steamClient.AccessToken)) { - _steamClient.Log($"{nameof(KeyDictionary)} is empty.", Logger.Level.Error); - return false; + var msg = $"{nameof(_steamClient.AccessToken)} is empty."; + _steamClient.Log(msg, Logger.Level.Error); + throw new InvalidOperationException(msg); } - var sessionKey = CryptoHelper.GenerateRandomBlock(32); - byte[] encryptedSessionKey; - - using (RSACrypto rsa = new(publicKey)) - encryptedSessionKey = rsa.Encrypt(sessionKey); - - var loginKey = Encoding.UTF8.GetBytes(webApiUserNonce); - var encryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey); - - Dictionary postData = new(3, StringComparer.Ordinal) - { - { "encrypted_loginkey", encryptedLoginKey }, - { "sessionkey", encryptedSessionKey }, - { "steamid", _steamClient.SteamID.ConvertToUInt64() } - }; - - KeyValue response; - using (var steamUserAuthInterface = _steamClient.SteamConfiguration.GetAsyncWebAPIInterface(STEAM_USER_AUTH_INTERFACE)) - { - try - { - response = await steamUserAuthInterface.CallAsync(HttpMethod.Post, STEAM_AUTH_USER_METHOD, args: postData); - } - catch (HttpRequestException e) - { - _steamClient.Log($"Unable to authenticate user to web with status code {e.StatusCode}.", Logger.Level.Error); - return (false); - } - - if (response == null) - { - _steamClient.Log("Unable to authenticate user to web.", Logger.Level.Error); - return false; - } - } - - string? steamLogin = response["token"].AsString(); - if (string.IsNullOrWhiteSpace(steamLogin)) - { - _steamClient.Log($"{nameof(steamLogin)} is empty.", Logger.Level.Error); - return false; - } - - string? steamLoginSecure = response["tokensecure"].AsString(); - if (string.IsNullOrWhiteSpace(steamLoginSecure)) - { - _steamClient.Log($"{nameof(steamLoginSecure)} is empty.", Logger.Level.Error); - return false; - } + string steamLoginSecure = HttpUtility.UrlEncode($"{_steamClient.SteamID.ConvertToUInt64()}||{_steamClient.AccessToken}"); Random rnd = new Random(); byte[] sessionBytes = new byte[12]; @@ -264,10 +203,6 @@ internal async Task InitAsync(string webApiUserNonce) new Cookie("sessionid", SessionID, "/", ".steamcommunity.com"), new Cookie("sessionid", SessionID, "/", ".help.steampowered.com"), new Cookie("sessionid", SessionID, "/", ".store.steampowered.com"), - new Cookie("steamLogin", steamLogin, "/", ".checkout.steampowered.com"), - new Cookie("steamLogin", steamLogin, "/", ".steamcommunity.com"), - new Cookie("steamLogin", steamLogin, "/", ".help.steampowered.com"), - new Cookie("steamLogin", steamLogin, "/", ".store.steampowered.com"), new Cookie("steamLoginSecure", steamLoginSecure, "/", ".checkout.steampowered.com"), new Cookie("steamLoginSecure", steamLoginSecure, "/", ".steamcommunity.com"), new Cookie("steamLoginSecure", steamLoginSecure, "/", ".help.steampowered.com"), @@ -283,7 +218,7 @@ internal async Task InitAsync(string webApiUserNonce) _httpClient = new HttpClient(httpClientHandler, true); - return true; + return; } private async Task RunOrSleep()