Add SecureStore integration to ConfigManager for secure handling of connection strings and sensitive configuration values. Includes store/secret management UI, encrypted .store file support, and comprehensive test coverage.
16 KiB
SecureStoreManager Integration into ConfigManager
Date: 2026-01-20 Status: Ready for Implementation Estimated Phases: 9
Overview
Merge the complete functionality of SecureStoreManager into ConfigManager, creating a unified configuration and secrets management application. This integration adds encrypted secret store management to the existing configuration editing capabilities.
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Integration model | Tree node with explicit auth | Security: secrets require conscious authentication |
| Store discovery | Hybrid (auto-discover + manual add) | Convenience for co-located stores, flexibility for external |
| Credential caching | Prompt every time (session cache opt-in) | Default to most secure behavior |
| Save behavior | Separate saves (config vs stores) | Different security domains shouldn't couple |
| Service architecture | Use cases layer | Matches existing pattern, separation of concerns |
Architecture
Tree Structure
📁 Configuration
├── DataSync
├── DataAccess
├── Auth
├── LDAP
├── Search
└── Excel Export
📁 Pipelines
└── (pipeline nodes)
🛡️ Secure Stores
├── 🔒 production.secrets (locked)
└── 🔓 development.secrets (unlocked)
├── 🔑 ConnectionStrings:JDE
└── 🔑 LdapPassword
Service Layer
MainWindowViewModel
├── IConfigFileService (existing - config operations)
├── IStoreUseCases (new - secure store operations)
└── ISecretUseCases (new - secret operations)
└── ISecureStoreManager (new - low-level encryption)
Phase 1: Project Setup & Dependencies
1.1 Add SecureStore NuGet Package
File: NEW/src/Utils/JdeScoping.ConfigManager/JdeScoping.ConfigManager.csproj
Add:
<PackageReference Include="SecureStore" Version="1.2.0" />
1.2 Add Avalonia.Headless.XUnit to Test Project
File: NEW/tests/JdeScoping.ConfigManager.Tests/JdeScoping.ConfigManager.Tests.csproj
Add:
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.2.*" />
Phase 2: Copy Service Layer
2.1 Copy Core Service Files
Copy from SecureStoreManager/Services/ to ConfigManager/Services/SecureStore/:
| Source | Destination |
|---|---|
ISecureStoreManager.cs |
Services/SecureStore/ISecureStoreManager.cs |
SecureStoreManager.cs |
Services/SecureStore/SecureStoreManager.cs |
Update namespace: JdeScoping.SecureStoreManager.Services → JdeScoping.ConfigManager.Services.SecureStore
2.2 Copy Use Cases
Copy from SecureStoreManager/Application/ to ConfigManager/Application/:
| Source | Destination |
|---|---|
StoreUseCases.cs |
Application/StoreUseCases.cs |
SecretUseCases.cs |
Application/SecretUseCases.cs |
Update namespaces accordingly.
2.3 Copy Constants
Copy from SecureStoreManager/Constants/ to ConfigManager/Constants/:
| Source | Destination |
|---|---|
DialogStrings.cs |
Constants/SecureStoreStrings.cs (rename to avoid conflicts) |
FileExtensions.cs |
Constants/SecureStoreFileExtensions.cs |
2.4 Copy Clipboard Service
Copy from SecureStoreManager/Services/ to ConfigManager/Services/:
| Source | Destination |
|---|---|
IClipboardService.cs |
Services/IClipboardService.cs |
AvaloniaClipboardService.cs |
Services/AvaloniaClipboardService.cs |
Phase 3: Extend Tree Node Model
3.1 Update TreeNodeType Enum
File: ViewModels/TreeNodeViewModel.cs
public enum TreeNodeType
{
Folder,
SettingsSection,
Pipeline,
SecureStoresFolder, // New
SecureStore, // New
Secret // New
}
3.2 Add SecureStore-Specific Properties
File: ViewModels/TreeNodeViewModel.cs
Add to TreeNodeViewModel:
/// <summary>
/// Gets or sets whether this secure store is currently unlocked.
/// Only applicable for SecureStore node types.
/// </summary>
public bool IsUnlocked
{
get => _isUnlocked;
set
{
if (SetProperty(ref _isUnlocked, value))
{
OnPropertyChanged(nameof(LockIcon));
OnPropertyChanged(nameof(IsLocked));
}
}
}
/// <summary>
/// Gets whether this secure store is locked.
/// </summary>
public bool IsLocked => !IsUnlocked;
/// <summary>
/// Gets the lock icon for secure store nodes.
/// </summary>
public string LockIcon => IsUnlocked ? "🔓" : "🔒";
/// <summary>
/// Gets or sets the full path to the secure store file.
/// Only applicable for SecureStore node types.
/// </summary>
public string? StorePath { get; init; }
3.3 Update Icon Property Logic
Update the Icon property or add computed property for dynamic icons based on node type and lock state.
Phase 4: Create New ViewModels
4.1 SecureStoreFormViewModel (Locked State)
File: ViewModels/Forms/SecureStoreLockedFormViewModel.cs
Displays when a locked store is selected:
- Store name and path
- "This store is locked" message
- "Unlock Store..." button
- Last modified date (from file system)
4.2 SecureStoreFormViewModel (Unlocked State)
File: ViewModels/Forms/SecureStoreUnlockedFormViewModel.cs
Displays when an unlocked store is selected:
- Store name and path
- Secret count
- "Lock Store" button
- "Add Secret" button
- List of secrets (or indicate to select a secret from tree)
4.3 SecretFormViewModel
File: ViewModels/Forms/SecretFormViewModel.cs
Displays when a secret is selected:
- Key (read-only)
- Value (password-masked by default)
- Show/Hide toggle button
- Copy to Clipboard button
- Delete button
Properties:
public string Key { get; }
public string Value { get; set; }
public bool IsValueVisible { get; set; }
public string DisplayValue => IsValueVisible ? Value : new string('*', 8);
public ICommand ToggleVisibilityCommand { get; }
public ICommand CopyToClipboardCommand { get; }
4.4 Dialog ViewModels
Copy and adapt from SecureStoreManager:
| Source | Destination |
|---|---|
NewStoreDialogViewModel.cs |
ViewModels/Dialogs/NewStoreDialogViewModel.cs |
OpenStoreDialogViewModel.cs |
ViewModels/Dialogs/UnlockStoreDialogViewModel.cs (rename) |
SecretEditDialogViewModel.cs |
ViewModels/Dialogs/SecretEditDialogViewModel.cs |
Phase 5: Create New Views
5.1 Form Views
Directory: Views/Forms/
| File | Purpose |
|---|---|
SecureStoreLockedFormView.axaml |
Shows unlock button for locked stores |
SecureStoreUnlockedFormView.axaml |
Shows store info when unlocked |
SecretFormView.axaml |
Secret key/value editor with masking |
5.2 Dialog Views
Directory: Views/Dialogs/
| File | Purpose |
|---|---|
NewStoreDialog.axaml |
Create new secure store |
UnlockStoreDialog.axaml |
Enter credentials to unlock |
SecretEditDialog.axaml |
Add/edit secret key-value |
5.3 Update DataTemplates
File: App.axaml
Add DataTemplates for automatic form selection:
<DataTemplate DataType="{x:Type vm:SecureStoreLockedFormViewModel}">
<views:SecureStoreLockedFormView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecureStoreUnlockedFormViewModel}">
<views:SecureStoreUnlockedFormView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecretFormViewModel}">
<views:SecretFormView />
</DataTemplate>
Phase 6: Update MainWindowViewModel
6.1 Add Dependencies
private readonly IStoreUseCases _storeUseCases;
private readonly ISecretUseCases _secretUseCases;
private readonly IClipboardService _clipboardService;
6.2 Add Secure Store Commands
// Store commands
public ICommand NewStoreCommand { get; }
public ICommand AddExistingStoreCommand { get; }
public ICommand UnlockStoreCommand { get; }
public ICommand LockStoreCommand { get; }
public ICommand SaveStoreCommand { get; }
public ICommand LockAllStoresCommand { get; }
public ICommand GenerateKeyFileCommand { get; }
// Secret commands
public ICommand AddSecretCommand { get; }
public ICommand EditSecretCommand { get; }
public ICommand DeleteSecretCommand { get; }
public ICommand CopySecretCommand { get; }
6.3 Add Store State Tracking
// Track open stores (store path -> credentials for session caching if enabled)
private readonly Dictionary<string, SecureStoreState> _openStores = new();
private class SecureStoreState
{
public bool IsUnlocked { get; set; }
public TreeNodeViewModel TreeNode { get; set; }
}
6.4 Update BuildTreeNodes Method
Add logic to:
- Create "Secure Stores" folder node
- Discover
*.secrets.jsonfiles in config folder - Create SecureStore nodes for each discovered file
- Add manual store tracking
6.5 Update Form Selection Logic
Extend OnSelectedNodeChanged to handle:
SecureStoresFolder→ Show instructions/empty stateSecureStore(locked) → ShowSecureStoreLockedFormViewModelSecureStore(unlocked) → ShowSecureStoreUnlockedFormViewModelSecret→ ShowSecretFormViewModel
Phase 7: Update Menu Bar and Toolbar
7.1 Add Secure Stores Menu
File: Views/MainWindow.axaml
Add new menu between "Tools" and "Help":
<MenuItem Header="Secure Stores">
<MenuItem Header="New Store..." Command="{Binding NewStoreCommand}" InputGesture="Ctrl+Shift+N" />
<MenuItem Header="Add Existing Store..." Command="{Binding AddExistingStoreCommand}" InputGesture="Ctrl+Shift+O" />
<Separator />
<MenuItem Header="Save Store" Command="{Binding SaveStoreCommand}" InputGesture="Ctrl+Shift+S" />
<MenuItem Header="Lock All Stores" Command="{Binding LockAllStoresCommand}" />
<Separator />
<MenuItem Header="Generate Key File..." Command="{Binding GenerateKeyFileCommand}" />
</MenuItem>
7.2 Add Toolbar Buttons
Add after existing buttons with separator:
<Separator />
<Button Command="{Binding UnlockStoreCommand}" ToolTip.Tip="Unlock/Lock Store">
<TextBlock Text="🔓" />
</Button>
<Button Command="{Binding AddSecretCommand}" ToolTip.Tip="Add Secret">
<TextBlock Text="🔑+" />
</Button>
7.3 Add Context Menus
Add context menu to TreeView for right-click actions on nodes.
Phase 8: Register Services
8.1 Update App.axaml.cs
File: App.axaml.cs
Add to ConfigureServices:
// SecureStore Services
services.AddSingleton<ISecureStoreManager, SecureStoreManager>();
services.AddSingleton<IClipboardService>(sp =>
new AvaloniaClipboardService(GetMainWindow));
// SecureStore Use Cases
services.AddSingleton<IStoreUseCases, StoreUseCases>();
services.AddSingleton<ISecretUseCases, SecretUseCases>();
Phase 9: Migrate Tests
9.1 Copy Service Tests
Copy from SecureStoreManager.Tests/Services/ to ConfigManager.Tests/Services/SecureStore/:
| Source | Destination |
|---|---|
SecureStoreManagerTests.cs |
Services/SecureStore/SecureStoreManagerTests.cs |
Update namespaces.
9.2 Create New ViewModel Tests
Directory: ConfigManager.Tests/ViewModels/Forms/
| File | Tests |
|---|---|
SecureStoreLockedFormViewModelTests.cs |
Locked state display, unlock command |
SecureStoreUnlockedFormViewModelTests.cs |
Unlocked state, secret list, lock command |
SecretFormViewModelTests.cs |
Value masking, visibility toggle, copy |
9.3 Create Dialog ViewModel Tests
Directory: ConfigManager.Tests/ViewModels/Dialogs/
| File | Tests |
|---|---|
NewStoreDialogViewModelTests.cs |
Validation, path selection |
UnlockStoreDialogViewModelTests.cs |
Credential validation |
SecretEditDialogViewModelTests.cs |
Key/value validation |
9.4 Extend MainWindowViewModelTests
Add tests for:
- Secure store tree node creation
- Store unlock/lock workflow
- Secret CRUD operations
- Form selection for store/secret nodes
9.5 Extend TreeNodeViewModelTests
Add tests for:
- New node types (SecureStoresFolder, SecureStore, Secret)
- Lock state properties
- Icon computation for store nodes
9.6 Create UI Tests (Optional)
If UI testing is desired, copy patterns from SecureStoreManager.Tests/Views/:
- Test dialog visual structure
- Test form view elements
File Inventory
New Files to Create
Services (5 files):
Services/SecureStore/ISecureStoreManager.csServices/SecureStore/SecureStoreManager.csServices/IClipboardService.csServices/AvaloniaClipboardService.cs
Application (2 files):
Application/StoreUseCases.csApplication/SecretUseCases.cs
Constants (2 files):
Constants/SecureStoreStrings.csConstants/SecureStoreFileExtensions.cs
ViewModels (6 files):
ViewModels/Forms/SecureStoreLockedFormViewModel.csViewModels/Forms/SecureStoreUnlockedFormViewModel.csViewModels/Forms/SecretFormViewModel.csViewModels/Dialogs/NewStoreDialogViewModel.csViewModels/Dialogs/UnlockStoreDialogViewModel.csViewModels/Dialogs/SecretEditDialogViewModel.cs
Views (6 files):
Views/Forms/SecureStoreLockedFormView.axaml+.csViews/Forms/SecureStoreUnlockedFormView.axaml+.csViews/Forms/SecretFormView.axaml+.csViews/Dialogs/NewStoreDialog.axaml+.csViews/Dialogs/UnlockStoreDialog.axaml+.csViews/Dialogs/SecretEditDialog.axaml+.cs
Tests (8+ files):
Services/SecureStore/SecureStoreManagerTests.csViewModels/Forms/SecureStoreLockedFormViewModelTests.csViewModels/Forms/SecureStoreUnlockedFormViewModelTests.csViewModels/Forms/SecretFormViewModelTests.csViewModels/Dialogs/NewStoreDialogViewModelTests.csViewModels/Dialogs/UnlockStoreDialogViewModelTests.csViewModels/Dialogs/SecretEditDialogViewModelTests.cs
Files to Modify
JdeScoping.ConfigManager.csproj(add SecureStore package)App.axaml.cs(register services)App.axaml(add DataTemplates)ViewModels/TreeNodeViewModel.cs(add enum values, properties)ViewModels/MainWindowViewModel.cs(add commands, store handling)Views/MainWindow.axaml(add menu, toolbar, context menu)JdeScoping.ConfigManager.Tests.csproj(add Avalonia.Headless.XUnit)
Implementation Order
- Phase 1 - Project setup (dependencies)
- Phase 2 - Copy service layer (foundation)
- Phase 3 - Extend tree model (data structure)
- Phase 4 - Create ViewModels (business logic)
- Phase 5 - Create Views (UI)
- Phase 6 - Update MainWindowViewModel (orchestration)
- Phase 7 - Update menus/toolbar (discoverability)
- Phase 8 - Register services (wiring)
- Phase 9 - Migrate tests (verification)
Verification Checklist
After implementation, verify:
- Can create new secure store with key file
- Can create new secure store with password
- Can add existing store to tree
- Stores appear locked by default
- Double-click unlocks store (shows dialog)
- Unlock button in form works
- Unlocked store shows secrets in tree
- Can add new secret
- Can edit existing secret
- Can delete secret
- Secret value is masked by default
- Show/Hide toggle works
- Copy to clipboard works
- Can lock store manually
- Lock All Stores works
- Save Store saves only that store
- Save (Ctrl+S) only saves config
- Unsaved changes indicator works for stores
- Generate Key File works
- All tests pass