Private/Authentication/Curanet/Get-CuranetAccessToken.ps1

function Get-CuranetAccessToken {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet("3370", "3850")]
        [string]$Account,
        
        [Parameter()]
        [switch]$Force
    )

    begin {
        try {
            if (!$script:CuranetCredentials -or $script:CuranetCredentials.Account -ne $Account) {
                $script:CuranetCredentials = Get-CuranetCredentials -Account $Account
            }

            # Define account-specific scopes
            $accountScopes = @{
                '3850' = 'customers orders subscriptions invoicing microsoft365 microsoft365backup'
                '3370' = 'dns'
            }

            # Define OAuth endpoint
            $tokenEndpoint = 'https://apiauth.dk.team.blue/realms/Curanet/protocol/openid-connect/token'
        }
        catch {
            Write-ModuleLog -Message "Failed to get Curanet credentials" -Level Error -Component 'CuranetAccessToken' -ErrorRecord $_
            throw [CuranetTokenOperationException]::new(
                'Initialization',
                'Failed to get Curanet credentials',
                $_
            )
        }
    }

    process {
        try {
            $cacheKey = "Curanet_$Account"
            $now = Get-Date
            # Set refresh threshold to 1 minute before expiration (tokens valid for 5 minutes)
            $refreshThreshold = $now.AddMinutes(4)

            # Check cache for valid token
            if (!$Force -and $script:TokenCache.ContainsKey($cacheKey)) {
                $cachedToken = $script:TokenCache[$cacheKey]
                
                # Token is still valid
                if ($cachedToken.ExpirationDateTime -gt $refreshThreshold) {
                    Write-ModuleLog -Message "Using cached token for $cacheKey" -Level Verbose -Component 'CuranetAccessToken'
                    return $cachedToken
                }
            }

            Write-ModuleLog -Message "Getting new token for $cacheKey" -Level Verbose -Component 'CuranetAccessToken'

            # Prepare token request
            try {
                $body = @{
                    grant_type    = "client_credentials"
                    client_id     = $script:CuranetCredentials.ClientId
                    client_secret = $script:CuranetCredentials.ClientSecret
                    scope         = $accountScopes[$Account]
                }

                $response = Invoke-RestMethod -Uri $tokenEndpoint `
                    -Method POST `
                    -Body $body `
                    -ContentType 'application/x-www-form-urlencoded'

                # Create token object with formatted Authorization header value
                $token = [PSCustomObject]@{
                    AccessToken         = $response.access_token
                    AuthorizationHeader = "bearer $($response.access_token)"
                    TokenType          = $response.token_type
                    ExpirationDateTime = (Get-Date).AddSeconds(300) # 5 minutes
                    Scope             = $response.scope
                    Account           = $Account
                }
            }
            catch {
                $detailedError = switch -Regex ($_.Exception.Message) {
                    'invalid_client' { 'Invalid client credentials' }
                    'invalid_scope' { 'Invalid scope requested' }
                    'invalid_request' { 'Malformed request' }
                    default { $_.Exception.Message }
                }

                Write-ModuleLog -Message "Failed to acquire Curanet token: $detailedError" -Level Error -Component 'CuranetAccessToken' -ErrorRecord $_
                throw [CuranetTokenOperationException]::new(
                    'TokenAcquisition',
                    "Failed to acquire Curanet token: $detailedError",
                    $_
                )
            }
            
            # Manage cache
            try {
                if ($script:TokenCache.Count -ge $script:TokenCacheConfig.MaxSize) {
                    $oldestTokens = $script:TokenCache.GetEnumerator() | 
                    Sort-Object { $_.Value.ExpirationDateTime } |
                    Select-Object -First ($script:TokenCache.Count - $script:TokenCacheConfig.MaxSize + 1)
                    
                    foreach ($oldToken in $oldestTokens) {
                        $script:TokenCache.Remove($oldToken.Key)
                    }
                }

                $script:TokenCache[$cacheKey] = $token
                Save-TokenCache
            }
            catch {
                Write-ModuleLog -Message "Failed to manage token cache" -Level Error -Component 'CuranetAccessToken' -ErrorRecord $_
            }
            
            return $token
        }
        catch [CuranetTokenOperationException] {
            throw
        }
        catch {
            Write-ModuleLog -Message "An unexpected error occurred during token operation" -Level Error -Component 'CuranetAccessToken' -ErrorRecord $_
            throw [CuranetTokenOperationException]::new(
                'Unknown',
                'An unexpected error occurred during token operation',
                $_
            )
        }
    }
}