PinLogin.xaml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Automation.Peers;
  8. using System.Windows.Automation.Provider;
  9. using System.Windows.Controls;
  10. using System.Windows.Input;
  11. using Comal.Classes;
  12. using InABox.Clients;
  13. using InABox.Configuration;
  14. using InABox.Core;
  15. using InABox.Wpf;
  16. using InABox.WPF;
  17. using PRSDesktop.Forms;
  18. using ValidationResult = InABox.Clients.ValidationResult;
  19. namespace PRSDesktop;
  20. /// <summary>
  21. /// Interaction logic for PinLogin.xaml
  22. /// </summary>
  23. public partial class PinLogin : ThemableWindow
  24. {
  25. private bool _isloggedin = false;
  26. private enum Page
  27. {
  28. PIN,
  29. PASSWORD,
  30. _2FA
  31. }
  32. private bool PINPage;
  33. private Page CurrentPage;
  34. private bool Checking = false;
  35. private List<TextBox> codeInputs = new();
  36. public PinLogin(string version, ValidationResult status)
  37. {
  38. InitializeComponent();
  39. Title = $"{(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
  40. foreach (var codeInput in CodeInput.Children)
  41. {
  42. var tb = codeInput as TextBox;
  43. tb.PreviewKeyDown += Code2FA_PreviewKeyDown;
  44. tb.PreviewTextInput += Code2FA_Preview;
  45. tb.TextChanged += Code2FA_TextChanged;
  46. codeInputs.Add(tb);
  47. }
  48. UserID.Text = App.DatabaseSettings.UserID;
  49. Password.Password = App.DatabaseSettings.Password;
  50. if (status == ValidationResult.PASSWORD_EXPIRED)
  51. {
  52. status = TryValidation("", status);
  53. }
  54. PINPage = App.DatabaseSettings.LoginType == LoginType.PIN;
  55. if (status == ValidationResult.REQUIRE_2FA)
  56. Show2FAPage();
  57. else if (PINPage)
  58. ShowPINPage();
  59. else
  60. ShowPasswordPage();
  61. //AutoLogin.IsChecked = _settings.Autologin;
  62. }
  63. private void ShowPINPage()
  64. {
  65. CurrentPage = Page.PIN;
  66. OverallLayout.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
  67. OverallLayout.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
  68. OverallLayout.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
  69. PINLayout.RowDefinitions[3].Height = new GridLength(70, GridUnitType.Pixel);
  70. PINLayout.RowDefinitions[4].Height = new GridLength(70, GridUnitType.Pixel);
  71. Layout_2FA.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel);
  72. }
  73. private void ShowPasswordPage()
  74. {
  75. CurrentPage = Page.PASSWORD;
  76. OverallLayout.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star);
  77. OverallLayout.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
  78. OverallLayout.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
  79. PINLayout.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel);
  80. PINLayout.RowDefinitions[4].Height = new GridLength(0, GridUnitType.Pixel);
  81. Layout_2FA.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel);
  82. }
  83. private void Show2FAPage()
  84. {
  85. CurrentPage = Page._2FA;
  86. OverallLayout.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star);
  87. OverallLayout.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
  88. OverallLayout.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star);
  89. PINLayout.RowDefinitions[3].Height = new GridLength(0, GridUnitType.Pixel);
  90. PINLayout.RowDefinitions[4].Height = new GridLength(0, GridUnitType.Pixel);
  91. Layout_2FA.RowDefinitions[3].Height = new GridLength(70, GridUnitType.Pixel);
  92. foreach (var codeInput in codeInputs)
  93. {
  94. codeInput.Text = "";
  95. }
  96. codeInputs[0].Focus();
  97. Label_2FA.Content = string.Format("Please enter the code that was sent to {0}", ClientFactory.Recipient2FA);
  98. }
  99. private void ViewPINClick(object sender, RoutedEventArgs e)
  100. {
  101. ShowPINPage();
  102. PIN.Focus();
  103. PINPage = true;
  104. }
  105. private void ViewPasswordClick(object sender, RoutedEventArgs e)
  106. {
  107. ShowPasswordPage();
  108. UserID.Focus();
  109. PINPage = false;
  110. }
  111. private void Button_Click(object sender, RoutedEventArgs e)
  112. {
  113. var button = (Button)sender;
  114. PIN.Password += button.Content;
  115. }
  116. private void Back_Click(object sender, RoutedEventArgs e)
  117. {
  118. if (!string.IsNullOrEmpty(PIN.Password))
  119. PIN.Password = PIN.Password[..^1]; //.Reverse().Skip(1).Reverse().ToString();
  120. }
  121. private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
  122. {
  123. if(CurrentPage == Page._2FA)
  124. {
  125. if (e.Key == Key.Enter || e.Key == Key.Return)
  126. {
  127. e.Handled = true;
  128. Do2FA();
  129. }
  130. return;
  131. }
  132. if (CurrentPage != Page.PIN)
  133. return;
  134. e.Handled = true;
  135. Button button = null;
  136. if (e.Key == Key.Enter || e.Key == Key.Return)
  137. button = OK;
  138. else if (e.Key == Key.D1 || e.Key == Key.NumPad1)
  139. button = Key1;
  140. else if (e.Key == Key.D2 || e.Key == Key.NumPad2)
  141. button = Key2;
  142. else if (e.Key == Key.D3 || e.Key == Key.NumPad3)
  143. button = Key3;
  144. else if (e.Key == Key.D4 || e.Key == Key.NumPad4)
  145. button = Key4;
  146. else if (e.Key == Key.D5 || e.Key == Key.NumPad5)
  147. button = Key5;
  148. else if (e.Key == Key.D6 || e.Key == Key.NumPad6)
  149. button = Key6;
  150. else if (e.Key == Key.D7 || e.Key == Key.NumPad7)
  151. button = Key7;
  152. else if (e.Key == Key.D8 || e.Key == Key.NumPad8)
  153. button = Key8;
  154. else if (e.Key == Key.D9 || e.Key == Key.NumPad9)
  155. button = Key9;
  156. else if (e.Key == Key.D0 || e.Key == Key.NumPad0)
  157. button = Key0;
  158. else if (e.Key == Key.Back)
  159. button = Back;
  160. if (button == null || !button.IsEnabled)
  161. return;
  162. var peer = new ButtonAutomationPeer(button);
  163. var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
  164. invokeProv.Invoke();
  165. }
  166. private void PINChanged(object sender, RoutedEventArgs e)
  167. {
  168. Back.IsEnabled = !string.IsNullOrEmpty(PIN.Password);
  169. OK.IsEnabled = !string.IsNullOrEmpty(PIN.Password);
  170. }
  171. private string? ShowChangePassword(string userID)
  172. {
  173. using (var session = new BypassSession())
  174. {
  175. var user = new Client<User>().Query(
  176. new Filter<User>(x => x.UserID).IsEqualTo(userID),
  177. new Columns<User>(x => x.ID)).Rows.FirstOrDefault()?.ToObject<User>();
  178. if(user != null)
  179. {
  180. var changePassword = new ChangePassword(user.ID);
  181. if (changePassword.ShowDialog() == true && changePassword.Password != null)
  182. {
  183. ChangePassword.ChangeUserPassword(user, changePassword.Password);
  184. new Client<User>().Save(user, "Changed password");
  185. return changePassword.Password;
  186. }
  187. else
  188. {
  189. if (PINPage)
  190. ShowPINPage();
  191. else
  192. ShowPasswordPage();
  193. }
  194. }
  195. else
  196. {
  197. MessageBox.Show($"No user with username {userID}");
  198. }
  199. }
  200. return null;
  201. }
  202. private void OK_Click(object sender, RoutedEventArgs e)
  203. {
  204. ValidationResult? result;
  205. using (new WaitCursor())
  206. {
  207. try
  208. {
  209. result = ClientFactory.Validate(PIN.Password);
  210. }
  211. catch(Exception err)
  212. {
  213. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error connecting to server: {CoreUtils.FormatException(err)}");
  214. MessageBox.Show("Error connecting to server! Check the server URL and port number.");
  215. result = null;
  216. }
  217. }
  218. if (result == ValidationResult.REQUIRE_2FA)
  219. {
  220. Show2FAPage();
  221. return;
  222. }
  223. var bOK = result == ValidationResult.VALID;
  224. if (bOK)
  225. {
  226. App.DatabaseSettings.LoginType = LoginType.PIN;
  227. App.DatabaseSettings.Autologin = false;
  228. App.DatabaseSettings.UserID = "";
  229. App.DatabaseSettings.Password = "";
  230. DialogResult = true;
  231. Close();
  232. }
  233. else
  234. {
  235. if(result != null)
  236. {
  237. MessageBox.Show("Invalid PIN!");
  238. }
  239. PIN.Password = "";
  240. }
  241. }
  242. private void UserID_TextChanged(object sender, TextChangedEventArgs e)
  243. {
  244. Login.IsEnabled = !string.IsNullOrEmpty(UserID.Text) && !string.IsNullOrEmpty(Password.Password);
  245. }
  246. private void Password_PasswordChanged(object sender, RoutedEventArgs e)
  247. {
  248. Login.IsEnabled = !string.IsNullOrEmpty(UserID.Text) && !string.IsNullOrEmpty(Password.Password);
  249. }
  250. private void AutoLogin_Checked(object sender, RoutedEventArgs e)
  251. {
  252. }
  253. private ValidationResult TryValidation(string password, ValidationResult? result = null)
  254. {
  255. while (true)
  256. {
  257. if(result == null)
  258. {
  259. using (new WaitCursor())
  260. {
  261. try
  262. {
  263. result = ClientFactory.Validate(UserID.Text, password);
  264. }
  265. catch (Exception e)
  266. {
  267. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error connecting to server: {CoreUtils.FormatException(e)}");
  268. MessageBox.Show("Error connecting to server! Check the server URL and port number.");
  269. }
  270. }
  271. }
  272. if (result == ValidationResult.PASSWORD_EXPIRED)
  273. {
  274. MessageBox.Show("Your password has expired!");
  275. var newPassword = ShowChangePassword(UserID.Text);
  276. if (newPassword == null)
  277. {
  278. break;
  279. }
  280. else
  281. {
  282. password = newPassword;
  283. Password.Clear();
  284. }
  285. }
  286. else
  287. {
  288. break;
  289. }
  290. result = null;
  291. }
  292. return result ?? ValidationResult.INVALID;
  293. }
  294. private void Login_Click(object sender, RoutedEventArgs e)
  295. {
  296. ValidationResult result = TryValidation(Password.Password);
  297. if (result == ValidationResult.REQUIRE_2FA)
  298. {
  299. Show2FAPage();
  300. return;
  301. }
  302. var bOK = result == ValidationResult.VALID;
  303. if (bOK)
  304. {
  305. //if (AutoLogin.IsChecked == true)
  306. //{
  307. // _settings.LoginType = LoginType.UserID;
  308. // _settings.UserID = UserID.Text;
  309. // _settings.Password = Password.Password;
  310. // _settings.Autologin = true;
  311. // new LocalConfiguration<DatabaseSettings>().Save(_settings);
  312. //}
  313. DialogResult = true;
  314. Close();
  315. }
  316. else
  317. {
  318. MessageBox.Show("Login Failed!");
  319. }
  320. }
  321. private void Window_Closing(object sender, CancelEventArgs e)
  322. {
  323. //e.Cancel = ClientFactory.UserGuid == Guid.Empty;
  324. if(!PINPage && DialogResult == true && App.DatabaseSettings.Autologin
  325. && App.DatabaseSettings.UserID == UserID.Text
  326. && App.DatabaseSettings.Password != Password.Password)
  327. {
  328. 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?",
  329. "Update password?",
  330. MessageBoxButton.YesNo) == MessageBoxResult.Yes)
  331. {
  332. App.DatabaseSettings.Password = Password.Password;
  333. App.SaveDatabaseSettings();
  334. }
  335. }
  336. }
  337. private void Password_KeyUp(object sender, KeyEventArgs e)
  338. {
  339. if (e.Key == Key.Enter || e.Key == Key.Return)
  340. {
  341. e.Handled = true;
  342. var peer = new ButtonAutomationPeer(Login);
  343. var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
  344. invokeProv.Invoke();
  345. }
  346. }
  347. private void UserID_KeyUp(object sender, KeyEventArgs e)
  348. {
  349. if (e.Key == Key.Enter || e.Key == Key.Return)
  350. {
  351. e.Handled = true;
  352. Password.Focus();
  353. }
  354. }
  355. private bool HasFilled2FABoxes()
  356. {
  357. foreach (var codeInput in CodeInput.Children)
  358. {
  359. var digit = (codeInput as TextBox)?.Text;
  360. if (digit == null || digit.Length != 1 || !char.IsDigit(digit[0]))
  361. {
  362. return false;
  363. }
  364. }
  365. return true;
  366. }
  367. private void Do2FA(bool showMessage = true)
  368. {
  369. if (Checking) return;
  370. Checking = true;
  371. var code = "";
  372. foreach (var codeInput in CodeInput.Children)
  373. {
  374. var digit = (codeInput as TextBox)?.Text;
  375. if (digit != null && digit.Length == 1 && char.IsDigit(digit[0]))
  376. {
  377. code += digit[0];
  378. }
  379. else
  380. {
  381. MessageBox.Show("All boxes must be filled and must contain a number");
  382. return;
  383. }
  384. }
  385. Task.Run(() =>
  386. {
  387. if (ClientFactory.Check2FA(code))
  388. {
  389. Dispatcher.Invoke(() =>
  390. {
  391. DialogResult = true;
  392. Close();
  393. });
  394. }
  395. else
  396. {
  397. MessageBox.Show("Code is incorrect!");
  398. Checking = false;
  399. }
  400. });
  401. }
  402. private void Click_2FA(object sender, RoutedEventArgs e)
  403. {
  404. Do2FA();
  405. }
  406. private void Back_2FA(object sender, RoutedEventArgs e)
  407. {
  408. ClientFactory.InvalidateUser();
  409. if (PINPage)
  410. ShowPINPage();
  411. else
  412. ShowPasswordPage();
  413. }
  414. private void MoveFocus()
  415. {
  416. var element = Keyboard.FocusedElement;
  417. if (element is TextBox)
  418. {
  419. var tb = element as TextBox;
  420. if (tb.Text.Length == 1)
  421. {
  422. var index = codeInputs.IndexOf(tb) + 1;
  423. if (index >= codeInputs.Count)
  424. {
  425. Focus();
  426. if (HasFilled2FABoxes())
  427. {
  428. Do2FA();
  429. }
  430. }
  431. else
  432. {
  433. var nextChild = codeInputs[index];
  434. nextChild.Focus();
  435. }
  436. }
  437. }
  438. }
  439. private void Code2FA_Preview(object sender, TextCompositionEventArgs e)
  440. {
  441. var tb = sender as TextBox;
  442. var oldText = tb!.Text;
  443. var newText = e.Text;
  444. var valid = newText.Length == 0 || (newText.Length == 1 && char.IsDigit(newText[0]));
  445. if (oldText.Length != 0 && valid)
  446. {
  447. tb.Text = "";
  448. }
  449. e.Handled = !valid;
  450. if (!valid && oldText.Length + newText.Length > 0)
  451. {
  452. MoveFocus();
  453. }
  454. }
  455. private void Code2FA_TextChanged(object sender, TextChangedEventArgs e)
  456. {
  457. MoveFocus();
  458. }
  459. private void Code2FA_PreviewKeyDown(object sender, KeyEventArgs e)
  460. {
  461. if (sender is TextBox tb)
  462. {
  463. var currentIndex = codeInputs.IndexOf(tb);
  464. int? newIndex = null;
  465. switch (e.Key)
  466. {
  467. case Key.Back:
  468. tb.Text = "";
  469. newIndex = Math.Max(currentIndex - 1, 0);
  470. e.Handled = true;
  471. break;
  472. case Key.Left:
  473. newIndex = Math.Max(currentIndex - 1, 0);
  474. e.Handled = true;
  475. break;
  476. case Key.Right:
  477. newIndex = Math.Min(currentIndex + 1, codeInputs.Count - 1);
  478. e.Handled = true;
  479. break;
  480. }
  481. if(newIndex != null)
  482. {
  483. var nextChild = codeInputs[newIndex ?? 0];
  484. nextChild.Focus();
  485. }
  486. }
  487. }
  488. }