Skip to content

Commit 69aafaa

Browse files
Merge PR #15 command standards hardening
2 parents f177577 + e444b23 commit 69aafaa

42 files changed

Lines changed: 1381 additions & 322 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/functions/private/Auth/Get-DomeneshopConfig.ps1

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
1+
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }
2+
13
function Get-DomeneshopConfig {
4+
<#
5+
.SYNOPSIS
6+
Get the Domeneshop module configuration.
7+
8+
.DESCRIPTION
9+
Read the module configuration from the Context vault or return an in-memory default when no configuration has been stored.
10+
11+
.EXAMPLE
12+
Get-DomeneshopConfig
13+
14+
Get the current Domeneshop module configuration.
15+
16+
.INPUTS
17+
None
18+
19+
You can't pipe objects to Get-DomeneshopConfig.
20+
21+
.OUTPUTS
22+
System.Object
23+
24+
The stored or default Domeneshop module configuration.
25+
26+
.NOTES
27+
This read helper does not create or update Context vault entries.
28+
29+
.LINK
30+
https://psmodule.io/Context/
31+
#>
32+
[OutputType([object])]
233
[CmdletBinding()]
334
param()
435

536
$vault = 'Domeneshop'
637
$id = '__Domeneshop.Config'
7-
$config = Get-Context -ID $id -Vault $vault -ErrorAction SilentlyContinue
38+
$config = Get-Context -ID $id -Vault $vault
839

940
if (-not $config) {
10-
$config = [ordered]@{
41+
$config = [pscustomobject][ordered]@{
1142
DefaultContext = $null
1243
ApiBaseUri = 'https://api.domeneshop.no/v0'
1344
}
14-
Set-Context -ID $id -Vault $vault -Context $config
15-
$config = Get-Context -ID $id -Vault $vault
1645
}
1746

1847
$config
Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,52 @@
11
function Resolve-DomeneshopContext {
2+
<#
3+
.SYNOPSIS
4+
Validate a resolved Domeneshop context.
5+
6+
.DESCRIPTION
7+
Verify that a context resolved by a public command contains the credentials and API endpoint required by private helpers.
8+
9+
.EXAMPLE
10+
Resolve-DomeneshopContext -Context $storedContext
11+
12+
Validate and emit a stored Domeneshop context.
13+
14+
.INPUTS
15+
None
16+
17+
You can't pipe objects to Resolve-DomeneshopContext.
18+
19+
.OUTPUTS
20+
System.Object
21+
22+
The validated Domeneshop context.
23+
24+
.NOTES
25+
This helper never selects or defaults a context. Public commands own that behavior.
26+
27+
.LINK
28+
https://psmodule.io/Context/
29+
#>
30+
[OutputType([object])]
231
[CmdletBinding()]
332
param(
4-
[Parameter()]
5-
[string] $Context
33+
# The context object selected by a public command.
34+
[Parameter(Mandatory)]
35+
[AllowNull()]
36+
[object] $Context
637
)
738

8-
$resolvedContext = if ($Context) {
9-
Get-DomeneshopContext -Context $Context
10-
} else {
11-
Get-DomeneshopContext
39+
if ($null -eq $Context) {
40+
throw "No Domeneshop context found. Run 'Connect-DomeneshopAccount' first."
1241
}
1342

14-
if (-not $resolvedContext) {
15-
throw "No Domeneshop context found. Run 'Connect-DomeneshopAccount' first."
43+
if ([string]::IsNullOrWhiteSpace([string] $Context.Token) -or -not ($Context.Secret -is [securestring])) {
44+
throw "The Domeneshop context [$($Context.ID)] is missing valid credentials."
1645
}
1746

18-
if ([string]::IsNullOrEmpty($resolvedContext.Token) -or -not ($resolvedContext.Secret -is [securestring])) {
19-
throw "The Domeneshop context [$($resolvedContext.ID)] is missing valid credentials."
47+
if ([string]::IsNullOrWhiteSpace([string] $Context.ApiBaseUri)) {
48+
throw "The Domeneshop context [$($Context.ID)] is missing an API base URI."
2049
}
2150

22-
$resolvedContext
51+
$Context
2352
}
Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,46 @@
1+
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }
2+
13
function Set-DomeneshopDefaultContext {
2-
[CmdletBinding()]
4+
<#
5+
.SYNOPSIS
6+
Set the default Domeneshop context name.
7+
8+
.DESCRIPTION
9+
Persist the selected default context name in the Domeneshop configuration entry.
10+
11+
.EXAMPLE
12+
Set-DomeneshopDefaultContext -Context 'production'
13+
14+
Set production as the default Domeneshop context.
15+
16+
.INPUTS
17+
None
18+
19+
You can't pipe objects to Set-DomeneshopDefaultContext.
20+
21+
.OUTPUTS
22+
None
23+
24+
Set-DomeneshopDefaultContext doesn't emit output.
25+
26+
.NOTES
27+
The public caller is responsible for guarding this mutation with ShouldProcess.
28+
29+
.LINK
30+
https://psmodule.io/Context/
31+
#>
32+
[OutputType([void])]
33+
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
334
param(
35+
# The name of an existing Domeneshop context.
436
[Parameter(Mandatory)]
37+
[ValidateNotNullOrEmpty()]
538
[string] $Context
639
)
740

841
$config = Get-DomeneshopConfig
942
$config.DefaultContext = $Context
10-
Set-Context -ID '__Domeneshop.Config' -Vault 'Domeneshop' -Context $config
43+
if ($PSCmdlet.ShouldProcess('Domeneshop module configuration', "Set [$Context] as the default context")) {
44+
$null = Set-Context -ID '__Domeneshop.Config' -Vault 'Domeneshop' -Context $config
45+
}
1146
}
Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,50 @@
11
function Get-DomeneshopApiBaseUri {
2+
<#
3+
.SYNOPSIS
4+
Get the API base URI from a resolved Domeneshop context.
5+
6+
.DESCRIPTION
7+
Validate and normalize the API base URI stored on a resolved Domeneshop context.
8+
9+
.EXAMPLE
10+
Get-DomeneshopApiBaseUri -Context $resolvedContext
11+
12+
Get the normalized API base URI for a resolved context.
13+
14+
.INPUTS
15+
None
16+
17+
You can't pipe objects to Get-DomeneshopApiBaseUri.
18+
19+
.OUTPUTS
20+
System.String
21+
22+
The normalized Domeneshop API base URI.
23+
24+
.NOTES
25+
The caller must resolve and validate the context before invoking this helper.
26+
27+
.LINK
28+
https://api.domeneshop.no/docs/
29+
#>
30+
[OutputType([string])]
231
[CmdletBinding()]
332
param(
33+
# The resolved Domeneshop context containing an API base URI.
434
[Parameter(Mandatory)]
35+
[ValidateNotNull()]
536
[object] $Context
637
)
738

839
$apiBaseUri = [string] $Context.ApiBaseUri
9-
if ([string]::IsNullOrEmpty($apiBaseUri)) {
10-
$apiBaseUri = 'https://api.domeneshop.no/v0'
40+
if ([string]::IsNullOrWhiteSpace($apiBaseUri)) {
41+
throw "The Domeneshop context [$($Context.ID)] does not define an API base URI."
42+
}
43+
44+
$parsedUri = $null
45+
if (-not [uri]::TryCreate($apiBaseUri, [System.UriKind]::Absolute, [ref] $parsedUri)) {
46+
throw "The Domeneshop context [$($Context.ID)] contains an invalid API base URI."
1147
}
1248

13-
$apiBaseUri.TrimEnd('/')
49+
$parsedUri.AbsoluteUri.TrimEnd('/')
1450
}

src/functions/private/Invoke-DomeneshopApiRequest.ps1

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,56 @@
11
function Invoke-DomeneshopApiRequest {
2+
<#
3+
.SYNOPSIS
4+
Send an authenticated request to the Domeneshop API.
5+
6+
.DESCRIPTION
7+
Build an HTTP Basic credential from a resolved Domeneshop context and send a REST request, optionally with a JSON body.
8+
9+
.EXAMPLE
10+
Invoke-DomeneshopApiRequest -Method Get -Uri $uri -Context $resolvedContext
11+
12+
Send an authenticated GET request.
13+
14+
.EXAMPLE
15+
Invoke-DomeneshopApiRequest -Method Post -Uri $uri -Context $resolvedContext -Body $body
16+
17+
Send an authenticated POST request with a JSON body.
18+
19+
.INPUTS
20+
None
21+
22+
You can't pipe objects to Invoke-DomeneshopApiRequest.
23+
24+
.OUTPUTS
25+
System.Object
26+
27+
The response returned by the Domeneshop API.
28+
29+
.NOTES
30+
Transport errors are terminating so public callers fail fast.
31+
32+
.LINK
33+
https://api.domeneshop.no/docs/
34+
#>
35+
[OutputType([object])]
236
[CmdletBinding()]
337
param(
38+
# The HTTP method accepted by the Domeneshop API.
439
[Parameter(Mandatory)]
540
[ValidateSet('Get', 'Post', 'Put', 'Delete')]
641
[string] $Method,
742

43+
# The absolute Domeneshop API endpoint URI.
844
[Parameter(Mandatory)]
9-
[string] $Uri,
45+
[ValidateNotNull()]
46+
[uri] $Uri,
1047

48+
# The resolved Domeneshop context containing valid credentials.
1149
[Parameter(Mandatory)]
50+
[ValidateNotNull()]
1251
[object] $Context,
1352

53+
# The request payload to serialize as JSON.
1454
[Parameter()]
1555
[AllowNull()]
1656
[object] $Body
@@ -26,12 +66,14 @@ function Invoke-DomeneshopApiRequest {
2666
Uri = $Uri
2767
Authentication = 'Basic'
2868
Credential = $credential
69+
ErrorAction = 'Stop'
2970
}
3071

3172
if ($PSBoundParameters.ContainsKey('Body')) {
3273
$params['ContentType'] = 'application/json'
3374
$params['Body'] = ($Body | ConvertTo-Json -Depth 100)
3475
}
3576

77+
Write-Debug "Sending $Method request to [$Uri]."
3678
Invoke-RestMethod @params
3779
}

src/functions/public/Auth/Auth.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Auth
2+
3+
Public commands for storing and retrieving Domeneshop credential contexts.
4+
5+
## Commands
6+
7+
- `Connect-DomeneshopAccount`: Stores Domeneshop API credentials in the Context vault.
8+
- `Get-DomeneshopContext`: Gets a named or default context, or lists available contexts.

src/functions/public/Auth/Connect-DomeneshopAccount.ps1

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#Requires -Modules @{ ModuleName = 'Context'; ModuleVersion = '8.1.6' }
2+
13
function Connect-DomeneshopAccount {
24
<#
35
.SYNOPSIS
@@ -8,22 +10,52 @@ function Connect-DomeneshopAccount {
810
911
.EXAMPLE
1012
Connect-DomeneshopAccount -Token 'my-token' -Secret (Read-Host -AsSecureString)
13+
14+
Store credentials in the default named context.
15+
16+
.INPUTS
17+
None
18+
19+
You can't pipe objects to Connect-DomeneshopAccount.
20+
21+
.OUTPUTS
22+
System.Object
23+
24+
The stored context when PassThru is specified.
25+
26+
.NOTES
27+
Credentials are encrypted by the Context module.
28+
29+
.LINK
30+
https://api.domeneshop.no/docs/
1131
#>
32+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
33+
'PSAvoidUsingConvertToSecureStringWithPlainText', '',
34+
Justification = 'String secrets remain supported for compatibility and are converted before storage.'
35+
)]
1236
[OutputType([object])]
13-
[CmdletBinding()]
37+
[CmdletBinding(SupportsShouldProcess)]
1438
param(
39+
# The Domeneshop API token used as the Basic authentication username.
1540
[Parameter(Mandatory)]
41+
[ValidateNotNullOrEmpty()]
1642
[string] $Token,
1743

44+
# The Domeneshop API secret as a string or secure string.
1845
[Parameter(Mandatory)]
46+
[ValidateNotNull()]
1947
[object] $Secret,
2048

49+
# The name used to store and retrieve this credential context.
2150
[Parameter()]
51+
[ValidateNotNullOrEmpty()]
2252
[string] $Context = 'default',
2353

54+
# Set this context as the default for commands that omit Context.
2455
[Parameter()]
2556
[switch] $Default,
2657

58+
# Emit the stored context after it is saved.
2759
[Parameter()]
2860
[switch] $PassThru
2961
)
@@ -43,14 +75,16 @@ function Connect-DomeneshopAccount {
4375
ConnectedAt = Get-Date
4476
}
4577

46-
Set-Context -ID $Context -Vault 'Domeneshop' -Context $contextObject
78+
if ($PSCmdlet.ShouldProcess("Domeneshop context [$Context]", 'Store API credentials')) {
79+
$null = Set-Context -ID $Context -Vault 'Domeneshop' -Context $contextObject
4780

48-
$config = Get-DomeneshopConfig
49-
if ($Default -or [string]::IsNullOrEmpty($config.DefaultContext)) {
50-
Set-DomeneshopDefaultContext -Context $Context
51-
}
81+
$config = Get-DomeneshopConfig
82+
if ($Default -or [string]::IsNullOrWhiteSpace([string] $config.DefaultContext)) {
83+
Set-DomeneshopDefaultContext -Context $Context
84+
}
5285

53-
if ($PassThru) {
54-
Get-DomeneshopContext -Context $Context
86+
if ($PassThru) {
87+
Get-DomeneshopContext -Context $Context
88+
}
5589
}
5690
}

0 commit comments

Comments
 (0)