using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.BL; using Tango.BL.DTO; using Tango.BL.Entities; using Tango.Core.Commands; using Tango.Core.Cryptography; using Tango.FSE.Common; using Tango.FSE.Common.Navigation; using Tango.FSE.Common.Notifications; using Tango.MachineService.Gateway; namespace Tango.FSE.UI.ViewModels { public class LoginViewVM : FSEViewModel, INavigationObjectReceiver { #region Navigation /// /// Represents the navigation object. /// public class NavigationObject { /// /// Gets or sets a value indicating whether the navigation is due to user logging out from the application. /// public bool IsLoggingOut { get; set; } } #endregion public enum LoginViews { Login, Logging, ChangePassword, ChangingPassword, ForgotPassword, SendingForgotPasswordEmail } private Rfc2898Cryptographer _legacyCryptographer; #region Properties private LoginViews _selectedView; /// /// Gets or sets the selected navigation view. /// public LoginViews SelectedView { get { return _selectedView; } set { _selectedView = value; RaisePropertyChangedAuto(); } } private String _email; /// /// Gets or sets the user email. /// [EmailAddress(ErrorMessage = "Please enter a valid email address")] public String Email { get { return _email; } set { _email = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(DisplayEnvironment)); InvalidateRelayCommands(); } } public String ProcessedEmail { get { return Email.ToStringOrEmpty().EndsWith("TEST") ? Email.Replace("TEST", "") : Email; } } private String _password; /// /// Gets or sets the user password. /// [Required(ErrorMessage = "Password is required")] public String Password { get { return _password; } set { _password = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private String _currentPassword; /// /// Gets or sets the Current password when changing password. /// public String CurrentPassword { get { return _currentPassword; } set { _currentPassword = value; RaisePropertyChangedAuto(); } } private String _newPassword; /// /// Gets or sets the new password when changing password. /// [MinLength(6, ErrorMessage = "Password length must be between 6 to 16 characters")] [MaxLength(16, ErrorMessage = "Password length must be between 6 to 16 characters")] public String NewPassword { get { return _newPassword; } set { _newPassword = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private String _newPasswordConfirm; /// /// Gets or sets the new password confirmation when changing password. /// public String NewPasswordConfirm { get { return _newPasswordConfirm; } set { _newPasswordConfirm = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private bool _rememberMe; /// /// Gets or sets a value indicating whether to remember the user credentials on next restart. /// public bool RememberMe { get { return _rememberMe; } set { _rememberMe = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private EnvironmentConfiguration _selectedEnvironment; /// /// Gets or sets the selected environment. /// public EnvironmentConfiguration SelectedEnvironment { get { return _selectedEnvironment; } set { _selectedEnvironment = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private String _status; /// /// Gets or sets the current status message. /// public String Status { get { return _status; } set { _status = value; RaisePropertyChangedAuto(); } } /// /// Gets the application version. /// public String ApplicationVersion { get { return ApplicationManager.Version.ToString(3); } } /// /// Gets or sets a value indicating whether to display the environment selection. /// public bool DisplayEnvironment { get { return Email.IsNotNullOrEmpty() && (Email.ToLower().EndsWith("@twine-s.com") || Email.EndsWith("TEST")); } } #endregion #region Commands public RelayCommand LoginCommand { get; set; } public RelayCommand ChangePasswordCommand { get; set; } public RelayCommand ForgotPasswordCommand { get; set; } public RelayCommand SendPasswordResetEmailCommand { get; set; } public RelayCommand CancelForgotPasswordCommand { get; set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public LoginViewVM() { _legacyCryptographer = new Rfc2898Cryptographer(); LoginCommand = new RelayCommand(Login, () => Email.IsNotNullOrEmpty() && Password.IsNotNullOrEmpty() && SelectedEnvironment != null); ChangePasswordCommand = new RelayCommand(ChangePassword, () => CurrentPassword.IsNotNullOrEmpty() && NewPassword.IsNotNullOrEmpty() && NewPassword == NewPasswordConfirm); ForgotPasswordCommand = new RelayCommand(ForgotPassword); SendPasswordResetEmailCommand = new RelayCommand(SendPasswordResetEmail); CancelForgotPasswordCommand = new RelayCommand(CancelForgotPassword); } #endregion #region Override Methods /// /// Called when the application has been started. /// public override void OnApplicationStarted() { } /// /// Called before the navigation system has navigated to this VM view. /// public override void OnBeforeNavigatedTo() { base.OnBeforeNavigatedTo(); SelectedView = LoginViews.Login; } /// /// Called when the navigation system has navigated to this VM view. /// public override void OnNavigatedTo() { base.OnNavigatedTo(); try { SelectedEnvironment = Services.GatewayService.Environments.FirstOrDefault(x => x.ID == Settings.LastEnvironmentID); if (SelectedEnvironment == null) { SelectedEnvironment = Services.GatewayService.Environments.FirstOrDefault(); } Email = Settings.LastLoginEmail; if (Settings.LastLoginPassword.IsNotNullOrEmpty()) { try { Password = CryptographyProvider.Decrypt(Settings.LastLoginPassword); } catch (Exception ex) { LogManager.Log(ex, "Could not decrypt last user password using machine level encryption. Falling back to legacy encryption."); Password = _legacyCryptographer.Decrypt(Settings.LastLoginPassword); } } RememberMe = Settings.RememberMe; if (!Email.IsNotNullOrEmpty()) { this.SetFocus(() => Email); } else if (!Password.IsNotNullOrEmpty()) { this.SetFocus(() => Password); } if (RememberMe) { if (FileAssociationProvider.HasStartArgsPackage) { Login(); } } } catch (Exception ex) { LogManager.Log(ex, "Error occurred when landing on login view."); NotificationProvider.ShowError("Error occurred with the login procedure."); } } /// /// Called when navigation system is going to navigate to this instance with the specified object. /// /// The object. public void OnNavigatedToWithObject(NavigationObject obj) { if (obj.IsLoggingOut) { Password = null; RememberMe = false; SaveCredentials(); } } #endregion #region Private Methods private async void Login() { try { LogManager.Log($"Logging in user '{ProcessedEmail}'..."); if (!Validate()) { return; } IsFree = false; if (!DisplayEnvironment) //Force production environment if not a twine user. { SelectedEnvironment = Services.GatewayService.Environments.SingleOrDefault(x => x.Name == "PROD"); } SelectedView = LoginViews.Logging; var result = await AuthenticationProvider.Login(ProcessedEmail, Password, SelectedEnvironment, (status) => Status = status); if (result.Response.PasswordChangeRequired) { SelectedView = LoginViews.ChangePassword; return; } SaveCredentials(); Status = "Starting application..."; if (Settings.WindowMaximizedOnStartup && ApplicationManager.WindowState != System.Windows.WindowState.Maximized) { LogManager.Log("Application is configured to maximize the main window post login. Maximizing window..."); ApplicationManager.WindowState = System.Windows.WindowState.Maximized; } await Task.Delay(500); await NavigationManager.NavigateTo(NavigationView.Home); } catch (Exception ex) { LogManager.Log(ex, $"Error occurred while trying to login user '{ProcessedEmail}'."); IsFree = true; SelectedView = LoginViews.Login; await NotificationProvider.ShowError(GetErrorMessage(ex)); } finally { IsFree = true; } } private void SaveCredentials() { Settings.LastLoginEmail = Email; Settings.RememberMe = RememberMe; Settings.LastEnvironmentID = SelectedEnvironment.ID; if (RememberMe) { Settings.LastLoginPassword = CryptographyProvider.Encrypt(Password); } else { Settings.LastLoginPassword = null; } Settings.Save(); } private String GetErrorMessage(Exception ex) { var fullError = ex.FlattenMessage(); if (fullError.Contains("Invalid column")) { return $"There is a mismatch of data schema between your version of {BuildProvider.BuildName} and the remote service.\nPlease check the following:\n\u2022 The remote environment supports this application version.\n\u2022 Try to update your version to the latest."; } else { return fullError; } } private async void ChangePassword() { if (!Validate()) return; try { IsFree = false; SelectedView = LoginViews.ChangingPassword; await Task.Delay(1000); await AuthenticationProvider.ChangePassword(ProcessedEmail, CurrentPassword, NewPassword); Password = null; SelectedView = LoginViews.Login; Status = "Password changed..."; await NotificationProvider.ShowSuccess("Your password was updated successfully."); } catch (Exception ex) { LogManager.Log(ex, $"Error updating password for user '{ProcessedEmail}'."); await NotificationProvider.ShowError($"Error updating your password.\n{ex.FlattenMessage()}"); SelectedView = LoginViews.ChangePassword; } finally { IsFree = true; CurrentPassword = null; NewPassword = null; NewPasswordConfirm = null; } } protected override void OnValidating() { base.OnValidating(); if (SelectedView == LoginViews.ChangePassword) { if (NewPassword != NewPasswordConfirm) { InsertError(nameof(NewPasswordConfirm), "Passwords do not match."); } } } #endregion #region Forgot Password private void ForgotPassword() { SelectedView = LoginViews.ForgotPassword; } private async void SendPasswordResetEmail() { try { IsFree = false; SelectedView = LoginViews.SendingForgotPasswordEmail; await Services.AuthenticationService.SendForgotPasswordEmail(ProcessedEmail, SelectedEnvironment); SelectedView = LoginViews.Login; await NotificationProvider.ShowSuccess("The email was delivered successfully!\nPlease check your email account."); } catch (Exception ex) { LogManager.Log(ex, "Error resetting user password."); await NotificationProvider.ShowError($"An error occurred while trying to send the email.\n{ex.FlattenMessage()}"); } finally { IsFree = true; SelectedView = LoginViews.Login; } } private void CancelForgotPassword() { SelectedView = LoginViews.Login; } #endregion } }