Files
Nebula-Auth/NebulaAuth/Model/SessionHandler.cs
T
achiez 8ff960189e 1.5.6 Bug fixes and code clean-ups
- Update: Added "Copy Password" to Mafile context menu (if available)
- Update: "Save Password" now pre-fills the current password in the "Login Again" dialog when encryption is enabled
- Fix: Prevented overlapping confirmation cycles in AutoConfirmer
- Fix: Added a Snackbar notification when a confirmation cycle is skipped due to a too frequent timer interval
- Fix: Fixed a rare bug where the previous proxy was used instead of the current one during session refresh
- Fix: Corrected "Session Permanently Expired" message showing on unrelated errors (e.g. network issues)
- UI-Fix: Fixed visual glitch where proxy input appeared non-empty when switching to a mafile-specific proxy
- Cleanup: Refactored and cleaned up codebase, improved styling and formatting via ReSharper
2025-05-02 23:29:24 +03:00

199 lines
6.3 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using AchiesUtilities.Web.Models;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using NebulaAuth.View.Dialogs;
using SteamLib.Exceptions;
namespace NebulaAuth.Model;
public static partial class SessionHandler
{
private static readonly SemaphoreSlim Semaphore = new(1, 1);
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null)
{
chp ??= MaClient.GetHttpClientHandlerPair(mafile);
await Semaphore.WaitAsync();
try
{
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
}
finally
{
Semaphore.Release();
}
}
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
string? snackbarPrefix = null)
{
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
var mobileTokenExpired = MobileTokenExpired(mafile);
var refreshTokenExpired = RefreshTokenExpired(mafile);
var password = GetPassword(mafile);
if (!mobileTokenExpired)
{
try
{
return await func();
}
catch (SessionInvalidException ex)
when (refreshTokenExpired == false || password != null)
{
if (ex is SessionPermanentlyExpiredException)
{
Shell.Logger.Debug(ex, "RefreshToken on mafile {name} {steamid} is expired", mafile.AccountName,
mafile.SessionData?.SteamId);
refreshTokenExpired = true;
}
else
{
Shell.Logger.Debug(ex, "MobileToken on mafile {name} {steamid} is expired", mafile.AccountName,
mafile.SessionData?.SteamId);
}
}
}
//State: mobileToken invalid/expired, refreshToken maybe not expired
if (!refreshTokenExpired)
{
var refreshed = await RefreshInternal(chp, mafile);
if (refreshed)
{
SnackbarController.SendSnackbar(snackbarPrefix +
LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically",
"SessionHandler", "SessionWasRefreshedAutomatically"));
try
{
return await func();
}
catch (SessionInvalidException ex)
{
Shell.Logger.Info(ex, "MobileToken on {name} {steamid} was refreshed but after it, error occured",
mafile.AccountName, mafile.SessionData?.SteamId);
if (password == null)
throw;
}
}
}
Shell.Logger.Debug("Session on mafile {name} {steamid} is invalid/expired", mafile.AccountName,
mafile.SessionData?.SteamId);
//State: mobileToken invalid/expired, refreshToken invalid/expired
if (password != null)
{
var logged = await LoginAgainInternal(chp, mafile, password, true);
if (logged)
{
Shell.Logger.Debug("Mafile {name} {steamid} was succesfully auto-relogined", mafile.AccountName,
mafile.SessionData?.SteamId);
return await func();
}
}
//Nothing to do more, everything is expired
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG);
}
private static bool MobileTokenExpired(Mafile mafile)
{
var mobileToken = mafile.SessionData?.GetMobileToken();
return mobileToken == null || mobileToken.Value.IsExpired;
}
private static bool RefreshTokenExpired(Mafile mafile)
{
var refreshToken = mafile.SessionData?.RefreshToken;
return refreshToken == null || refreshToken.Value.IsExpired;
}
private static string? GetPassword(Mafile mafile)
{
try
{
if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password))
{
return PHandler.Decrypt(mafile.Password);
}
}
catch
{
// ignored
}
return null;
}
private static async Task<bool> RefreshInternal(HttpClientHandlerPair chp, Mafile mafile)
{
try
{
await RefreshMobileToken(chp, mafile);
return true;
}
catch (SessionInvalidException ex)
{
Shell.Logger.Debug(ex, "Failed to refresh session on mafile {name} {steamid}", mafile.AccountName,
mafile.SessionData?.SteamId);
return false;
}
}
private static async Task<bool> LoginAgainInternal(HttpClientHandlerPair chp, Mafile mafile, string password,
bool savePassword)
{
var t = Task.Run(OnLoginStarted);
try
{
await LoginAgain(chp, mafile, password, savePassword);
return true;
}
catch (Exception ex) //TODO: this will catch any error, even Proxy/Http/Socket errors.
{
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
mafile.SessionData?.SteamId);
return false;
}
finally
{
OnLoginCompleted();
await t;
}
}
private static async Task OnLoginStarted()
{
if (DialogHost.IsDialogOpen(null)) return;
await Application.Current.Dispatcher.BeginInvoke(async () => { await DialogHost.Show(new WaitLoginDialog()); });
}
private static void OnLoginCompleted()
{
var currentSession = DialogHost.GetDialogSession(null);
Application.Current.Dispatcher.BeginInvoke(() =>
{
if (currentSession is {Content: WaitLoginDialog, IsEnded: false})
{
try
{
currentSession.Close();
}
catch
{
//Ignored
}
}
});
}
}