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 {} } |