Public/Connect-OnePAM.ps1

function Connect-OnePAM {
    <#
    .SYNOPSIS
        Authenticates to OnePAM using the OAuth2 Device Code flow.
    .DESCRIPTION
        Initiates a device code flow, displays a verification URL and user code,
        then polls until the user completes browser-based authorization.
        The resulting access and refresh tokens are saved to ~/.onepam/token.json.
    .PARAMETER ApiBase
        Override the API base URL (default: from config or https://onepam.com).
    .EXAMPLE
        Connect-OnePAM
    .EXAMPLE
        Connect-OnePAM -ApiBase "https://my-onepam.example.com"
    #>

    [CmdletBinding()]
    param(
        [string]$ApiBase
    )

    $cfg = Get-OpConfig
    $baseUrl = if ($ApiBase) { $ApiBase } else { $cfg.api_base }
    Assert-OpValidApiBase -Url $baseUrl

    $codeUri = "$baseUrl/api/v1/auth/device/code"
    $codeBody = @{
        client_id = 'onepam-cli'
        scope     = 'ssh database resources sessions'
    }

    try {
        $codeResp = Invoke-RestMethod -Uri $codeUri -Method Post -Body $codeBody `
            -ContentType 'application/x-www-form-urlencoded' `
            -UserAgent "onepam-powershell/$script:ModuleVersion" `
            -ErrorAction Stop
    }
    catch {
        throw "Failed to start device code flow: $_"
    }

    $deviceCode      = $codeResp.device_code
    $userCode        = $codeResp.user_code
    $verificationUri = if ($codeResp.verification_uri_complete) { $codeResp.verification_uri_complete } else { $codeResp.verification_uri }
    $interval        = if ($codeResp.interval -and $codeResp.interval -gt 0) { $codeResp.interval } else { 5 }
    $expiresIn       = if ($codeResp.expires_in) { $codeResp.expires_in } else { 600 }

    Write-Host ''
    Write-Host ' To authenticate, open the following URL in your browser:' -ForegroundColor Cyan
    Write-Host ''
    Write-Host " $verificationUri" -ForegroundColor Yellow
    Write-Host ''
    Write-Host " And enter this code: $userCode" -ForegroundColor Green
    Write-Host ''
    Write-Host ' Waiting for authorization...' -ForegroundColor Gray

    $tokenUri = "$baseUrl/api/v1/auth/device/token"
    $deadline = [DateTime]::UtcNow.AddSeconds($expiresIn)
    $consecutiveErrors = 0

    while ([DateTime]::UtcNow -lt $deadline) {
        Start-Sleep -Seconds $interval

        $tokenBody = @{
            client_id   = 'onepam-cli'
            device_code = $deviceCode
            grant_type  = 'urn:ietf:params:oauth:grant-type:device_code'
        }

        try {
            $tokenResp = Invoke-RestMethod -Uri $tokenUri -Method Post -Body $tokenBody `
                -ContentType 'application/x-www-form-urlencoded' `
                -UserAgent "onepam-powershell/$script:ModuleVersion" `
                -ErrorAction Stop
            $consecutiveErrors = 0
        }
        catch {
            $consecutiveErrors++
            if ($consecutiveErrors -ge 3) {
                Write-Host " Warning: repeated connection errors ($consecutiveErrors): $_" -ForegroundColor Yellow
            }
            if ($consecutiveErrors -ge 10) {
                throw "Authorization polling failed after $consecutiveErrors consecutive errors: $_"
            }
            continue
        }

        if ($tokenResp.error) {
            switch ($tokenResp.error) {
                'authorization_pending' { continue }
                'slow_down' {
                    $interval = $interval + 5
                    continue
                }
                'expired_token' {
                    throw 'Device code expired. Please run Connect-OnePAM again.'
                }
                default {
                    throw "Authentication error: $($tokenResp.error)"
                }
            }
        }

        if ($tokenResp.access_token) {
            $tokenCache = [PSCustomObject]@{
                access_token  = $tokenResp.access_token
                refresh_token = $tokenResp.refresh_token
                expires_at    = [DateTime]::UtcNow.AddSeconds($tokenResp.expires_in).ToString('o')
                user_email    = $tokenResp.user_email
                org_uuid      = $tokenResp.org_uuid
            }

            Save-OpToken $tokenCache

            Write-Host ''
            Write-Host " Authenticated as $($tokenResp.user_email)" -ForegroundColor Green
            Write-Host " Organization: $($tokenResp.org_uuid)" -ForegroundColor Gray
            Write-Host ''
            return
        }
    }

    throw 'Device code flow timed out. Please try again.'
}