Skip to content
Open
Changes from all commits
Commits
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
318 changes: 75 additions & 243 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/<VaultName>/`. Each vault has its own `shard` file (used for encryption) and one JSON
file per context, named with a unique GUID:

```plaintext
$HOME/.contextvaults/
Expand All @@ -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
Comment thread
MariusStorhaug marked this conversation as resolved.
Comment thread
MariusStorhaug marked this conversation as resolved.
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/<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.
```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://github.com/ghapi'
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/<VaultName>/`
- **SecureString Support**: The module automatically handles encryption/decryption of `SecureString` values
Loading