PINLoginPage.xaml.cs 19 KB

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