|
20 | 20 | [Refreshing user access tokens](https://docs.github.com/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens) |
21 | 21 | #> |
22 | 22 | [CmdletBinding(SupportsShouldProcess)] |
23 | | - [OutputType([securestring])] |
| 23 | + [OutputType([GitHubContext])] |
24 | 24 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Is the CLI part of the module.')] |
25 | 25 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The tokens are recieved as clear text. Mitigating exposure by removing variables and performing garbage collection.')] |
26 | 26 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Reason for suppressing')] |
|
32 | 32 |
|
33 | 33 | # Return the new access token. |
34 | 34 | [Parameter()] |
35 | | - [switch] $PassThru |
| 35 | + [switch] $PassThru, |
| 36 | + |
| 37 | + # Suppress output messages. |
| 38 | + [Parameter()] |
| 39 | + [switch] $Silent, |
| 40 | + |
| 41 | + # Timeout in milliseconds for waiting on mutex. Default is 30 seconds. |
| 42 | + [Parameter()] |
| 43 | + [int] $TimeoutMs = 30000 |
36 | 44 | ) |
37 | 45 |
|
38 | 46 | begin { |
|
42 | 50 | } |
43 | 51 |
|
44 | 52 | process { |
45 | | - Write-Verbose "Reusing previously stored ClientID: [$($Context.AuthClientID)]" |
46 | | - $authClientID = $Context.AuthClientID |
47 | | - $accessTokenValidity = [datetime]($Context.TokenExpirationDate) - (Get-Date) |
48 | | - $accessTokenIsValid = $accessTokenValidity.Seconds -gt 0 |
49 | | - $hours = $accessTokenValidity.Hours.ToString().PadLeft(2, '0') |
50 | | - $minutes = $accessTokenValidity.Minutes.ToString().PadLeft(2, '0') |
51 | | - $seconds = $accessTokenValidity.Seconds.ToString().PadLeft(2, '0') |
52 | | - $accessTokenValidityText = "$hours`:$minutes`:$seconds" |
53 | | - if ($accessTokenIsValid) { |
54 | | - if ($accessTokenValidity.TotalHours -gt $script:GitHub.Config.AccessTokenGracePeriodInHours) { |
55 | | - if (-not $Silent) { |
56 | | - Write-Host '✓ ' -ForegroundColor Green -NoNewline |
57 | | - Write-Host "Access token is still valid for $accessTokenValidityText ..." |
58 | | - } |
59 | | - return |
60 | | - } else { |
61 | | - if (-not $Silent) { |
62 | | - Write-Host 'âš ' -ForegroundColor Yellow -NoNewline |
63 | | - Write-Host "Access token remaining validity $accessTokenValidityText. Refreshing access token..." |
| 53 | + if (Test-GitHubAccessTokenRefreshRequired -Context $Context) { |
| 54 | + $lockName = "PSModule.GitHub/$($Context.ID)" |
| 55 | + $lock = $null |
| 56 | + try { |
| 57 | + $lock = [System.Threading.Mutex]::new($false, $lockName) |
| 58 | + $updateToken = $lock.WaitOne(0) |
| 59 | + |
| 60 | + if ($updateToken) { |
| 61 | + try { |
| 62 | + $refreshTokenValidity = [datetime]($Context.RefreshTokenExpirationDate) - [datetime]::Now |
| 63 | + $refreshTokenIsValid = $refreshTokenValidity.TotalSeconds -gt 0 |
| 64 | + if ($refreshTokenIsValid) { |
| 65 | + if (-not $Silent) { |
| 66 | + Write-Host 'âš ' -ForegroundColor Yellow -NoNewline |
| 67 | + Write-Host 'Access token expired. Refreshing access token...' |
| 68 | + } |
| 69 | + $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $Context.AuthClientID -RefreshToken $Context.RefreshToken -HostName $Context.HostName |
| 70 | + } else { |
| 71 | + Write-Verbose "Using $($Context.DeviceFlowType) authentication..." |
| 72 | + $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $Context.AuthClientID -HostName $Context.HostName |
| 73 | + } |
| 74 | + $Context.Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token |
| 75 | + $Context.TokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.expires_in) |
| 76 | + $Context.TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern |
| 77 | + $Context.RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token |
| 78 | + $Context.RefreshTokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.refresh_token_expires_in) |
| 79 | + |
| 80 | + if ($PSCmdlet.ShouldProcess('Access token', 'Update/refresh')) { |
| 81 | + Set-Context -Context $Context -Vault $script:GitHub.ContextVault |
| 82 | + } |
| 83 | + } finally { |
| 84 | + $lock.ReleaseMutex() |
| 85 | + } |
| 86 | + } else { |
| 87 | + Write-Verbose "Access token is not valid. Waiting for mutex to be released (timeout: $($TimeoutMs)ms)..." |
| 88 | + try { |
| 89 | + if ($lock.WaitOne($TimeoutMs)) { |
| 90 | + # Re-read context to get updated token from other process |
| 91 | + $Context = Resolve-GitHubContext -Context $Context.ID |
| 92 | + $lock.ReleaseMutex() |
| 93 | + } else { |
| 94 | + Write-Warning 'Timeout waiting for token update. Proceeding with current token state.' |
| 95 | + } |
| 96 | + } catch [System.Threading.AbandonedMutexException] { |
| 97 | + Write-Debug 'Mutex was abandoned by another process. Re-checking token state...' |
| 98 | + $Context = Get-Context -Context $Context.ID -Vault $script:GitHub.ContextVault |
| 99 | + } |
64 | 100 | } |
65 | | - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -RefreshToken ($Context.RefreshToken) -HostName $Context.HostName |
66 | | - } |
67 | | - } else { |
68 | | - $refreshTokenValidity = [datetime]($Context.RefreshTokenExpirationDate) - (Get-Date) |
69 | | - $refreshTokenIsValid = $refreshTokenValidity.Seconds -gt 0 |
70 | | - if ($refreshTokenIsValid) { |
71 | | - if (-not $Silent) { |
72 | | - Write-Host 'âš ' -ForegroundColor Yellow -NoNewline |
73 | | - Write-Host 'Access token expired. Refreshing access token...' |
| 101 | + } finally { |
| 102 | + if ($lock) { |
| 103 | + $lock.Dispose() |
74 | 104 | } |
75 | | - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -RefreshToken ($Context.RefreshToken) -HostName $Context.HostName |
76 | | - } else { |
77 | | - Write-Verbose "Using $Mode authentication..." |
78 | | - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -Scope $Scope -HostName $Context.HostName |
79 | 105 | } |
80 | 106 | } |
81 | | - $Context.Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token |
82 | | - $Context.TokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.expires_in) |
83 | | - $Context.TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern |
84 | | - $Context.RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token |
85 | | - $Context.RefreshTokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.refresh_token_expires_in) |
86 | | - |
87 | | - if ($PSCmdlet.ShouldProcess('Access token', 'Update/refresh')) { |
88 | | - Set-Context -Context $Context -Vault $script:GitHub.ContextVault |
89 | | - } |
90 | | - |
91 | 107 | if ($PassThru) { |
92 | | - $Context.Token |
| 108 | + return $Context |
93 | 109 | } |
94 | | - |
95 | 110 | } |
96 | 111 |
|
97 | 112 | end { |
|
0 commit comments