From 9ef8f389302fc4357ffb7526138466cf22b01a62 Mon Sep 17 00:00:00 2001 From: silversword411 Date: Mon, 26 Jan 2026 10:59:28 -0500 Subject: [PATCH] Adding remember password option to login dialog (#121) * remove CodeAnalysisRuleSet from Debug and Release configurations * add VSCode task for building MeshCentralRouter * refactor: use auto-properties for displayCrop and displayOrigin in KVMControl, suppress warnings for unused event in KVMResizeControl. Fixing build errors * feat: add password encryption and decryption functionality with UI support for remembering passwords --- .vscode/tasks.json | 18 +++++++++++++++ MeshCentralRouter.csproj | 3 +-- MeshUtils.cs | 31 +++++++++++++++++++++++++ Properties/AssemblyInfo.cs | 2 +- src/KVMControl.cs | 8 ++++--- src/KVMResizeControl.cs | 2 ++ src/MainForm.Designer.cs | 9 ++++++++ src/MainForm.cs | 44 ++++++++++++++++++++++++++++++++++++ src/MainForm.resx | 46 +++++++++++++++++++++++++++++++------- 9 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3bbdd20 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build MeshCentralRouter", + "type": "shell", + "command": "& '${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\2022\\BuildTools\\MSBuild\\Current\\Bin\\MSBuild.exe' MeshCentralRouter.sln", + "isBackground": false, + "problemMatcher": [ + "$msCompile" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/MeshCentralRouter.csproj b/MeshCentralRouter.csproj index 9a6fccf..0f49d39 100644 --- a/MeshCentralRouter.csproj +++ b/MeshCentralRouter.csproj @@ -45,7 +45,6 @@ DEBUG;TRACE prompt 4 - AllRules.ruleset false @@ -55,7 +54,6 @@ TRACE prompt 4 - AllRules.ruleset false @@ -262,6 +260,7 @@ + diff --git a/MeshUtils.cs b/MeshUtils.cs index 2e1f667..5f74fe9 100644 --- a/MeshUtils.cs +++ b/MeshUtils.cs @@ -395,5 +395,36 @@ namespace MeshMiniRouterTool public static StringBuilder GetStringBuilder() { lock (StringBuilderRecycleList) { return (StringBuilderRecycleList.Count == 0) ? new StringBuilder(16000) : StringBuilderRecycleList.Pop(); } } public static void RecycleStringBuilder(StringBuilder obj) { lock (StringBuilderRecycleList) { obj.Length = 0; StringBuilderRecycleList.Push(obj); } } + // Password encryption/decryption using DPAPI (Data Protection API) + public static string EncryptPassword(string password) + { + if (string.IsNullOrEmpty(password)) return null; + try + { + byte[] data = Encoding.UTF8.GetBytes(password); + byte[] encrypted = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser); + return Convert.ToBase64String(encrypted); + } + catch (Exception) + { + return null; + } + } + + public static string DecryptPassword(string encryptedPassword) + { + if (string.IsNullOrEmpty(encryptedPassword)) return null; + try + { + byte[] data = Convert.FromBase64String(encryptedPassword); + byte[] decrypted = ProtectedData.Unprotect(data, null, DataProtectionScope.CurrentUser); + return Encoding.UTF8.GetString(decrypted); + } + catch (Exception) + { + return null; + } + } + } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 09526a6..af6197f 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.8.*")] +[assembly: AssemblyVersion("1.9.*")] //[assembly: AssemblyVersion("1.0.0.0")] //[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/KVMControl.cs b/src/KVMControl.cs index 7fa6fc3..d942215 100644 --- a/src/KVMControl.cs +++ b/src/KVMControl.cs @@ -61,8 +61,8 @@ namespace MeshCentralRouter private long killNextKeyPress = 0; private bool controlLoaded = false; public Rectangle[] displayInfo = null; - public Rectangle displayCrop = Rectangle.Empty; - public Point displayOrigin = Point.Empty; + public Rectangle displayCrop { get; set; } = Rectangle.Empty; + public Point displayOrigin { get; set; } = Point.Empty; //System level functions to be used for hook and unhook keyboard input @@ -949,7 +949,8 @@ namespace MeshCentralRouter return false; } - // Structure contain information about low-level keyboard input event + // Structure contain information about low-level keyboard input event +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value (filled by Windows via P/Invoke) private struct KBDLLHOOKSTRUCT { public Keys key; @@ -958,6 +959,7 @@ namespace MeshCentralRouter public int time; public IntPtr extra; } +#pragma warning restore CS0649 public void cropDisplay(Point o, Rectangle r) { diff --git a/src/KVMResizeControl.cs b/src/KVMResizeControl.cs index c1369bb..08a8d39 100644 --- a/src/KVMResizeControl.cs +++ b/src/KVMResizeControl.cs @@ -29,7 +29,9 @@ namespace MeshCentralRouter [Category("Action")] [Description("Fires when the connection state changes.")] +#pragma warning disable CS0067 // Event is declared but never invoked in this class public event EventHandler StateChanged; +#pragma warning restore CS0067 [Category("Action")] [Description("Fires when the display list is received.")] diff --git a/src/MainForm.Designer.cs b/src/MainForm.Designer.cs index a0dc636..d128e21 100644 --- a/src/MainForm.Designer.cs +++ b/src/MainForm.Designer.cs @@ -42,6 +42,7 @@ this.label27 = new System.Windows.Forms.Label(); this.label26 = new System.Windows.Forms.Label(); this.passwordTextBox = new System.Windows.Forms.TextBox(); + this.rememberPasswordCheckBox = new System.Windows.Forms.CheckBox(); this.serverNameComboBox = new System.Windows.Forms.ComboBox(); this.userNameTextBox = new System.Windows.Forms.TextBox(); this.licenseLinkLabel = new System.Windows.Forms.LinkLabel(); @@ -213,6 +214,7 @@ this.panel1.Controls.Add(this.label27); this.panel1.Controls.Add(this.label26); this.panel1.Controls.Add(this.passwordTextBox); + this.panel1.Controls.Add(this.rememberPasswordCheckBox); this.panel1.Controls.Add(this.serverNameComboBox); this.panel1.Controls.Add(this.userNameTextBox); this.panel1.Controls.Add(this.licenseLinkLabel); @@ -266,6 +268,12 @@ this.passwordTextBox.TextChanged += new System.EventHandler(this.updatePanel1); this.passwordTextBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.passwordTextBox_KeyPress); // + // rememberPasswordCheckBox + // + resources.ApplyResources(this.rememberPasswordCheckBox, "rememberPasswordCheckBox"); + this.rememberPasswordCheckBox.Name = "rememberPasswordCheckBox"; + this.rememberPasswordCheckBox.UseVisualStyleBackColor = true; + // // serverNameComboBox // resources.ApplyResources(this.serverNameComboBox, "serverNameComboBox"); @@ -1094,6 +1102,7 @@ private System.Windows.Forms.ComboBox serverNameComboBox; private System.Windows.Forms.TextBox userNameTextBox; private System.Windows.Forms.TextBox passwordTextBox; + private System.Windows.Forms.CheckBox rememberPasswordCheckBox; private System.Windows.Forms.Label label28; private System.Windows.Forms.Label label27; private System.Windows.Forms.Label label26; diff --git a/src/MainForm.cs b/src/MainForm.cs index 8b869ed..582e921 100644 --- a/src/MainForm.cs +++ b/src/MainForm.cs @@ -31,6 +31,7 @@ using System.Drawing; using System.Text; using System.Web; using System.Threading.Tasks; +using MeshMiniRouterTool; namespace MeshCentralRouter { @@ -253,6 +254,33 @@ namespace MeshCentralRouter userNameTextBox.Text = Settings.GetRegValue("UserName", ""); notifyIcon.Visible = Settings.GetRegValue("NotifyIcon", false); + // Load saved password if available and not expired (30 days) + string savedPassword = Settings.GetRegValue("SavedPassword", ""); + string savedPasswordDate = Settings.GetRegValue("SavedPasswordDate", ""); + if (!string.IsNullOrEmpty(savedPassword) && !string.IsNullOrEmpty(savedPasswordDate)) + { + try + { + DateTime savedDate = DateTime.Parse(savedPasswordDate); + if ((DateTime.Now - savedDate).TotalDays <= 30) + { + string decryptedPassword = MeshUtils.DecryptPassword(savedPassword); + if (decryptedPassword != null) + { + passwordTextBox.Text = decryptedPassword; + rememberPasswordCheckBox.Checked = true; + } + } + else + { + // Password expired, clear it + Settings.SetRegValue("SavedPassword", ""); + Settings.SetRegValue("SavedPasswordDate", ""); + } + } + catch (Exception) { } + } + title = this.Text; initialHeight = this.Height; @@ -1158,6 +1186,22 @@ namespace MeshCentralRouter { Settings.SetRegValue("ServerName", serverNameComboBox.Text); Settings.SetRegValue("UserName", userNameTextBox.Text); + + // Save or clear password based on remember checkbox + if (rememberPasswordCheckBox.Checked) + { + string encryptedPassword = MeshUtils.EncryptPassword(passwordTextBox.Text); + if (encryptedPassword != null) + { + Settings.SetRegValue("SavedPassword", encryptedPassword); + Settings.SetRegValue("SavedPasswordDate", DateTime.Now.ToString("o")); + } + } + else + { + Settings.SetRegValue("SavedPassword", ""); + Settings.SetRegValue("SavedPasswordDate", ""); + } } if (meshcentral.username != null) { diff --git a/src/MainForm.resx b/src/MainForm.resx index 5c1f7e1..baad787 100644 --- a/src/MainForm.resx +++ b/src/MainForm.resx @@ -348,6 +348,36 @@ 6 + + Top, Left + + + True + + + 241, 218 + + + 200, 17 + + + 107 + + + Remember password for 30 days + + + rememberPasswordCheckBox + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 7 + Top, Left, Right @@ -373,7 +403,7 @@ panel1 - 7 + 8 Top, Left, Right @@ -397,7 +427,7 @@ panel1 - 8 + 9 Bottom, Right @@ -430,7 +460,7 @@ panel1 - 9 + 10 Bottom, Left @@ -463,7 +493,7 @@ panel1 - 10 + 11 Bottom, Right @@ -493,7 +523,7 @@ panel1 - 11 + 12 Bottom, Left, Right @@ -523,7 +553,7 @@ panel1 - 12 + 13 Top, Left, Right @@ -553,7 +583,7 @@ panel1 - 13 + 14 Top, Bottom, Left, Right @@ -583,7 +613,7 @@ panel1 - 14 + 15 Fill