PINLoginPage.xaml.cs 19 KB

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