327 lines
9.3 KiB
C#
327 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LANCommander.Launcher.Services;
|
|
using LANCommander.SDK.Exceptions;
|
|
using LANCommander.SDK.Providers;
|
|
using LANCommander.SDK.Services;
|
|
using AuthenticationProvider = LANCommander.SDK.Models.AuthenticationProvider;
|
|
|
|
namespace LANCommander.Launcher.ViewModels;
|
|
|
|
public partial class LoginViewModel : ViewModelBase
|
|
{
|
|
private readonly IConnectionClient _connectionClient;
|
|
private readonly AuthenticationService _authenticationService;
|
|
private readonly AuthenticationClient _authenticationClient;
|
|
private readonly SettingsProvider<Settings.Settings> _settingsProvider;
|
|
|
|
[ObservableProperty]
|
|
private string _username = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _password = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _passwordConfirmation = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isRegistering;
|
|
|
|
[ObservableProperty]
|
|
private bool _allowRegistration = true;
|
|
|
|
[ObservableProperty]
|
|
private string _statusMessage = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isLoading;
|
|
|
|
[ObservableProperty]
|
|
private bool _hasError;
|
|
|
|
[ObservableProperty]
|
|
private string _serverAddress = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isServerOffline;
|
|
|
|
[ObservableProperty]
|
|
private bool _autoRedirectToProvider;
|
|
|
|
public ObservableCollection<AuthenticationProvider> AuthenticationProviders { get; } = new();
|
|
|
|
public event EventHandler? LoginSucceeded;
|
|
public event EventHandler? ChangeServerRequested;
|
|
|
|
public LoginViewModel(
|
|
IConnectionClient connectionClient,
|
|
AuthenticationService authenticationService,
|
|
AuthenticationClient authenticationClient,
|
|
SettingsProvider<Settings.Settings> settingsProvider)
|
|
{
|
|
_connectionClient = connectionClient;
|
|
_authenticationService = authenticationService;
|
|
_authenticationClient = authenticationClient;
|
|
_settingsProvider = settingsProvider;
|
|
|
|
ServerAddress = _connectionClient.GetServerAddress()?.ToString() ?? "Not connected";
|
|
}
|
|
|
|
public async Task LoadAuthenticationProvidersAsync()
|
|
{
|
|
AuthenticationProviders.Clear();
|
|
|
|
try
|
|
{
|
|
var providers = await _authenticationClient.GetAuthenticationProvidersAsync();
|
|
|
|
if (providers != null)
|
|
{
|
|
foreach (var provider in providers)
|
|
AuthenticationProviders.Add(provider);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// External providers unavailable - username/password login still works
|
|
}
|
|
|
|
AllowRegistration = await _authenticationClient.GetRegistrationAllowedAsync();
|
|
|
|
if (!AllowRegistration && IsRegistering)
|
|
IsRegistering = false;
|
|
|
|
AutoRedirectToProvider = await _authenticationClient.GetAutoRedirectToProviderAsync();
|
|
}
|
|
|
|
public async Task TryAutoRedirectToProviderAsync()
|
|
{
|
|
if (IsServerOffline || !AutoRedirectToProvider || IsLoading)
|
|
return;
|
|
|
|
// Mirror server behavior: only auto-challenge when exactly one provider exists.
|
|
if (AuthenticationProviders.Count == 1)
|
|
await LoginWithProviderAsync(AuthenticationProviders[0]);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task LoginAsync()
|
|
{
|
|
if (IsServerOffline)
|
|
{
|
|
StatusMessage = "Server is offline. Please try again later or change server.";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))
|
|
{
|
|
StatusMessage = "Please enter username and password";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
IsLoading = true;
|
|
HasError = false;
|
|
StatusMessage = "Logging in...";
|
|
|
|
try
|
|
{
|
|
var serverAddress = _connectionClient.GetServerAddress();
|
|
|
|
if (serverAddress == null)
|
|
{
|
|
StatusMessage = "No server configured";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
await _authenticationService.Login(serverAddress, Username, Password);
|
|
|
|
StatusMessage = "Login successful!";
|
|
LoginSucceeded?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Login failed: {ex.Message}";
|
|
HasError = true;
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task RegisterAsync()
|
|
{
|
|
if (IsServerOffline)
|
|
{
|
|
StatusMessage = "Server is offline. Please try again later or change server.";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))
|
|
{
|
|
StatusMessage = "Please enter username and password";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
if (Password != PasswordConfirmation)
|
|
{
|
|
StatusMessage = "Passwords do not match";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
IsLoading = true;
|
|
HasError = false;
|
|
StatusMessage = "Registering...";
|
|
|
|
try
|
|
{
|
|
await _authenticationService.Register(Username, Password, PasswordConfirmation);
|
|
|
|
StatusMessage = "Registration successful!";
|
|
LoginSucceeded?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
catch (RegisterFailedException ex)
|
|
{
|
|
if (ex.ErrorData?.DetailsMessages?.Any() == true)
|
|
StatusMessage = string.Join(" ", ex.ErrorData.DetailsMessages);
|
|
else if (!string.IsNullOrWhiteSpace(ex.ErrorData?.Message))
|
|
StatusMessage = ex.ErrorData.Message;
|
|
else
|
|
StatusMessage = ex.Message;
|
|
|
|
HasError = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Registration failed: {ex.Message}";
|
|
HasError = true;
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void ToggleMode()
|
|
{
|
|
IsRegistering = !IsRegistering;
|
|
HasError = false;
|
|
StatusMessage = string.Empty;
|
|
PasswordConfirmation = string.Empty;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task LoginWithProviderAsync(AuthenticationProvider provider)
|
|
{
|
|
if (IsServerOffline)
|
|
{
|
|
StatusMessage = "Server is offline. Please try again later or change server.";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
IsLoading = true;
|
|
HasError = false;
|
|
StatusMessage = $"Signing in with {provider.Name}...";
|
|
|
|
try
|
|
{
|
|
var serverAddress = _connectionClient.GetServerAddress();
|
|
if (serverAddress == null)
|
|
{
|
|
StatusMessage = "No server configured";
|
|
HasError = true;
|
|
return;
|
|
}
|
|
|
|
var requestId = Guid.NewGuid().ToString();
|
|
var loginUrl = _authenticationClient.GetAuthenticationProviderLoginUrl(provider.Slug, requestId);
|
|
|
|
OpenBrowser(loginUrl);
|
|
|
|
var token = await PollForTokenAsync(requestId, TimeSpan.FromMinutes(5));
|
|
|
|
if (token != null)
|
|
{
|
|
await _authenticationService.Login(serverAddress, token);
|
|
StatusMessage = "Login successful!";
|
|
LoginSucceeded?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
else
|
|
{
|
|
StatusMessage = "Login timed out or was cancelled.";
|
|
HasError = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Login failed: {ex.Message}";
|
|
HasError = true;
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
private CancellationTokenSource? _pollCts;
|
|
|
|
private async Task<LANCommander.SDK.Models.AuthToken?> PollForTokenAsync(string requestId, TimeSpan timeout)
|
|
{
|
|
_pollCts?.Cancel();
|
|
_pollCts = new CancellationTokenSource(timeout);
|
|
|
|
try
|
|
{
|
|
while (!_pollCts.Token.IsCancellationRequested)
|
|
{
|
|
var token = await _authenticationClient.RedeemTokenAsync(requestId);
|
|
|
|
if (token != null)
|
|
return token;
|
|
|
|
await Task.Delay(2000, _pollCts.Token);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Timeout or cancellation
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void OpenBrowser(Uri url)
|
|
{
|
|
var urlString = url.ToString();
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
Process.Start(new ProcessStartInfo(urlString) { UseShellExecute = true });
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
Process.Start("xdg-open", urlString);
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
Process.Start("open", urlString);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void ChangeServer()
|
|
{
|
|
_pollCts?.Cancel();
|
|
ChangeServerRequested?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|