PINLoginPage.xaml.cs 19 KB

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