Private/TokenManager.ps1

$script:TokenFileName = 'token.json'

function Get-OpToken {
    [OutputType([PSCustomObject])]
    param()

    $path = Join-Path (Get-OpConfigDir) $script:TokenFileName
    if (-not (Test-Path $path)) { return $null }

    try {
        $token = Get-Content -Path $path -Raw -ErrorAction Stop | ConvertFrom-Json
        if (-not $token.access_token) { return $null }
        $token
    }
    catch {
        $null
    }
}

function Save-OpToken {
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Token
    )

    $dir = Confirm-OpConfigDir
    $path = Join-Path $dir $script:TokenFileName
    $Token | ConvertTo-Json -Depth 4 | Set-Content -Path $path -Encoding UTF8 -Force
    if (-not $IsWindows -and -not ($env:OS -eq 'Windows_NT')) {
        chmod 600 $path 2>$null
    }
}

function Remove-OpToken {
    $path = Join-Path (Get-OpConfigDir) $script:TokenFileName
    if (Test-Path $path) {
        Remove-Item -Path $path -Force -ErrorAction SilentlyContinue
    }
}

function Test-OpTokenExpired {
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Token
    )

    if (-not $Token.expires_at) { return $true }

    try {
        $expiresAt = [DateTimeOffset]::Parse($Token.expires_at).UtcDateTime
        return [DateTime]::UtcNow -ge $expiresAt
    }
    catch {
        return $true
    }
}

function Test-OpTokenNearExpiry {
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Token,
        [int]$ThresholdSeconds = 60
    )

    if (-not $Token.expires_at) { return $true }

    try {
        $expiresAt = [DateTimeOffset]::Parse($Token.expires_at).UtcDateTime
        return ([DateTime]::UtcNow.AddSeconds($ThresholdSeconds)) -ge $expiresAt
    }
    catch {
        return $true
    }
}

function Invoke-OpTokenRefresh {
    [OutputType([bool])]
    param()

    $token = Get-OpToken
    if (-not $token -or -not $token.refresh_token) { return $false }

    $cfg = Get-OpConfig
    $uri = "$($cfg.api_base)/api/v1/auth/device/refresh"

    try {
        $body = @{ refresh_token = $token.refresh_token }
        $resp = Invoke-RestMethod -Uri $uri -Method Post -Body $body `
            -ContentType 'application/x-www-form-urlencoded' `
            -UserAgent "onepam-powershell/$script:ModuleVersion" `
            -ErrorAction Stop

        $token.access_token  = $resp.access_token
        $token.refresh_token = $resp.refresh_token
        $token.expires_at    = [DateTime]::UtcNow.AddSeconds($resp.expires_in).ToString('o')

        Save-OpToken $token
        return $true
    }
    catch {
        return $false
    }
}