refactor(securestore): remove password-based authentication in favor of key-file only

Simplify SecureStore by removing MasterKeyEnvVar and password-based methods, leaving only key-file authentication for better security practices.
This commit is contained in:
Joseph Doherty
2026-01-23 00:17:19 -05:00
parent 9c4a184233
commit 1b7bb26def
22 changed files with 101 additions and 1421 deletions
@@ -252,15 +252,10 @@ public class SecureStoreSection
public string StorePath { get; set; } = "data/secrets.json";
/// <summary>
/// Gets or sets the path to the key file for decryption.
/// Gets or sets the path to the key file for encryption/decryption.
/// </summary>
public string KeyFilePath { get; set; } = "data/secrets.key";
/// <summary>
/// Gets or sets the environment variable name for the master key.
/// </summary>
public string MasterKeyEnvVar { get; set; } = "SCOPINGTOOL_MASTER_KEY";
/// <summary>
/// Gets or sets a value indicating whether to auto-create the store if it doesn't exist.
/// </summary>
@@ -10,10 +10,6 @@ public class NewStoreDialogViewModel : ViewModelBase
{
private string _storePath = string.Empty;
private string _keyFilePath = string.Empty;
private string _password = string.Empty;
private string _confirmPassword = string.Empty;
private bool _useKeyFile = true;
private bool _usePassword;
/// <summary>
/// Initializes a new instance of the <see cref="NewStoreDialogViewModel"/> class.
@@ -51,64 +47,6 @@ public class NewStoreDialogViewModel : ViewModelBase
}
}
/// <summary>
/// Gets or sets the password for store encryption.
/// </summary>
public string Password
{
get => _password;
set
{
if (SetProperty(ref _password, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets the password confirmation value.
/// </summary>
public string ConfirmPassword
{
get => _confirmPassword;
set
{
if (SetProperty(ref _confirmPassword, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets whether to use key file for store encryption.
/// </summary>
public bool UseKeyFile
{
get => _useKeyFile;
set
{
if (SetProperty(ref _useKeyFile, value))
{
if (value) UsePassword = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets or sets whether to use password for store encryption.
/// </summary>
public bool UsePassword
{
get => _usePassword;
set
{
if (SetProperty(ref _usePassword, value))
{
if (value) UseKeyFile = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets the command to browse for store path location.
/// </summary>
@@ -127,22 +65,7 @@ public class NewStoreDialogViewModel : ViewModelBase
/// <summary>
/// Gets a value indicating whether the dialog input is valid.
/// </summary>
public bool IsValid
{
get
{
if (string.IsNullOrWhiteSpace(StorePath))
return false;
if (UseKeyFile)
return !string.IsNullOrWhiteSpace(KeyFilePath);
if (UsePassword)
return !string.IsNullOrWhiteSpace(Password) && Password == ConfirmPassword;
return false;
}
}
public bool IsValid => !string.IsNullOrWhiteSpace(StorePath) && !string.IsNullOrWhiteSpace(KeyFilePath);
/// <summary>
/// Gets the validation error message, or null if valid.
@@ -154,18 +77,9 @@ public class NewStoreDialogViewModel : ViewModelBase
if (string.IsNullOrWhiteSpace(StorePath))
return SecureStoreStrings.StorePathRequired;
if (UseKeyFile && string.IsNullOrWhiteSpace(KeyFilePath))
if (string.IsNullOrWhiteSpace(KeyFilePath))
return SecureStoreStrings.KeyFilePathRequired;
if (UsePassword)
{
if (string.IsNullOrWhiteSpace(Password))
return SecureStoreStrings.PasswordRequired;
if (Password != ConfirmPassword)
return SecureStoreStrings.PasswordsDoNotMatch;
}
return null;
}
}
@@ -10,9 +10,6 @@ public class UnlockStoreDialogViewModel : ViewModelBase
{
private readonly string _storePath;
private string _keyFilePath = string.Empty;
private string _password = string.Empty;
private bool _useKeyFile = true;
private bool _usePassword;
/// <summary>
/// Initializes a new instance of the <see cref="UnlockStoreDialogViewModel"/> class.
@@ -42,51 +39,6 @@ public class UnlockStoreDialogViewModel : ViewModelBase
}
}
/// <summary>
/// Gets or sets the password for store decryption.
/// </summary>
public string Password
{
get => _password;
set
{
if (SetProperty(ref _password, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets whether to use key file for store decryption.
/// </summary>
public bool UseKeyFile
{
get => _useKeyFile;
set
{
if (SetProperty(ref _useKeyFile, value))
{
if (value) UsePassword = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets or sets whether to use password for store decryption.
/// </summary>
public bool UsePassword
{
get => _usePassword;
set
{
if (SetProperty(ref _usePassword, value))
{
if (value) UseKeyFile = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets the command to browse for key file path location.
/// </summary>
@@ -99,19 +51,10 @@ public class UnlockStoreDialogViewModel : ViewModelBase
{
get
{
if (UseKeyFile)
{
if (string.IsNullOrWhiteSpace(KeyFilePath))
return false;
if (string.IsNullOrWhiteSpace(KeyFilePath))
return false;
if (!System.IO.File.Exists(KeyFilePath))
return false;
}
if (UsePassword)
return !string.IsNullOrWhiteSpace(Password);
return false;
return System.IO.File.Exists(KeyFilePath);
}
}
@@ -122,17 +65,11 @@ public class UnlockStoreDialogViewModel : ViewModelBase
{
get
{
if (UseKeyFile)
{
if (string.IsNullOrWhiteSpace(KeyFilePath))
return SecureStoreStrings.KeyFilePathRequired;
if (string.IsNullOrWhiteSpace(KeyFilePath))
return SecureStoreStrings.KeyFilePathRequired;
if (!System.IO.File.Exists(KeyFilePath))
return SecureStoreStrings.KeyFileNotFound;
}
if (UsePassword && string.IsNullOrWhiteSpace(Password))
return SecureStoreStrings.PasswordRequired;
if (!System.IO.File.Exists(KeyFilePath))
return SecureStoreStrings.KeyFileNotFound;
return null;
}
@@ -4,8 +4,8 @@
x:Class="JdeScoping.ConfigManager.Views.Dialogs.NewStoreDialog"
x:DataType="vm:NewStoreDialogViewModel"
Title="Create New Secure Store"
Width="550" Height="480"
MinWidth="450" MinHeight="400"
Width="550" Height="350"
MinWidth="450" MinHeight="300"
Background="#151920"
WindowStartupLocation="CenterOwner"
CanResize="False"
@@ -56,28 +56,14 @@
</StackPanel>
</Border>
<!-- Encryption Method -->
<!-- Key File -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="Encryption Method" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<RadioButton Content="Use Key File (recommended for production)"
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
Foreground="#9BA8B8" Margin="0,4"/>
<RadioButton Content="Use Password"
IsChecked="{Binding UsePassword, Mode=TwoWay}"
Foreground="#9BA8B8" Margin="0,4"/>
</StackPanel>
</Border>
<!-- Key File Settings -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16"
IsVisible="{Binding UseKeyFile}">
<StackPanel Spacing="8">
<TextBlock Text="Key File" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<TextBlock Text="The key file is required to encrypt and decrypt the store."
Foreground="#9BA8B8" FontSize="12" TextWrapping="Wrap"/>
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,4,0,0">
<TextBox Grid.Column="0"
Text="{Binding KeyFilePath, Mode=TwoWay}"
@@ -96,32 +82,6 @@
</StackPanel>
</Border>
<!-- Password Settings -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16"
IsVisible="{Binding UsePassword}">
<StackPanel Spacing="8">
<TextBlock Text="Password" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<Grid ColumnDefinitions="120,*" RowDefinitions="Auto,Auto" Margin="0,4,0,0">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Password:"
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
<TextBox Grid.Row="0" Grid.Column="1"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}"
Background="#0D0F12" Foreground="#E6EDF5"
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Confirm:"
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
<TextBox Grid.Row="1" Grid.Column="1"
PasswordChar="*"
Text="{Binding ConfirmPassword, Mode=TwoWay}"
Background="#0D0F12" Foreground="#E6EDF5"
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
</Grid>
</StackPanel>
</Border>
<!-- Validation Error -->
<TextBlock Text="{Binding ValidationError}"
Foreground="#FF6B6B" FontSize="12"
@@ -4,8 +4,8 @@
x:Class="JdeScoping.ConfigManager.Views.Dialogs.UnlockStoreDialog"
x:DataType="vm:UnlockStoreDialogViewModel"
Title="Unlock Secure Store"
Width="500" Height="400"
MinWidth="400" MinHeight="350"
Width="500" Height="320"
MinWidth="400" MinHeight="280"
Background="#151920"
WindowStartupLocation="CenterOwner"
CanResize="False"
@@ -49,28 +49,14 @@
</StackPanel>
</Border>
<!-- Decryption Method -->
<!-- Key File -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="Decryption Method" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<RadioButton Content="Use Key File"
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
Foreground="#9BA8B8" Margin="0,4"/>
<RadioButton Content="Use Password"
IsChecked="{Binding UsePassword, Mode=TwoWay}"
Foreground="#9BA8B8" Margin="0,4"/>
</StackPanel>
</Border>
<!-- Key File Settings -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16"
IsVisible="{Binding UseKeyFile}">
<StackPanel Spacing="8">
<TextBlock Text="Key File" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<TextBlock Text="Select the key file used to encrypt this store."
Foreground="#9BA8B8" FontSize="12" TextWrapping="Wrap"/>
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
<TextBox Grid.Column="0"
Text="{Binding KeyFilePath, Mode=TwoWay}"
@@ -85,25 +71,6 @@
</StackPanel>
</Border>
<!-- Password Settings -->
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
CornerRadius="4" Padding="16"
IsVisible="{Binding UsePassword}">
<StackPanel Spacing="8">
<TextBlock Text="Password" Foreground="#E6EDF5"
FontWeight="SemiBold" FontSize="14"/>
<Grid ColumnDefinitions="100,*" Margin="0,4,0,0">
<TextBlock Text="Password:" Foreground="#9BA8B8"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}"
Background="#0D0F12" Foreground="#E6EDF5"
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
</Grid>
</StackPanel>
</Border>
<!-- Validation Error -->
<TextBlock Text="{Binding ValidationError}"
Foreground="#FF6B6B" FontSize="12"
@@ -23,55 +23,33 @@ public class StoreUseCases
}
/// <summary>
/// Creates a new store with either key file or password authentication.
/// Creates a new store with key file authentication.
/// </summary>
/// <param name="storePath">The path where the store will be created.</param>
/// <param name="keyFilePath">The path to the key file, or null for password-based authentication.</param>
/// <param name="password">The password for authentication, or null for key file-based authentication.</param>
public void CreateStore(string storePath, string? keyFilePath, string? password)
/// <param name="keyFilePath">The path to the key file.</param>
public void CreateStore(string storePath, string keyFilePath)
{
_logger.LogInformation("Creating store at {StorePath}", storePath);
if (string.IsNullOrEmpty(keyFilePath))
throw new ArgumentException("Key file path must be provided.", nameof(keyFilePath));
if (!string.IsNullOrEmpty(keyFilePath))
{
_storeManager.CreateStore(storePath, keyFilePath);
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
}
else if (!string.IsNullOrEmpty(password))
{
_storeManager.CreateStoreWithPassword(storePath, password);
_logger.LogInformation("Password-protected store created");
}
else
{
throw new ArgumentException("Either key file path or password must be provided.");
}
_logger.LogInformation("Creating store at {StorePath}", storePath);
_storeManager.CreateStore(storePath, keyFilePath);
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
}
/// <summary>
/// Opens an existing store with either key file or password authentication.
/// Opens an existing store with key file authentication.
/// </summary>
/// <param name="storePath">The path to the existing store.</param>
/// <param name="keyFilePath">The path to the key file, or null for password-based authentication.</param>
/// <param name="password">The password for authentication, or null for key file-based authentication.</param>
public void OpenStore(string storePath, string? keyFilePath, string? password)
/// <param name="keyFilePath">The path to the key file.</param>
public void OpenStore(string storePath, string keyFilePath)
{
_logger.LogInformation("Opening store at {StorePath}", storePath);
if (string.IsNullOrEmpty(keyFilePath))
throw new ArgumentException("Key file path must be provided.", nameof(keyFilePath));
if (!string.IsNullOrEmpty(keyFilePath))
{
_storeManager.OpenStore(storePath, keyFilePath);
_logger.LogDebug("Store opened with key file");
}
else if (!string.IsNullOrEmpty(password))
{
_storeManager.OpenStoreWithPassword(storePath, password);
_logger.LogDebug("Store opened with password");
}
else
{
throw new ArgumentException("Either key file path or password must be provided.");
}
_logger.LogInformation("Opening store at {StorePath}", storePath);
_storeManager.OpenStore(storePath, keyFilePath);
_logger.LogDebug("Store opened with key file");
}
/// <summary>
@@ -27,13 +27,6 @@ public interface ISecureStoreManager
/// <param name="keyFilePath">Path for the key file (.key).</param>
void CreateStore(string storePath, string keyFilePath);
/// <summary>
/// Creates a new store secured with a password.
/// </summary>
/// <param name="storePath">Path for the new store file (.json).</param>
/// <param name="password">Password to encrypt the store.</param>
void CreateStoreWithPassword(string storePath, string password);
/// <summary>
/// Opens an existing store using a key file.
/// </summary>
@@ -41,13 +34,6 @@ public interface ISecureStoreManager
/// <param name="keyFilePath">Path to the key file (.key).</param>
void OpenStore(string storePath, string keyFilePath);
/// <summary>
/// Opens an existing store using a password.
/// </summary>
/// <param name="storePath">Path to the store file (.json).</param>
/// <param name="password">Password to decrypt the store.</param>
void OpenStoreWithPassword(string storePath, string password);
/// <summary>
/// Closes the currently open store without saving.
/// </summary>
@@ -72,29 +72,6 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
}
/// <inheritdoc />
public void CreateStoreWithPassword(string storePath, string password)
{
ThrowIfDisposed();
_logger.LogInformation("Creating password-protected store at {StorePath}", storePath);
CloseStoreInternal();
if (string.IsNullOrEmpty(password))
throw new ArgumentException("Password cannot be empty.", nameof(password));
EnsureDirectory(storePath);
_secretsManager = SecretsManager.CreateStore();
_secretsManager.LoadKeyFromPassword(password);
_currentStorePath = storePath;
_keys.Clear();
_hasUnsavedChanges = true;
Save();
_logger.LogInformation("Password-protected store created");
}
/// <inheritdoc />
public void OpenStore(string storePath, string keyFilePath)
{
@@ -117,28 +94,6 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
_logger.LogDebug("Store opened with key file, contains {KeyCount} keys", _keys.Count);
}
/// <inheritdoc />
public void OpenStoreWithPassword(string storePath, string password)
{
ThrowIfDisposed();
_logger.LogInformation("Opening store at {StorePath} with password", storePath);
CloseStoreInternal();
if (!File.Exists(storePath))
throw new FileNotFoundException("Store file not found.", storePath);
if (string.IsNullOrEmpty(password))
throw new ArgumentException("Password cannot be empty.", nameof(password));
_secretsManager = SecretsManager.LoadStore(storePath);
_secretsManager.LoadKeyFromPassword(password);
_currentStorePath = storePath;
LoadKeysMetadata();
_hasUnsavedChanges = false;
_logger.LogDebug("Store opened with password, contains {KeyCount} keys", _keys.Count);
}
/// <inheritdoc />
public void CloseStore()
{
@@ -10,10 +10,6 @@ public class NewStoreDialogViewModel : ViewModelBase
{
private string _storePath = string.Empty;
private string _keyFilePath = string.Empty;
private string _password = string.Empty;
private string _confirmPassword = string.Empty;
private bool _useKeyFile = true;
private bool _usePassword;
/// <summary>
/// Initializes a new instance of the <see cref="NewStoreDialogViewModel"/> class.
@@ -50,64 +46,6 @@ public class NewStoreDialogViewModel : ViewModelBase
}
}
/// <summary>
/// Gets or sets the password for store encryption.
/// </summary>
public string Password
{
get => _password;
set
{
if (SetProperty(ref _password, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets the password confirmation value.
/// </summary>
public string ConfirmPassword
{
get => _confirmPassword;
set
{
if (SetProperty(ref _confirmPassword, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets whether to use key file for store encryption.
/// </summary>
public bool UseKeyFile
{
get => _useKeyFile;
set
{
if (SetProperty(ref _useKeyFile, value))
{
if (value) UsePassword = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets or sets whether to use password for store encryption.
/// </summary>
public bool UsePassword
{
get => _usePassword;
set
{
if (SetProperty(ref _usePassword, value))
{
if (value) UseKeyFile = false;
NotifyValidationChanged();
}
}
}
private void NotifyValidationChanged()
{
OnPropertyChanged(nameof(IsValid));
@@ -127,22 +65,7 @@ public class NewStoreDialogViewModel : ViewModelBase
/// <summary>
/// Gets a value indicating whether the dialog input is valid.
/// </summary>
public bool IsValid
{
get
{
if (string.IsNullOrWhiteSpace(StorePath))
return false;
if (UseKeyFile)
return !string.IsNullOrWhiteSpace(KeyFilePath);
if (UsePassword)
return !string.IsNullOrWhiteSpace(Password) && Password == ConfirmPassword;
return false;
}
}
public bool IsValid => !string.IsNullOrWhiteSpace(StorePath) && !string.IsNullOrWhiteSpace(KeyFilePath);
/// <summary>
/// Gets the validation error message, or null if valid.
@@ -154,18 +77,9 @@ public class NewStoreDialogViewModel : ViewModelBase
if (string.IsNullOrWhiteSpace(StorePath))
return DialogStrings.StorePathRequired;
if (UseKeyFile && string.IsNullOrWhiteSpace(KeyFilePath))
if (string.IsNullOrWhiteSpace(KeyFilePath))
return DialogStrings.KeyFilePathRequired;
if (UsePassword)
{
if (string.IsNullOrWhiteSpace(Password))
return DialogStrings.PasswordRequired;
if (Password != ConfirmPassword)
return DialogStrings.PasswordsDoNotMatch;
}
return null;
}
}
@@ -217,9 +131,6 @@ public class OpenStoreDialogViewModel : ViewModelBase
{
private string _storePath = string.Empty;
private string _keyFilePath = string.Empty;
private string _password = string.Empty;
private bool _useKeyFile = true;
private bool _usePassword;
/// <summary>
/// Initializes a new instance of the <see cref="OpenStoreDialogViewModel"/> class.
@@ -256,51 +167,6 @@ public class OpenStoreDialogViewModel : ViewModelBase
}
}
/// <summary>
/// Gets or sets the password for store decryption.
/// </summary>
public string Password
{
get => _password;
set
{
if (SetProperty(ref _password, value))
NotifyValidationChanged();
}
}
/// <summary>
/// Gets or sets whether to use key file for store decryption.
/// </summary>
public bool UseKeyFile
{
get => _useKeyFile;
set
{
if (SetProperty(ref _useKeyFile, value))
{
if (value) UsePassword = false;
NotifyValidationChanged();
}
}
}
/// <summary>
/// Gets or sets whether to use password for store decryption.
/// </summary>
public bool UsePassword
{
get => _usePassword;
set
{
if (SetProperty(ref _usePassword, value))
{
if (value) UseKeyFile = false;
NotifyValidationChanged();
}
}
}
private void NotifyValidationChanged()
{
OnPropertyChanged(nameof(IsValid));
@@ -327,13 +193,7 @@ public class OpenStoreDialogViewModel : ViewModelBase
if (string.IsNullOrWhiteSpace(StorePath))
return false;
if (UseKeyFile)
return !string.IsNullOrWhiteSpace(KeyFilePath);
if (UsePassword)
return !string.IsNullOrWhiteSpace(Password);
return false;
return !string.IsNullOrWhiteSpace(KeyFilePath);
}
}
@@ -350,17 +210,11 @@ public class OpenStoreDialogViewModel : ViewModelBase
if (!System.IO.File.Exists(StorePath))
return DialogStrings.StoreFileNotFound;
if (UseKeyFile)
{
if (string.IsNullOrWhiteSpace(KeyFilePath))
return DialogStrings.KeyFilePathRequired;
if (string.IsNullOrWhiteSpace(KeyFilePath))
return DialogStrings.KeyFilePathRequired;
if (!System.IO.File.Exists(KeyFilePath))
return DialogStrings.KeyFileNotFound;
}
if (UsePassword && string.IsNullOrWhiteSpace(Password))
return DialogStrings.PasswordRequired;
if (!System.IO.File.Exists(KeyFilePath))
return DialogStrings.KeyFileNotFound;
return null;
}
@@ -161,30 +161,22 @@ public class MainWindowViewModel : ViewModelBase
public ICommand ExportKeyCommand { get; }
/// <summary>
/// Creates a new store. Called by the dialog.
/// Creates a new store with a key file. Called by the dialog.
/// </summary>
/// <param name="storePath">The path where the store file will be created.</param>
/// <param name="keyFilePath">Optional path to a key file for encryption.</param>
/// <param name="password">Optional password for encryption.</param>
public async Task CreateNewStoreAsync(string storePath, string? keyFilePath, string? password)
/// <param name="keyFilePath">The path to a key file for encryption.</param>
public async Task CreateNewStoreAsync(string storePath, string keyFilePath)
{
try
{
if (!string.IsNullOrEmpty(keyFilePath))
if (string.IsNullOrEmpty(keyFilePath))
{
_storeManager.CreateStore(storePath, keyFilePath);
StatusMessage = $"Created store with key file: {keyFilePath}";
}
else if (!string.IsNullOrEmpty(password))
{
_storeManager.CreateStoreWithPassword(storePath, password);
StatusMessage = "Created password-protected store";
}
else
{
throw new ArgumentException("Either key file path or password must be provided.");
throw new ArgumentException("Key file path must be provided.");
}
_storeManager.CreateStore(storePath, keyFilePath);
StatusMessage = $"Created store with key file: {keyFilePath}";
RefreshSecrets();
NotifyStoreChanged();
}
@@ -198,30 +190,22 @@ public class MainWindowViewModel : ViewModelBase
}
/// <summary>
/// Opens an existing store. Called by the dialog.
/// Opens an existing store with a key file. Called by the dialog.
/// </summary>
/// <param name="storePath">The path to the store file to open.</param>
/// <param name="keyFilePath">Optional path to a key file for decryption.</param>
/// <param name="password">Optional password for decryption.</param>
public async Task OpenExistingStoreAsync(string storePath, string? keyFilePath, string? password)
/// <param name="keyFilePath">The path to a key file for decryption.</param>
public async Task OpenExistingStoreAsync(string storePath, string keyFilePath)
{
try
{
if (!string.IsNullOrEmpty(keyFilePath))
if (string.IsNullOrEmpty(keyFilePath))
{
_storeManager.OpenStore(storePath, keyFilePath);
StatusMessage = "Opened store with key file";
}
else if (!string.IsNullOrEmpty(password))
{
_storeManager.OpenStoreWithPassword(storePath, password);
StatusMessage = "Opened password-protected store";
}
else
{
throw new ArgumentException("Either key file path or password must be provided.");
throw new ArgumentException("Key file path must be provided.");
}
_storeManager.OpenStore(storePath, keyFilePath);
StatusMessage = "Opened store with key file";
RefreshSecrets();
NotifyStoreChanged();
}
@@ -61,10 +61,7 @@ public partial class MainWindow : Window
if (result == true)
{
var vm = dialog.ViewModel;
await ViewModel.CreateNewStoreAsync(
vm.StorePath,
vm.UseKeyFile ? vm.KeyFilePath : null,
vm.UsePassword ? vm.Password : null);
await ViewModel.CreateNewStoreAsync(vm.StorePath, vm.KeyFilePath);
}
}
@@ -77,10 +74,7 @@ public partial class MainWindow : Window
if (result == true)
{
var vm = dialog.ViewModel;
await ViewModel.OpenExistingStoreAsync(
vm.StorePath,
vm.UseKeyFile ? vm.KeyFilePath : null,
vm.UsePassword ? vm.Password : null);
await ViewModel.OpenExistingStoreAsync(vm.StorePath, vm.KeyFilePath);
}
}
@@ -3,13 +3,13 @@
xmlns:vm="clr-namespace:JdeScoping.SecureStoreManager.ViewModels"
x:Class="JdeScoping.SecureStoreManager.Views.NewStoreDialog"
Title="Create New Store"
Height="400" Width="500"
Height="280" Width="500"
WindowStartupLocation="CenterOwner"
CanResize="False"
ShowInTaskbar="False">
<!-- DataContext is set in code-behind -->
<Grid Margin="15" RowDefinitions="Auto,Auto,Auto,*,Auto">
<Grid Margin="15" RowDefinitions="Auto,Auto,*,Auto">
<!-- Store Path -->
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
@@ -26,25 +26,13 @@
</StackPanel>
</Border>
<!-- Encryption Method -->
<Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Encryption Method" FontWeight="SemiBold" Margin="0,0,0,10" />
<RadioButton Content="Use Key File (recommended for production)"
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
Margin="0,5" />
<RadioButton Content="Use Password"
IsChecked="{Binding UsePassword, Mode=TwoWay}"
Margin="0,5" />
</StackPanel>
</Border>
<!-- Key File Settings -->
<Border Grid.Row="2"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
IsVisible="{Binding UseKeyFile}">
<Border Grid.Row="1"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Key File" FontWeight="SemiBold" Margin="0,0,0,10" />
<TextBlock Text="The key file is required to encrypt and decrypt the store."
FontSize="11" Foreground="Gray" Margin="0,0,0,10" TextWrapping="Wrap" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
Text="{Binding KeyFilePath, Mode=TwoWay}"
@@ -57,31 +45,8 @@
</StackPanel>
</Border>
<!-- Password Settings -->
<Border Grid.Row="2"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
IsVisible="{Binding UsePassword}">
<StackPanel>
<TextBlock Text="Password" FontWeight="SemiBold" Margin="0,0,0,10" />
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Password:" VerticalAlignment="Center" Margin="0,5" />
<TextBox Grid.Row="0" Grid.Column="1"
x:Name="PasswordBox"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}"
Margin="0,5" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Confirm:" VerticalAlignment="Center" Margin="0,5" />
<TextBox Grid.Row="1" Grid.Column="1"
x:Name="ConfirmPasswordBox"
PasswordChar="*"
Text="{Binding ConfirmPassword, Mode=TwoWay}"
Margin="0,5" />
</Grid>
</StackPanel>
</Border>
<!-- Validation Error -->
<TextBlock Grid.Row="3"
<TextBlock Grid.Row="2"
Text="{Binding ValidationError}"
Foreground="Red"
FontSize="11"
@@ -90,7 +55,7 @@
VerticalAlignment="Top" />
<!-- Buttons -->
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
<Button Content="Create" Click="CreateButton_Click" IsEnabled="{Binding IsValid}" MinWidth="80" Padding="10,5" />
<Button Content="Cancel" Click="CancelButton_Click" MinWidth="80" Padding="10,5" />
</StackPanel>
@@ -3,13 +3,13 @@
xmlns:vm="clr-namespace:JdeScoping.SecureStoreManager.ViewModels"
x:Class="JdeScoping.SecureStoreManager.Views.OpenStoreDialog"
Title="Open Store"
Height="370" Width="500"
Height="280" Width="500"
WindowStartupLocation="CenterOwner"
CanResize="False"
ShowInTaskbar="False">
<!-- DataContext is set in code-behind -->
<Grid Margin="15" RowDefinitions="Auto,Auto,Auto,*,Auto">
<Grid Margin="15" RowDefinitions="Auto,Auto,*,Auto">
<!-- Store Path -->
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
@@ -26,25 +26,13 @@
</StackPanel>
</Border>
<!-- Decryption Method -->
<Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Decryption Method" FontWeight="SemiBold" Margin="0,0,0,10" />
<RadioButton Content="Use Key File"
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
Margin="0,5" />
<RadioButton Content="Use Password"
IsChecked="{Binding UsePassword, Mode=TwoWay}"
Margin="0,5" />
</StackPanel>
</Border>
<!-- Key File Settings -->
<Border Grid.Row="2"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
IsVisible="{Binding UseKeyFile}">
<Border Grid.Row="1"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="Key File" FontWeight="SemiBold" Margin="0,0,0,10" />
<TextBlock Text="Select the key file used to encrypt this store."
FontSize="11" Foreground="Gray" Margin="0,0,0,10" TextWrapping="Wrap" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
Text="{Binding KeyFilePath, Mode=TwoWay}"
@@ -57,25 +45,8 @@
</StackPanel>
</Border>
<!-- Password Settings -->
<Border Grid.Row="2"
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
IsVisible="{Binding UsePassword}">
<StackPanel>
<TextBlock Text="Password" FontWeight="SemiBold" Margin="0,0,0,10" />
<Grid ColumnDefinitions="100,*">
<TextBlock Text="Password:" VerticalAlignment="Center" />
<TextBox Grid.Column="1"
x:Name="PasswordBox"
PasswordChar="*"
Text="{Binding Password, Mode=TwoWay}"
Margin="0,5" />
</Grid>
</StackPanel>
</Border>
<!-- Validation Error -->
<TextBlock Grid.Row="3"
<TextBlock Grid.Row="2"
Text="{Binding ValidationError}"
Foreground="Red"
FontSize="11"
@@ -84,7 +55,7 @@
VerticalAlignment="Top" />
<!-- Buttons -->
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
<Button Content="Open" Click="OpenButton_Click" IsEnabled="{Binding IsValid}" MinWidth="80" Padding="10,5" />
<Button Content="Cancel" Click="CancelButton_Click" MinWidth="80" Padding="10,5" />
</StackPanel>