Update 2FA and Captcha controls

This commit is contained in:
Mbucari 2023-02-27 10:36:37 -07:00
parent 1f1f34b6ce
commit ded58f687d
9 changed files with 51 additions and 35 deletions

View File

@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>9.4.0.1</Version>
<Version>9.3.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="5.0.0" />

View File

@ -18,7 +18,7 @@ namespace AudibleUtilities
public class ApiExtended
{
public Api Api { get; private set; }
private const string DeviceName = "Libation";
private ApiExtended(Api api) => Api = api;
/// <summary>Get api from existing tokens else login with 'eager' choice. External browser url is provided. Response can be external browser login or continuing with native api callbacks.</summary>

View File

@ -35,13 +35,13 @@
Text="Password:" />
<TextBox
Name="passwordBox"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,10,0,0"
PasswordChar="*"
Text="{Binding Password}"
IsEnabled="{Binding Password, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
Text="{Binding Password, Mode=TwoWay}" />
<TextBlock
Grid.Row="3"
@ -51,10 +51,11 @@
Text="CAPTCHA&#xa;answer:" />
<TextBox
Name="captchaBox"
Grid.Row="3"
Grid.Column="1"
Margin="0,10,0,0"
Text="{Binding Answer}" />
Text="{Binding Answer, Mode=TwoWay}" />
<Button
Grid.Row="4"

View File

@ -1,3 +1,4 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using LibationAvalonia.ViewModels;
@ -12,10 +13,12 @@ namespace LibationAvalonia.Dialogs.Login
public string Password => _viewModel.Password;
public string Answer => _viewModel.Answer;
private CaptchaDialogViewModel _viewModel;
private readonly CaptchaDialogViewModel _viewModel;
public CaptchaDialog()
{
InitializeComponent();
passwordBox = this.FindControl<TextBox>(nameof(passwordBox));
captchaBox = this.FindControl<TextBox>(nameof(captchaBox));
}
public CaptchaDialog(string password, byte[] captchaImage) :this()
@ -25,19 +28,25 @@ namespace LibationAvalonia.Dialogs.Login
using var gif = SixLabors.ImageSharp.Image.Load(captchaImage);
var gifEncoder = new SixLabors.ImageSharp.Formats.Gif.GifEncoder();
var gifFrames = new Bitmap[gif.Frames.Count];
var frameDelayMs = new int[gif.Frames.Count];
for (int i = 0; i < gif.Frames.Count; i++)
{
using var framems = new MemoryStream();
var frameMetadata = gif.Frames[i].Metadata.GetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance);
using var clonedFrame = gif.Frames.CloneFrame(i);
using var framems = new MemoryStream();
clonedFrame.Save(framems, gifEncoder);
framems.Position = 0;
gifFrames[i] = new Bitmap(framems);
frameDelayMs[i] = frameMetadata.FrameDelay * 10;
}
DataContext = _viewModel = new(password, gifFrames);
DataContext = _viewModel = new(password, gifFrames, frameDelayMs);
Opened += (_, _) => (string.IsNullOrEmpty(password) ? passwordBox : captchaBox).Focus();
}
private void InitializeComponent()
@ -59,6 +68,12 @@ namespace LibationAvalonia.Dialogs.Login
await base.SaveAndCloseAsync();
}
protected override async Task CancelAndCloseAsync()
{
await _viewModel.StopAsync();
await base.CancelAndCloseAsync();
}
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await SaveAndCloseAsync();
}
@ -72,14 +87,11 @@ namespace LibationAvalonia.Dialogs.Login
private Bitmap _captchaImage;
private bool keepSwitching = true;
private readonly Task FrameSwitch;
private readonly Bitmap[] GifFrames;
private const int FRAME_INTERVAL_MS = 100;
public CaptchaDialogViewModel(string password, Bitmap[] gifFrames)
public CaptchaDialogViewModel(string password, Bitmap[] gifFrames, int[] frameDelayMs)
{
Password = password;
GifFrames = gifFrames;
FrameSwitch = SwitchFramesAsync();
FrameSwitch = SwitchFramesAsync(gifFrames, frameDelayMs);
}
public async Task StopAsync()
@ -88,16 +100,19 @@ namespace LibationAvalonia.Dialogs.Login
await FrameSwitch;
}
private async Task SwitchFramesAsync()
private async Task SwitchFramesAsync(Bitmap[] gifFrames, int[] frameDelayMs)
{
int index = 0;
while(keepSwitching)
{
CaptchaImage = GifFrames[index++];
CaptchaImage = gifFrames[index];
await Task.Delay(frameDelayMs[index++]);
index %= GifFrames.Length;
await Task.Delay(FRAME_INTERVAL_MS);
index %= gifFrames.Length;
}
foreach (var frame in gifFrames)
frame.Dispose();
}
}
}

View File

@ -9,10 +9,12 @@
Title="2FA Code"
Icon="/Assets/libation.ico">
<Grid VerticalAlignment="Stretch" RowDefinitions="*,Auto,Auto,Auto">
<Grid
VerticalAlignment="Stretch"
ColumnDefinitions="*" Margin="5"
RowDefinitions="*,Auto,Auto,Auto">
<TextBlock
Margin="5"
TextAlignment="Center"
TextWrapping="Wrap"
Text="{Binding Prompt}" />
@ -24,14 +26,15 @@
Text="Enter 2FA Code" />
<TextBox
Name="_2FABox"
Margin="5,0,5,0"
Grid.Row="2"
HorizontalContentAlignment="Center"
Text="{Binding Code, Mode=TwoWay}" />
<Button
Margin="5"
Grid.Row="3"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="Submit"

View File

@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System.Threading.Tasks;
@ -8,20 +7,20 @@ namespace LibationAvalonia.Dialogs.Login
public partial class _2faCodeDialog : DialogWindow
{
public string Code { get; set; }
public string Prompt { get; }
public string Prompt { get; } = "For added security, please enter the One Time Password (OTP) generated by your Authenticator App";
public _2faCodeDialog() => InitializeComponent();
public _2faCodeDialog()
{
AvaloniaXamlLoader.Load(this);
_2FABox = this.FindControl<TextBox>(nameof(_2FABox));
}
public _2faCodeDialog(string prompt) : this()
{
Prompt = prompt;
DataContext = this;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
Opened += (_, _) => _2FABox.Focus();
}
protected override Task SaveAndCloseAsync()

View File

@ -24,10 +24,8 @@ namespace LibationWinForms.Dialogs.Login
this.captchaPb.Image = image;
passwordTb.Text = password;
passwordTb.Enabled = string.IsNullOrEmpty(password);
if (passwordTb.Enabled)
answerTb.Select();
(string.IsNullOrEmpty(password) ? passwordTb : answerTb).Select();
}
private void submitBtn_Click(object sender, EventArgs e)

View File

@ -47,7 +47,7 @@ namespace LibationWinForms
// load init state to menu checkbox
Load += updateAutoScanLibraryToolStripMenuItem;
// if enabled: begin on load
Load += startAutoScan;
Shown += startAutoScan;
// if new 'default' account is added, run autoscan
AccountsSettingsPersister.Saving += accountsPreSave;