PINLoginPage.xaml.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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. Guid jobid = new LocalConfiguration<SiteModuleSettings>().Load().JobID;
  255. Navigation.PushAsync(new SiteModule() { JobID = jobid });
  256. }
  257. private void CheckForLatestVersion()
  258. {
  259. Task.Run(async () =>
  260. {
  261. try
  262. {
  263. bool isLatest = true;
  264. try
  265. {
  266. isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion();
  267. }
  268. catch (Exception eLatest)
  269. {
  270. string s = eLatest.Message;
  271. }
  272. if (!isLatest)
  273. {
  274. string latestVersionNumber = await MobileUtils.AppVersion.GetLatestVersionNumber();
  275. Device.BeginInvokeOnMainThread(async () =>
  276. {
  277. string chosenOption = await DisplayActionSheet(String.Format("Version {0} Available. Update now?", latestVersionNumber), "You will be reminded again in 10 minutes.", null, "Yes", "No");
  278. switch (chosenOption)
  279. {
  280. case "No":
  281. break;
  282. case "Cancel":
  283. break;
  284. case "Yes":
  285. Dispatcher.BeginInvokeOnMainThread(() => { MobileUtils.AppVersion.OpenAppInStore(); });
  286. break;
  287. default:
  288. break;
  289. }
  290. });
  291. }
  292. }
  293. catch { }
  294. });
  295. }
  296. private void SelectSubPage(SubPage page)
  297. {
  298. _page = page;
  299. SplashLayout.IsVisible = _page == SubPage.Autologin;
  300. PINLayout.IsVisible = _page == SubPage.Pin;
  301. TwoFALayout.IsVisible = _page == SubPage.TwoFactor;
  302. PasswordLayout.IsEnabled = _page == SubPage.Password;
  303. MasterGrid.ColumnDefinitions[0].Width = _page == SubPage.Autologin
  304. ? new GridLength(1, GridUnitType.Star)
  305. : new GridLength(0, GridUnitType.Absolute);
  306. MasterGrid.ColumnDefinitions[1].Width = _page == SubPage.Pin
  307. ? new GridLength(1, GridUnitType.Star)
  308. : new GridLength(0, GridUnitType.Absolute);
  309. MasterGrid.ColumnDefinitions[2].Width = _page == SubPage.TwoFactor
  310. ? new GridLength(1, GridUnitType.Star)
  311. : new GridLength(0, GridUnitType.Absolute);
  312. UpdatePIN('\0');
  313. UpdateTwoFA('\0');
  314. NewPassword.Text = "";
  315. NewPassword2.Text = "";
  316. ChangePasswordButton.IsEnabled = false;
  317. }
  318. #region PIN Page
  319. private void UpdatePIN(char digit)
  320. {
  321. _pin = digit switch
  322. {
  323. '\0' => "",
  324. '\b' => _pin.Length > 0 ? _pin.Substring(0, _pin.Length - 1) : "",
  325. _ => String.Concat(_pin, digit)
  326. };
  327. Pin0.Text = _pin.Length > 0 ? "*" : "";
  328. Pin1.Text = _pin.Length > 1 ? "*" : "";
  329. Pin2.Text = _pin.Length > 2 ? "*" : "";
  330. Pin3.Text = _pin.Length > 3 ? "*" : "";
  331. PinBack.IsEnabled = !String.IsNullOrEmpty(_pin);
  332. PinOK.IsEnabled = _pin?.Length == 4;
  333. }
  334. void Pin_Button_Click(object sender, EventArgs args)
  335. {
  336. UpdatePIN((sender as Button).Text.ToCharArray().First());
  337. }
  338. void Pin_Back_Click(object sender, EventArgs args)
  339. {
  340. UpdatePIN('\b');
  341. }
  342. async void Pin_OK_Click(object sender, EventArgs args)
  343. {
  344. var status = ClientFactory.Validate(_pin);
  345. if (status == ValidationStatus.INVALID)
  346. {
  347. DisplayAlert("PIN Error", "Invalid PIN Entered!", "OK").Wait();
  348. UpdatePIN('\0');
  349. }
  350. else if (status == ValidationStatus.REQUIRE_2FA)
  351. SelectSubPage(SubPage.TwoFactor);
  352. else
  353. LaunchMainPage();
  354. }
  355. #endregion
  356. #region Two Factor Page
  357. private void UpdateTwoFA(char digit)
  358. {
  359. _2fa = digit switch
  360. {
  361. '\0' => "",
  362. '\b' => _2fa.Length > 0 ? _2fa.Substring(0, _2fa.Length - 1) : "",
  363. _ => String.Concat(_2fa, digit)
  364. };
  365. TwoFA0.Text = _2fa.Length > 0 ? _2fa.Substring(0,1) : "";
  366. TwoFA1.Text = _2fa.Length > 1 ? _2fa.Substring(1,1) : "";
  367. TwoFA2.Text = _2fa.Length > 2 ? _2fa.Substring(2,1) : "";
  368. TwoFA3.Text = _2fa.Length > 3 ? _2fa.Substring(3,1) : "";
  369. TwoFA4.Text = _2fa.Length > 4 ? _2fa.Substring(4,1) : "";
  370. TwoFA5.Text = _2fa.Length > 5 ? _2fa.Substring(5,1) : "";
  371. TwoFABack.IsEnabled = !String.IsNullOrEmpty(_2fa);
  372. TwoFAOK.IsEnabled = _2fa?.Length == 6;
  373. }
  374. void TwoFA_Button_Click(object sender, EventArgs args)
  375. {
  376. UpdateTwoFA((sender as Button).Text.ToCharArray().First());
  377. }
  378. void TwoFA_Back_Click(object sender, EventArgs args)
  379. {
  380. UpdateTwoFA('\b');
  381. }
  382. void TwoFA_OK_Click(object sender, EventArgs args)
  383. {
  384. if (ClientFactory.Check2FA(_2fa))
  385. LaunchMainPage();
  386. DisplayAlert("2FA Error", "Invalid 2FA Code", "OK").Wait();
  387. UpdateTwoFA('\0');
  388. }
  389. #endregion
  390. #region Change Password Page
  391. private void NewPassword_OnTextChanged(object sender, TextChangedEventArgs e)
  392. {
  393. ChangePasswordButton.IsEnabled =
  394. !String.IsNullOrWhiteSpace(NewPassword.Text)
  395. && NewPassword.Text.Length >= 5
  396. && !String.IsNullOrWhiteSpace(NewPassword2.Text)
  397. && String.Equals(NewPassword.Text, NewPassword2.Text);
  398. }
  399. private async void ChangePasswordButton_OnClicked(object sender, EventArgs e)
  400. {
  401. using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Saving"))
  402. {
  403. var user = new User() { ID = ClientFactory.UserGuid };
  404. user.Password = NewPassword.Text;
  405. new Client<User>().Save(user, "Password updated by User from Timebench");
  406. _settings.Password = user.Password;
  407. new LocalConfiguration<DatabaseSettings>().Save(_settings);
  408. LaunchMainPage();
  409. }
  410. }
  411. private void CancelPasswordButton_OnClicked(object sender, EventArgs e)
  412. {
  413. LaunchMainPage();
  414. }
  415. #endregion
  416. private void Settings_OnClicked(object sender, EventArgs e)
  417. {
  418. // Always try autologin when returning from Settings Page
  419. SelectSubPage(SubPage.Autologin);
  420. Navigation.PushAsync(new SettingsPage());
  421. }
  422. }
  423. }