PINLoginPage.xaml.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Comal.Classes;
  7. using InABox.Clients;
  8. using InABox.Configuration;
  9. using InABox.Core;
  10. using InABox.Mobile;
  11. using InABox.Rpc;
  12. using Xamarin.Essentials;
  13. using Xamarin.Forms;
  14. using XF.Material.Forms.UI;
  15. using XF.Material.Forms.UI.Dialogs;
  16. namespace comal.timesheets
  17. {
  18. public partial class PinLoginPage : ContentPage
  19. {
  20. private enum SubPage
  21. {
  22. Autologin,
  23. Pin,
  24. TwoFactor,
  25. Password
  26. }
  27. private SubPage _page;
  28. private String _pin = "";
  29. private String _2fa = "";
  30. private DatabaseSettings _settings { get; set; }
  31. public PinLoginPage()
  32. {
  33. InitializeComponent();
  34. SelectSubPage(SubPage.Autologin);
  35. }
  36. protected override async void OnAppearing()
  37. {
  38. MobileLogging.Log("PINLoginPage.Appearing()");
  39. base.OnAppearing();
  40. // IS there a new version? If so, then get that before doing anything else
  41. MobileLogging.Log("PINLoginPage.CheckForLatestVersion()");
  42. CheckForLatestVersion();
  43. //TODO this is only for the temporary site app login - needs to be removed
  44. SelectSubPage(SubPage.Autologin);
  45. // Is this the first time through?
  46. // If so, then try to validate
  47. if (_page == SubPage.Autologin)
  48. {
  49. MobileLogging.Log("PINLoginPage.CheckAutoConfigurationLink()");
  50. CheckAutoConfigurationLink();
  51. MobileLogging.Log("PINLoginPage.LoadSettings()");
  52. LoadSettings();
  53. MobileLogging.Log("PINLoginPage.Connecting()");
  54. TransportStatus connection = TransportStatus.None;
  55. using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Connecting"))
  56. {
  57. await Task.Run(() =>
  58. {
  59. connection = App.ConnectTransport(_settings.URLs);
  60. });
  61. }
  62. if (connection != TransportStatus.OK)
  63. {
  64. await DisplayAlert("Connection Error", $"Unable to establish a connection!\n\nERR: {connection}", "OK");
  65. Navigation.PushAsync(new SettingsPage());
  66. return;
  67. }
  68. MobileLogging.Log("PINLoginPage.AutoLogin()");
  69. ValidationStatus status = ValidationStatus.INVALID;
  70. using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Validating"))
  71. {
  72. Task.Run(() => { status = AutoLogin(); }).Wait();
  73. }
  74. if (status == ValidationStatus.VALID)
  75. {
  76. MobileLogging.Log("PINLoginPage.CheckPasswordExpiration()");
  77. if (await CheckPasswordExpiration())
  78. {
  79. PasswordCancelColumn.Width = new GridLength(1, GridUnitType.Star);
  80. SelectSubPage(SubPage.Password);
  81. }
  82. else
  83. {
  84. MobileLogging.Log("PINLoginPage.LaunchMainPage()");
  85. LaunchMainPage();
  86. }
  87. }
  88. else if (status == ValidationStatus.INVALID)
  89. {
  90. // If we _should_ have been able to log in (ie we have a username)
  91. // show an error message. Otherwise, jump to the PIN page
  92. if (!String.IsNullOrWhiteSpace(_settings.UserID))
  93. await DisplayAlert("Error logging in", "Invalid User ID, Password or PIN", "OK");
  94. MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.Pin)");
  95. SelectSubPage(SubPage.Pin);
  96. }
  97. else if (status == ValidationStatus.REQUIRE_2FA)
  98. {
  99. MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.TwoFactor)");
  100. SelectSubPage(SubPage.TwoFactor);
  101. }
  102. else if (status == ValidationStatus.PASSWORD_EXPIRED)
  103. {
  104. MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.Password)");
  105. await DisplayAlert("Alert", "Your password has expired - please change it", "OK");
  106. PasswordCancelColumn.Width = new GridLength(0, GridUnitType.Absolute);
  107. SelectSubPage(SubPage.Password);
  108. }
  109. }
  110. else if (_page == SubPage.Pin)
  111. {
  112. UpdatePIN('\0');
  113. }
  114. else if (_page == SubPage.TwoFactor)
  115. {
  116. // Has the user left the app to go and get the 2fa code from sms or google authenticator?
  117. // If so, then try to retrieve the 2fa code from the clipboard
  118. var text = Clipboard.GetTextAsync().Result;
  119. if ((text?.Length == 6) && (text?.All(Char.IsDigit) == true))
  120. {
  121. _2fa = text;
  122. TwoFA0.Text = text.Substring(0, 1);
  123. TwoFA1.Text = text.Substring(1, 1);
  124. TwoFA2.Text = text.Substring(2, 1);
  125. TwoFA3.Text = text.Substring(3, 1);
  126. TwoFA4.Text = text.Substring(4, 1);
  127. TwoFA5.Text = text.Substring(5, 1);
  128. }
  129. else
  130. UpdateTwoFA('\0');
  131. }
  132. else if (_page == SubPage.Password)
  133. {
  134. NewPassword.Text = "";
  135. NewPassword2.Text = "";
  136. ChangePasswordButton.IsEnabled = false;
  137. }
  138. }
  139. private void CheckAutoConfigurationLink()
  140. {
  141. var parameters = App.LaunchParameters;
  142. App.LaunchParameters = "";
  143. if (String.IsNullOrWhiteSpace(parameters))
  144. return;
  145. var decrypted = Encryption.Decrypt(parameters, "logindetailslink");
  146. if (decrypted?.ToUpper()?.Contains("ENDURLS") == true)
  147. {
  148. var comps = decrypted.Split("ENDURLS");
  149. if (comps.Length == 2)
  150. {
  151. var urls = comps.First().Split(',');
  152. var settings = comps.Last().Split(',');
  153. if (urls.Any() && (settings.Length == 3))
  154. {
  155. String user = settings[0];
  156. String password = settings[1];
  157. DateTime expiry = DateTime.Parse(settings[2]);
  158. if (expiry > DateTime.Now)
  159. {
  160. _settings.URLs = urls;
  161. _settings.UserID = user;
  162. _settings.Password = password;
  163. new LocalConfiguration<DatabaseSettings>().Save(_settings);
  164. DisplayAlert("Autoconfiguration", "Configuration Updated Successfully!!", "OK").Wait();
  165. }
  166. DisplayAlert("AutoConfiguration", "AutoConfiguration Link has expired!", "OK").Wait();
  167. }
  168. DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration content!", "OK").Wait();
  169. }
  170. DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration structure!", "OK").Wait();
  171. }
  172. DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration format!", "OK").Wait();
  173. }
  174. private void LoadSettings()
  175. {
  176. _settings = new LocalConfiguration<DatabaseSettings>().Load();
  177. _settings.URLs = new String[]
  178. {
  179. "remote.com-al.com.au:8050",
  180. };
  181. new LocalConfiguration<DatabaseSettings>().Save(_settings);
  182. if (!_settings.URLs.Any())
  183. {
  184. // Do we have old-style settings we can migrate from?
  185. var Settings = new LocalConfiguration<ConnectionSettings>().Load();
  186. if (!String.IsNullOrWhiteSpace(Settings.URL))
  187. {
  188. _settings.URLs = new string[] { Settings.URL };
  189. _settings.UserID = Settings.UserID;
  190. _settings.Password = Settings.Password;
  191. // Get rid of the old ConnectionSettings storage
  192. new LocalConfiguration<ConnectionSettings>().Delete();
  193. }
  194. }
  195. if (!_settings.URLs.Any())
  196. _settings.URLs = new String[]
  197. {
  198. "remote.com-al.com.au:8050",
  199. };
  200. if (string.IsNullOrWhiteSpace(_settings.UserID) || string.IsNullOrWhiteSpace(_settings.Password))
  201. {
  202. Navigation.PushAsync(new SettingsPage());
  203. }
  204. // Still nothing? Populate with Defaults
  205. if (!_settings.URLs.Any())
  206. {
  207. _settings.URLs = new String[]
  208. {
  209. "remote.com-al.com.au:8050",
  210. //"192.168.0.247:8010"
  211. //"demo.prsdigital.com.au:8003",
  212. //"demo2.prsdigital.com.au:8003",
  213. //"demo3.prsdigital.com.au:8003"
  214. };
  215. //_settings.UserID = "frank";
  216. //_settings.Password = "frankvb22";
  217. _settings.UserID = "GUEST";
  218. _settings.Password = "guest";
  219. //_settings.UserID = "ADMIN";
  220. //_settings.Password = "admin";
  221. new LocalConfiguration<DatabaseSettings>().Save(_settings);
  222. }
  223. //TODO - re enable this when this app is not just the site module
  224. //App.IsSharedDevice = String.IsNullOrWhiteSpace(_settings.UserID);
  225. }
  226. public ValidationStatus AutoLogin(string pin = "")
  227. {
  228. ValidationStatus result = ValidationStatus.INVALID;
  229. Guid sessionID = App.Current.Properties.ContainsKey("SessionID")
  230. ? Guid.Parse(App.Current.Properties["SessionID"].ToString())
  231. : Guid.Empty;
  232. if (!Guid.Equals(sessionID, Guid.Empty))
  233. result = ClientFactory.Validate(sessionID);
  234. if (result == ValidationStatus.INVALID)
  235. {
  236. if (!String.IsNullOrWhiteSpace(_settings.UserID) && !String.IsNullOrWhiteSpace(_settings.Password))
  237. result = ClientFactory.Validate(_settings.UserID, _settings.Password);
  238. }
  239. return result;
  240. }
  241. private async Task<bool> CheckPasswordExpiration()
  242. {
  243. if (!ClientFactory.PasswordExpiration.IsEmpty())
  244. {
  245. var timeUntilExpiration = ClientFactory.PasswordExpiration - DateTime.Now;
  246. if (timeUntilExpiration.Days < 14)
  247. {
  248. string chosenOption = await DisplayActionSheet("Alert",
  249. $"Password will expire in {timeUntilExpiration.Days} days. Change password now?", null, "Yes", "No");
  250. return String.Equals(chosenOption, "Yes");
  251. }
  252. }
  253. return false;
  254. }
  255. private async void LaunchMainPage()
  256. {
  257. Device.BeginInvokeOnMainThread(() => SelectSubPage(String.IsNullOrWhiteSpace(_settings.UserID) ? SubPage.Pin : SubPage.Autologin));
  258. using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Starting Up"))
  259. App.Data.Setup();
  260. Navigation.PushAsync(new SiteModule());
  261. }
  262. private void CheckForLatestVersion()
  263. {
  264. Task.Run(async () =>
  265. {
  266. try
  267. {
  268. bool isLatest = true;
  269. try
  270. {
  271. isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion();
  272. }
  273. catch (Exception eLatest)
  274. {
  275. string s = eLatest.Message;
  276. }
  277. if (!isLatest)
  278. {
  279. string latestVersionNumber = await MobileUtils.AppVersion.GetLatestVersionNumber();
  280. Device.BeginInvokeOnMainThread(async () =>
  281. {
  282. string chosenOption = await DisplayActionSheet(String.Format("Version {0} Available. Update now?", latestVersionNumber), "You will be reminded again in 10 minutes.", null, "Yes", "No");
  283. switch (chosenOption)
  284. {
  285. case "No":
  286. break;
  287. case "Cancel":
  288. break;
  289. case "Yes":
  290. Dispatcher.BeginInvokeOnMainThread(() => { MobileUtils.AppVersion.OpenAppInStore(); });
  291. break;
  292. default:
  293. break;
  294. }
  295. });
  296. }
  297. }
  298. catch { }
  299. });
  300. }
  301. private void SelectSubPage(SubPage page)
  302. {
  303. _page = page;
  304. SplashLayout.IsVisible = _page == SubPage.Autologin;
  305. PINLayout.IsVisible = _page == SubPage.Pin;
  306. TwoFALayout.IsVisible = _page == SubPage.TwoFactor;
  307. PasswordLayout.IsEnabled = _page == SubPage.Password;
  308. MasterGrid.ColumnDefinitions[0].Width = _page == SubPage.Autologin
  309. ? new GridLength(1, GridUnitType.Star)
  310. : new GridLength(0, GridUnitType.Absolute);
  311. MasterGrid.ColumnDefinitions[1].Width = _page == SubPage.Pin
  312. ? new GridLength(1, GridUnitType.Star)
  313. : new GridLength(0, GridUnitType.Absolute);
  314. MasterGrid.ColumnDefinitions[2].Width = _page == SubPage.TwoFactor
  315. ? new GridLength(1, GridUnitType.Star)
  316. : new GridLength(0, GridUnitType.Absolute);
  317. UpdatePIN('\0');
  318. UpdateTwoFA('\0');
  319. NewPassword.Text = "";
  320. NewPassword2.Text = "";
  321. ChangePasswordButton.IsEnabled = false;
  322. }
  323. #region PIN Page
  324. private void UpdatePIN(char digit)
  325. {
  326. _pin = digit switch
  327. {
  328. '\0' => "",
  329. '\b' => _pin.Length > 0 ? _pin.Substring(0, _pin.Length - 1) : "",
  330. _ => String.Concat(_pin, digit)
  331. };
  332. Pin0.Text = _pin.Length > 0 ? "*" : "";
  333. Pin1.Text = _pin.Length > 1 ? "*" : "";
  334. Pin2.Text = _pin.Length > 2 ? "*" : "";
  335. Pin3.Text = _pin.Length > 3 ? "*" : "";
  336. PinBack.IsEnabled = !String.IsNullOrEmpty(_pin);
  337. PinOK.IsEnabled = _pin?.Length == 4;
  338. }
  339. void Pin_Button_Click(object sender, EventArgs args)
  340. {
  341. UpdatePIN((sender as Button).Text.ToCharArray().First());
  342. }
  343. void Pin_Back_Click(object sender, EventArgs args)
  344. {
  345. UpdatePIN('\b');
  346. }
  347. async void Pin_OK_Click(object sender, EventArgs args)
  348. {
  349. var status = ClientFactory.Validate(_pin);
  350. if (status == ValidationStatus.INVALID)
  351. {
  352. DisplayAlert("PIN Error", "Invalid PIN Entered!", "OK").Wait();
  353. UpdatePIN('\0');
  354. }
  355. else if (status == ValidationStatus.REQUIRE_2FA)
  356. SelectSubPage(SubPage.TwoFactor);
  357. else
  358. LaunchMainPage();
  359. }
  360. #endregion
  361. #region Two Factor Page
  362. private void UpdateTwoFA(char digit)
  363. {
  364. _2fa = digit switch
  365. {
  366. '\0' => "",
  367. '\b' => _2fa.Length > 0 ? _2fa.Substring(0, _2fa.Length - 1) : "",
  368. _ => String.Concat(_2fa, digit)
  369. };
  370. TwoFA0.Text = _2fa.Length > 0 ? _2fa.Substring(0,1) : "";
  371. TwoFA1.Text = _2fa.Length > 1 ? _2fa.Substring(1,1) : "";
  372. TwoFA2.Text = _2fa.Length > 2 ? _2fa.Substring(2,1) : "";
  373. TwoFA3.Text = _2fa.Length > 3 ? _2fa.Substring(3,1) : "";
  374. TwoFA4.Text = _2fa.Length > 4 ? _2fa.Substring(4,1) : "";
  375. TwoFA5.Text = _2fa.Length > 5 ? _2fa.Substring(5,1) : "";
  376. TwoFABack.IsEnabled = !String.IsNullOrEmpty(_2fa);
  377. TwoFAOK.IsEnabled = _2fa?.Length == 6;
  378. }
  379. void TwoFA_Button_Click(object sender, EventArgs args)
  380. {
  381. UpdateTwoFA((sender as Button).Text.ToCharArray().First());
  382. }
  383. void TwoFA_Back_Click(object sender, EventArgs args)
  384. {
  385. UpdateTwoFA('\b');
  386. }
  387. void TwoFA_OK_Click(object sender, EventArgs args)
  388. {
  389. if (ClientFactory.Check2FA(_2fa))
  390. LaunchMainPage();
  391. DisplayAlert("2FA Error", "Invalid 2FA Code", "OK").Wait();
  392. UpdateTwoFA('\0');
  393. }
  394. #endregion
  395. #region Change Password Page
  396. private void NewPassword_OnTextChanged(object sender, TextChangedEventArgs e)
  397. {
  398. ChangePasswordButton.IsEnabled =
  399. !String.IsNullOrWhiteSpace(NewPassword.Text)
  400. && NewPassword.Text.Length >= 5
  401. && !String.IsNullOrWhiteSpace(NewPassword2.Text)
  402. && String.Equals(NewPassword.Text, NewPassword2.Text);
  403. }
  404. private async void ChangePasswordButton_OnClicked(object sender, EventArgs e)
  405. {
  406. using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Saving"))
  407. {
  408. var user = new User() { ID = ClientFactory.UserGuid };
  409. user.Password = NewPassword.Text;
  410. new Client<User>().Save(user, "Password updated by User from Timebench");
  411. _settings.Password = user.Password;
  412. new LocalConfiguration<DatabaseSettings>().Save(_settings);
  413. LaunchMainPage();
  414. }
  415. }
  416. private void CancelPasswordButton_OnClicked(object sender, EventArgs e)
  417. {
  418. LaunchMainPage();
  419. }
  420. #endregion
  421. private void Settings_OnClicked(object sender, EventArgs e)
  422. {
  423. // Always try autologin when returning from Settings Page
  424. SelectSubPage(SubPage.Autologin);
  425. Navigation.PushAsync(new SettingsPage());
  426. }
  427. }
  428. }