Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6f12a0b
Initial plan for issue
Copilot Jun 3, 2025
91460a5
Implement core ContextVaults multi-vault functionality
Copilot Jun 3, 2025
816b9c4
Complete ContextVaults implementation with tests and documentation
Copilot Jun 3, 2025
ce4fa41
Add Set-ContextVault function and update documentation
Copilot Jun 3, 2025
90ea688
Remove main.ps1 loading logic and private Set-ContextVault function
Copilot Jun 3, 2025
0b756c3
Refactor context vault management functions and update configuration …
MariusStorhaug Jun 4, 2025
3e7639b
Fix argument completer command name filtering for context vault and c…
MariusStorhaug Jun 4, 2025
31511ac
Fix context folder and shard path references in Set-ContextVault func…
MariusStorhaug Jun 4, 2025
3b913b0
Refactor error handling in Remove-ContextVault and Reset-ContextVault…
MariusStorhaug Jun 4, 2025
11b4dc8
Enhance context vault management:
MariusStorhaug Jun 4, 2025
a89c2d8
Refactor Get-ContextVault function to store vaults in a variable for …
MariusStorhaug Jun 4, 2025
f1cfd0d
Fix Remove-ContextVault call in Reset-ContextVault function to suppre…
MariusStorhaug Jun 4, 2025
1d42c3c
Update Set-Context and Get-ContextVault functions for improved contex…
MariusStorhaug Jun 4, 2025
373adb6
Add early return in Get-ContextVault for empty vaults to improve effi…
MariusStorhaug Jun 4, 2025
c3c4568
Enhance ContextVault class with additional properties and update rela…
MariusStorhaug Jun 4, 2025
5af3048
Refactor ContextVaultList view to group ListItems for improved struct…
MariusStorhaug Jun 4, 2025
3a707e7
Refactor Reset-ContextVault to use vault item names for improved clar…
MariusStorhaug Jun 4, 2025
348fd72
Refactor ContextVaults.Tests to improve readability by removing unnec…
MariusStorhaug Jun 4, 2025
f587fb7
Remove unnecessary comments in Reset-ContextVault test for clarity
MariusStorhaug Jun 4, 2025
133aca6
Refactor Set-Context function to streamline vault path determination …
MariusStorhaug Jun 5, 2025
3bd7e0d
Refactor context management functions: remove deprecated functions an…
MariusStorhaug Jun 5, 2025
19cb291
Refactor Get-ContextInfo function to simplify context ID matching and…
MariusStorhaug Jun 7, 2025
2a6795d
Enhance context management: add ToString method in ContextVault, impr…
MariusStorhaug Jun 7, 2025
d7728f7
Refactor context vault management: simplify ContextVault class by rem…
MariusStorhaug Jun 7, 2025
91ae365
Refactor context vault tests: remove assertions for unused properties…
MariusStorhaug Jun 7, 2025
e5e8e73
Fix variable reference in Get-ContextVault function to correctly inst…
MariusStorhaug Jun 7, 2025
bd9aeea
Fix variable reference in Get-ContextVault function to use correct pa…
MariusStorhaug Jun 7, 2025
bf3aced
Refactor Get-ContextVault function: update loop to iterate over name …
MariusStorhaug Jun 7, 2025
a545574
Refactor Get-ContextInfo and Set-Context functions: streamline vault …
MariusStorhaug Jun 7, 2025
00db4a7
Refactor context tests: streamline vault management and enhance clari…
MariusStorhaug Jun 7, 2025
b5bb0a9
Refactor context tests: remove unnecessary Out-Null calls for vault c…
MariusStorhaug Jun 7, 2025
9d3dca0
Enhance Set-Context test: add -Debug flag to improve debugging output…
MariusStorhaug Jun 7, 2025
4e3253b
Refactor context vault key retrieval: rename Get-ContextVaultKeys to …
MariusStorhaug Jun 7, 2025
33fc784
Refactor argument completer registration: improve readability by form…
MariusStorhaug Jun 7, 2025
c785966
Add Get-ContextVaultKeyPair function: implement key retrieval from co…
MariusStorhaug Jun 7, 2025
4d0c3d1
Fix output type documentation for multiple functions: standardize out…
MariusStorhaug Jun 7, 2025
37b7337
Enhance Set-Context function: improve debug output by adding stack pa…
MariusStorhaug Jun 7, 2025
0c8ada6
Enhance Set-Context function: add verbose output for vault object and…
MariusStorhaug Jun 7, 2025
9bc07eb
Enhance Set-Context test: add verbose flag to improve output detail d…
MariusStorhaug Jun 7, 2025
ed0d4a7
Refactor Get-ContextInfo function: improve file processing and debug …
MariusStorhaug Jun 7, 2025
2307592
Enhance Set-Context function: improve verbose output for context info…
MariusStorhaug Jun 7, 2025
6f1f927
Enhance Set-Context test: add -Confirm:$false flag to prevent confirm…
MariusStorhaug Jun 7, 2025
c7a0924
Refactor Get-ContextInfo function: rename variable for clarity in fil…
MariusStorhaug Jun 7, 2025
4b24450
Enhance Get-ContextInfo function: add detailed debug output for conte…
MariusStorhaug Jun 7, 2025
9f81cd1
Enhance Get-ContextInfo function: improve debug output handling for b…
MariusStorhaug Jun 7, 2025
db3bce5
Refactor Get-Context and Get-ContextInfo functions: standardize strin…
MariusStorhaug Jun 7, 2025
a346c64
Cleanup Context.Tests: remove commented-out tests and redundant clean…
MariusStorhaug Jun 7, 2025
d18c82c
Implement context vault key retrieval and JSON conversion functions: …
MariusStorhaug Jun 7, 2025
ef5a905
Add tests for Get-ContextInfo function: validate context retrieval fo…
MariusStorhaug Jun 7, 2025
80be221
Add tests for Get-ContextInfo function: validate context retrieval fo…
MariusStorhaug Jun 7, 2025
caadb7b
Fix test setup and teardown in ContextVaults.Tests: ensure proper vau…
MariusStorhaug Jun 7, 2025
f883a61
Refactor Context.Tests: replace Write-Verbose with Write-Host for out…
MariusStorhaug Jun 7, 2025
57607d8
Update Get-ContextInfo test: expect three contexts for wildcard ID ma…
MariusStorhaug Jun 7, 2025
e724071
Refactor README.md: streamline context and vault explanations, enhanc…
MariusStorhaug Jun 7, 2025
e6148ef
Refactor test files: update suppression messages for clarity and cons…
MariusStorhaug Jun 7, 2025
4b30664
Add argument completers for Context ID and Vault Name parameters
MariusStorhaug Jun 7, 2025
585d3b4
Refactor completer functions: add CmdletBinding attribute to Complete…
MariusStorhaug Jun 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 203 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
# Context

