- add: TFA think

This commit is contained in:
2025-02-01 18:19:18 +03:00
parent 77956d35f3
commit 32fd63c5f3
16 changed files with 351 additions and 148 deletions

View File

@@ -0,0 +1,20 @@
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Shared.Services;
namespace Nebula.Launcher;
public static class LauncherConVar
{
public static readonly ConVar<ProfileAuthCredentials[]> AuthProfiles =
ConVarBuilder.Build<ProfileAuthCredentials[]>("auth.profiles.v2", []);
public static readonly ConVar<CurrentAuthInfo?> AuthCurrent =
ConVarBuilder.Build<CurrentAuthInfo?>("auth.current.v2");
public static readonly ConVar<string[]> Favorites =
ConVarBuilder.Build<string[]>("server.favorites", []);
public static readonly ConVar<string[]> AuthServers = ConVarBuilder.Build<string[]>("launcher.authServers", [
"https://auth.spacestation14.com/"
]);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -38,7 +39,7 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
[GenerateProperty] private AuthService AuthService { get; } = default!;
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
public ObservableCollection<AuthLoginPasswordModel> Accounts { get; } = new();
public ObservableCollection<ProfileAuthCredentials> Accounts { get; } = new();
public ObservableCollection<string> AuthUrls { get; } = new();
private AuthLoginPassword CurrentAlp
@@ -52,6 +53,9 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
}
}
private CurrentAuthInfo? _currAuthTemp;
public string AuthItemSelect
{
set => CurrentAuthServer = value;
@@ -71,37 +75,61 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
ReadAuthConfig();
}
public void AuthByAlp(AuthLoginPassword authLoginPassword)
public void AuthByProfile(ProfileAuthCredentials credentials)
{
CurrentAlp = authLoginPassword;
CurrentAlp = new AuthLoginPassword(credentials.Login, credentials.Password, credentials.AuthServer);
DoAuth();
}
public void DoAuth()
public void DoAuth(string? code = null)
{
var message = ViewHelperService.GetViewModel<InfoPopupViewModel>();
message.InfoText = "Auth think, please wait...";
message.IsInfoClosable = false;
Console.WriteLine("AUTH SHIT");
PopupMessageService.Popup(message);
Task.Run(async () =>
{
if (await AuthService.Auth(CurrentAlp))
try
{
await AuthService.Auth(CurrentAlp, code);
message.Dispose();
IsLogged = true;
ConfigurationService.SetConfigValue(CurrentConVar.AuthCurrent, CurrentAlp);
ConfigurationService.SetConfigValue(LauncherConVar.AuthCurrent, AuthService.SelectedAuth);
}
else
catch (AuthException e)
{
message.Dispose();
switch (e.Error.Code)
{
case AuthenticateDenyCode.TfaRequired:
case AuthenticateDenyCode.TfaInvalid:
var p = ViewHelperService.GetViewModel<TfaViewModel>();
p.OnTfaEntered += OnTfaEntered;
PopupMessageService.Popup(p);
break;
case AuthenticateDenyCode.InvalidCredentials:
PopupMessageService.Popup("Invalid Credentials!");
break;
default:
throw;
}
}
catch (Exception e)
{
message.Dispose();
Logout();
PopupMessageService.Popup("Well, shit is happened: " + AuthService.Reason);
PopupMessageService.Popup(e);
}
});
}
private void OnTfaEntered(string code)
{
DoAuth(code);
}
public void Logout()
{
IsLogged = false;
@@ -118,10 +146,10 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
private void AddAccount(AuthLoginPassword authLoginPassword)
{
var onDelete = new DelegateCommand<AuthLoginPasswordModel>(OnDeleteProfile);
var onSelect = new DelegateCommand<AuthLoginPasswordModel>(AuthByAlp);
var onDelete = new DelegateCommand<ProfileAuthCredentials>(OnDeleteProfile);
var onSelect = new DelegateCommand<ProfileAuthCredentials>(AuthByProfile);
var alpm = new AuthLoginPasswordModel(
var alpm = new ProfileAuthCredentials(
authLoginPassword.Login,
authLoginPassword.Password,
authLoginPassword.AuthServer,
@@ -134,25 +162,39 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
Accounts.Add(alpm);
}
private void ReadAuthConfig()
private async void ReadAuthConfig()
{
var message = ViewHelperService.GetViewModel<InfoPopupViewModel>();
message.InfoText = "Read configuration file, please wait...";
message.IsInfoClosable = false;
PopupMessageService.Popup(message);
foreach (var profile in
ConfigurationService.GetConfigValue(CurrentConVar.AuthProfiles)!)
AddAccount(profile);
ConfigurationService.GetConfigValue(LauncherConVar.AuthProfiles)!)
AddAccount(new AuthLoginPassword(profile.Login, profile.Password, profile.AuthServer));
if (Accounts.Count == 0) UpdateAuthMenu();
var currProfile = ConfigurationService.GetConfigValue(CurrentConVar.AuthCurrent);
AuthUrls.Clear();
var authUrls = ConfigurationService.GetConfigValue(LauncherConVar.AuthServers)!;
foreach (var url in authUrls) AuthUrls.Add(url);
var currProfile = ConfigurationService.GetConfigValue(LauncherConVar.AuthCurrent);
if (currProfile != null)
{
CurrentAlp = currProfile;
DoAuth();
try
{
CurrentAlp = new AuthLoginPassword(currProfile.Login, string.Empty, currProfile.AuthServer);
IsLogged = await AuthService.SetAuth(currProfile);
}
catch (Exception e)
{
message.Dispose();
PopupMessageService.Popup(e);
}
}
AuthUrls.Clear();
var authUrls = ConfigurationService.GetConfigValue(CurrentConVar.AuthServers)!;
foreach (var url in authUrls) AuthUrls.Add(url);
message.Dispose();
}
[RelayCommand]
@@ -164,7 +206,7 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
DirtyProfile();
}
private void OnDeleteProfile(AuthLoginPasswordModel account)
private void OnDeleteProfile(ProfileAuthCredentials account)
{
Accounts.Remove(account);
_isProfilesEmpty = Accounts.Count == 0;
@@ -187,20 +229,18 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
private void DirtyProfile()
{
ConfigurationService.SetConfigValue(CurrentConVar.AuthProfiles,
Accounts.Select(a => (AuthLoginPassword)a).ToArray());
ConfigurationService.SetConfigValue(LauncherConVar.AuthProfiles,
Accounts.ToArray());
}
public void OnPageOpen(object? args)
{
}
}
public record AuthLoginPasswordModel(
public sealed record ProfileAuthCredentials(
string Login,
string Password,
string AuthServer,
ICommand OnSelect = default!,
ICommand OnDelete = default!)
: AuthLoginPassword(Login, Password, AuthServer);
[property: JsonIgnore] ICommand OnSelect = default!,
[property: JsonIgnore] ICommand OnDelete = default!
);

View File

@@ -22,7 +22,7 @@ public partial class ServerListViewModel
{
FavoriteServers.Clear();
var servers = ConfigurationService.GetConfigValue(CurrentConVar.Favorites);
var servers = ConfigurationService.GetConfigValue(LauncherConVar.Favorites);
if (servers is null || servers.Length == 0)
{
return;
@@ -44,17 +44,17 @@ public partial class ServerListViewModel
public void AddFavorite(RobustUrl robustUrl)
{
var servers = (ConfigurationService.GetConfigValue(CurrentConVar.Favorites) ?? []).ToList();
var servers = (ConfigurationService.GetConfigValue(LauncherConVar.Favorites) ?? []).ToList();
servers.Add(robustUrl.ToString());
ConfigurationService.SetConfigValue(CurrentConVar.Favorites, servers.ToArray());
ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
UpdateFavoriteEntries();
}
public void RemoveFavorite(ServerEntryModelView entryModelView)
{
var servers = (ConfigurationService.GetConfigValue(CurrentConVar.Favorites) ?? []).ToList();
var servers = (ConfigurationService.GetConfigValue(LauncherConVar.Favorites) ?? []).ToList();
servers.Remove(entryModelView.Address.ToString());
ConfigurationService.SetConfigValue(CurrentConVar.Favorites, servers.ToArray());
ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
entryModelView.IsFavorite = false;
UpdateFavoriteEntries();
}

View File

@@ -0,0 +1,29 @@
using System;
using Nebula.Launcher.Views.Popup;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels.Popup;
[ConstructGenerator, ViewModelRegister(typeof(TfaView))]
public partial class TfaViewModel : PopupViewModelBase
{
public Action<string>? OnTfaEntered;
protected override void InitialiseInDesignMode()
{
}
protected override void Initialise()
{
}
public void OnTfaEnter(string code)
{
OnTfaEntered?.Invoke(code);
Dispose();
}
[GenerateProperty] public override PopupMessageService PopupMessageService { get; }
public override string Title => "2fa";
public override bool IsClosable => true;
}

View File

@@ -185,10 +185,10 @@ public partial class ServerEntryModelView : ViewModelBase
{
{ "ROBUST_AUTH_USERID", authProv?.UserId.ToString() },
{ "ROBUST_AUTH_TOKEN", authProv?.Token.Token },
{ "ROBUST_AUTH_SERVER", authProv?.AuthLoginPassword.AuthServer },
{ "ROBUST_AUTH_SERVER", authProv?.AuthServer },
{ "ROBUST_AUTH_PUBKEY", buildInfo.BuildInfo.Auth.PublicKey },
{ "GAME_URL", Address.ToString() },
{ "AUTH_LOGIN", authProv?.AuthLoginPassword.Login }
{ "AUTH_LOGIN", authProv?.Login }
},
CreateNoWindow = true,
UseShellExecute = false,
@@ -253,8 +253,7 @@ public partial class ServerEntryModelView : ViewModelBase
CurrLog.Append(e.Data);
}
}
public void ReadLog()
{
PopupMessageService.Popup(CurrLog);
@@ -286,7 +285,6 @@ public partial class ServerEntryModelView : ViewModelBase
{
Links.Add(link);
}
}
private static string FindDotnetPath()

View File

@@ -31,7 +31,7 @@
ItemsSource="{Binding Accounts}"
Padding="0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type pages:AuthLoginPasswordModel}">
<DataTemplate DataType="{x:Type pages:ProfileAuthCredentials}">
<Border
Background="{StaticResource DefaultBackground}"
BoxShadow="0 1 15 -2 #121212"

View File

@@ -0,0 +1,39 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:popup="clr-namespace:Nebula.Launcher.ViewModels.Popup"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Nebula.Launcher.Views.Popup.TfaView">
<Design.DataContext>
<popup:TfaViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Stretch" Spacing="25" VerticalAlignment="Center">
<Label HorizontalAlignment="Center">You have two-factor authentication enabled. Please enter the code.</Label>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" x:Name="TContainer">
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
<Border BoxShadow="{StaticResource DefaultShadow}">
<TextBox MaxLength="1"/>
</Border>
</StackPanel>
<Border BoxShadow="{StaticResource DefaultShadow}" Background="{StaticResource DefaultSelected}" HorizontalAlignment="Center">
<Button Click="Button_OnClick">
<Label HorizontalAlignment="Center" Margin="15,5,15,5">OK</Label>
</Button>
</Border>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Nebula.Launcher.ViewModels.Popup;
namespace Nebula.Launcher.Views.Popup;
public partial class TfaView : UserControl
{
public List<TextBox> Boxes = new();
public TfaView()
{
InitializeComponent();
foreach (var textBox in TContainer.Children.Select(UnzipBox))
{
var currIndex = Boxes.Count;
Boxes.Add(textBox);
textBox.TextChanged += (_,_) => OnTextChanged(currIndex);
textBox.PastingFromClipboard += OnPasteFromClipboard;
textBox.KeyUp += (sender, args) =>
{
if (args.Key == Key.Back && string.IsNullOrEmpty(textBox.Text)) OnTextChanged(currIndex);
};
textBox.KeyDown += (sender, args) =>
{
textBox.Text = args.KeySymbol;
textBox.SelectionStart = 1;
//OnTextChanged(currIndex);
};
}
}
private void OnPasteFromClipboard(object? sender, RoutedEventArgs e)
{
// TODO: CLIPBOARD THINK
}
private void OnTextChanged(int index)
{
var box = Boxes[index];
if (string.IsNullOrEmpty(box.Text))
{
if(index == 0) return;
index--;
}
else
{
if(!int.TryParse(box.Text, out var _))
{
box.Text = "";
return;
}
if (index == 5)
{
CheckupCode();
return;
}
index++;
}
Boxes[index].Focus();
}
private void CheckupCode()
{
var str = "";
foreach (var vtTextBox in Boxes)
{
if(string.IsNullOrEmpty(vtTextBox.Text)) return;
str += vtTextBox.Text;
}
((TfaViewModel)DataContext!).OnTfaEnter(str);
}
private TextBox UnzipBox(Control control)
{
var box = (Border)control;
return (TextBox)box.Child!;
}
public TfaView(TfaViewModel tfaViewModel) : this()
{
DataContext = tfaViewModel;
}
private void Button_OnClick(object? sender, RoutedEventArgs e)
{
CheckupCode();
}
}

View File

@@ -228,7 +228,7 @@
</StackPanel>
</Border>
<StackPanel
Grid.Column="2"
Grid.Column="3"
Grid.Row="1"
IsVisible="{Binding ExpandInfo}"
Margin="5,5,0,0"