Update 2FA and Captcha controls
This commit is contained in:
parent
1f1f34b6ce
commit
ded58f687d
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
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"
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user