Private/Invoke-DuneApiAuthSocial.ps1

function Invoke-DuneApiAuthSocial {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet("Prod", "Dev","Test","Local")]
        [string]$DuneInstance,

        [Parameter(Mandatory)]
        [string]$Tenant
    )

    begin {
        $DuneApiUrl = Get-DuneApiUrl -DuneInstance $DuneInstance
        $DuneAuthUrl = Get-DuneAuthUrl -DuneInstance $DuneInstance
    }

    process {
        # Function to get an available port
        function Get-AvailablePort {
            param(
                [int]$StartPort = 49152,
                [int]$EndPort = 65535,
                [int]$MaxAttempts = 50
            )
            
            $usedPorts = @()
            try {
                # Get all ports currently in use
                $netstatOutput = netstat -ano
                $usedPorts = $netstatOutput | Select-String "LISTENING" | % { 
                    $parts = $_ -split '\s+'
                    $portPart = $parts | ? { $_ -match ':\d+$' } | Select-Object -First 1
                    if ($portPart) {
                        [int]($portPart -split ':')[-1]
                    }
                } | ? { $_ -ge $StartPort -and $_ -le $EndPort } | Sort-Object -Unique
            }
            catch {
                Write-Warning "Could not retrieve used ports via netstat: $_"
            }
            
            # Try to find an available port
            $attempts = 0
            do {
                $port = Get-Random -Minimum $StartPort -Maximum $EndPort
                $attempts++
                
                if ($port -notin $usedPorts) {
                    # Double-check by trying to create a listener on this port
                    try {
                        $testListener = New-Object System.Net.HttpListener
                        $testListener.Prefixes.Add("http://localhost:$port/")
                        $testListener.Start()
                        $testListener.Stop()
                        $testListener.Close()
                        return $port
                    }
                    catch {
                        Write-Debug "Port $port is in use, trying another..."
                    }
                }
            } while ($attempts -lt $MaxAttempts)
            
            throw "Could not find an available port after $MaxAttempts attempts"
        }

        # Get an available port with retry logic
        $Port = Get-AvailablePort
        $CallbackUrl = "http://localhost:$Port/"
        Write-Debug "Found available port: $Port"

        # starting a local http listener
        $Listener = $null
        try {
            $Listener = New-Object System.Net.HttpListener
            $Listener.Prefixes.Add($CallbackUrl)
            $Listener.Start()
            Write-Debug "Started HTTP Listener: $CallbackUrl"
        }
        catch {
            throw "Could not start HTTP Listener: $_"
        }

        Write-Host "Please login to browser..."

        try {
            # open browser with dynamic callback URL
            Start-Process "${DuneAuthUrl}?tenantname=${Tenant}&continue=${CallbackUrl}"

            # waiting for Redirect-Request
            $Context = $Listener.GetContext()  # blocks until it gets called
            $Request = $Context.Request

            Write-Debug "Received request from: $($Request.Url)"

            $Session = if ($Request.Url.Query -match 's=(?<s>[^&]+)') { $Matches.s } # not sure if needed
            $TempToken = if ($Request.Url.Query -match 'tmptok=(?<tmptok>[^&]+)') { $Matches.tmptok }
            $ErrorCode = if ($Request.Url.Query -match 'f=(?<f>[^&]+)') { $Matches.f }
            $ErrorMessage = if ($Request.Url.Query -match 'm=(?<m>[^&]+)') { $Matches.m }

            Write-Debug "TempToken: $TempToken"

            # response to browser - send response BEFORE closing
            $ResponseString = '<html><head><style>body { font-family: "Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }</style></head><body style="color:#e3e3e3; background-color:black;"><p>Login successfull. Please close this window</p></body></html>'
            $Buffer = [System.Text.Encoding]::UTF8.GetBytes($ResponseString)
            
            try {
                $Context.Response.ContentLength64 = $Buffer.Length
                $Context.Response.OutputStream.Write($Buffer, 0, $Buffer.Length)
                $Context.Response.OutputStream.Flush()
            }
            catch {
                Write-Warning "Error writing response: $_"
            }
            finally {
                $Context.Response.Close()
            }

            if ($ErrorCode) {
                if ($TempToken) {
                    Write-Error -ErrorAction Continue "Received token with error $($ErrorCode): $ErrorMessage"
                }
                else {
                    throw "Received error $($ErrorCode): $ErrorMessage"
                }
            }

            $ReturnedTokens = Get-DuneJWTTokens -DuneInstance $DuneInstance -Tenant $Tenant -TemporaryToken $TempToken
            $Token = $ReturnedTokens.accesstoken
            $RefreshToken = $ReturnedTokens.refreshToken

            if ($Token) {
                $ParsedToken = Parse-JwtToken -Token $Token
                $TokenExpiryDate = [System.DateTimeOffset]::FromUnixTimeSeconds($ParsedToken.exp).DateTime
                # save auth info
                $Script:DuneSession = [PSCustomObject]@{
                    Type         = 'SocialLogin'
                    DuneApiUrl   = $DuneApiUrl
                    Token        = ($Token | ConvertTo-SecureString -AsPlainText -Force)
                    ExpiryDate   = $TokenExpiryDate
                    RefreshToken = ($RefreshToken | ConvertTo-SecureString -AsPlainText -Force)
                    Tenant       = $Tenant
                }
                Write-Verbose "Login successfull"

                Save-DuneSession
            }
            else {
                throw "Token not set"
            }
        }
        catch {
            throw "Could not process HTTP Listener: $_"
        }
        finally {
            # closing the HTTP Listener
            if ($Listener) {
                try {
                    $Listener.Stop()
                    $Listener.Close()
                }
                catch {
                    Write-Debug "Error closing listener: $_"
                }
            }
        }
    }

    end {}
}