using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; using System.Windows.Controls; using System.Windows.Input; using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.Wpf; using InABox.WPF; using PRSDesktop.Forms; using ValidationResult = InABox.Clients.ValidationResult; namespace PRSDesktop; /// /// Interaction logic for PinLogin.xaml /// public partial class PinLogin : ThemableWindow { private bool _isloggedin = false; private enum Page { PIN, PASSWORD, _2FA } private bool PINPage; private Page CurrentPage; private bool Checking = false; private List codeInputs = new(); public PinLogin(string version, ValidationResult status) { InitializeComponent(); Title = $"{(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})"; foreach (var codeInput in CodeInput.Children) { var tb = codeInput as TextBox; tb.PreviewKeyDown += Code2FA_PreviewKeyDown; tb.PreviewTextInput += Code2FA_Preview; tb.TextChanged += Code2FA_TextChanged; codeInputs.Add(tb); } UserID.Text = App.DatabaseSettings.UserID; Password.Password = App.DatabaseSettings.Password; if (status == ValidationResult.PASSWORD_EXPIRED) { status = TryValidation("", status); } PINPage = App.DatabaseSettings.LoginType == LoginType.PIN; if (status == ValidationResult.REQUIRE_2FA) Show2FAPage(); else if (PINPage) ShowPINPage(); else ShowPasswordPage(); //AutoLogin.IsChecked = _settings.Autologin; } private void ShowPINPage() { CurrentPage = Page.PIN; OverallLayout.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star); OverallLayout.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star); OverallLayout.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star); PINLayout.RowDefinitions[3].Height = new GridLength(70, GridUnitType.Pixel); PINLayout.RowDefinitions[4].Height = new GridLength(70, GridUnitType.Pixel); Layout_2FA.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel); } private void ShowPasswordPage() { CurrentPage = Page.PASSWORD; OverallLayout.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star); OverallLayout.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star); OverallLayout.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star); PINLayout.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel); PINLayout.RowDefinitions[4].Height = new GridLength(0, GridUnitType.Pixel); Layout_2FA.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel); } private void Show2FAPage() { CurrentPage = Page._2FA; OverallLayout.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star); OverallLayout.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star); OverallLayout.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star); PINLayout.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel); PINLayout.RowDefinitions[4].Height = new GridLength(0, GridUnitType.Pixel); Layout_2FA.RowDefinitions[3].Height = new GridLength(70, GridUnitType.Pixel); foreach (var codeInput in codeInputs) { codeInput.Text = ""; } codeInputs[0].Focus(); Label_2FA.Content = string.Format("Please enter the code that was sent to {0}", ClientFactory.Recipient2FA); } private void ViewPINClick(object sender, RoutedEventArgs e) { ShowPINPage(); PIN.Focus(); PINPage = true; } private void ViewPasswordClick(object sender, RoutedEventArgs e) { ShowPasswordPage(); UserID.Focus(); PINPage = false; } private void Button_Click(object sender, RoutedEventArgs e) { var button = (Button)sender; PIN.Password += button.Content; } private void Back_Click(object sender, RoutedEventArgs e) { if (!string.IsNullOrEmpty(PIN.Password)) PIN.Password = PIN.Password[..^1]; //.Reverse().Skip(1).Reverse().ToString(); } private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { if(CurrentPage == Page._2FA) { if (e.Key == Key.Enter || e.Key == Key.Return) { e.Handled = true; Do2FA(); } return; } if (CurrentPage != Page.PIN) return; e.Handled = true; Button button = null; if (e.Key == Key.Enter || e.Key == Key.Return) button = OK; else if (e.Key == Key.D1 || e.Key == Key.NumPad1) button = Key1; else if (e.Key == Key.D2 || e.Key == Key.NumPad2) button = Key2; else if (e.Key == Key.D3 || e.Key == Key.NumPad3) button = Key3; else if (e.Key == Key.D4 || e.Key == Key.NumPad4) button = Key4; else if (e.Key == Key.D5 || e.Key == Key.NumPad5) button = Key5; else if (e.Key == Key.D6 || e.Key == Key.NumPad6) button = Key6; else if (e.Key == Key.D7 || e.Key == Key.NumPad7) button = Key7; else if (e.Key == Key.D8 || e.Key == Key.NumPad8) button = Key8; else if (e.Key == Key.D9 || e.Key == Key.NumPad9) button = Key9; else if (e.Key == Key.D0 || e.Key == Key.NumPad0) button = Key0; else if (e.Key == Key.Back) button = Back; if (button == null || !button.IsEnabled) return; var peer = new ButtonAutomationPeer(button); var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider; invokeProv.Invoke(); } private void PINChanged(object sender, RoutedEventArgs e) { Back.IsEnabled = !string.IsNullOrEmpty(PIN.Password); OK.IsEnabled = !string.IsNullOrEmpty(PIN.Password); } private string? ShowChangePassword(string userID) { using (var session = new BypassSession()) { var user = new Client().Query( new Filter(x => x.UserID).IsEqualTo(userID), new Columns(x => x.ID)).Rows.FirstOrDefault()?.ToObject(); if(user != null) { var changePassword = new ChangePassword(user.ID); if (changePassword.ShowDialog() == true && changePassword.Password != null) { ChangePassword.ChangeUserPassword(user, changePassword.Password); new Client().Save(user, "Changed password"); return changePassword.Password; } else { if (PINPage) ShowPINPage(); else ShowPasswordPage(); } } else { MessageBox.Show($"No user with username {userID}"); } } return null; } private void OK_Click(object sender, RoutedEventArgs e) { ValidationResult? result; using (new WaitCursor()) { try { result = ClientFactory.Validate(PIN.Password); } catch(Exception err) { Logger.Send(LogType.Error, ClientFactory.UserID, $"Error connecting to server: {CoreUtils.FormatException(err)}"); MessageBox.Show("Error connecting to server! Check the server URL and port number."); result = null; } } if (result == ValidationResult.REQUIRE_2FA) { Show2FAPage(); return; } var bOK = result == ValidationResult.VALID; if (bOK) { App.DatabaseSettings.LoginType = LoginType.PIN; App.DatabaseSettings.Autologin = false; App.DatabaseSettings.UserID = ""; App.DatabaseSettings.Password = ""; DialogResult = true; Close(); } else { if(result != null) { MessageBox.Show("Invalid PIN!"); } PIN.Password = ""; } } private void UserID_TextChanged(object sender, TextChangedEventArgs e) { Login.IsEnabled = !string.IsNullOrEmpty(UserID.Text) && !string.IsNullOrEmpty(Password.Password); } private void Password_PasswordChanged(object sender, RoutedEventArgs e) { Login.IsEnabled = !string.IsNullOrEmpty(UserID.Text) && !string.IsNullOrEmpty(Password.Password); } private void AutoLogin_Checked(object sender, RoutedEventArgs e) { } private ValidationResult TryValidation(string password, ValidationResult? result = null) { while (true) { if(result == null) { using (new WaitCursor()) { try { result = ClientFactory.Validate(UserID.Text, password); } catch (Exception e) { Logger.Send(LogType.Error, ClientFactory.UserID, $"Error connecting to server: {CoreUtils.FormatException(e)}"); MessageBox.Show("Error connecting to server! Check the server URL and port number."); } } } if (result == ValidationResult.PASSWORD_EXPIRED) { MessageBox.Show("Your password has expired!"); var newPassword = ShowChangePassword(UserID.Text); if (newPassword == null) { break; } else { password = newPassword; Password.Clear(); } } else { break; } result = null; } return result ?? ValidationResult.INVALID; } private void Login_Click(object sender, RoutedEventArgs e) { ValidationResult result = TryValidation(Password.Password); if (result == ValidationResult.REQUIRE_2FA) { Show2FAPage(); return; } var bOK = result == ValidationResult.VALID; if (bOK) { //if (AutoLogin.IsChecked == true) //{ // _settings.LoginType = LoginType.UserID; // _settings.UserID = UserID.Text; // _settings.Password = Password.Password; // _settings.Autologin = true; // new LocalConfiguration().Save(_settings); //} DialogResult = true; Close(); } else { MessageBox.Show("Login Failed!"); } } private void Window_Closing(object sender, CancelEventArgs e) { //e.Cancel = ClientFactory.UserGuid == Guid.Empty; if(!PINPage && DialogResult == true && App.DatabaseSettings.Autologin && App.DatabaseSettings.UserID == UserID.Text && App.DatabaseSettings.Password != Password.Password) { if(MessageBox.Show("You have logged in with a new password. Would you like to update your password in your settings so that you login automatically next time?", "Update password?", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { App.DatabaseSettings.Password = Password.Password; App.SaveDatabaseSettings(); } } } private void Password_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Enter || e.Key == Key.Return) { e.Handled = true; var peer = new ButtonAutomationPeer(Login); var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider; invokeProv.Invoke(); } } private void UserID_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Enter || e.Key == Key.Return) { e.Handled = true; Password.Focus(); } } private bool HasFilled2FABoxes() { foreach (var codeInput in CodeInput.Children) { var digit = (codeInput as TextBox)?.Text; if (digit == null || digit.Length != 1 || !char.IsDigit(digit[0])) { return false; } } return true; } private void Do2FA(bool showMessage = true) { if (Checking) return; Checking = true; var code = ""; foreach (var codeInput in CodeInput.Children) { var digit = (codeInput as TextBox)?.Text; if (digit != null && digit.Length == 1 && char.IsDigit(digit[0])) { code += digit[0]; } else { MessageBox.Show("All boxes must be filled and must contain a number"); return; } } Task.Run(() => { if (ClientFactory.Check2FA(code)) { Dispatcher.Invoke(() => { DialogResult = true; Close(); }); } else { MessageBox.Show("Code is incorrect!"); Checking = false; } }); } private void Click_2FA(object sender, RoutedEventArgs e) { Do2FA(); } private void Back_2FA(object sender, RoutedEventArgs e) { ClientFactory.InvalidateUser(); if (PINPage) ShowPINPage(); else ShowPasswordPage(); } private void MoveFocus() { var element = Keyboard.FocusedElement; if (element is TextBox) { var tb = element as TextBox; if (tb.Text.Length == 1) { var index = codeInputs.IndexOf(tb) + 1; if (index >= codeInputs.Count) { Focus(); if (HasFilled2FABoxes()) { Do2FA(); } } else { var nextChild = codeInputs[index]; nextChild.Focus(); } } } } private void Code2FA_Preview(object sender, TextCompositionEventArgs e) { var tb = sender as TextBox; var oldText = tb!.Text; var newText = e.Text; var valid = newText.Length == 0 || (newText.Length == 1 && char.IsDigit(newText[0])); if (oldText.Length != 0 && valid) { tb.Text = ""; } e.Handled = !valid; if (!valid && oldText.Length + newText.Length > 0) { MoveFocus(); } } private void Code2FA_TextChanged(object sender, TextChangedEventArgs e) { MoveFocus(); } private void Code2FA_PreviewKeyDown(object sender, KeyEventArgs e) { if (sender is TextBox tb) { var currentIndex = codeInputs.IndexOf(tb); int? newIndex = null; switch (e.Key) { case Key.Back: tb.Text = ""; newIndex = Math.Max(currentIndex - 1, 0); e.Handled = true; break; case Key.Left: newIndex = Math.Max(currentIndex - 1, 0); e.Handled = true; break; case Key.Right: newIndex = Math.Min(currentIndex + 1, codeInputs.Count - 1); e.Handled = true; break; } if(newIndex != null) { var nextChild = codeInputs[newIndex ?? 0]; nextChild.Focus(); } } } }