mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-02-04 10:53:31 +00:00
Submission wizard - Added logging in and email verification check
This commit is contained in:
parent
eeaf3999cf
commit
661242ebf9
@ -34,8 +34,11 @@ public class CurrentUserViewModel : ActivatableViewModelBase
|
|||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
|
Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
|
||||||
|
|
||||||
this.WhenActivated(d => ReactiveCommand.CreateFromTask(ExecuteAutoLogin).Execute().Subscribe().DisposeWith(d));
|
this.WhenActivated(d =>
|
||||||
this.WhenActivated(d => _authenticationService.IsLoggedIn.Subscribe(_ => LoadCurrentUser().DisposeWith(d)));
|
{
|
||||||
|
Task.Run(AutoLogin);
|
||||||
|
_authenticationService.IsLoggedIn.Subscribe(_ => Task.Run(LoadCurrentUser)).DisposeWith(d);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Loading
|
public bool Loading
|
||||||
@ -86,7 +89,7 @@ public class CurrentUserViewModel : ActivatableViewModelBase
|
|||||||
await LoadCurrentUser();
|
await LoadCurrentUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteAutoLogin(CancellationToken cancellationToken)
|
private async Task AutoLogin()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,10 +2,51 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.WelcomeStepView">
|
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.WelcomeStepView"
|
||||||
|
x:DataType="steps:WelcomeStepViewModel">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock>Welcome to the Workshop Submission Wizard 🧙</TextBlock>
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Welcome to the Workshop Submission Wizard 🧙</TextBlock>
|
||||||
<TextBlock>Here we'll take you, step by step, through the process of uploading your submission to the workshop.</TextBlock>
|
<TextBlock>Here we'll take you, step by step, through the process of uploading your submission to the workshop.</TextBlock>
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{CompiledBinding !IsLoggedIn}" Margin="0 35 0 0">
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}" TextWrapping="Wrap">First things first</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run>In order to submit anything to the workshop you must be logged in.</Run>
|
||||||
|
<LineBreak />
|
||||||
|
<LineBreak />
|
||||||
|
<Run>Creating an account is free and we'll not bother you with a newsletter or crap like that. You can also log in with Google or Discord.</Run>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Margin="0 15 0 0" Command="{CompiledBinding Login}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<avalonia:MaterialIcon Kind="ExternalLink" />
|
||||||
|
<TextBlock>Create account or log in</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{CompiledBinding ShowMissingVerification}" Margin="0 35 0 0">
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}" TextWrapping="Wrap">Confirm email address</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run>Before you can continue, please confirm your email address.</Run> (<Run Text="{CompiledBinding Email.Value}"></Run>)
|
||||||
|
<LineBreak />
|
||||||
|
<Run>You'll find the confirmation mail in your inbox.</Run>
|
||||||
|
<LineBreak />
|
||||||
|
<LineBreak />
|
||||||
|
<TextBlock>Don't see an email? Check your spam box!</TextBlock>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Margin="0 15 0 5" Command="{CompiledBinding Refresh}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<avalonia:MaterialIcon Kind="Refresh" />
|
||||||
|
<TextBlock>Refresh</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource CaptionTextBlockStyle}">PS: We take this step to avoid the workshop getting flooded with bogus content.</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,11 +1,51 @@
|
|||||||
|
using System;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Screens.Workshop.CurrentUser;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using IdentityModel;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
||||||
|
|
||||||
public class WelcomeStepViewModel : SubmissionViewModel
|
public class WelcomeStepViewModel : SubmissionViewModel
|
||||||
{
|
{
|
||||||
#region Overrides of SubmissionViewModel
|
private readonly IAuthenticationService _authenticationService;
|
||||||
|
private readonly ObservableAsPropertyHelper<bool> _showMissingVerification;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private ObservableAsPropertyHelper<Claim?>? _email;
|
||||||
|
private ObservableAsPropertyHelper<Claim?>? _emailVerified;
|
||||||
|
private ObservableAsPropertyHelper<bool>? _isLoggedIn;
|
||||||
|
|
||||||
|
public WelcomeStepViewModel(IAuthenticationService authenticationService, IWindowService windowService)
|
||||||
|
{
|
||||||
|
_authenticationService = authenticationService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_showMissingVerification = this.WhenAnyValue(vm => vm.IsLoggedIn, vm => vm.EmailVerified, (l, v) => l && (v == null || v.Value == "false")).ToProperty(this, vm => vm.ShowMissingVerification);
|
||||||
|
|
||||||
|
ShowGoBack = false;
|
||||||
|
Continue = ReactiveCommand.Create(ExecuteContinue);
|
||||||
|
Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
|
||||||
|
Refresh = ReactiveCommand.CreateFromTask(ExecuteRefresh);
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
_isLoggedIn = authenticationService.IsLoggedIn.ToProperty(this, vm => vm.IsLoggedIn).DisposeWith(d);
|
||||||
|
_emailVerified = authenticationService.GetClaim(JwtClaimTypes.EmailVerified).ToProperty(this, vm => vm.EmailVerified).DisposeWith(d);
|
||||||
|
_email = authenticationService.GetClaim(JwtClaimTypes.Email).ToProperty(this, vm => vm.Email).DisposeWith(d);
|
||||||
|
|
||||||
|
Timer updateTimer = new(TimeSpan.FromSeconds(15));
|
||||||
|
updateTimer.Elapsed += (_, _) => Task.Run(Update);
|
||||||
|
updateTimer.Start();
|
||||||
|
|
||||||
|
updateTimer.DisposeWith(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ReactiveCommand<Unit, Unit> Continue { get; }
|
public override ReactiveCommand<Unit, Unit> Continue { get; }
|
||||||
@ -13,10 +53,43 @@ public class WelcomeStepViewModel : SubmissionViewModel
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ReactiveCommand<Unit, Unit> GoBack { get; } = null!;
|
public override ReactiveCommand<Unit, Unit> GoBack { get; } = null!;
|
||||||
|
|
||||||
public WelcomeStepViewModel()
|
public ReactiveCommand<Unit, Unit> Login { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> Refresh { get; }
|
||||||
|
|
||||||
|
public bool ShowMissingVerification => _showMissingVerification.Value;
|
||||||
|
public bool IsLoggedIn => _isLoggedIn?.Value ?? false;
|
||||||
|
public Claim? EmailVerified => _emailVerified?.Value;
|
||||||
|
public Claim? Email => _email?.Value;
|
||||||
|
|
||||||
|
private async Task Update()
|
||||||
{
|
{
|
||||||
ShowGoBack = false;
|
if (EmailVerified?.Value == "true")
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use the refresh token to login again, updating claims
|
||||||
|
await _authenticationService.AutoLogin(true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored, meh
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private void ExecuteContinue()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteLogin(CancellationToken ct)
|
||||||
|
{
|
||||||
|
await _windowService.CreateContentDialog().WithViewModel(out WorkshopLoginViewModel _).WithTitle("Workshop login").ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteRefresh(CancellationToken ct)
|
||||||
|
{
|
||||||
|
await Update();
|
||||||
|
await Task.Delay(1000, ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -10,9 +10,9 @@ public class SubmissionWizardViewModel : DialogViewModelBase<bool>
|
|||||||
{
|
{
|
||||||
private SubmissionViewModel _screen;
|
private SubmissionViewModel _screen;
|
||||||
|
|
||||||
public SubmissionWizardViewModel(CurrentUserViewModel currentUserViewModel)
|
public SubmissionWizardViewModel(CurrentUserViewModel currentUserViewModel, WelcomeStepViewModel welcomeStepViewModel)
|
||||||
{
|
{
|
||||||
_screen = new WelcomeStepViewModel();
|
_screen = welcomeStepViewModel;
|
||||||
CurrentUserViewModel = currentUserViewModel;
|
CurrentUserViewModel = currentUserViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.WebClient.Workshop.Repositories;
|
using Artemis.WebClient.Workshop.Repositories;
|
||||||
|
using DynamicData;
|
||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
using IdentityModel.Client;
|
using IdentityModel.Client;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
private const string CLIENT_ID = "artemis.desktop";
|
private const string CLIENT_ID = "artemis.desktop";
|
||||||
private readonly IAuthenticationRepository _authenticationRepository;
|
private readonly IAuthenticationRepository _authenticationRepository;
|
||||||
private readonly SemaphoreSlim _authLock = new(1);
|
private readonly SemaphoreSlim _authLock = new(1);
|
||||||
private readonly ObservableCollection<Claim> _claims = new();
|
private readonly SourceList<Claim> _claims;
|
||||||
|
|
||||||
private readonly IDiscoveryCache _discoveryCache;
|
private readonly IDiscoveryCache _discoveryCache;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
@ -32,7 +33,9 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
_discoveryCache = discoveryCache;
|
_discoveryCache = discoveryCache;
|
||||||
_authenticationRepository = authenticationRepository;
|
_authenticationRepository = authenticationRepository;
|
||||||
|
|
||||||
Claims = new ReadOnlyObservableCollection<Claim>(_claims);
|
_claims = new SourceList<Claim>();
|
||||||
|
_claims.Connect().Bind(out ReadOnlyObservableCollection<Claim> claims).Subscribe();
|
||||||
|
Claims = claims;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<DiscoveryDocumentResponse> GetDiscovery()
|
private async Task<DiscoveryDocumentResponse> GetDiscovery()
|
||||||
@ -54,9 +57,11 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
if (token == null)
|
if (token == null)
|
||||||
throw new ArtemisWebClientException("Failed to read JWT token");
|
throw new ArtemisWebClientException("Failed to read JWT token");
|
||||||
|
|
||||||
_claims.Clear();
|
_claims.Edit(c =>
|
||||||
foreach (Claim responseClaim in token.Claims)
|
{
|
||||||
_claims.Add(responseClaim);
|
c.Clear();
|
||||||
|
c.AddRange(token.Claims);
|
||||||
|
});
|
||||||
|
|
||||||
_isLoggedInSubject.OnNext(true);
|
_isLoggedInSubject.OnNext(true);
|
||||||
}
|
}
|
||||||
@ -96,6 +101,15 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ReadOnlyObservableCollection<Claim> Claims { get; }
|
public ReadOnlyObservableCollection<Claim> Claims { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IObservable<Claim?> GetClaim(string type)
|
||||||
|
{
|
||||||
|
return _claims.Connect()
|
||||||
|
.Filter(c => c.Type == JwtClaimTypes.Email)
|
||||||
|
.ToCollection()
|
||||||
|
.Select(f => f.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string?> GetBearer()
|
public async Task<string?> GetBearer()
|
||||||
{
|
{
|
||||||
await _authLock.WaitAsync();
|
await _authLock.WaitAsync();
|
||||||
@ -122,13 +136,13 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> AutoLogin()
|
public async Task<bool> AutoLogin(bool force = false)
|
||||||
{
|
{
|
||||||
await _authLock.WaitAsync();
|
await _authLock.WaitAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_isLoggedInSubject.Value)
|
if (!force && _isLoggedInSubject.Value)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
string? refreshToken = _authenticationRepository.GetRefreshToken();
|
string? refreshToken = _authenticationRepository.GetRefreshToken();
|
||||||
|
|||||||
@ -9,8 +9,9 @@ public interface IAuthenticationService : IProtectedArtemisService
|
|||||||
IObservable<bool> IsLoggedIn { get; }
|
IObservable<bool> IsLoggedIn { get; }
|
||||||
ReadOnlyObservableCollection<Claim> Claims { get; }
|
ReadOnlyObservableCollection<Claim> Claims { get; }
|
||||||
|
|
||||||
|
IObservable<Claim?> GetClaim(string type);
|
||||||
Task<string?> GetBearer();
|
Task<string?> GetBearer();
|
||||||
Task<bool> AutoLogin();
|
Task<bool> AutoLogin(bool force = false);
|
||||||
Task Login(CancellationToken cancellationToken);
|
Task Login(CancellationToken cancellationToken);
|
||||||
void Logout();
|
void Logout();
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user