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-FreePort {
            $socket = New-Object System.Net.Sockets.Socket([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
            $socket.Bind((New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)))
            $freePort = $socket.LocalEndPoint.Port
            $socket.Close() # Release it so HttpListener can grab it
            return $freePort
        }

        # Get an available port with retry logic
        $Port = Get-FreePort
        $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
            # asynchronous wait allows to be stopped instead of GetContext()
            $Task = $Listener.GetContextAsync() # allows to be stopped
            while (-not $Task.IsCompleted) {
                Start-Sleep -Milliseconds 200
            }
            $Context = $Task.Result
            $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.Replace('+',' ') }

            $ResponseMessage = if ($ErrorCode) { 
                'Login failed: {0} ({1})' -f $ErrorCode, $ErrorMessage
            }
            else {
                'Login successful. Please close this window'
            }

            # 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>$($ResponseMessage)</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 ($TempToken) {
                Write-Debug "TempToken: $TempToken"

                $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"
                }
            }
            else {
                throw "Login failed: $($ErrorCode) ($($ErrorMessage))"
            }
            
        }
        catch {
            throw $_
        }
        finally {
            # closing the HTTP Listener
            if ($Listener) {
                try {
                    $Listener.Stop()
                    $Listener.Close()
                }
                catch {
                    Write-Debug "Error closing listener: $_"
                }
            }
        }
    }

    end {}
}