From fcd405661978cca3e4c2ad8297ba6320d390d660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=B2=D0=B8=D0=B4=20=D0=A7=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D1=8F=D1=82=D0=BE=D0=B2?= Date: Thu, 25 Apr 2024 02:59:17 +0300 Subject: [PATCH] Added a temporary solution for validating old mafiles when reading SteamID, as well as changes to the UI and minor fixes --- NebulaAuth.sln | 1 + NebulaAuth/App.xaml | 1 + .../AnyMafilesToVisibilityConverter.cs | 30 +++++++ NebulaAuth/MainWindow.xaml | 11 ++- NebulaAuth/Model/ProxyStorage.cs | 1 + NebulaAuth/NebulaAuth.csproj | 4 +- NebulaAuth/View/Dialogs/LoginAgainDialog.xaml | 4 +- .../Dialogs/LoginAgainOnImportDialog.xaml | 4 +- NebulaAuth/View/ProxyManagerView.xaml | 58 ++++++++----- NebulaAuth/ViewModel/MainVM.cs | 2 + .../ViewModel/Other/LoginAgainOnImportVM.cs | 6 +- NebulaAuth/ViewModel/Other/LoginAgainVM.cs | 15 +++- NebulaAuth/ViewModel/Other/ProxyManagerVM.cs | 34 ++++++-- NebulaAuth/localization.loc.json | 9 +- NebulaAuth/update.xml | 6 +- .../Utility/MaFiles/MafileCredits.cs | 2 +- .../MaFiles/MafileSerializer_SessionData.cs | 53 ++++++++++-- .../MaFiles/MafileSerializer_Utility.cs | 6 +- .../MaFiles/MafileSerializer_Validate.cs | 3 +- changelog/1.4.9.html | 86 +++++++++++++++++++ 20 files changed, 275 insertions(+), 61 deletions(-) create mode 100644 NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs create mode 100644 changelog/1.4.9.html diff --git a/NebulaAuth.sln b/NebulaAuth.sln index d813747..7b38f52 100644 --- a/NebulaAuth.sln +++ b/NebulaAuth.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{ changelog\1.4.6.html = changelog\1.4.6.html changelog\1.4.7.html = changelog\1.4.7.html changelog\1.4.8.html = changelog\1.4.8.html + changelog\1.4.9.html = changelog\1.4.9.html EndProjectSection EndProject Global diff --git a/NebulaAuth/App.xaml b/NebulaAuth/App.xaml index ba657d7..bb7f207 100644 --- a/NebulaAuth/App.xaml +++ b/NebulaAuth/App.xaml @@ -21,6 +21,7 @@ + diff --git a/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs b/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs new file mode 100644 index 0000000..9d20158 --- /dev/null +++ b/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace NebulaAuth.Converters; + +public class AnyMafilesToVisibilityConverter : IValueConverter +{ + private static bool EverAnyMafiles; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (EverAnyMafiles) + { + return Visibility.Collapsed; + } + if (value is 0) + { + return Visibility.Visible; + } + + EverAnyMafiles = true; + return Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/NebulaAuth/MainWindow.xaml b/NebulaAuth/MainWindow.xaml index ee42c42..96f4bab 100644 --- a/NebulaAuth/MainWindow.xaml +++ b/NebulaAuth/MainWindow.xaml @@ -68,7 +68,11 @@ - + + @@ -29,40 +29,39 @@ - - - - - + + + + + - + + + - - + - - - + + + + - - - + + + + + + + + - + + + + + - + diff --git a/NebulaAuth/ViewModel/MainVM.cs b/NebulaAuth/ViewModel/MainVM.cs index 3ced846..1b9efcf 100644 --- a/NebulaAuth/ViewModel/MainVM.cs +++ b/NebulaAuth/ViewModel/MainVM.cs @@ -23,6 +23,8 @@ public partial class MainVM : ObservableObject private ObservableCollection _maFiles = Storage.MaFiles; public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue; + + public Mafile? SelectedMafile { get => _selectedMafile; diff --git a/NebulaAuth/ViewModel/Other/LoginAgainOnImportVM.cs b/NebulaAuth/ViewModel/Other/LoginAgainOnImportVM.cs index ec99415..860c16f 100644 --- a/NebulaAuth/ViewModel/Other/LoginAgainOnImportVM.cs +++ b/NebulaAuth/ViewModel/Other/LoginAgainOnImportVM.cs @@ -10,7 +10,9 @@ namespace NebulaAuth.ViewModel.Other; public partial class LoginAgainOnImportVM : ObservableObject { public ObservableCollection Proxies { get; } = new(); - [ObservableProperty] private string _password = null!; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsFormValid))] + private string _password = null!; [ObservableProperty] private bool _savePassword; [ObservableProperty] private string _userName = null!; [ObservableProperty] private bool _mafileHasProxy; @@ -38,7 +40,7 @@ public partial class LoginAgainOnImportVM : ObservableObject } } - + public bool IsFormValid => !string.IsNullOrWhiteSpace(Password); private MaProxy? _selectedProxy; diff --git a/NebulaAuth/ViewModel/Other/LoginAgainVM.cs b/NebulaAuth/ViewModel/Other/LoginAgainVM.cs index 597d0c5..1135370 100644 --- a/NebulaAuth/ViewModel/Other/LoginAgainVM.cs +++ b/NebulaAuth/ViewModel/Other/LoginAgainVM.cs @@ -9,9 +9,18 @@ namespace NebulaAuth.ViewModel.Other; public partial class LoginAgainVM : ObservableObject { - [ObservableProperty] private string _password = null!; - [ObservableProperty] private bool _savePassword; - [ObservableProperty] private string _userName = null!; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsFormValid))] + private string _password = null!; + + [ObservableProperty] + private bool _savePassword; + + [ObservableProperty] + private string _userName = null!; + + + public bool IsFormValid => !string.IsNullOrWhiteSpace(Password); public LoginAgainVM() { } diff --git a/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs b/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs index 7a562f0..cd2e5e7 100644 --- a/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs +++ b/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs @@ -42,7 +42,7 @@ public partial class ProxyManagerVM : ObservableObject var idPresent = (bool?)null; var proxies = new List>(); var i = 0; - foreach (var str in split) + foreach (var str in split.Where(s => string.IsNullOrWhiteSpace(s) == false)) { i++; int? id = null; @@ -113,17 +113,37 @@ public partial class ProxyManagerVM : ObservableObject [RelayCommand] private void RemoveProxy() { - if (SelectedProxy == null) return; - ProxyStorage.RemoveProxy(SelectedProxy.Value.Key); + var selected = SelectedProxy; + if (selected == null) return; + var s = selected.Value; + + + KeyValuePair? nextNeighbor = null; + KeyValuePair? prevNeighbor = null; + foreach (var id in Proxies.Keys.Order()) + { + if (id < s.Key) + { + prevNeighbor = KeyValuePair.Create(id, Proxies[id]); + } + else if (id > s.Key) + { + nextNeighbor = KeyValuePair.Create(id, Proxies[id]); + break; + } + } + + ProxyStorage.RemoveProxy(s.Key); + SelectedProxy = nextNeighbor ?? prevNeighbor; CheckIfDefaultProxyStay(); } [RelayCommand] - private void SetDefault() + private void SetDefault(object? arg) { - if (SelectedProxy == null) return; - DefaultProxy = SelectedProxy; - MaClient.DefaultProxy = SelectedProxy.Value.Value; + if (arg is not KeyValuePair proxy) return; + DefaultProxy = proxy; + MaClient.DefaultProxy = proxy.Value; ProxyStorage.Save(); } diff --git a/NebulaAuth/localization.loc.json b/NebulaAuth/localization.loc.json index 4de10cf..76a1366 100644 --- a/NebulaAuth/localization.loc.json +++ b/NebulaAuth/localization.loc.json @@ -64,11 +64,16 @@ "ru": "Отпустите для импорта мафайлов", "ua": "Відпустіть для імпорту мафайлів" }, - "LoadingHint":{ + "LoadingHint": { "en": "Loading...", "ru": "Загрузка...", "ua": "Завантаження..." - } + }, + "StartTip": { + "ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"", + "en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu", + "ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\"" + } }, "Menu": { "File": { diff --git a/NebulaAuth/update.xml b/NebulaAuth/update.xml index 7c6fcf6..0ef2a14 100644 --- a/NebulaAuth/update.xml +++ b/NebulaAuth/update.xml @@ -1,7 +1,7 @@  - 1.4.8.0 - https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.4.8/NebulaAuth.1.4.8.zip - https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.8.html + 1.4.9.0 + https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.4.9/NebulaAuth.1.4.9.zip + https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.9.html false \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileCredits.cs b/SteamLibForked/Utility/MaFiles/MafileCredits.cs index 67d29d2..1a79ed9 100644 --- a/SteamLibForked/Utility/MaFiles/MafileCredits.cs +++ b/SteamLibForked/Utility/MaFiles/MafileCredits.cs @@ -4,7 +4,7 @@ public class MafileCredits : IMafileCredits { internal static readonly MafileCredits Instance = new(); private const string ORIGINAL_AUTHOR = "Achies"; - private const string MOBILE_APP = "https://github.com/achiez/NebulaAuth"; + private const string MOBILE_APP = "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"; public string OriginalAuthor => ORIGINAL_AUTHOR; public string BestOpenSourceMobileApp => MOBILE_APP; diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs index 2a3cc56..8ec1607 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using AchiesUtilities.Models; +using Newtonsoft.Json.Linq; using SteamLib.Account; using SteamLib.Authentication; using SteamLib.Core.Enums; @@ -14,7 +15,7 @@ public partial class MafileSerializer //SessionData "refresh", "OAuthToken"); - SteamAuthToken refreshToken; + SteamAuthToken? refreshToken = null; if (refreshTokenToken == null || refreshTokenToken.Type == JTokenType.Null) return null; if (refreshTokenToken.Type == JTokenType.String && SteamTokenHelper.TryParse(refreshTokenToken.Value()!, out var parsed)) { @@ -28,13 +29,9 @@ public partial class MafileSerializer //SessionData } catch { - return null; + //Ignored } } - else - { - return null; - } var sessionId = GetString(j, "sessionid", "session_id", "session"); @@ -66,12 +63,20 @@ public partial class MafileSerializer //SessionData } - var sessionData = new MobileSessionData(sessionId, refreshToken.SteamId, refreshToken, accessToken, new Dictionary()); + var steamId = refreshToken?.SteamId ?? GetSessionSteamId(j); + if (steamId == null) + { + result = DeserializedMafileSessionResult.Invalid; + return null; + } + + refreshToken ??= CreateInvalid(steamId.Value); + var sessionData = new MobileSessionData(sessionId, steamId.Value, refreshToken.Value, accessToken, new Dictionary()); sessionData.IsValid = SessionDataValidator.Validate(null, sessionData).Succeeded; if(sessionData.IsValid == false) return null; - if (refreshToken.IsExpired || refreshToken.Type != SteamAccessTokenType.MobileRefresh) + if (refreshToken.Value.IsExpired || refreshToken.Value.Type != SteamAccessTokenType.MobileRefresh) { result = DeserializedMafileSessionResult.Expired; } @@ -82,4 +87,34 @@ public partial class MafileSerializer //SessionData return sessionData; } + + private static SteamId? GetSessionSteamId(JObject j) + { + var token = GetToken(j, "steamid"); + if (token == null || token.Type == JTokenType.Null) + return null; + + if(token.Type == JTokenType.Integer) + return SteamId.FromSteam64(token.Value()); + + if (token.Type == JTokenType.String && long.TryParse(token.Value()!, out var steamId)) + { + return SteamId.FromSteam64(steamId); + } + + return null; + } + + //Workaround to avoid session being invalidated due to missing a valid token. + //The reason for this change is the inability to proxy/change group for old mafiles, which creates more problems than benefits. + //A temporary solution until I decide how to read the SteamID correctly without invalidating the entire session. + //It also makes the LoginAgainOnImport mechanism useless, which is good outcome. + //Most likely I need to reconsider the reaction to an “invalid” session and simply feed it to the software as “expired”. + //Also, when deciding not to validate RefreshToken, I need to reconsider the entire validation method in the Validator class and think through the consequences in the rest of the code. + //FIXME: Refactor code to avoid this workaround and make it more organic. + //TODO: after fixing the issue, reflect changes in the original library + private static SteamAuthToken CreateInvalid(SteamId steamId) + { + return new SteamAuthToken("invalid", steamId, UnixTimeStamp.FromDateTime(DateTime.Now - TimeSpan.FromSeconds(1)), SteamDomain.Community, SteamAccessTokenType.MobileRefresh); + } } \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs index 43486ff..8211cfa 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs @@ -8,7 +8,7 @@ public partial class MafileSerializer //Utility { foreach (var name in aliases) { - if (j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) + if (j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) { return token; } @@ -21,7 +21,7 @@ public partial class MafileSerializer //Utility { foreach (var name in aliases) { - if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue; + if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue; var parent = token.Parent as JProperty; removeFrom.Remove(parent!.Name); return token; @@ -34,7 +34,7 @@ public partial class MafileSerializer //Utility { foreach (var name in aliases) { - if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue; + if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue; if (token.Type == JTokenType.Null) { throw new ArgumentException($"Required property {propertyName} is null"); diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs index 7e7acfa..83b37c5 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs @@ -38,7 +38,7 @@ public partial class MafileSerializer //Validate public static void IsValidBase64(string name, string base64) { var buffer = new Span(new byte[base64.Length]); - if(Convert.TryFromBase64String(base64, buffer, out _) == false) + if (Convert.TryFromBase64String(base64, buffer, out _) == false) throw new ArgumentException($"{name} is not valid base64 string"); } @@ -63,7 +63,6 @@ public partial class MafileSerializer //Validate if (d.SessionData.RefreshToken.IsExpired) { sessionResult = DeserializedMafileSessionResult.Expired; - return null; } d.SessionData.IsValid = SessionDataValidator.Validate(null, d.SessionData).Succeeded; diff --git a/changelog/1.4.9.html b/changelog/1.4.9.html new file mode 100644 index 0000000..72b0be6 --- /dev/null +++ b/changelog/1.4.9.html @@ -0,0 +1,86 @@ + + + + + + Changelog + + + + +
+ +
+
Version 1.4.9
+
25.04.2024
+
+ - Improved compatibility with old mafiles
+ - FIX: Now 'Login' button is disabled if password is empty (caused crash before)
+ - FIX: The tutorial for beginners is no longer shown after ever hiding (for example, when searching without suitable mafiles)
+ - UI/UX improvements in proxy manager:
+     - Favourite button is placed on proxy item
+     - Pressing 'DEL' now removes selected proxy
+     - After proxy is removed next to it is selected (allows to remove proxies quickly)
+     - Empty lines on import now ignored (previously entire import was cancelling)
+ - Mafile credits now corresponds to the actual github repository
+
+
+
+ + + \ No newline at end of file