diff --git a/README.md b/README.md index 00a1f92..13f2198 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,31 @@ # 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, -provided the service supports session refresh mechanisms (e.g., refresh tokens). +Most modules work with two kinds of data that benefit from persistent, secure storage: module settings and the user's own +settings and secrets. Context provides a single, secure place to keep that data so it lives separately from your module code. +Users can pick up where they left off without reconfiguring the module or signing in again, as long as the service supports +session refresh (for example, refresh tokens). -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. +Context data is encrypted at rest using NaCl-based encryption from the `libsodium` library, delivered through the +[`Sodium`](https://github.com/PSModule/Sodium) module. `Sodium` is installed automatically when you install this module. -## What is a `Context`? +## 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 in the module that implements it. -Any data that can be represented in JSON format can be stored in a `Context`. +A `context` is a collection of data that is relevant to a specific use case, such as user settings, secrets, and module +configuration. Context stores that data securely and gives you a consistent set of commands to manage it across every module +that adopts it. Each context is identified by a unique ID, and anything that can be represented as JSON can be stored in it. -## Grouping Contexts into Vaults +When you save a context, any `SecureString` values are handled for you: they are marked, encrypted with `Sodium`, and restored +back to `SecureString` when the context is read. `Get-ContextInfo` returns a context's metadata (its ID, vault, and storage +path) without decrypting the stored data. -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. +## Vaults -### Directory Structure +Contexts are grouped into vaults, which are logical containers for related contexts. Vaults keep data organized when you work +with multiple users or modules, and each vault is isolated with its own encryption key and storage directory. A vault is +created automatically the first time you store a context in it. + +Vaults live under `$HOME/.contextvaults//`. Each vault has its own `shard` file (used for encryption) and one JSON +file per context, named with a unique GUID: ```plaintext $HOME/.contextvaults/ @@ -37,271 +39,101 @@ $HOME/.contextvaults/ │ └── 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. +Contexts in different vaults are completely isolated from each other. -### 1. Storing data (object or dictionary) to disk using `Set-Context` +## Installation -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. +Install the module from the PowerShell Gallery: -```pwsh -Set-Context -ID 'john_doe' -Vault 'GitHub' -Context ([PSCustomObject]@{ - Username = 'john_doe' - AuthToken = 'ghp_12345ABCDE67890FGHIJ' | ConvertTo-SecureString -AsPlainText -Force # gitleaks:allow - LoginTime = Get-Date - IsTwoFactorAuth = $true - TwoFactorMethods = @('TOTP', 'SMS') -}) +```powershell +Install-PSResource -Name Context +Import-Module -Name Context ``` -### 2. The object is converted to JSON and prepared for encryption +## Usage -The object that is passed into `Set-Context` is first analyzed. If the object contains any `SecureString` values, they are converted to plain-text and -prefixed with `[SECURESTRING]`. This indicates that these values should be restored back to `SecureString`. The whole object is then converted to a -JSON string. +### Example: Store and retrieve a context -```json -{ - "ID": "john_doe", - "Username": "john_doe", - "AuthToken": "[SECURESTRING]ghp_12345ABCDE67890FGHIJ", - "LoginTime": "2024-11-21T21:16:56.2518249+01:00", - "IsTwoFactorAuth": true, - "TwoFactorMethods": ["TOTP", "SMS"] -} -``` +Store an object with `Set-Context`, then read it back with `Get-Context`. The vault is created automatically if it does not +exist, and `SecureString` values round-trip securely. -### 3. Storing the context object to disk - -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//.json`, where `` 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. +```powershell +Set-Context -ID 'john_doe' -Vault 'GitHub' -Context ([PSCustomObject]@{ + Username = 'john_doe' + AuthToken = 'ghp_12345ABCDE67890FGHIJ' | ConvertTo-SecureString -AsPlainText -Force # gitleaks:allow + LoginTime = Get-Date + TwoFactorMethods = @('TOTP', 'SMS') +}) -```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" -} +Get-Context -ID 'john_doe' -Vault 'GitHub' ``` -The metadata can be accessed using the `Get-ContextInfo` function. +### Example: Inspect metadata without decrypting -## Installation +```powershell +Get-ContextInfo -Vault 'GitHub' +``` -You can install the module from the PowerShell Gallery using the following command: +### Example: Manage vaults and contexts ```powershell -Install-PSResource -Name Context -TrustRepository -Repository PSGallery -Import-Module -Name Context -``` +# List every vault +Get-ContextVault + +# Rename a context +Rename-Context -ID 'john_doe' -NewID 'jdoe' -Vault 'GitHub' -## Implementation Guide for Module Developers +# Remove a single context +Remove-Context -ID 'jdoe' -Vault 'GitHub' -This section shows how to integrate the Context module into your PowerShell module to provide persistent, secure storage for module settings and user data. +# Remove an entire vault and all of its contexts (use with caution) +Remove-ContextVault -Name 'GitHub' +``` -### Quick Start +## Implementing Context in your module -The simplest way to implement Contexts is using `Set-Context`, which automatically creates the vault if it doesn't exist: +Use Context to give your own module persistent, secure storage for settings and user data. The simplest approach is to call +`Set-Context` with your module name as the vault; the vault is created on first use and existing encryption keys are preserved. -```pwsh -# Store module configuration - vault is created automatically +```powershell Set-Context -ID 'ModuleSettings' -Vault 'MyModule' -Context @{ DefaultApiEndpoint = 'https://api.example.com' - TimeoutSeconds = 30 -} - -# Store user credentials -Set-Context -ID 'User.JohnDoe' -Vault 'MyModule' -Context @{ - Username = 'johndoe' - ApiKey = 'secret-key' | ConvertTo-SecureString -AsPlainText -Force - LastLogin = Get-Date + TimeoutSeconds = 30 } ``` -### Best Practices for Module Integration +A common pattern is to wrap the Context commands so callers never need to pass the vault parameter: -#### 1. Create Wrapper Functions - -Create module-specific wrapper functions to provide a familiar interface for your users: - -```pwsh -# In your module +```powershell function Set-MyModuleContext { param( - [Parameter(Mandatory)] - [string] $ID, - - [Parameter(Mandatory)] - [object] $Context + [Parameter(Mandatory)] [string] $ID, + [Parameter(Mandatory)] [object] $Context ) - Set-Context -ID $ID -Vault 'MyModule' -Context $Context } function Get-MyModuleContext { - param( - [string] $ID = '*' - ) - + param([string] $ID = '*') Get-Context -ID $ID -Vault 'MyModule' } ``` -#### 2. Module Configuration Pattern - -Store module-wide settings that persist across sessions: - -```pwsh -# 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 - } -} -``` +Key points to keep in mind: -#### 3. User Context Pattern +- Every context lives in a named vault — there is no default vault. +- `Set-Context` creates vaults automatically and preserves existing encryption keys. +- Use your module name as the vault name to keep related contexts together. +- Store module-wide settings separately from per-user data. +- `SecureString` values are encrypted and decrypted for you. -Handle multiple user accounts within your module: +## Documentation -```pwsh -# Store user-specific data -function Connect-MyService { - param( - [Parameter(Mandatory)] - [string] $Username, +Documentation is published at [psmodule.io/Context](https://psmodule.io/Context/). - [Parameter(Mandatory)] - [SecureString] $ApiKey - ) +Use PowerShell help and command discovery for module details: - # 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 -} -``` - -### Complete Example - -Here's a complete example of how to implement Contexts in a hypothetical GitHub module: - -```pwsh -# 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 - } -} -``` - -### 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 -# 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" -``` - -## Context Operations - -### Basic Operations - -```pwsh -# Store a context (creates vault automatically) -Set-Context -ID 'UserSettings' -Vault 'MyModule' -Context @{ - Theme = 'Dark' - Language = 'en-US' -} - -# Retrieve a context -Get-Context -ID 'UserSettings' -Vault 'MyModule' - -# Get all contexts in a vault -Get-Context -Vault 'MyModule' - -# Remove a context -Remove-Context -ID 'UserSettings' -Vault 'MyModule' - -# Rename a context -Rename-Context -ID 'OldName' -NewID 'NewName' -Vault 'MyModule' - -# Get context metadata (without decrypting) -Get-ContextInfo -Vault 'MyModule' +```powershell +Get-Command -Module Context +Get-Help -Name Get-Context -Examples ``` - -## Important Notes - -- **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//` -- **SecureString Support**: The module automatically handles encryption/decryption of `SecureString` values