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

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.ServicesJdeScoping.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:

  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":

<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.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