From cfec5327dd156b0a9a94c6750b5fc14dbdbe1035 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 16 Jan 2020 18:43:50 -0800 Subject: [PATCH] Created login and error handling to get auth token. Refreshing not handled. --- BodyshopUploader/App.config | 22 ++- BodyshopUploader/App.xaml | 10 +- BodyshopUploader/BodyshopUploader.csproj | 155 +++++++++++++++++- .../Properties/Resources.Designer.cs | 36 ++++ BodyshopUploader/Properties/Resources.resx | 12 ++ .../Properties/Settings.Designer.cs | 45 +++-- BodyshopUploader/Properties/Settings.settings | 15 +- BodyshopUploader/Utils/AppMetaData.cs | 14 ++ BodyshopUploader/Utils/Auth.cs | 69 ++++++++ BodyshopUploader/Utils/LoginHelpers.cs | 30 ++++ BodyshopUploader/Utils/UiConverters.cs | 44 +++++ .../ViewModels/LoginViewModel.commands.cs | 16 +- BodyshopUploader/ViewModels/LoginViewModel.cs | 27 ++- .../ViewModels/LoginViewModel.props.cs | 40 ++++- BodyshopUploader/Views/Login.xaml | 49 +++++- BodyshopUploader/Views/Login.xaml.cs | 39 +++++ BodyshopUploader/packages.config | 49 ++++++ 17 files changed, 625 insertions(+), 47 deletions(-) create mode 100644 BodyshopUploader/Utils/AppMetaData.cs create mode 100644 BodyshopUploader/Utils/Auth.cs create mode 100644 BodyshopUploader/Utils/LoginHelpers.cs create mode 100644 BodyshopUploader/Utils/UiConverters.cs diff --git a/BodyshopUploader/App.config b/BodyshopUploader/App.config index 56efbc7..f5e0f5a 100644 --- a/BodyshopUploader/App.config +++ b/BodyshopUploader/App.config @@ -1,6 +1,26 @@ - + + + +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BodyshopUploader/App.xaml b/BodyshopUploader/App.xaml index 66b9cb7..a1b0cb1 100644 --- a/BodyshopUploader/App.xaml +++ b/BodyshopUploader/App.xaml @@ -2,8 +2,16 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:BodyshopUploader" + ShutdownMode="OnExplicitShutdown" StartupUri="Views\Login.xaml"> - + + + + + + + + diff --git a/BodyshopUploader/BodyshopUploader.csproj b/BodyshopUploader/BodyshopUploader.csproj index 7b7d84c..7c0cb35 100644 --- a/BodyshopUploader/BodyshopUploader.csproj +++ b/BodyshopUploader/BodyshopUploader.csproj @@ -29,6 +29,8 @@ false false true + + AnyCPU @@ -53,9 +55,23 @@ favicon.ico + + ..\packages\FirebaseAuthentication.net.3.4.0\lib\netstandard1.1\Firebase.Auth.dll + ..\packages\Hardcodet.NotifyIcon.Wpf.1.0.8\lib\net451\Hardcodet.Wpf.TaskbarNotification.dll + + ..\packages\MaterialDesignColors.1.2.2\lib\net45\MaterialDesignColors.dll + + + ..\packages\MaterialDesignThemes.3.0.1\lib\net45\MaterialDesignThemes.Wpf.dll + + + ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll + True + True + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll @@ -63,21 +79,145 @@ ..\packages\NLog.4.6.8\lib\net45\NLog.dll + + ..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll + True + True + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + True + True + - + + ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll + True + True + + + ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll + True + True + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + + ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll + True + True + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + True + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + True + + + ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + True + True + + + ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + True + True + + + ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll + True + True + + + ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll + True + True + + + + ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + True + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + True + True + + + ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll + True + True + + + ..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll + True + True + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + True + + + ..\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll + True + True + - 4.0 + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + True + True + @@ -97,9 +237,13 @@ True True + + + + @@ -183,4 +327,11 @@ + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/BodyshopUploader/Properties/Resources.Designer.cs b/BodyshopUploader/Properties/Resources.Designer.cs index 38d2755..58301f0 100644 --- a/BodyshopUploader/Properties/Resources.Designer.cs +++ b/BodyshopUploader/Properties/Resources.Designer.cs @@ -60,6 +60,42 @@ namespace BodyshopUploader.Properties { } } + /// + /// Looks up a localized string similar to An issue has occured. Please check the log files.. + /// + public static string Error_Generic { + get { + return ResourceManager.GetString("Error_Generic", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The username and password combination is not valid. Please try again. . + /// + public static string Error_Login { + get { + return ResourceManager.GetString("Error_Login", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Too many login attempts.. + /// + public static string Error_Login_Attempts { + get { + return ResourceManager.GetString("Error_Login_Attempts", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An unknown error occured while logging in. Please check the log files.. + /// + public static string Error_Login_Unknown { + get { + return ResourceManager.GetString("Error_Login_Unknown", resourceCulture); + } + } + /// /// Looks up a localized string similar to Exit. /// diff --git a/BodyshopUploader/Properties/Resources.resx b/BodyshopUploader/Properties/Resources.resx index f5eed73..79bbf2d 100644 --- a/BodyshopUploader/Properties/Resources.resx +++ b/BodyshopUploader/Properties/Resources.resx @@ -117,6 +117,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + An issue has occured. Please check the log files. + + + The username and password combination is not valid. Please try again. + + + Too many login attempts. + + + An unknown error occured while logging in. Please check the log files. + Exit diff --git a/BodyshopUploader/Properties/Settings.Designer.cs b/BodyshopUploader/Properties/Settings.Designer.cs index c02164e..7f43b15 100644 --- a/BodyshopUploader/Properties/Settings.Designer.cs +++ b/BodyshopUploader/Properties/Settings.Designer.cs @@ -8,23 +8,42 @@ // //------------------------------------------------------------------------------ -namespace BodyshopUploader.Properties -{ - - +namespace BodyshopUploader.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string Username { + get { + return ((string)(this["Username"])); + } + set { + this["Username"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public global::System.Security.SecureString Password { + get { + return ((global::System.Security.SecureString)(this["Password"])); + } + set { + this["Password"] = value; + } + } } } diff --git a/BodyshopUploader/Properties/Settings.settings b/BodyshopUploader/Properties/Settings.settings index 033d7a5..e75ac4e 100644 --- a/BodyshopUploader/Properties/Settings.settings +++ b/BodyshopUploader/Properties/Settings.settings @@ -1,7 +1,12 @@  - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/BodyshopUploader/Utils/AppMetaData.cs b/BodyshopUploader/Utils/AppMetaData.cs new file mode 100644 index 0000000..0df018d --- /dev/null +++ b/BodyshopUploader/Utils/AppMetaData.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BodyshopUploader.Utils +{ + public static class AppMetaData + { + public static string FirebaseAPIKey_DEV = "AIzaSyDV9MsSHZmpLtjoaTK_ObvjFaJ-nMSd2KA"; + + } +} diff --git a/BodyshopUploader/Utils/Auth.cs b/BodyshopUploader/Utils/Auth.cs new file mode 100644 index 0000000..22a132c --- /dev/null +++ b/BodyshopUploader/Utils/Auth.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Firebase.Auth; +using System.Timers; + +namespace BodyshopUploader.Utils +{ + public static class Auth + { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + public static FirebaseAuthLink authlink; + public static string authToken; + static string refreshToken; + static int tokenExpiration; + static FirebaseAuthProvider ap = new FirebaseAuthProvider(new FirebaseConfig(Utils.AppMetaData.FirebaseAPIKey_DEV)); //TODO: Update this to be a dynamic key. Perhaps a function that fetches? + private static Timer tokenTimer = new Timer(); + + public async static Task<(bool, string)> LoginAsync(string Username, string Password) + { + try + { + authlink = await ap.SignInWithEmailAndPasswordAsync(Username, Password); + authToken = authlink.FirebaseToken; + refreshToken = authlink.RefreshToken; + tokenExpiration = authlink.ExpiresIn; + logger.Trace("Firebase Auth Token {0}.", authToken); + logger.Trace("Firebase Refresh Token {0}.", refreshToken); + logger.Trace("Firebase Auth Token expires in {0} seconds.", tokenExpiration); + + tokenTimer.Interval = (tokenExpiration - 600) * 1000; //Set the token to refresh 10 minutes before it has to. + tokenTimer.Elapsed += TokenTimer_Elapsed; + return (true, null); + } + catch (FirebaseAuthException Ex) + { + switch (Ex.Reason) + { + case AuthErrorReason.WrongPassword: + logger.Error("Incorrect password provided for user."); + return (false, Properties.Resources.Error_Login); + + case AuthErrorReason.UnknownEmailAddress: + logger.Error("User does not exist."); + return (false, Properties.Resources.Error_Login); + case AuthErrorReason.TooManyAttemptsTryLater: + logger.Error("Too many attempts logging in."); + return (false, Properties.Resources.Error_Login_Attempts); + default: + logger.Error(Ex, "Unknown error occured while logging in. {0}", Ex.Reason); + return (false, Properties.Resources.Error_Login_Unknown); + } + } + catch (Exception Ex) + { + logger.Error(Ex, "Unknown error encountered while obtaining auth token."); + return (false, Properties.Resources.Error_Generic); + } + } + + private static void TokenTimer_Elapsed(object sender, ElapsedEventArgs e) + { + //Gotta do some stuff now that i got a new token! + //Maybe the token auto refreshes? + } + } +} diff --git a/BodyshopUploader/Utils/LoginHelpers.cs b/BodyshopUploader/Utils/LoginHelpers.cs new file mode 100644 index 0000000..4489986 --- /dev/null +++ b/BodyshopUploader/Utils/LoginHelpers.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; + +namespace BodyshopUploader.Utils +{ + public static class LoginHelpers + { + public static void SaveLoginSettings(string Username, SecureString Password) + { + Properties.Settings.Default.Username = Username; + //Todo: Figure out how to save the secure string. Perhaps serialize to JSON? + Properties.Settings.Default.Password = Password; + Properties.Settings.Default.Save(); + } + + public static string DecodePassword (SecureString s) + { + //Manage the secure string for the password. + IntPtr stringPointer = Marshal.SecureStringToBSTR(s); + string UP = Marshal.PtrToStringBSTR(stringPointer); + Marshal.ZeroFreeBSTR(stringPointer); + return UP; + } + } +} diff --git a/BodyshopUploader/Utils/UiConverters.cs b/BodyshopUploader/Utils/UiConverters.cs new file mode 100644 index 0000000..b49dc57 --- /dev/null +++ b/BodyshopUploader/Utils/UiConverters.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace BodyshopUploader.Utils +{ + public class BoolVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if ((bool)value) + return Visibility.Visible; + else + return Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Two-way binding not supported by BoolVisibilityConverter"); + } + } + + public class NullVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return Visibility.Collapsed; + else + return Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Two-way binding not supported by ZeroVisibilityConverter"); + } + } + +} diff --git a/BodyshopUploader/ViewModels/LoginViewModel.commands.cs b/BodyshopUploader/ViewModels/LoginViewModel.commands.cs index 8f4e67f..ec0f6b0 100644 --- a/BodyshopUploader/ViewModels/LoginViewModel.commands.cs +++ b/BodyshopUploader/ViewModels/LoginViewModel.commands.cs @@ -1,24 +1,24 @@ using System; +using System.Windows; using System.Windows.Input; namespace BodyshopUploader.ViewModels { public partial class LoginViewModel : BaseViewModel { - private ICommand _openMainCommand; - public ICommand OpenMainCommand + private ICommand _loginCommand; + public ICommand LoginCommand { get { - if (_openMainCommand == null) + if (_loginCommand == null) { - _openMainCommand = new RelayCommand( - p => true, - p => { Views.Main m = new Views.Main(); m.Show(); }); + _loginCommand = new RelayCommand( + p => !string.IsNullOrEmpty(UserName) && UserPassword?.Length > 6, + async p => { await LoginAsync(p as Window); }); } - return _openMainCommand; + return _loginCommand; } } - } } diff --git a/BodyshopUploader/ViewModels/LoginViewModel.cs b/BodyshopUploader/ViewModels/LoginViewModel.cs index 889a083..7bfc1a1 100644 --- a/BodyshopUploader/ViewModels/LoginViewModel.cs +++ b/BodyshopUploader/ViewModels/LoginViewModel.cs @@ -1,18 +1,37 @@ -using System; + +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace BodyshopUploader.ViewModels -{ +{ public partial class LoginViewModel : BaseViewModel - { + { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); public LoginViewModel() { - logger.Trace("We're in boys."); + logger.Trace("Login VM Created."); + } + + private async Task LoginAsync(Window W) + { + Loading = true; + + Utils.LoginHelpers.SaveLoginSettings(UserName, UserPassword); + + logger.Trace("Attempting to login as user: {0}", UserName); + (Error, ErrorMsg) = await Utils.Auth.LoginAsync(UserName, Utils.LoginHelpers.DecodePassword(UserPassword)); + if(ErrorMsg ==null) + { + Views.Main m = new Views.Main(); + m.Show(); + W.Hide(); + } + Loading = false; } } } diff --git a/BodyshopUploader/ViewModels/LoginViewModel.props.cs b/BodyshopUploader/ViewModels/LoginViewModel.props.cs index be93b78..27301e5 100644 --- a/BodyshopUploader/ViewModels/LoginViewModel.props.cs +++ b/BodyshopUploader/ViewModels/LoginViewModel.props.cs @@ -2,25 +2,47 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Security; using System.Text; using System.Threading.Tasks; namespace BodyshopUploader.ViewModels { - public partial class LoginViewModel: BaseViewModel + public partial class LoginViewModel : BaseViewModel { - private ObservableCollection _invoiceList; - public ObservableCollection InvoiceList + private string _userName = Properties.Settings.Default.Username; + public string UserName { - get { return _invoiceList; } - set { SetProperty(ref _invoiceList, value); } + get { return _userName; } + set { SetProperty(ref _userName, value); } } - private int _progress = 5; - public int Progress + private SecureString _userPassword = Properties.Settings.Default.Password; + public SecureString UserPassword { - get { return _progress; } - set { SetProperty(ref _progress, value); } + get { return _userPassword; } + set { SetProperty(ref _userPassword, value); } + } + + private bool _error = false; + public bool Error + { + get { return _error; } + set { SetProperty(ref _error, value); } + } + + private string _errorMsg; + public string ErrorMsg + { + get { return _errorMsg; } + set { SetProperty(ref _errorMsg, value); } + } + + private bool _loading = false; + public bool Loading + { + get { return _loading; } + set { SetProperty(ref _loading, value); } } } } diff --git a/BodyshopUploader/Views/Login.xaml b/BodyshopUploader/Views/Login.xaml index 53187f4..f90f512 100644 --- a/BodyshopUploader/Views/Login.xaml +++ b/BodyshopUploader/Views/Login.xaml @@ -9,15 +9,56 @@ Title="{x:Static p:Resources.Title_Login}" Height="450" Width="800" - xmlns:p="clr-namespace:BodyshopUploader.Properties"> + xmlns:p="clr-namespace:BodyshopUploader.Properties" + xmlns:util="clr-namespace:BodyshopUploader.Utils" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + TextElement.Foreground="{DynamicResource MaterialDesignBody}" + TextElement.FontWeight="Regular" + TextElement.FontSize="13" + TextOptions.TextFormattingMode="Ideal" + TextOptions.TextRenderingMode="Auto" + Background="{DynamicResource MaterialDesignPaper}" + FontFamily="{DynamicResource MaterialDesignFont}" + Closing="Window_Closing"> + + + -