Modules typically handle two types of data that benefit from persistent secure storage and management: module settings and user settings and secrets.
This module introduces the concept of `Contexts`, which enable persistent and secure data storage for PowerShell modules. It allows module developers to
separate user and module data from the module code, enabling users to resume their work without needing to reconfigure the module or log in again,
This module introduces the concept of `Contexts`, which enable persistent and secure data storage for PowerShell modules. It allows module developers
to separate user and module data from the module code, enabling users to resume their work without needing to reconfigure the module or log in again,
provided the service supports session refresh mechanisms (e.g., refresh tokens).

The module uses NaCl-based encryption, provided by the `libsodium` library, to encrypt and decrypt `Context` data. The module that delivers this
functionality is called [`Sodium`](https://github.com/PSModule/Sodium) and is a dependency of this module. The
[`Sodium`](https://github.com/PSModule/Sodium) module is automatically installed when you install this module.
The module uses NaCl-based encryption, provided by the `libsodium` library (delivered via the [`Sodium`](https://github.com/PSModule/Sodium) module),
to encrypt and decrypt `Context` data. The [`Sodium`](https://github.com/PSModule/Sodium) module is automatically installed when you install this
module.

## What is a `Context`?

The concept of `Context` is widely used to represent a collection of data that is relevant to a specific use-case. In this module,
a `Context` is a way to securely persist user and module data and offers a set of functions to manage this across modules that implement it.
Data that is stored in a `Context` can include user-specific settings, secrets, and module configuration data.
A `Context` is identified by a unique ID, which is typically a string that represents the module and user of a module (e.g., `GitHub/john_doe`), but
this is just an example. Any data that can be represented in JSON format can be stored in a `Context`.
A `Context` is identified by a unique ID in the module that implements it.
Any data that can be represented in JSON format can be stored in a `Context`.

## Grouping Contexts into Vaults

Contexts can be grouped into `ContextVaults`, which are logical containers for related contexts. This allows for organization and management of
contexts, especially when dealing with multiple users or modules. Vaults are automatically created when you store a context, and they can be managed
using the provided functions in this module.

### Directory Structure

```plaintext
$HOME/.contextvaults/
├── GitHub/
│ ├── 64a5bbaf-96b8-4090-a77d-75e02ab6c4e0.json
│ ├── f201dc50-c163-4a7a-8d69-aea7f696737d.json
│ └── shard
├── AzureDevOps/
│ ├── cf49fceb-38d1-47da-a0ae-219ac40e4d8c.json
│ ├── b521a424-dd1c-445b-a0d6-c26a29d93654.json
│ └── shard
```

In this example there are two named vaults (`GitHub` and `AzureDevOps`). Each vault contains its own `shard` file (for encryption) and two context
files (with unique GUID filenames) that store encrypted context data. Contexts in different vaults are completely isolated from each other.

### 1. Storing data (object or dictionary) to disk using `Set-Context`

To store data to disk, use the `Set-Context` function. The function needs an ID and the data object.
The object can be anything that can be converted and represented in JSON format.

```pwsh
Set-Context -ID 'john_doe' -Context ([PSCustomObject]@{
Set-Context -ID 'john_doe' -Vault 'GitHub' -Context ([PSCustomObject]@{
Username = 'john_doe'
AuthToken = 'ghp_12345ABCDE67890FGHIJ' | ConvertTo-SecureString -AsPlainText -Force # gitleaks:allow
LoginTime = Get-Date
Expand Down Expand Up @@ -51,15 +74,16 @@ JSON string.

### 3. Storing the context object to disk

When the data is primed for storage, it is finally encrypted using the `Sodium` module and saved to disk. The file is stored in the user's home
directory `$HOME\.contextvault\<context_id>.json`, where `<context_id>` is a generated GUID, providing a unique name for the file.
Finally the data is encrypted using the `Sodium` module and saved to disk. The file is stored in the user's home
directory `$HOME/.contextvaults/<VaultName>/<context_id>.json`, where `<context_id>` is a generated GUID, providing a unique name for the file.
The encrypted JSON representation of the data is added to metadata object that holds other info such as the ID of the `Context` and the path to the
file where it is stored.

```json
{
"ID": "PSModule.GitHub/github.com/john_doe",
"Path": "C:\\Users\\JohnDoe\\.contextvault\\d2edaa6e-95a1-41a0-b6ef-0ecc5d116030.json",
"ID": "github.com/john_doe",
"Vault": "GitHub",
"Path": "C:\\Users\\JohnDoe\\.contextvaults\\GitHub\\d2edaa6e-95a1-41a0-b6ef-0ecc5d116030.json",
"Context": "0kGmtbQiEtih7 --< encrypted context data >-- ceqbMiBilUvEzO1Lk"
}
```
Expand All @@ -75,89 +99,209 @@ Install-PSResource -Name Context -TrustRepository -Repository PSGallery
Import-Module -Name Context
```

## Usage
## Implementation Guide for Module Developers

Let's take a closer look at how to store these types of data using the module.
This section shows how to integrate the Context module into your PowerShell module to provide persistent, secure storage for module settings and user data.

### Module Settings
### Quick Start

A module developer can create additional `Contexts` for settings that share the same lifecycle, such as those associated with a module extension.
The simplest way to implement Contexts is using `Set-Context`, which automatically creates the vault if it doesn't exist:

For example, if we have a module called `GitHub` that needs to store some settings, the module developer could initialize a `Context` called `GitHub`
as part of the loading section in the module code. The module configuration is accessed using the ID `GitHub`.
```pwsh
# Store module configuration - vault is created automatically
Set-Context -ID 'ModuleSettings' -Vault 'MyModule' -Context @{
DefaultApiEndpoint = 'https://api.example.com'
TimeoutSeconds = 30
}

### User Configuration
# Store user credentials
Set-Context -ID 'User.JohnDoe' -Vault 'MyModule' -Context @{
Username = 'johndoe'
ApiKey = 'secret-key' | ConvertTo-SecureString -AsPlainText -Force
LastLogin = Get-Date
}
```

To store user data, a module developer can create a `Context` that serves as a "namespace" for user-specific configurations.
### Best Practices for Module Integration

Imagine a user named `BobMarley` logs into the `GitHub` module. The following logical structure would be created:
#### 1. Create Wrapper Functions

- `GitHub`: Contains module configuration, like default user, host, and client ID.
- `GitHub/BobMarley`: Contains user configuration, secrets, and default values for API calls.
Create module-specific wrapper functions to provide a familiar interface for your users:

If the same user logs in with another account (`LennyKravitz`), an additional `Context` will be created:
```pwsh
# In your module
function Set-MyModuleContext {
param(
[Parameter(Mandatory)]
[string] $ID,

- `GitHub/LennyKravitz`: Contains user-specific settings and secrets.
[Parameter(Mandatory)]
[object] $Context
)

This allows users to set a default `Context`, storing its name in the module `Context`, enabling automatic login to the correct account when the
module loads. Users can also switch between accounts by changing the default `Context`.
Set-Context -ID $ID -Vault 'MyModule' -Context $Context
}

### Setup for a New Module
function Get-MyModuleContext {
param(
[string] $ID = '*'
)

1. Create a new context for the module:
Get-Context -ID $ID -Vault 'MyModule'
}
```

#### 2. Module Configuration Pattern

Store module-wide settings that persist across sessions:

```pwsh
Set-Context -ID 'GitHub' -Context @{ Name = 'GitHub' }
# Initialize module settings on first load
if (-not (Get-Context -ID 'Settings' -Vault 'MyModule' -ErrorAction SilentlyContinue)) {
Set-Context -ID 'Settings' -Vault 'MyModule' -Context @{
DefaultUser = $null
ApiEndpoint = 'https://api.example.com'
EnableLogging = $false
}
}
```

2. Add module configuration:
#### 3. User Context Pattern

Handle multiple user accounts within your module:

```pwsh
$context = Get-Context -ID 'GitHub'
# Modify settings as needed
Set-Context -ID 'GitHub' -Context $context
# Store user-specific data
function Connect-MyService {
param(
[Parameter(Mandatory)]
[string] $Username,

[Parameter(Mandatory)]
[SecureString] $ApiKey
)

# Store user context
Set-Context -ID "User.$Username" -Vault 'MyModule' -Context @{
Username = $Username
ApiKey = $ApiKey
ConnectedAt = Get-Date
}

# Update module settings to remember the current user
$moduleSettings = Get-Context -ID 'Settings' -Vault 'MyModule'
$moduleSettings.DefaultUser = $Username
Set-Context -ID 'Settings' -Vault 'MyModule' -Context $moduleSettings
}
```

### Setup for a New User Context
### Complete Example

1. Create a set of public integration functions using the `Context` module to store user data. This is highly recommended, as it allows module
developers to define a structured `Context` while providing users with familiar function names for interaction.
- `Set-<ModuleName>Context` that uses `Set-Context`.
- `Get-<ModuleName>Context` that uses `Get-Context`.
- `Remove-<ModuleName>Context` that uses `Remove-Context`.

2. Create a new `Context` for the user:
Here's a complete example of how to implement Contexts in a hypothetical GitHub module:

```pwsh
Connect-GitHub ...
Set-Context -ID 'GitHub.BobMarley'
# Module initialization (in .psm1 file)
$VaultName = 'GitHub'

# Initialize module settings if they don't exist
if (-not (Get-Context -ID 'ModuleSettings' -Vault $VaultName -ErrorAction SilentlyContinue)) {
Set-Context -ID 'ModuleSettings' -Vault $VaultName -Context @{
DefaultOrganization = $null
ApiEndpoint = 'https://api.github.com'
DefaultUser = $null
}
}

# Public function to connect user
function Connect-GitHub {
param(
[Parameter(Mandatory)]
[string] $Username,

[Parameter(Mandatory)]
[string] $Token
)

# Store user context with secure token
Set-Context -ID "User.$Username" -Vault $VaultName -Context @{
Username = $Username
Token = $Token | ConvertTo-SecureString -AsPlainText -Force
Organizations = @()
LastConnected = Get-Date
}

# Set as default user
$settings = Get-Context -ID 'ModuleSettings' -Vault $VaultName
$settings.DefaultUser = $Username
Set-Context -ID 'ModuleSettings' -Vault $VaultName -Context $settings

Write-Host "Connected to GitHub as $Username"
}

# Public function to get current user context
function Get-GitHubUser {
$settings = Get-Context -ID 'ModuleSettings' -Vault $VaultName
if ($settings.DefaultUser) {
Get-Context -ID "User.$($settings.DefaultUser)" -Vault $VaultName
}
}
```

3. Modify user configuration:
### Key Implementation Points

- **Automatic Vault Creation**: `Set-Context` creates the vault automatically if it doesn't exist, and preserves existing encryption keys
- **Consistent Vault Naming**: Use your module name as the vault name for organization
- **Wrapper Functions**: Provide module-specific functions that hide the vault parameter from users
- **SecureString Support**: The Context module automatically handles `SecureString` encryption and decryption
- **Module Settings**: Store module-wide configuration separate from user-specific data

## Vault Management (Advanced)

For most use cases, you don't need to manage vaults directly since `Set-Context` creates them automatically. However, you can manage vaults explicitly when needed:

```pwsh
$context = Get-Context -ID 'GitHub.BobMarley'
# Modify settings
Set-Context -ID 'GitHub.BobMarley' -Context $context
# List all vaults
Get-ContextVault

# Get specific vault information
Get-ContextVault -Name "MyModule"

# Remove a vault and all its contexts (use with caution)
Remove-ContextVault -Name "OldModule"
```

4. Retrieve user configuration:
## Context Operations

### Basic Operations

```pwsh
Get-Context -ID 'GitHub/BobMarley'
```
# Store a context (creates vault automatically)
Set-Context -ID 'UserSettings' -Vault 'MyModule' -Context @{
Theme = 'Dark'
Language = 'en-US'
}

## Contributing
# Retrieve a context
Get-Context -ID 'UserSettings' -Vault 'MyModule'

### For Users
# Get all contexts in a vault
Get-Context -Vault 'MyModule'

Even if you don’t code, your insights can help improve the project. If you experience unexpected behavior, errors, or missing functionality, submit a
bug or feature request in the project's issue tracker.
# Remove a context
Remove-Context -ID 'UserSettings' -Vault 'MyModule'

### For Developers
# Rename a context
Rename-Context -ID 'OldName' -NewID 'NewName' -Vault 'MyModule'

If you code, we'd love your contributions! Please read the [Contribution Guidelines](CONTRIBUTING.md) for more details.
# Get context metadata (without decrypting)
Get-ContextInfo -Vault 'MyModule'
```

## Links
## Important Notes

- Sodium [GitHub](https://github.com/PSModule/Sodium) | [PSGallery](https://www.powershellgallery.com/packages/Sodium)
- **Vault Requirement**: Every context must be stored in a named vault - there is no default vault
- **Automatic Vault Creation**: `Set-Context` automatically creates vaults if they don't exist
- **Encryption Key Preservation**: Existing vault encryption keys are preserved when using `Set-Context`
- **Vault Isolation**: Each vault is isolated with its own encryption keys and storage directory
- **Storage Location**: Vaults are stored under `$HOME/.contextvaults/<VaultName>/`
- **SecureString Support**: The module automatically handles encryption/decryption of `SecureString` values
15 changes: 15 additions & 0 deletions src/classes/public/ContextVault.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class ContextVault {
[string] $Name
[string] $Path

ContextVault() {}

ContextVault([string] $name, [string] $path) {
$this.Name = $name
$this.Path = $path
}

[string] ToString() {
return $this.Name
}
}
9 changes: 0 additions & 9 deletions src/completers.ps1

This file was deleted.

Loading
Loading