Private/Invoke-JIMApi.ps1
|
function Invoke-JIMApi { <# .SYNOPSIS Internal function to invoke JIM REST API endpoints. .DESCRIPTION This is a private helper function that handles all REST API calls to JIM. It manages authentication headers (API key or Bearer token), error handling, and response processing. Supports automatic token refresh for OAuth connections - both proactively (before the request, when the token is near expiry) and reactively (on 401 response, in case of clock skew or server-side revocation). .PARAMETER Endpoint The API endpoint path (without base URL), e.g., '/api/v1/synchronisation/connected-systems' .PARAMETER Method The HTTP method to use. Defaults to 'GET'. .PARAMETER Body Optional body for POST/PUT/PATCH requests. Will be converted to JSON. .PARAMETER ContentType Content type for the request. Defaults to 'application/json'. .OUTPUTS The API response object, or throws an error if the request fails. .EXAMPLE Invoke-JIMApi -Endpoint '/api/v1/synchronisation/connected-systems' .EXAMPLE Invoke-JIMApi -Endpoint '/api/v1/synchronisation/connected-systems' -Method 'POST' -Body @{ Name = 'Test' } #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Endpoint, [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')] [string]$Method = 'GET', [object]$Body, [string]$ContentType = 'application/json' ) # Check connection if (-not $script:JIMConnection) { throw "Not connected to JIM. Use Connect-JIM first." } # Proactive token refresh: check before the request if token is near expiry if ($script:JIMConnection.AuthMethod -eq 'OAuth') { if ($script:JIMConnection.TokenExpiresAt -and $script:JIMConnection.TokenExpiresAt -lt (Get-Date).AddMinutes(2)) { Invoke-TokenRefresh -Reason "Access token expired or expiring soon" } } # Build and execute the request, with reactive 401 retry for OAuth $response = Invoke-JIMApiRequest -Endpoint $Endpoint -Method $Method -Body $Body -ContentType $ContentType return $response } function Invoke-TokenRefresh { <# .SYNOPSIS Refreshes the OAuth access token using the stored refresh token. #> [CmdletBinding()] param( [string]$Reason = "Token refresh required" ) if ($script:JIMConnection.RefreshToken -and $script:JIMConnection.OAuthConfig) { try { Write-Verbose "$Reason, refreshing..." $tokens = Invoke-OAuthTokenRefresh ` -TokenEndpoint $script:JIMConnection.OAuthConfig.TokenEndpoint ` -ClientId $script:JIMConnection.OAuthConfig.ClientId ` -RefreshToken $script:JIMConnection.RefreshToken ` -Scopes $script:JIMConnection.OAuthConfig.Scopes $script:JIMConnection.AccessToken = $tokens.AccessToken $script:JIMConnection.RefreshToken = $tokens.RefreshToken $script:JIMConnection.TokenExpiresAt = $tokens.ExpiresAt Write-Verbose "Successfully refreshed access token" } catch { throw "Access token expired and refresh failed. Please run Connect-JIM again to re-authenticate. Error: $_" } } else { throw "Access token expired and no refresh token available. Please run Connect-JIM again to re-authenticate." } } function Invoke-JIMApiRequest { <# .SYNOPSIS Executes a single API request with authentication and error handling. For OAuth connections, automatically retries once on 401 after refreshing the token. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Endpoint, [string]$Method = 'GET', [object]$Body, [string]$ContentType = 'application/json', [switch]$IsRetry ) # Build the full URI $uri = "$($script:JIMConnection.Url.TrimEnd('/'))$Endpoint" Write-Debug "Invoking JIM API: $Method $uri" # Build request parameters with appropriate authentication header $headers = @{ 'Accept' = 'application/json' } if ($script:JIMConnection.AuthMethod -eq 'ApiKey') { $headers['X-API-Key'] = $script:JIMConnection.ApiKey } elseif ($script:JIMConnection.AuthMethod -eq 'OAuth') { $headers['Authorization'] = "Bearer $($script:JIMConnection.AccessToken)" } else { throw "Unknown authentication method: $($script:JIMConnection.AuthMethod)" } $params = @{ Uri = $uri Method = $Method ContentType = $ContentType Headers = $headers } # Add body if provided if ($Body) { if ($Body -is [string]) { $params.Body = $Body } else { $params.Body = $Body | ConvertTo-Json -Depth 10 } Write-Debug "Request body: $($params.Body)" } try { $response = Invoke-RestMethod @params -ErrorAction Stop -MaximumRedirection 0 Write-Debug "API response received successfully" return $response } catch { $statusCode = $_.Exception.Response.StatusCode.value__ $errorMessage = $_.ErrorDetails.Message if ($errorMessage) { try { $errorObj = $errorMessage | ConvertFrom-Json $errorMessage = $errorObj.message ?? $errorObj.Message ?? $errorMessage } catch { # Keep original error message if JSON parsing fails } } switch ($statusCode) { 401 { # For OAuth: attempt a reactive token refresh and retry once if ($script:JIMConnection.AuthMethod -eq 'OAuth' -and -not $IsRetry) { try { Invoke-TokenRefresh -Reason "Server rejected token (401), attempting refresh" # Retry the request once with the new token return Invoke-JIMApiRequest -Endpoint $Endpoint -Method $Method -Body $Body -ContentType $ContentType -IsRetry } catch { throw "Authentication failed after token refresh. Please run Connect-JIM to re-authenticate. Error: $_" } } elseif ($script:JIMConnection.AuthMethod -eq 'OAuth') { throw "Authentication failed. Token refresh was already attempted. Please run Connect-JIM to re-authenticate." } else { throw "Authentication failed. Your API key may be invalid or expired. Use Connect-JIM to reconnect." } } 403 { throw "Access denied. You do not have permission to perform this operation." } 404 { throw "Resource not found: $errorMessage" } default { throw "JIM API error ($statusCode): $errorMessage" } } } } |