Fix MFA and 2FA
This commit is contained in:
parent
b2f68760b2
commit
ffadf90f4f
@ -3,7 +3,6 @@ using Dinah.Core.Net.Http;
|
|||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -225,6 +224,7 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
nfsp.NetworkFileStream.RequestHeaders["User-Agent"] = DownloadOptions.UserAgent;
|
||||||
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
nfsp.NetworkFileStream.SpeedLimit = DownloadOptions.DownloadSpeedBps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ namespace AudibleUtilities
|
|||||||
public class ApiExtended
|
public class ApiExtended
|
||||||
{
|
{
|
||||||
public Api Api { get; private set; }
|
public Api Api { get; private set; }
|
||||||
|
private const string DeviceName = "Libation";
|
||||||
private ApiExtended(Api api) => Api = api;
|
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>
|
/// <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>
|
||||||
@ -39,42 +39,6 @@ namespace AudibleUtilities
|
|||||||
return new ApiExtended(api);
|
return new ApiExtended(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get api from existing tokens else login with native api callbacks.</summary>
|
|
||||||
public static async Task<ApiExtended> CreateAsync(Account account, ILoginCallback loginCallback)
|
|
||||||
{
|
|
||||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
|
||||||
{
|
|
||||||
LoginType = nameof(ILoginCallback),
|
|
||||||
Account = account?.MaskedLogEntry ?? "[null]",
|
|
||||||
LocaleName = account?.Locale?.Name
|
|
||||||
});
|
|
||||||
|
|
||||||
var api = await EzApiCreator.GetApiAsync(
|
|
||||||
loginCallback,
|
|
||||||
account.Locale,
|
|
||||||
AudibleApiStorage.AccountsSettingsFile,
|
|
||||||
account.GetIdentityTokensJsonPath());
|
|
||||||
return new ApiExtended(api);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get api from existing tokens else login with external browser</summary>
|
|
||||||
public static async Task<ApiExtended> CreateAsync(Account account, ILoginExternal loginExternal)
|
|
||||||
{
|
|
||||||
Serilog.Log.Logger.Information("{@DebugInfo}", new
|
|
||||||
{
|
|
||||||
LoginType = nameof(ILoginExternal),
|
|
||||||
Account = account?.MaskedLogEntry ?? "[null]",
|
|
||||||
LocaleName = account?.Locale?.Name
|
|
||||||
});
|
|
||||||
|
|
||||||
var api = await EzApiCreator.GetApiAsync(
|
|
||||||
loginExternal,
|
|
||||||
account.Locale,
|
|
||||||
AudibleApiStorage.AccountsSettingsFile,
|
|
||||||
account.GetIdentityTokensJsonPath());
|
|
||||||
return new ApiExtended(api);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary>
|
/// <summary>Get api from existing tokens. Assumes you have valid login tokens. Else exception</summary>
|
||||||
public static async Task<ApiExtended> CreateAsync(Account account)
|
public static async Task<ApiExtended> CreateAsync(Account account)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AudibleApi" Version="8.0.0.1" />
|
<PackageReference Include="AudibleApi" Version="8.1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -4,13 +4,14 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AudibleApi;
|
using AudibleApi;
|
||||||
using AudibleApi.Authorization;
|
using AudibleApi.Authorization;
|
||||||
|
using AudibleApi.Cryptography;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace AudibleUtilities
|
namespace AudibleUtilities
|
||||||
{
|
{
|
||||||
public partial class Mkb79Auth : IIdentityMaintainer
|
public partial class Mkb79Auth : IIdentityMaintainer
|
||||||
{
|
{
|
||||||
[JsonProperty("website_cookies")]
|
[JsonProperty("website_cookies")]
|
||||||
private JObject _websiteCookies { get; set; }
|
private JObject _websiteCookies { get; set; }
|
||||||
@ -178,7 +179,7 @@ namespace AudibleUtilities
|
|||||||
LocaleCode = account.Locale.CountryCode,
|
LocaleCode = account.Locale.CountryCode,
|
||||||
RefreshToken = account.IdentityTokens.RefreshToken.Value,
|
RefreshToken = account.IdentityTokens.RefreshToken.Value,
|
||||||
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
|
StoreAuthenticationCookie = account.IdentityTokens.StoreAuthenticationCookie,
|
||||||
WebsiteCookies = new(account.IdentityTokens.Cookies.ToKeyValuePair()),
|
WebsiteCookies = new(account.IdentityTokens.Cookies),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
Width="60"
|
Width="60"
|
||||||
Height="30"
|
Height="30"
|
||||||
Content="X"
|
Content="X"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
IsEnabled="{Binding !IsDefault}"
|
IsEnabled="{Binding !IsDefault}"
|
||||||
Click="DeleteButton_Clicked" />
|
Click="DeleteButton_Clicked" />
|
||||||
|
|
||||||
|
|||||||
@ -9,26 +9,28 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
{
|
{
|
||||||
private Account _account { get; }
|
private Account _account { get; }
|
||||||
|
|
||||||
|
public string DeviceName { get; } = "Libation";
|
||||||
|
|
||||||
public AvaloniaLoginCallback(Account account)
|
public AvaloniaLoginCallback(Account account)
|
||||||
{
|
{
|
||||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Get2faCodeAsync()
|
public async Task<string> Get2faCodeAsync(string prompt)
|
||||||
{
|
{
|
||||||
var dialog = new _2faCodeDialog();
|
var dialog = new _2faCodeDialog(prompt);
|
||||||
if (await ShowDialog(dialog))
|
if (await ShowDialog(dialog))
|
||||||
return dialog.Code;
|
return dialog.Code;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetCaptchaAnswerAsync(byte[] captchaImage)
|
public async Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
|
||||||
{
|
{
|
||||||
var dialog = new CaptchaDialog(captchaImage);
|
var dialog = new CaptchaDialog(password, captchaImage);
|
||||||
if (await ShowDialog(dialog))
|
if (await ShowDialog(dialog))
|
||||||
return dialog.Answer;
|
return (dialog.Password, dialog.Answer);
|
||||||
return null;
|
return (null,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
public async Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
||||||
|
|||||||
@ -2,22 +2,22 @@
|
|||||||
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"
|
||||||
mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="180"
|
mc:Ignorable="d" d:DesignWidth="220" d:DesignHeight="250"
|
||||||
MinWidth="220" MinHeight="180"
|
MinWidth="220" MinHeight="250"
|
||||||
MaxWidth="220" MaxHeight="180"
|
MaxWidth="220" MaxHeight="250"
|
||||||
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
|
x:Class="LibationAvalonia.Dialogs.Login.CaptchaDialog"
|
||||||
Title="CAPTCHA"
|
Title="CAPTCHA"
|
||||||
Icon="/Assets/libation.ico">
|
Icon="/Assets/libation.ico">
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
RowDefinitions="Auto,Auto,*"
|
RowDefinitions="Auto,Auto,Auto,Auto,*"
|
||||||
ColumnDefinitions="Auto,*">
|
ColumnDefinitions="Auto,*"
|
||||||
|
Margin="10">
|
||||||
|
|
||||||
<Panel
|
<Panel
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
Margin="10"
|
|
||||||
MinWidth="200"
|
MinWidth="200"
|
||||||
MinHeight="70"
|
MinHeight="70"
|
||||||
Background="LightGray">
|
Background="LightGray">
|
||||||
@ -30,23 +30,39 @@
|
|||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="Password:" />
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="10,0,10,0"
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
PasswordChar="*"
|
||||||
|
Text="{Binding Password}"
|
||||||
|
IsEnabled="{Binding Password, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0,10,10,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="CAPTCHA
answer:" />
|
Text="CAPTCHA
answer:" />
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Grid.Row="1"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="10,0,10,0" Text="{Binding Answer}" />
|
Margin="0,10,0,0"
|
||||||
|
Text="{Binding Answer}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="2"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="10"
|
|
||||||
Padding="0,5,0,5"
|
Padding="0,5,0,5"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
Content="Submit"
|
Content="Submit"
|
||||||
Click="Submit_Click" />
|
Click="Submit_Click" />
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
|
using LibationAvalonia.ViewModels;
|
||||||
|
using ReactiveUI;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -7,18 +9,35 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
{
|
{
|
||||||
public partial class CaptchaDialog : DialogWindow
|
public partial class CaptchaDialog : DialogWindow
|
||||||
{
|
{
|
||||||
public string Answer { get; set; }
|
public string Password => _viewModel.Password;
|
||||||
public Bitmap CaptchaImage { get; }
|
public string Answer => _viewModel.Answer;
|
||||||
|
|
||||||
|
private CaptchaDialogViewModel _viewModel;
|
||||||
public CaptchaDialog()
|
public CaptchaDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CaptchaDialog(byte[] captchaImage) :this()
|
public CaptchaDialog(string password, byte[] captchaImage) :this()
|
||||||
{
|
{
|
||||||
using var ms = new MemoryStream(captchaImage);
|
//Avalonia doesn't support animated gifs.
|
||||||
CaptchaImage = new Bitmap(ms);
|
//Deconstruct gifs into frames and manually switch them.
|
||||||
DataContext = this;
|
using var gif = SixLabors.ImageSharp.Image.Load(captchaImage);
|
||||||
|
var gifEncoder = new SixLabors.ImageSharp.Formats.Gif.GifEncoder();
|
||||||
|
var gifFrames = new Bitmap[gif.Frames.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < gif.Frames.Count; i++)
|
||||||
|
{
|
||||||
|
using var framems = new MemoryStream();
|
||||||
|
|
||||||
|
using var clonedFrame = gif.Frames.CloneFrame(i);
|
||||||
|
|
||||||
|
clonedFrame.Save(framems, gifEncoder);
|
||||||
|
framems.Position = 0;
|
||||||
|
gifFrames[i] = new Bitmap(framems);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataContext = _viewModel = new(password, gifFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
@ -26,15 +45,59 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async Task SaveAndCloseAsync()
|
||||||
protected override Task SaveAndCloseAsync()
|
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
|
if (string.IsNullOrWhiteSpace(_viewModel.Password))
|
||||||
|
{
|
||||||
|
await MessageBox.Show(this, "Please re-enter your password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return base.SaveAndCloseAsync();
|
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { _viewModel.Answer });
|
||||||
|
|
||||||
|
await _viewModel.StopAsync();
|
||||||
|
await base.SaveAndCloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void Submit_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
=> await SaveAndCloseAsync();
|
=> await SaveAndCloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CaptchaDialogViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public string Answer { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public Bitmap CaptchaImage { get => _captchaImage; private set => this.RaiseAndSetIfChanged(ref _captchaImage, value); }
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Password = password;
|
||||||
|
GifFrames = gifFrames;
|
||||||
|
FrameSwitch = SwitchFramesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync()
|
||||||
|
{
|
||||||
|
keepSwitching = false;
|
||||||
|
await FrameSwitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SwitchFramesAsync()
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
while(keepSwitching)
|
||||||
|
{
|
||||||
|
CaptchaImage = GifFrames[index++];
|
||||||
|
|
||||||
|
index %= GifFrames.Length;
|
||||||
|
await Task.Delay(FRAME_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using AudibleApi;
|
|
||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@ -49,7 +48,7 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
protected override async Task SaveAndCloseAsync()
|
protected override async Task SaveAndCloseAsync()
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
|
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { ResponseUrl });
|
||||||
if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out var result))
|
if (!Uri.TryCreate(ResponseUrl, UriKind.Absolute, out _))
|
||||||
{
|
{
|
||||||
await MessageBox.Show("Invalid response URL");
|
await MessageBox.Show("Invalid response URL");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
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"
|
||||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="160"
|
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
|
||||||
MinWidth="400" MinHeight="160"
|
MinWidth="400" MinHeight="200"
|
||||||
MaxWidth="400" MaxHeight="160"
|
MaxWidth="400" MaxHeight="200"
|
||||||
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
|
x:Class="LibationAvalonia.Dialogs.Login.MfaDialog"
|
||||||
Title="Two-Step Verification"
|
Title="Two-Step Verification"
|
||||||
Icon="/Assets/libation.ico">
|
Icon="/Assets/libation.ico">
|
||||||
|
|||||||
@ -2,30 +2,38 @@
|
|||||||
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"
|
||||||
mc:Ignorable="d" d:DesignWidth="140" d:DesignHeight="100"
|
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200"
|
||||||
MinWidth="140" MinHeight="100"
|
MinWidth="200" MinHeight="200"
|
||||||
MaxWidth="140" MaxHeight="100"
|
MaxWidth="200" MaxHeight="200"
|
||||||
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
|
x:Class="LibationAvalonia.Dialogs.Login._2faCodeDialog"
|
||||||
Title="2FA Code"
|
Title="2FA Code"
|
||||||
Icon="/Assets/libation.ico">
|
Icon="/Assets/libation.ico">
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,Auto,*">
|
<Grid VerticalAlignment="Stretch" RowDefinitions="*,Auto,Auto,Auto">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5"
|
Margin="5"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{Binding Prompt}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Margin="5"
|
||||||
|
Grid.Row="1"
|
||||||
|
TextAlignment="Center"
|
||||||
Text="Enter 2FA Code" />
|
Text="Enter 2FA Code" />
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
Grid.Row="1"
|
Grid.Row="2"
|
||||||
Text="{Binding Code, Mode=TwoWay}" />
|
Text="{Binding Code, Mode=TwoWay}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Grid.Row="2"
|
Grid.Row="3"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
Content="Submit"
|
Content="Submit"
|
||||||
Click="Submit_Click" />
|
Click="Submit_Click" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -8,10 +8,14 @@ namespace LibationAvalonia.Dialogs.Login
|
|||||||
public partial class _2faCodeDialog : DialogWindow
|
public partial class _2faCodeDialog : DialogWindow
|
||||||
{
|
{
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
|
public string Prompt { get; }
|
||||||
|
|
||||||
public _2faCodeDialog()
|
|
||||||
|
public _2faCodeDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
public _2faCodeDialog(string prompt) : this()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
Prompt = prompt;
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,8 @@
|
|||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " />
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-preview4 " />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview4" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview4" />
|
||||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||||
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace LibationAvalonia.Views
|
namespace LibationAvalonia.Views
|
||||||
{
|
{
|
||||||
//DONE
|
|
||||||
public partial class MainWindow
|
public partial class MainWindow
|
||||||
{
|
{
|
||||||
private InterruptableTimer autoScanTimer;
|
private InterruptableTimer autoScanTimer;
|
||||||
@ -17,11 +16,7 @@ namespace LibationAvalonia.Views
|
|||||||
private void Configure_ScanAuto()
|
private void Configure_ScanAuto()
|
||||||
{
|
{
|
||||||
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
||||||
var hours = 0;
|
autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
|
||||||
var minutes = 5;
|
|
||||||
var seconds = 0;
|
|
||||||
var _5_minutes = new TimeSpan(hours, minutes, seconds);
|
|
||||||
autoScanTimer = new InterruptableTimer(_5_minutes);
|
|
||||||
|
|
||||||
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
||||||
autoScanTimer.Elapsed += async (_, __) =>
|
autoScanTimer.Elapsed += async (_, __) =>
|
||||||
@ -46,7 +41,7 @@ namespace LibationAvalonia.Views
|
|||||||
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
_viewModel.AutoScanChecked = Configuration.Instance.AutoScan;
|
||||||
|
|
||||||
// if enabled: begin on load
|
// if enabled: begin on load
|
||||||
Load += startAutoScan;
|
Opened += startAutoScan;
|
||||||
|
|
||||||
// if new 'default' account is added, run autoscan
|
// if new 'default' account is added, run autoscan
|
||||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||||
|
|||||||
@ -28,68 +28,95 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.captchaPb = new System.Windows.Forms.PictureBox();
|
captchaPb = new System.Windows.Forms.PictureBox();
|
||||||
this.answerTb = new System.Windows.Forms.TextBox();
|
answerTb = new System.Windows.Forms.TextBox();
|
||||||
this.submitBtn = new System.Windows.Forms.Button();
|
submitBtn = new System.Windows.Forms.Button();
|
||||||
this.answerLbl = new System.Windows.Forms.Label();
|
answerLbl = new System.Windows.Forms.Label();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.captchaPb)).BeginInit();
|
label1 = new System.Windows.Forms.Label();
|
||||||
this.SuspendLayout();
|
passwordTb = new System.Windows.Forms.TextBox();
|
||||||
|
((System.ComponentModel.ISupportInitialize)captchaPb).BeginInit();
|
||||||
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// captchaPb
|
// captchaPb
|
||||||
//
|
//
|
||||||
this.captchaPb.Location = new System.Drawing.Point(12, 12);
|
captchaPb.Location = new System.Drawing.Point(13, 14);
|
||||||
this.captchaPb.Name = "captchaPb";
|
captchaPb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.captchaPb.Size = new System.Drawing.Size(200, 70);
|
captchaPb.Name = "captchaPb";
|
||||||
this.captchaPb.TabIndex = 0;
|
captchaPb.Size = new System.Drawing.Size(235, 81);
|
||||||
this.captchaPb.TabStop = false;
|
captchaPb.TabIndex = 0;
|
||||||
|
captchaPb.TabStop = false;
|
||||||
//
|
//
|
||||||
// answerTb
|
// answerTb
|
||||||
//
|
//
|
||||||
this.answerTb.Location = new System.Drawing.Point(118, 88);
|
answerTb.Location = new System.Drawing.Point(136, 130);
|
||||||
this.answerTb.Name = "answerTb";
|
answerTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.answerTb.Size = new System.Drawing.Size(94, 20);
|
answerTb.Name = "answerTb";
|
||||||
this.answerTb.TabIndex = 1;
|
answerTb.Size = new System.Drawing.Size(111, 23);
|
||||||
|
answerTb.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// submitBtn
|
// submitBtn
|
||||||
//
|
//
|
||||||
this.submitBtn.Location = new System.Drawing.Point(137, 114);
|
submitBtn.Location = new System.Drawing.Point(159, 171);
|
||||||
this.submitBtn.Name = "submitBtn";
|
submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.submitBtn.Size = new System.Drawing.Size(75, 23);
|
submitBtn.Name = "submitBtn";
|
||||||
this.submitBtn.TabIndex = 2;
|
submitBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
this.submitBtn.Text = "Submit";
|
submitBtn.TabIndex = 2;
|
||||||
this.submitBtn.UseVisualStyleBackColor = true;
|
submitBtn.Text = "Submit";
|
||||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
submitBtn.UseVisualStyleBackColor = true;
|
||||||
|
submitBtn.Click += submitBtn_Click;
|
||||||
//
|
//
|
||||||
// answerLbl
|
// answerLbl
|
||||||
//
|
//
|
||||||
this.answerLbl.AutoSize = true;
|
answerLbl.AutoSize = true;
|
||||||
this.answerLbl.Location = new System.Drawing.Point(12, 91);
|
answerLbl.Location = new System.Drawing.Point(13, 133);
|
||||||
this.answerLbl.Name = "answerLbl";
|
answerLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||||
this.answerLbl.Size = new System.Drawing.Size(100, 13);
|
answerLbl.Name = "answerLbl";
|
||||||
this.answerLbl.TabIndex = 0;
|
answerLbl.Size = new System.Drawing.Size(106, 15);
|
||||||
this.answerLbl.Text = "CAPTCHA answer: ";
|
answerLbl.TabIndex = 0;
|
||||||
|
answerLbl.Text = "CAPTCHA answer: ";
|
||||||
|
//
|
||||||
|
// label1
|
||||||
|
//
|
||||||
|
label1.AutoSize = true;
|
||||||
|
label1.Location = new System.Drawing.Point(13, 104);
|
||||||
|
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||||
|
label1.Name = "label1";
|
||||||
|
label1.Size = new System.Drawing.Size(60, 15);
|
||||||
|
label1.TabIndex = 0;
|
||||||
|
label1.Text = "Password:";
|
||||||
|
//
|
||||||
|
// passwordTb
|
||||||
|
//
|
||||||
|
passwordTb.Location = new System.Drawing.Point(81, 101);
|
||||||
|
passwordTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
|
passwordTb.Name = "passwordTb";
|
||||||
|
passwordTb.PasswordChar = '*';
|
||||||
|
passwordTb.Size = new System.Drawing.Size(167, 23);
|
||||||
|
passwordTb.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// CaptchaDialog
|
// CaptchaDialog
|
||||||
//
|
//
|
||||||
this.AcceptButton = this.submitBtn;
|
AcceptButton = submitBtn;
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.ClientSize = new System.Drawing.Size(224, 149);
|
ClientSize = new System.Drawing.Size(261, 210);
|
||||||
this.Controls.Add(this.answerLbl);
|
Controls.Add(passwordTb);
|
||||||
this.Controls.Add(this.submitBtn);
|
Controls.Add(label1);
|
||||||
this.Controls.Add(this.answerTb);
|
Controls.Add(answerLbl);
|
||||||
this.Controls.Add(this.captchaPb);
|
Controls.Add(submitBtn);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
Controls.Add(answerTb);
|
||||||
this.MaximizeBox = false;
|
Controls.Add(captchaPb);
|
||||||
this.MinimizeBox = false;
|
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
this.Name = "CaptchaDialog";
|
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.ShowIcon = false;
|
MaximizeBox = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
MinimizeBox = false;
|
||||||
this.Text = "CAPTCHA";
|
Name = "CaptchaDialog";
|
||||||
((System.ComponentModel.ISupportInitialize)(this.captchaPb)).EndInit();
|
ShowIcon = false;
|
||||||
this.ResumeLayout(false);
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.PerformLayout();
|
Text = "CAPTCHA";
|
||||||
|
((System.ComponentModel.ISupportInitialize)captchaPb).EndInit();
|
||||||
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -98,5 +125,7 @@
|
|||||||
private System.Windows.Forms.TextBox answerTb;
|
private System.Windows.Forms.TextBox answerTb;
|
||||||
private System.Windows.Forms.Button submitBtn;
|
private System.Windows.Forms.Button submitBtn;
|
||||||
private System.Windows.Forms.Label answerLbl;
|
private System.Windows.Forms.Label answerLbl;
|
||||||
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.TextBox passwordTb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,23 +9,37 @@ namespace LibationWinForms.Dialogs.Login
|
|||||||
public partial class CaptchaDialog : Form
|
public partial class CaptchaDialog : Form
|
||||||
{
|
{
|
||||||
public string Answer { get; private set; }
|
public string Answer { get; private set; }
|
||||||
|
public string Password { get; private set; }
|
||||||
|
|
||||||
private MemoryStream ms { get; }
|
private MemoryStream ms { get; }
|
||||||
private Image image { get; }
|
private Image image { get; }
|
||||||
|
|
||||||
public CaptchaDialog(byte[] captchaImage)
|
public CaptchaDialog() => InitializeComponent();
|
||||||
|
public CaptchaDialog(string password, byte[] captchaImage) : this()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
|
||||||
this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); };
|
this.FormClosed += (_, __) => { ms?.Dispose(); image?.Dispose(); };
|
||||||
|
|
||||||
ms = new MemoryStream(captchaImage);
|
ms = new MemoryStream(captchaImage);
|
||||||
image = Image.FromStream(ms);
|
image = Image.FromStream(ms);
|
||||||
this.captchaPb.Image = image;
|
this.captchaPb.Image = image;
|
||||||
|
|
||||||
|
passwordTb.Text = password;
|
||||||
|
passwordTb.Enabled = string.IsNullOrEmpty(password);
|
||||||
|
|
||||||
|
if (passwordTb.Enabled)
|
||||||
|
answerTb.Select();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitBtn_Click(object sender, EventArgs e)
|
private void submitBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Answer = this.answerTb.Text;
|
if (string.IsNullOrWhiteSpace(passwordTb.Text))
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "Please re-enter your password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Answer = answerTb.Text;
|
||||||
|
Password = passwordTb.Text;
|
||||||
|
|
||||||
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
|
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Answer });
|
||||||
|
|
||||||
|
|||||||
@ -10,25 +10,27 @@ namespace LibationWinForms.Login
|
|||||||
{
|
{
|
||||||
private Account _account { get; }
|
private Account _account { get; }
|
||||||
|
|
||||||
|
public string DeviceName { get; } = "Libation";
|
||||||
|
|
||||||
public WinformLoginCallback(Account account)
|
public WinformLoginCallback(Account account)
|
||||||
{
|
{
|
||||||
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
_account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> Get2faCodeAsync()
|
public Task<string> Get2faCodeAsync(string prompt)
|
||||||
{
|
{
|
||||||
using var dialog = new _2faCodeDialog();
|
using var dialog = new _2faCodeDialog(prompt);
|
||||||
if (ShowDialog(dialog))
|
if (ShowDialog(dialog))
|
||||||
return Task.FromResult(dialog.Code);
|
return Task.FromResult(dialog.Code);
|
||||||
return Task.FromResult<string>(null);
|
return Task.FromResult<string>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GetCaptchaAnswerAsync(byte[] captchaImage)
|
public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage)
|
||||||
{
|
{
|
||||||
using var dialog = new CaptchaDialog(captchaImage);
|
using var dialog = new CaptchaDialog(password, captchaImage);
|
||||||
if (ShowDialog(dialog))
|
if (ShowDialog(dialog))
|
||||||
return Task.FromResult(dialog.Answer);
|
return Task.FromResult((dialog.Password, dialog.Answer));
|
||||||
return Task.FromResult<string>(null);
|
return Task.FromResult<(string, string)>((null,null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig)
|
||||||
|
|||||||
@ -28,62 +28,77 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.submitBtn = new System.Windows.Forms.Button();
|
submitBtn = new System.Windows.Forms.Button();
|
||||||
this.codeTb = new System.Windows.Forms.TextBox();
|
codeTb = new System.Windows.Forms.TextBox();
|
||||||
this.label1 = new System.Windows.Forms.Label();
|
label1 = new System.Windows.Forms.Label();
|
||||||
this.SuspendLayout();
|
promptLbl = new System.Windows.Forms.Label();
|
||||||
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// submitBtn
|
// submitBtn
|
||||||
//
|
//
|
||||||
this.submitBtn.Location = new System.Drawing.Point(15, 51);
|
submitBtn.Location = new System.Drawing.Point(18, 108);
|
||||||
this.submitBtn.Name = "SaveBtn";
|
submitBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.submitBtn.Size = new System.Drawing.Size(79, 23);
|
submitBtn.Name = "submitBtn";
|
||||||
this.submitBtn.TabIndex = 1;
|
submitBtn.Size = new System.Drawing.Size(191, 27);
|
||||||
this.submitBtn.Text = "Submit";
|
submitBtn.TabIndex = 1;
|
||||||
this.submitBtn.UseVisualStyleBackColor = true;
|
submitBtn.Text = "Submit";
|
||||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
submitBtn.UseVisualStyleBackColor = true;
|
||||||
|
submitBtn.Click += submitBtn_Click;
|
||||||
//
|
//
|
||||||
// codeTb
|
// codeTb
|
||||||
//
|
//
|
||||||
this.codeTb.Location = new System.Drawing.Point(15, 25);
|
codeTb.Location = new System.Drawing.Point(108, 79);
|
||||||
this.codeTb.Name = "newTagsTb";
|
codeTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
codeTb.Name = "codeTb";
|
||||||
this.codeTb.Size = new System.Drawing.Size(79, 20);
|
codeTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||||
this.codeTb.TabIndex = 0;
|
codeTb.Size = new System.Drawing.Size(101, 23);
|
||||||
|
codeTb.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// label1
|
// label1
|
||||||
//
|
//
|
||||||
this.label1.AutoSize = true;
|
label1.AutoSize = true;
|
||||||
this.label1.Location = new System.Drawing.Point(12, 9);
|
label1.Location = new System.Drawing.Point(13, 82);
|
||||||
this.label1.Name = "label1";
|
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||||
this.label1.Size = new System.Drawing.Size(82, 13);
|
label1.Name = "label1";
|
||||||
this.label1.TabIndex = 2;
|
label1.Size = new System.Drawing.Size(87, 15);
|
||||||
this.label1.Text = "Enter 2FA Code";
|
label1.TabIndex = 2;
|
||||||
|
label1.Text = "Enter 2FA Code";
|
||||||
|
//
|
||||||
|
// promptLbl
|
||||||
|
//
|
||||||
|
promptLbl.Location = new System.Drawing.Point(13, 9);
|
||||||
|
promptLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||||
|
promptLbl.Name = "promptLbl";
|
||||||
|
promptLbl.Size = new System.Drawing.Size(196, 59);
|
||||||
|
promptLbl.TabIndex = 2;
|
||||||
|
promptLbl.Text = "[Prompt]";
|
||||||
//
|
//
|
||||||
// _2faCodeDialog
|
// _2faCodeDialog
|
||||||
//
|
//
|
||||||
this.AcceptButton = this.submitBtn;
|
AcceptButton = submitBtn;
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.ClientSize = new System.Drawing.Size(106, 86);
|
ClientSize = new System.Drawing.Size(222, 147);
|
||||||
this.Controls.Add(this.label1);
|
Controls.Add(promptLbl);
|
||||||
this.Controls.Add(this.codeTb);
|
Controls.Add(label1);
|
||||||
this.Controls.Add(this.submitBtn);
|
Controls.Add(codeTb);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
Controls.Add(submitBtn);
|
||||||
this.MaximizeBox = false;
|
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
this.MinimizeBox = false;
|
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.Name = "_2faCodeDialog";
|
MaximizeBox = false;
|
||||||
this.ShowIcon = false;
|
MinimizeBox = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
Name = "_2faCodeDialog";
|
||||||
this.Text = "2FA Code";
|
ShowIcon = false;
|
||||||
this.ResumeLayout(false);
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.PerformLayout();
|
Text = "2FA Code";
|
||||||
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
private System.Windows.Forms.Button submitBtn;
|
private System.Windows.Forms.Button submitBtn;
|
||||||
private System.Windows.Forms.TextBox codeTb;
|
private System.Windows.Forms.TextBox codeTb;
|
||||||
private System.Windows.Forms.Label label1;
|
private System.Windows.Forms.Label label1;
|
||||||
|
private System.Windows.Forms.Label promptLbl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,9 +8,10 @@ namespace LibationWinForms.Dialogs.Login
|
|||||||
{
|
{
|
||||||
public string Code { get; private set; }
|
public string Code { get; private set; }
|
||||||
|
|
||||||
public _2faCodeDialog()
|
public _2faCodeDialog() => InitializeComponent();
|
||||||
|
public _2faCodeDialog(string prompt) : this()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
promptLbl.Text = prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitBtn_Click(object sender, EventArgs e)
|
private void submitBtn_Click(object sender, EventArgs e)
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<root>
|
||||||
<root>
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
|||||||
@ -17,11 +17,8 @@ namespace LibationWinForms
|
|||||||
private void Configure_ScanAuto()
|
private void Configure_ScanAuto()
|
||||||
{
|
{
|
||||||
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
||||||
var hours = 0;
|
|
||||||
var minutes = 5;
|
autoScanTimer = new InterruptableTimer(TimeSpan.FromMinutes(5));
|
||||||
var seconds = 0;
|
|
||||||
var _5_minutes = new TimeSpan(hours, minutes, seconds);
|
|
||||||
autoScanTimer = new InterruptableTimer(_5_minutes);
|
|
||||||
|
|
||||||
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
||||||
autoScanTimer.Elapsed += async (_, __) =>
|
autoScanTimer.Elapsed += async (_, __) =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user