Files
jdescopingtool/PLANS/2026-01-20-securestore-integration.md
T
Joseph Doherty 94d5a864e0 feat(configmanager): integrate SecureStore for credential management
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.
2026-01-20 02:51:16 -05:00

514 lines
16 KiB
Markdown

# 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:
```xml
<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:
```xml
<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`
```csharp
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`:
```csharp
/// <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:
```csharp
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:
```xml
<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
```csharp
private readonly IStoreUseCases _storeUseCases;
private readonly ISecretUseCases _secretUseCases;
private readonly IClipboardService _clipboardService;
```
### 6.2 Add Secure Store Commands
```csharp
// 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
```csharp
// 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:
1. Create "Secure Stores" folder node
2. Discover `*.secrets.json` files in config folder
3. Create SecureStore nodes for each discovered file
4. Add manual store tracking
### 6.5 Update Form Selection Logic
Extend `OnSelectedNodeChanged` to handle:
- `SecureStoresFolder` → Show instructions/empty state
- `SecureStore` (locked) → Show `SecureStoreLockedFormViewModel`
- `SecureStore` (unlocked) → Show `SecureStoreUnlockedFormViewModel`
- `Secret` → Show `SecretFormViewModel`
---
## Phase 7: Update Menu Bar and Toolbar
### 7.1 Add Secure Stores Menu
**File:** `Views/MainWindow.axaml`
Add new menu between "Tools" and "Help":
```xml
<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:
```xml
<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`:
```csharp
// 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.cs`
- `Services/SecureStore/SecureStoreManager.cs`
- `Services/IClipboardService.cs`
- `Services/AvaloniaClipboardService.cs`
**Application (2 files):**
- `Application/StoreUseCases.cs`
- `Application/SecretUseCases.cs`
**Constants (2 files):**
- `Constants/SecureStoreStrings.cs`
- `Constants/SecureStoreFileExtensions.cs`
**ViewModels (6 files):**
- `ViewModels/Forms/SecureStoreLockedFormViewModel.cs`
- `ViewModels/Forms/SecureStoreUnlockedFormViewModel.cs`
- `ViewModels/Forms/SecretFormViewModel.cs`
- `ViewModels/Dialogs/NewStoreDialogViewModel.cs`
- `ViewModels/Dialogs/UnlockStoreDialogViewModel.cs`
- `ViewModels/Dialogs/SecretEditDialogViewModel.cs`
**Views (6 files):**
- `Views/Forms/SecureStoreLockedFormView.axaml` + `.cs`
- `Views/Forms/SecureStoreUnlockedFormView.axaml` + `.cs`
- `Views/Forms/SecretFormView.axaml` + `.cs`
- `Views/Dialogs/NewStoreDialog.axaml` + `.cs`
- `Views/Dialogs/UnlockStoreDialog.axaml` + `.cs`
- `Views/Dialogs/SecretEditDialog.axaml` + `.cs`
**Tests (8+ files):**
- `Services/SecureStore/SecureStoreManagerTests.cs`
- `ViewModels/Forms/SecureStoreLockedFormViewModelTests.cs`
- `ViewModels/Forms/SecureStoreUnlockedFormViewModelTests.cs`
- `ViewModels/Forms/SecretFormViewModelTests.cs`
- `ViewModels/Dialogs/NewStoreDialogViewModelTests.cs`
- `ViewModels/Dialogs/UnlockStoreDialogViewModelTests.cs`
- `ViewModels/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
1. **Phase 1** - Project setup (dependencies)
2. **Phase 2** - Copy service layer (foundation)
3. **Phase 3** - Extend tree model (data structure)
4. **Phase 4** - Create ViewModels (business logic)
5. **Phase 5** - Create Views (UI)
6. **Phase 6** - Update MainWindowViewModel (orchestration)
7. **Phase 7** - Update menus/toolbar (discoverability)
8. **Phase 8** - Register services (wiring)
9. **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