PinLogin.xaml.cs 16 KB

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