Merge pull request #29 from achiez/feat/login-again-on-session-expired

feat(session): show LoginAgainDialog on permanent session expiry
This commit is contained in:
Achies
2026-06-19 22:06:44 +03:00
committed by GitHub
8 changed files with 65 additions and 11 deletions
@@ -77,6 +77,10 @@ public partial class HintBox : UserControl
IconKind = PackIconKind.ErrorOutline; IconKind = PackIconKind.ErrorOutline;
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!; IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
break; break;
case HintBoxSeverity.Warning:
IconKind = PackIconKind.WarningOutline;
IconBrush = (Brush) Application.Current.FindResource("WarningBrush")!;
break;
default: default:
IconKind = PackIconKind.InfoCircleOutline; IconKind = PackIconKind.InfoCircleOutline;
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!; IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
@@ -94,5 +98,6 @@ public partial class HintBox : UserControl
public enum HintBoxSeverity public enum HintBoxSeverity
{ {
Info, Info,
Warning,
Error Error
} }
+4 -2
View File
@@ -15,13 +15,15 @@ namespace NebulaAuth.Core;
public static class DialogsController public static class DialogsController
{ {
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null) public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null,
bool showNoProxyWarning = false)
{ {
var vm = new LoginAgainVM var vm = new LoginAgainVM
{ {
UserName = username, UserName = username,
Password = currentPassword ?? string.Empty, Password = currentPassword ?? string.Empty,
SavePassword = PHandler.IsPasswordSet SavePassword = PHandler.IsPasswordSet,
ShowNoProxyWarning = showNoProxyWarning
}; };
var content = new LoginAgainDialog var content = new LoginAgainDialog
{ {
@@ -79,6 +79,16 @@
"es": "Pulsa 'DEL' para eliminar", "es": "Pulsa 'DEL' para eliminar",
"tr": "Silmek için 'DEL' tuşuna bas", "tr": "Silmek için 'DEL' tuşuna bas",
"kk": "Жою үшін 'DEL' пернесін басыңыз" "kk": "Жою үшін 'DEL' пернесін басыңыз"
},
"NoProxyWarning": {
"en": "No proxy is assigned",
"ru": "Прокси не установлен",
"zh": "未分配代理",
"uk": "Проксі не призначено",
"fr": "Aucun proxy assigné",
"es": "No hay ningún proxy asignado",
"tr": "Proxy atanmamış",
"kk": "Прокси тағайындалмаған"
} }
} }
} }
@@ -82,7 +82,7 @@ public static class MAACRequestHandler
{ {
return withSessionHandler return withSessionHandler
? Result<T>.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(), ? Result<T>.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(),
GetTimerPrefix(client.Mafile))) GetTimerPrefix(client.Mafile), false))
: Result<T>.Success(await req()); : Result<T>.Success(await req());
} }
catch (SessionInvalidException ex) catch (SessionInvalidException ex)
@@ -6,7 +6,9 @@ using AchiesUtilities.Web.Models;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using NebulaAuth.Core; using NebulaAuth.Core;
using NebulaAuth.Model.Entities; using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs; using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
using SteamLib.Exceptions.Authorization; using SteamLib.Exceptions.Authorization;
namespace NebulaAuth.Model; namespace NebulaAuth.Model;
@@ -16,13 +18,13 @@ public static partial class SessionHandler
private static readonly SemaphoreSlim Semaphore = new(1, 1); private static readonly SemaphoreSlim Semaphore = new(1, 1);
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile, public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null) HttpClientHandlerPair? chp = null, string? snackbarPrefix = null, bool allowInteractiveLogin = true)
{ {
chp ??= MaClient.GetHttpClientHandlerPair(mafile); chp ??= MaClient.GetHttpClientHandlerPair(mafile);
await Semaphore.WaitAsync(); await Semaphore.WaitAsync();
try try
{ {
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix); return await HandleInternal(func, chp.Value, mafile, snackbarPrefix, allowInteractiveLogin);
} }
finally finally
{ {
@@ -31,7 +33,7 @@ public static partial class SessionHandler
} }
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile, private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
string? snackbarPrefix = null) string? snackbarPrefix = null, bool allowInteractiveLogin = true)
{ {
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler"); using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
Exception? currentException = null; Exception? currentException = null;
@@ -86,6 +88,32 @@ public static partial class SessionHandler
} }
} }
if (allowInteractiveLogin && !DialogHost.IsDialogOpen(null))
{
var noProxy = mafile.Proxy == null && MaClient.DefaultProxy == null;
var currentPassword = GetPassword(mafile);
Task<LoginAgainVM?> dialogTask = null!;
Application.Current.Dispatcher.Invoke(() =>
{
dialogTask = DialogsController.ShowLoginAgainDialog(mafile.AccountName, currentPassword, noProxy);
});
var vm = await dialogTask;
if (vm != null)
{
Shell.Logger.Info("User provided credentials interactively for {name}, attempting login",
mafile.AccountName);
var logged = await LoginAgainInternal(chp, mafile, vm.Password, vm.SavePassword);
if (logged)
{
Shell.Logger.Debug("Interactive login for {name} succeeded, retrying operation",
mafile.AccountName);
return await func();
}
}
}
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG, throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG,
currentException); currentException);
} }
@@ -118,6 +146,7 @@ public static partial class SessionHandler
{ {
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName, Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
mafile.SessionData?.SteamId); mafile.SessionData?.SteamId);
ExceptionHandler.Handle(ex);
return false; return false;
} }
finally finally
+1 -1
View File
@@ -93,7 +93,7 @@ public static class ExceptionHandler
} }
case LoginException e: case LoginException e:
{ {
return "LoginException".GetCodeBehindLocalization() + ": " + return "LoginException".GetCodeBehindLocalization() +
ErrorTranslatorHelper.TranslateLoginError(e.Error); ErrorTranslatorHelper.TranslateLoginError(e.Error);
} }
case UnsupportedAuthTypeException e: case UnsupportedAuthTypeException e:
@@ -25,24 +25,30 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBox TabIndex="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" <nebulaAuth:HintBox Grid.Row="0"
Margin="10,10,10,0"
Severity="Warning"
FontSize="14"
Visibility="{Binding ShowNoProxyWarning, Converter={StaticResource BooleanToVisibilityConverter}}"
Text="{Tr LoginAgainDialog.NoProxyWarning}" />
<TextBox TabIndex="0" Grid.Row="1" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.HasClearButton="True" materialDesign:TextFieldAssist.HasClearButton="True"
Padding="10" Padding="10"
FontSize="15" FontSize="15"
Margin="10,10,10,0" Margin="10,10,10,0"
Grid.Row="0"
Style="{StaticResource MaterialDesignOutlinedTextBox}" Style="{StaticResource MaterialDesignOutlinedTextBox}"
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"
materialDesign:HintAssist.IsFloating="False" materialDesign:HintAssist.IsFloating="False"
materialDesign:TextFieldAssist.LeadingIcon="Key" materialDesign:TextFieldAssist.LeadingIcon="Key"
materialDesign:TextFieldAssist.HasLeadingIcon="True" /> materialDesign:TextFieldAssist.HasLeadingIcon="True" />
<CheckBox FontSize="15" Grid.Row="1" Margin="10,5,10,0" <CheckBox FontSize="15" Grid.Row="2" Margin="10,5,10,0"
IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}"
IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" /> IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" />
<Grid Grid.Row="2" Margin="10,10,10,0"> <Grid Grid.Row="3" Margin="10,10,10,0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition /> <ColumnDefinition />
@@ -11,5 +11,7 @@ public partial class LoginAgainVM : ObservableObject
[ObservableProperty] private bool _savePassword; [ObservableProperty] private bool _savePassword;
[ObservableProperty] private bool _showNoProxyWarning;
[ObservableProperty] private string _userName = null!; [ObservableProperty] private string _userName = null!;
} }