Compare commits

..

10 Commits

Author SHA1 Message Date
Achies f2daaeb64e Update README.md 2026-06-20 18:00:23 +03:00
github-actions fd3e131d33 chore(release): 1.8.5 2026-06-20 11:46:25 +00:00
Achies f7cc7dcf99 Merge pull request #26 from achiez/dev
Release 1.8.5
2026-06-20 14:45:51 +03:00
achiez 16f0cc2b2d chore: update AssemblyVersion to 1.8.5 and add changelog for new features and fixes 2026-06-20 00:49:26 +03:00
achiez cd0416705f fix: add missing SignAt scheme in Proxy Assignment 2026-06-19 23:36:04 +03:00
Achies 2d06cb81f9 Merge pull request #30 from achiez/feat/website-links
feat(links): fetch website and docs URLs from GitHub at startup
2026-06-19 22:36:21 +03:00
achiez ed5d3c2eda feat(links): fetch website and docs URLs from GitHub at startup
- Add links.json to repo (alongside update.xml) with Website and Documentation URLs.
- Add LinksManager to support link fetching on startup via raw.githubusercontent.com
- Suppress TimeAligner failure instead of blocking app launch
2026-06-19 22:34:37 +03:00
Achies 07eb9f15a9 Merge pull request #29 from achiez/feat/login-again-on-session-expired
feat(session): show LoginAgainDialog on permanent session expiry
2026-06-19 22:06:44 +03:00
achiez c39660fc55 feat(session): show LoginAgainDialog on permanent session expiry
- Replace snackbar with interactive LoginAgainDialog when session cannot be   recovered automatically on user-initiated actions
- Show proxy warning hint in dialog when no proxy assigned to account or set as default
- Pass allowInteractiveLogin: false from MAACRequestHandler to skip dialog in  automated MAAC confirmations
2026-06-19 22:05:49 +03:00
Achies 038f62b4db Merge pull request #28 from achiez/feat/export-zip-and-improvements
feat(export): add zip export, export-all, and login:password input parsing
2026-06-19 21:21:51 +03:00
21 changed files with 236 additions and 29 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"Website": "https://achiefy.pro/?nebula=true",
"Documentation": "https://achiefy-project.gitbook.io/nebulaauth"
}
+4 -4
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.8.4</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.4/NebulaAuth.1.8.4.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.4.html</changelog>
<version>1.8.5</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.5/NebulaAuth.1.8.5.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.5.html</changelog>
<mandatory>false</mandatory>
<checksum algorithm="SHA256">5ecad7a711bab7e98f5f3bfa92b7a00e21efc18aa92251bb30bd3e50a2f7d2f2</checksum>
<checksum algorithm="SHA256">7b2d7768ff5497664c0223a535e35006022d7aed62ebf5400fbf9be3d2893bd5</checksum>
</item>
+1 -1
View File
@@ -21,7 +21,7 @@ NebulaAuth is an application for emulating actions from the Steam Mobile App, re
<img src="misc/main_window.png" width="600"/>
</p>
- **Localization in six languages**: English, Russian, Ukrainian, Spanish, Turkish and Kazakh.
- **Localization in eight languages**: English, Russian, Ukrainian, Simplified Chinese, French, Spanish, Turkish, and Kazakh.
- **Full functionality of Steam Desktop Authenticator** reimagining [old app](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Proxy support** in all account work processes.
- **Mafile grouping** for improved management.
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Changelog</title>
<style>
body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }
.change { background:white; padding:25px; border-radius:10px; }
li { margin-bottom:6px; }
</style>
</head>
<body>
<div class="change">
<ul>
<li><b>NEW:</b> Bulk proxy assignment: assign proxies to multiple accounts at once via flexible login:proxy, login:ID or login-only input <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
<li><b>NEW:</b> Import dialog now allows assigning imported mafiles to a group directly; added option to skip confirmation when no conflicts are detected <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/24">details</a></li>
<li><b>NEW:</b> Assign random free proxy to the account from context menu <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
<li><b>NEW:</b> Export: added ZIP archive output, export-all mode when the input field is empty, and support for login:password input format <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/28">details</a></li>
<li><b>IMPROVEMENT:</b> When a session permanently expires during a user-initiated action, a login dialog is now shown instead of a generic error notification <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/29">details</a></li>
<li><b>IMPROVEMENT:</b> Proxy manager: account count badges now show how many accounts are assigned to each proxy entry <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
<li><b>IMPROVEMENT:</b> Added login:pass@ip:port proxy format support <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/25">details</a></li>
<li><b>IMPROVEMENT:</b> Improved dialog header design <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/27">details</a></li>
<li><b>IMPROVEMENT:</b> Website and Documentation links are now available from the links window <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/30">details</a></li>
<li><b>FIX:</b> Application no longer fails to start when Steam time synchronization is unavailable (network issues, firewall)</li>
<li><b>FIX:</b> Fixed incorrect Steam Guard error message shown in the Mafile Mover dialog <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/23">details</a></li>
</ul>
</div>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
{
"version": "1.8.5",
"date": "2026-06-19",
"changes": [
{
"type": "NEW",
"text": "Bulk proxy assignment: assign proxies to multiple accounts at once via flexible login:proxy, login:ID or login-only input",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
},
{
"type": "NEW",
"text": "Import dialog now allows assigning imported mafiles to a group directly; added option to skip confirmation when no conflicts are detected",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/24"
},
{
"type": "NEW",
"text": "Assign random free proxy to the account from context menu",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
},
{
"type": "NEW",
"text": "Export: added ZIP archive output, export-all mode when the input field is empty, and support for login:password input format",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/28"
},
{
"type": "IMPROVEMENT",
"text": "When a session permanently expires during a user-initiated action, a login dialog is now shown instead of a generic error notification",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/29"
},
{
"type": "IMPROVEMENT",
"text": "Proxy manager: account count badges now show how many accounts are assigned to each proxy entry",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
},
{
"type": "IMPROVEMENT",
"text": "Added login:pass@ip:port proxy format support",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/25"
},
{
"type": "IMPROVEMENT",
"text": "Improved dialog header design",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/27"
},
{
"type": "IMPROVEMENT",
"text": "Website and Documentation links are now available from the links window",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/30"
},
{
"type": "FIX",
"text": "Application no longer fails to start when Steam time synchronization is unavailable (network issues, firewall)"
},
{
"type": "FIX",
"text": "Fixed incorrect Steam Guard error message shown in the Mafile Mover dialog",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/23"
}
]
}
@@ -77,6 +77,10 @@ public partial class HintBox : UserControl
IconKind = PackIconKind.ErrorOutline;
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
break;
case HintBoxSeverity.Warning:
IconKind = PackIconKind.WarningOutline;
IconBrush = (Brush) Application.Current.FindResource("WarningBrush")!;
break;
default:
IconKind = PackIconKind.InfoCircleOutline;
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
@@ -94,5 +98,6 @@ public partial class HintBox : UserControl
public enum HintBoxSeverity
{
Info,
Warning,
Error
}
+4 -2
View File
@@ -15,13 +15,15 @@ namespace NebulaAuth.Core;
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
{
UserName = username,
Password = currentPassword ?? string.Empty,
SavePassword = PHandler.IsPasswordSet
SavePassword = PHandler.IsPasswordSet,
ShowNoProxyWarning = showNoProxyWarning
};
var content = new LoginAgainDialog
{
+33
View File
@@ -0,0 +1,33 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using NebulaAuth.Model;
using Newtonsoft.Json;
namespace NebulaAuth.Core;
public static class LinksManager
{
private const string LINKS_URL =
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/NebulaAuth/links.json";
private static readonly HttpClient HttpClient = new();
public static string? WebsiteUrl { get; private set; }
public static string? DocumentationUrl { get; private set; }
public static async Task FetchAsync()
{
try
{
var json = await HttpClient.GetStringAsync(LINKS_URL).ConfigureAwait(false);
var links = JsonConvert.DeserializeObject<RemoteLinks>(json);
WebsiteUrl = links?.Website;
DocumentationUrl = links?.Documentation;
}
catch (Exception ex)
{
Shell.Logger.Debug(ex, "Failed to fetch remote links");
}
}
}
@@ -79,6 +79,16 @@
"es": "Pulsa 'DEL' para eliminar",
"tr": "Silmek için 'DEL' tuşuna bas",
"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
? Result<T>.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(),
GetTimerPrefix(client.Mafile)))
GetTimerPrefix(client.Mafile), false))
: Result<T>.Success(await req());
}
catch (SessionInvalidException ex)
+7
View File
@@ -0,0 +1,7 @@
namespace NebulaAuth.Model;
public class RemoteLinks
{
public string? Website { get; set; }
public string? Documentation { get; set; }
}
@@ -6,7 +6,9 @@ using AchiesUtilities.Web.Models;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
using SteamLib.Exceptions.Authorization;
namespace NebulaAuth.Model;
@@ -16,13 +18,13 @@ 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)
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null, bool allowInteractiveLogin = true)
{
chp ??= MaClient.GetHttpClientHandlerPair(mafile);
await Semaphore.WaitAsync();
try
{
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix, allowInteractiveLogin);
}
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,
string? snackbarPrefix = null)
string? snackbarPrefix = null, bool allowInteractiveLogin = true)
{
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
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,
currentException);
}
@@ -118,6 +146,7 @@ public static partial class SessionHandler
{
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
mafile.SessionData?.SteamId);
ExceptionHandler.Handle(ex);
return false;
}
finally
+3 -2
View File
@@ -3,7 +3,6 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NebulaAuth.Core;
using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
using NebulaAuth.Model.MafileExport;
using NebulaAuth.Model.Mafiles;
@@ -40,7 +39,8 @@ public static class Shell
}
catch (Exception ex)
{
throw new CantAlignTimeException("", ex);
Logger.Error(ex, "Failed to align time with Steam");
TimeAligner.SetTimeDifference(0);
}
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
@@ -48,6 +48,7 @@ public static class Shell
ProxyAssignmentCache.Initialize(Storage.MaFiles);
MAACStorage.Initialize();
MafileExporterStorage.Initialize();
_ = LinksManager.FetchAsync();
ExtensionsLogger.LogDebug("Application started");
}
+1 -1
View File
@@ -10,7 +10,7 @@
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.8.4</AssemblyVersion>
<AssemblyVersion>1.8.5</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
+1 -1
View File
@@ -93,7 +93,7 @@ public static class ExceptionHandler
}
case LoginException e:
{
return "LoginException".GetCodeBehindLocalization() + ": " +
return "LoginException".GetCodeBehindLocalization() +
ErrorTranslatorHelper.TranslateLoginError(e.Error);
}
case UnsupportedAuthTypeException e:
@@ -25,24 +25,30 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</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"
Padding="10"
FontSize="15"
Margin="10,10,10,0"
Grid.Row="0"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"
materialDesign:HintAssist.IsFloating="False"
materialDesign:TextFieldAssist.LeadingIcon="Key"
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}}"
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>
<ColumnDefinition />
<ColumnDefinition />
+11 -6
View File
@@ -8,7 +8,8 @@
d:DesignHeight="450" d:DesignWidth="800"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
Background="Transparent"
RenderOptions.BitmapScalingMode="HighQuality">
RenderOptions.BitmapScalingMode="HighQuality"
Loaded="LinksView_Loaded">
<materialDesign:Card Padding="24" HorizontalAlignment="Center" VerticalAlignment="Center" UniformCornerRadius="12">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
@@ -49,16 +50,20 @@
</StackPanel>
</Button>
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
Click="Website_Click" IsEnabled="False">
<Button x:Name="WebsiteButton" Margin="0,0,0,5" FontSize="18"
Style="{StaticResource MaterialDesignFlatButton}"
IsEnabled="False"
Click="Website_Click">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<materialDesign:PackIcon Kind="Web" Width="24" Height="24" Margin="0 0 8 0" />
<TextBlock Text="Web-site" VerticalAlignment="Center" />
<TextBlock Text="Achiefy" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
Click="Documentation_Click" IsEnabled="False">
<Button x:Name="DocumentationButton" Margin="0,0,0,5" FontSize="18"
Style="{StaticResource MaterialDesignFlatButton}"
IsEnabled="False"
Click="Documentation_Click">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Width="22" Height="22" Margin="0 0 8 0">
<Canvas Width="22" Height="22">
+11 -3
View File
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using MaterialDesignThemes.Wpf;
@@ -13,6 +13,12 @@ public partial class LinksView : UserControl
InitializeComponent();
}
private void LinksView_Loaded(object sender, RoutedEventArgs e)
{
WebsiteButton.IsEnabled = LinksManager.WebsiteUrl != null;
DocumentationButton.IsEnabled = LinksManager.DocumentationUrl != null;
}
private void Telegram_Click(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://t.me/nebulaauth") {UseShellExecute = true});
@@ -26,12 +32,14 @@ public partial class LinksView : UserControl
private void Website_Click(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
if (LinksManager.WebsiteUrl != null)
Process.Start(new ProcessStartInfo(LinksManager.WebsiteUrl) {UseShellExecute = true});
}
private void Documentation_Click(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
if (LinksManager.DocumentationUrl != null)
Process.Start(new ProcessStartInfo(LinksManager.DocumentationUrl) {UseShellExecute = true});
}
private void CheckForUpdates_Click(object sender, RoutedEventArgs e)
@@ -11,5 +11,7 @@ public partial class LoginAgainVM : ObservableObject
[ObservableProperty] private bool _savePassword;
[ObservableProperty] private bool _showNoProxyWarning;
[ObservableProperty] private string _userName = null!;
}
@@ -80,7 +80,7 @@ public partial class ProxyManagerVM
resolvedProxy = new MaProxy(parsedId, byId);
}
else if (ProxyStorage.DefaultScheme.TryParse(proxyPart, out var parsedProxy))
else if (ProxyStorage.DefaultScheme.TryParse(proxyPart, out var parsedProxy) || ProxyStorage.SignAtScheme.TryParse(proxyPart, out parsedProxy))
{
// Mode: login:proxy_string — find existing or add
resolvedProxy = FindOrAddProxy(parsedProxy);
@@ -80,4 +80,10 @@ public static class TimeAligner
client.Dispose();
}
}
public static void SetTimeDifference(int timeDifference)
{
_timeDifference = timeDifference;
_aligned = true;
}
}