using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.Mobile; using InABox.Rpc; using Xamarin.Essentials; using Xamarin.Forms; using XF.Material.Forms.UI; using XF.Material.Forms.UI.Dialogs; namespace comal.timesheets { public partial class PinLoginPage : ContentPage { private enum SubPage { Autologin, Pin, TwoFactor, Password } private SubPage _page; private String _pin = ""; private String _2fa = ""; private DatabaseSettings _settings { get; set; } public PinLoginPage() { InitializeComponent(); SelectSubPage(SubPage.Autologin); } protected override async void OnAppearing() { MobileLogging.Log("PINLoginPage.Appearing()"); base.OnAppearing(); // IS there a new version? If so, then get that before doing anything else MobileLogging.Log("PINLoginPage.CheckForLatestVersion()"); CheckForLatestVersion(); //TODO this is only for the temporary site app login - needs to be removed SelectSubPage(SubPage.Autologin); // Is this the first time through? // If so, then try to validate if (_page == SubPage.Autologin) { MobileLogging.Log("PINLoginPage.CheckAutoConfigurationLink()"); CheckAutoConfigurationLink(); MobileLogging.Log("PINLoginPage.LoadSettings()"); LoadSettings(); MobileLogging.Log("PINLoginPage.Connecting()"); TransportStatus connection = TransportStatus.None; using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Connecting")) { await Task.Run(() => { connection = App.ConnectTransport(_settings.URLs); }); } if (connection != TransportStatus.OK) { await DisplayAlert("Connection Error", $"Unable to establish a connection!\n\nERR: {connection}", "OK"); Navigation.PushAsync(new SettingsPage()); return; } MobileLogging.Log("PINLoginPage.AutoLogin()"); ValidationStatus status = ValidationStatus.INVALID; using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Validating")) { Task.Run(() => { status = AutoLogin(); }).Wait(); } if (status == ValidationStatus.VALID) { MobileLogging.Log("PINLoginPage.CheckPasswordExpiration()"); if (await CheckPasswordExpiration()) { PasswordCancelColumn.Width = new GridLength(1, GridUnitType.Star); SelectSubPage(SubPage.Password); } else { MobileLogging.Log("PINLoginPage.LaunchMainPage()"); LaunchMainPage(); } } else if (status == ValidationStatus.INVALID) { // If we _should_ have been able to log in (ie we have a username) // show an error message. Otherwise, jump to the PIN page if (!String.IsNullOrWhiteSpace(_settings.UserID)) await DisplayAlert("Error logging in", "Invalid User ID, Password or PIN", "OK"); MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.Pin)"); SelectSubPage(SubPage.Pin); } else if (status == ValidationStatus.REQUIRE_2FA) { MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.TwoFactor)"); SelectSubPage(SubPage.TwoFactor); } else if (status == ValidationStatus.PASSWORD_EXPIRED) { MobileLogging.Log("PINLoginPage.SelectSubPage(SubPage.Password)"); await DisplayAlert("Alert", "Your password has expired - please change it", "OK"); PasswordCancelColumn.Width = new GridLength(0, GridUnitType.Absolute); SelectSubPage(SubPage.Password); } } else if (_page == SubPage.Pin) { UpdatePIN('\0'); } else if (_page == SubPage.TwoFactor) { // Has the user left the app to go and get the 2fa code from sms or google authenticator? // If so, then try to retrieve the 2fa code from the clipboard var text = Clipboard.GetTextAsync().Result; if ((text?.Length == 6) && (text?.All(Char.IsDigit) == true)) { _2fa = text; TwoFA0.Text = text.Substring(0, 1); TwoFA1.Text = text.Substring(1, 1); TwoFA2.Text = text.Substring(2, 1); TwoFA3.Text = text.Substring(3, 1); TwoFA4.Text = text.Substring(4, 1); TwoFA5.Text = text.Substring(5, 1); } else UpdateTwoFA('\0'); } else if (_page == SubPage.Password) { NewPassword.Text = ""; NewPassword2.Text = ""; ChangePasswordButton.IsEnabled = false; } } private void CheckAutoConfigurationLink() { var parameters = App.LaunchParameters; App.LaunchParameters = ""; if (String.IsNullOrWhiteSpace(parameters)) return; var decrypted = Encryption.Decrypt(parameters, "logindetailslink"); if (decrypted?.ToUpper()?.Contains("ENDURLS") == true) { var comps = decrypted.Split("ENDURLS"); if (comps.Length == 2) { var urls = comps.First().Split(','); var settings = comps.Last().Split(','); if (urls.Any() && (settings.Length == 3)) { String user = settings[0]; String password = settings[1]; DateTime expiry = DateTime.Parse(settings[2]); if (expiry > DateTime.Now) { _settings.URLs = urls; _settings.UserID = user; _settings.Password = password; new LocalConfiguration().Save(_settings); DisplayAlert("Autoconfiguration", "Configuration Updated Successfully!!", "OK").Wait(); } DisplayAlert("AutoConfiguration", "AutoConfiguration Link has expired!", "OK").Wait(); } DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration content!", "OK").Wait(); } DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration structure!", "OK").Wait(); } DisplayAlert("AutoConfiguration", "Invalid AutoConfiguration format!", "OK").Wait(); } private void LoadSettings() { _settings = new LocalConfiguration().Load(); _settings.URLs = new String[] { "remote.com-al.com.au:8050", }; new LocalConfiguration().Save(_settings); if (!_settings.URLs.Any()) { // Do we have old-style settings we can migrate from? var Settings = new LocalConfiguration().Load(); if (!String.IsNullOrWhiteSpace(Settings.URL)) { _settings.URLs = new string[] { Settings.URL }; _settings.UserID = Settings.UserID; _settings.Password = Settings.Password; // Get rid of the old ConnectionSettings storage new LocalConfiguration().Delete(); } } if (!_settings.URLs.Any()) _settings.URLs = new String[] { "remote.com-al.com.au:8050", }; if (string.IsNullOrWhiteSpace(_settings.UserID) || string.IsNullOrWhiteSpace(_settings.Password)) { Navigation.PushAsync(new SettingsPage()); } // Still nothing? Populate with Defaults if (!_settings.URLs.Any()) { _settings.URLs = new String[] { "remote.com-al.com.au:8050", //"192.168.0.247:8010" //"demo.prsdigital.com.au:8003", //"demo2.prsdigital.com.au:8003", //"demo3.prsdigital.com.au:8003" }; //_settings.UserID = "frank"; //_settings.Password = "frankvb22"; _settings.UserID = "GUEST"; _settings.Password = "guest"; //_settings.UserID = "ADMIN"; //_settings.Password = "admin"; new LocalConfiguration().Save(_settings); } //TODO - re enable this when this app is not just the site module //App.IsSharedDevice = String.IsNullOrWhiteSpace(_settings.UserID); } public ValidationStatus AutoLogin(string pin = "") { ValidationStatus result = ValidationStatus.INVALID; Guid sessionID = App.Current.Properties.ContainsKey("SessionID") ? Guid.Parse(App.Current.Properties["SessionID"].ToString()) : Guid.Empty; if (!Guid.Equals(sessionID, Guid.Empty)) result = ClientFactory.Validate(sessionID); if (result == ValidationStatus.INVALID) { if (!String.IsNullOrWhiteSpace(_settings.UserID) && !String.IsNullOrWhiteSpace(_settings.Password)) result = ClientFactory.Validate(_settings.UserID, _settings.Password); } return result; } private async Task CheckPasswordExpiration() { if (!ClientFactory.PasswordExpiration.IsEmpty()) { var timeUntilExpiration = ClientFactory.PasswordExpiration - DateTime.Now; if (timeUntilExpiration.Days < 14) { string chosenOption = await DisplayActionSheet("Alert", $"Password will expire in {timeUntilExpiration.Days} days. Change password now?", null, "Yes", "No"); return String.Equals(chosenOption, "Yes"); } } return false; } private async void LaunchMainPage() { Device.BeginInvokeOnMainThread(() => SelectSubPage(String.IsNullOrWhiteSpace(_settings.UserID) ? SubPage.Pin : SubPage.Autologin)); using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Starting Up")) App.Data.Setup(); Navigation.PushAsync(new SiteModule()); } private void CheckForLatestVersion() { Task.Run(async () => { try { bool isLatest = true; try { isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion(); } catch (Exception eLatest) { string s = eLatest.Message; } if (!isLatest) { string latestVersionNumber = await MobileUtils.AppVersion.GetLatestVersionNumber(); Device.BeginInvokeOnMainThread(async () => { string chosenOption = await DisplayActionSheet(String.Format("Version {0} Available. Update now?", latestVersionNumber), "You will be reminded again in 10 minutes.", null, "Yes", "No"); switch (chosenOption) { case "No": break; case "Cancel": break; case "Yes": Dispatcher.BeginInvokeOnMainThread(() => { MobileUtils.AppVersion.OpenAppInStore(); }); break; default: break; } }); } } catch { } }); } private void SelectSubPage(SubPage page) { _page = page; SplashLayout.IsVisible = _page == SubPage.Autologin; PINLayout.IsVisible = _page == SubPage.Pin; TwoFALayout.IsVisible = _page == SubPage.TwoFactor; PasswordLayout.IsEnabled = _page == SubPage.Password; MasterGrid.ColumnDefinitions[0].Width = _page == SubPage.Autologin ? new GridLength(1, GridUnitType.Star) : new GridLength(0, GridUnitType.Absolute); MasterGrid.ColumnDefinitions[1].Width = _page == SubPage.Pin ? new GridLength(1, GridUnitType.Star) : new GridLength(0, GridUnitType.Absolute); MasterGrid.ColumnDefinitions[2].Width = _page == SubPage.TwoFactor ? new GridLength(1, GridUnitType.Star) : new GridLength(0, GridUnitType.Absolute); UpdatePIN('\0'); UpdateTwoFA('\0'); NewPassword.Text = ""; NewPassword2.Text = ""; ChangePasswordButton.IsEnabled = false; } #region PIN Page private void UpdatePIN(char digit) { _pin = digit switch { '\0' => "", '\b' => _pin.Length > 0 ? _pin.Substring(0, _pin.Length - 1) : "", _ => String.Concat(_pin, digit) }; Pin0.Text = _pin.Length > 0 ? "*" : ""; Pin1.Text = _pin.Length > 1 ? "*" : ""; Pin2.Text = _pin.Length > 2 ? "*" : ""; Pin3.Text = _pin.Length > 3 ? "*" : ""; PinBack.IsEnabled = !String.IsNullOrEmpty(_pin); PinOK.IsEnabled = _pin?.Length == 4; } void Pin_Button_Click(object sender, EventArgs args) { UpdatePIN((sender as Button).Text.ToCharArray().First()); } void Pin_Back_Click(object sender, EventArgs args) { UpdatePIN('\b'); } async void Pin_OK_Click(object sender, EventArgs args) { var status = ClientFactory.Validate(_pin); if (status == ValidationStatus.INVALID) { DisplayAlert("PIN Error", "Invalid PIN Entered!", "OK").Wait(); UpdatePIN('\0'); } else if (status == ValidationStatus.REQUIRE_2FA) SelectSubPage(SubPage.TwoFactor); else LaunchMainPage(); } #endregion #region Two Factor Page private void UpdateTwoFA(char digit) { _2fa = digit switch { '\0' => "", '\b' => _2fa.Length > 0 ? _2fa.Substring(0, _2fa.Length - 1) : "", _ => String.Concat(_2fa, digit) }; TwoFA0.Text = _2fa.Length > 0 ? _2fa.Substring(0,1) : ""; TwoFA1.Text = _2fa.Length > 1 ? _2fa.Substring(1,1) : ""; TwoFA2.Text = _2fa.Length > 2 ? _2fa.Substring(2,1) : ""; TwoFA3.Text = _2fa.Length > 3 ? _2fa.Substring(3,1) : ""; TwoFA4.Text = _2fa.Length > 4 ? _2fa.Substring(4,1) : ""; TwoFA5.Text = _2fa.Length > 5 ? _2fa.Substring(5,1) : ""; TwoFABack.IsEnabled = !String.IsNullOrEmpty(_2fa); TwoFAOK.IsEnabled = _2fa?.Length == 6; } void TwoFA_Button_Click(object sender, EventArgs args) { UpdateTwoFA((sender as Button).Text.ToCharArray().First()); } void TwoFA_Back_Click(object sender, EventArgs args) { UpdateTwoFA('\b'); } void TwoFA_OK_Click(object sender, EventArgs args) { if (ClientFactory.Check2FA(_2fa)) LaunchMainPage(); DisplayAlert("2FA Error", "Invalid 2FA Code", "OK").Wait(); UpdateTwoFA('\0'); } #endregion #region Change Password Page private void NewPassword_OnTextChanged(object sender, TextChangedEventArgs e) { ChangePasswordButton.IsEnabled = !String.IsNullOrWhiteSpace(NewPassword.Text) && NewPassword.Text.Length >= 5 && !String.IsNullOrWhiteSpace(NewPassword2.Text) && String.Equals(NewPassword.Text, NewPassword2.Text); } private async void ChangePasswordButton_OnClicked(object sender, EventArgs e) { using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Saving")) { var user = new User() { ID = ClientFactory.UserGuid }; user.Password = NewPassword.Text; new Client().Save(user, "Password updated by User from Timebench"); _settings.Password = user.Password; new LocalConfiguration().Save(_settings); LaunchMainPage(); } } private void CancelPasswordButton_OnClicked(object sender, EventArgs e) { LaunchMainPage(); } #endregion private void Settings_OnClicked(object sender, EventArgs e) { // Always try autologin when returning from Settings Page SelectSubPage(SubPage.Autologin); Navigation.PushAsync(new SettingsPage()); } } }