Skip to content

Commit a2e49c2

Browse files
🚀[Feature]: Add functions to revoke tokens + remove some functions (#432)
This pull request introduces new functionality for revoking GitHub access tokens, enhances the `Disconnect-GitHubAccount` command to revoke tokens, and refactors or removes outdated examples and tests. These changes improve security and streamline token management. - Fixes #414 ### Hiding some functions that were public previously - New-GitHubAppInstallationAccessToken - Get-GitHubAppJSONWebToken ### New Features for Token Revocation * Added `Revoke-GitHubAppInstallationAccessToken` (private) function to revoke installation access tokens for GitHub Apps. This invalidates tokens and ensures they cannot be reused. * Introduced `Revoke-GitHubAccessToken` (public) function to revoke a list of exposed or unused credentials. Supports batch processing for up to 1000 tokens per request. ### Enhancements to Existing Commands * Updated `Disconnect-GitHubAccount` to revoke access tokens during disconnection, improving security by preventing token reuse for Installation Access Tokens. ### Refactoring and Cleanup * Removed outdated examples from `CallingAPIs.ps1` related to JWT and installation access tokens. * Refactored tests in `Apps.Tests.ps1` by removing redundant JWT and installation token tests, and added a test to verify that revoked tokens fail API calls. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: MariusStorhaug <[email protected]> Co-authored-by: Marius Storhaug <[email protected]>
1 parent 66a92bf commit a2e49c2

File tree

7 files changed

+164
-56
lines changed

7 files changed

+164
-56
lines changed

‎examples/CallingAPIs.ps1‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@ Get-GitHubApp
2626
# More complex example - output is parts of the web response
2727
Invoke-GitHubAPI -ApiEndpoint /app
2828

29-
# Most complex example - output is the entire web response
30-
$context = Get-GitHubContext
31-
$jwt = Get-GitHubAppJSONWebToken -ClientId $context.ClientID -PrivateKey $context.Token
32-
Invoke-RestMethod -Uri "$($context.ApiBaseUri)/app" -Token ($jwt.token) -Authentication Bearer
33-
Invoke-WebRequest -Uri "$($context.ApiBaseUri)/app" -Token ($jwt.token) -Authentication Bearer
34-
3529
#endregion
3630

3731

File renamed without changes.

src/functions/public/Apps/GitHub App/New-GitHubAppInstallationAccessToken.ps1 renamed to src/functions/private/Apps/GitHub Apps/New-GitHubAppInstallationAccessToken.ps1

File renamed without changes.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
function Revoke-GitHubAppInstallationAccessToken {
2+
<#
3+
.SYNOPSIS
4+
Revoke an installation access token.
5+
6+
.DESCRIPTION
7+
Revokes the installation token you're using to authenticate as an installation and access this endpoint.
8+
Once an installation token is revoked, the token is invalidated and cannot be used. Other endpoints that require the revoked installation
9+
token must have a new installation token to work. You can create a new token using the `Connect-GitHubApp` function.
10+
11+
.LINK
12+
https://psmodule.io/GitHub/Functions/Apps/GitHub%20App/Revoke-GitHubAppInstallationAccessToken
13+
14+
.NOTES
15+
[Revoke an installation access token](https://docs.github.com/rest/apps/installations#revoke-an-installation-access-token)
16+
#>
17+
[CmdletBinding(SupportsShouldProcess)]
18+
param(
19+
# The context to run the command in. Used to get the details for the API call.
20+
# Can be either a string or a GitHubContext object.
21+
[Parameter(Mandatory)]
22+
[object] $Context
23+
)
24+
25+
begin {
26+
$stackPath = Get-PSCallStackPath
27+
Write-Debug "[$stackPath] - Start"
28+
Assert-GitHubContext -Context $Context -AuthType IAT
29+
}
30+
31+
process {
32+
$InputObject = @{
33+
Method = 'DELETE'
34+
APIEndpoint = '/installation/token'
35+
Context = $Context
36+
}
37+
38+
if ($PSCmdlet.ShouldProcess('GitHub App installation access token', 'Revoke')) {
39+
Invoke-GitHubAPI @InputObject
40+
}
41+
}
42+
43+
end {
44+
Write-Debug "[$stackPath] - End"
45+
}
46+
}

‎src/functions/public/Auth/Disconnect-GitHubAccount.ps1‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
Disconnects from GitHub and removes the GitHub context.
55
66
.DESCRIPTION
7-
Disconnects from GitHub and removes the GitHub context.
7+
Disconnects from GitHub and removes the GitHub context. Optionally revokes the access token
8+
to ensure it cannot be used after disconnection.
89
910
.EXAMPLE
1011
Disconnect-GitHubAccount
@@ -16,6 +17,16 @@
1617
1718
Disconnects from GitHub and removes the context 'github.com/Octocat'.
1819
20+
.EXAMPLE
21+
Disconnect-GitHubAccount -RevokeToken
22+
23+
Disconnects from GitHub, revokes the access token, and removes the default GitHub context.
24+
25+
.EXAMPLE
26+
Disconnect-GithubAccount -Context 'github.com/Octocat' -RevokeToken
27+
28+
Disconnects from GitHub, revokes the access token, and removes the context 'github.com/Octocat'.
29+
1930
.LINK
2031
https://psmodule.io/GitHub/Functions/Auth/Disconnect-GitHubAccount
2132
#>
@@ -46,6 +57,13 @@
4657
}
4758
foreach ($contextItem in $Context) {
4859
$contextItem = Resolve-GitHubContext -Context $contextItem
60+
61+
$contextToken = Get-GitHubAccessToken -Context $contextItem -AsPlainText
62+
$isGitHubToken = $contextToken -eq (Get-GitHubToken | ConvertFrom-SecureString -AsPlainText)
63+
if (-not $isGitHubToken -and $contextItem.AuthType -eq 'IAT') {
64+
Revoke-GitHubAppInstallationAccessToken -Context $contextItem
65+
}
66+
4967
Remove-GitHubContext -Context $contextItem
5068
$isDefaultContext = $contextItem.Name -eq $script:GitHub.Config.DefaultContext
5169
if ($isDefaultContext) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
function Revoke-GitHubAccessToken {
2+
<#
3+
.SYNOPSIS
4+
Revoke a list of tokens.
5+
6+
.DESCRIPTION
7+
Submit a list of credentials to be revoked. This endpoint is intended to revoke credentials the caller does not own and may have found
8+
exposed on GitHub.com or elsewhere. It can also be used for credentials associated with an old user account that you no longer have access to.
9+
Credential owners will be notified of the revocation.
10+
11+
.LINK
12+
https://psmodule.io/GitHub/Functions/Auth/Revoke-GitHubAccessToken/
13+
14+
.NOTES
15+
[Revoke a list of credentials](https://docs.github.com/rest/credentials/revoke#revoke-a-list-of-credentials)
16+
#>
17+
[CmdletBinding(SupportsShouldProcess)]
18+
param(
19+
# An array of tokens to revoke.
20+
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
21+
[string[]] $Token
22+
)
23+
24+
begin {
25+
$stackPath = Get-PSCallStackPath
26+
Write-Debug "[$stackPath] - Start"
27+
$tokenList = [System.Collections.ArrayList]::new()
28+
}
29+
30+
process {
31+
$Token | ForEach-Object {
32+
$tokenList.Add($_)
33+
}
34+
}
35+
36+
end {
37+
for ($i = 0; $i -lt $tokenList.Count; $i += 1000) {
38+
$batch = $tokenList[$i..([Math]::Min($i + 999, $tokenList.Count - 1))]
39+
$body = @{ credentials = $batch }
40+
$InputObject = @{
41+
Method = 'POST'
42+
APIEndpoint = '/credentials/revoke'
43+
Body = $body
44+
Anonymous = $true
45+
}
46+
if ($PSCmdlet.ShouldProcess('Tokens', 'Revoke')) {
47+
Invoke-GitHubAPI @InputObject
48+
}
49+
}
50+
Write-Debug "[$stackPath] - End"
51+
}
52+
}
53+
#SkipTest:FunctionTest:Will add a test for this function in a future PR

‎tests/Apps.Tests.ps1‎

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,6 @@ Describe 'Apps' {
6161
$app.Installations | Should -Not -BeNullOrEmpty
6262
}
6363

64-
It 'Get-GitHubAppJSONWebToken - Can get a JWT for the app' {
65-
$jwt = Get-GitHubAppJSONWebToken @connectParams
66-
LogGroup 'JWT' {
67-
Write-Host ($jwt | Format-Table | Out-String)
68-
}
69-
$jwt | Should -Not -BeNullOrEmpty
70-
}
71-
7264
It 'Get-GitHubAppInstallationRequest - Can get installation requests' {
7365
$installationRequests = Get-GitHubAppInstallationRequest
7466
LogGroup 'Installation requests' {
@@ -104,17 +96,6 @@ Describe 'Apps' {
10496
}
10597
}
10698

107-
It 'New-GitHubAppInstallationAccessToken - Can get app installation access tokens' {
108-
$installations = Get-GitHubAppInstallation
109-
LogGroup 'Tokens' {
110-
$installations | ForEach-Object {
111-
$token = New-GitHubAppInstallationAccessToken -InstallationID $_.id
112-
Write-Host ($token | Format-List | Out-String)
113-
}
114-
$token | Should -Not -BeNullOrEmpty
115-
}
116-
}
117-
11899
It 'Get-GitHubAppInstallation - <ownerType>' {
119100
$githubApp = Get-GitHubApp
120101
$installation = Get-GitHubAppInstallation | Where-Object { ($_.Target.Name -eq $owner) -and ($_.Type -eq $ownerType) }
@@ -183,37 +164,53 @@ Describe 'Apps' {
183164
}
184165
}
185166
}
186-
It 'Connect-GitHubApp - Connects as a GitHub App to <Owner>' {
187-
$githubApp = Get-GitHubApp
188-
$config = Get-GitHubConfig
189-
$context = Connect-GitHubApp @connectAppParams -PassThru -Silent
190-
LogGroup 'Context' {
191-
Write-Host ($context | Format-List | Out-String)
167+
168+
Context 'Installation' {
169+
BeforeAll {
170+
$githubApp = Get-GitHubApp
171+
$config = Get-GitHubConfig
172+
$context = Connect-GitHubApp @connectAppParams -PassThru -Silent
173+
LogGroup 'Context' {
174+
Write-Host ($context | Format-List | Out-String)
175+
}
176+
}
177+
178+
It 'Connect-GitHubApp - Connects as a GitHub App to <Owner>' {
179+
$context | Should -BeOfType 'InstallationGitHubContext'
180+
$context.ClientID | Should -Be $githubApp.ClientID
181+
$context.TokenExpirationDate | Should -BeOfType [datetime]
182+
$context.InstallationID | Should -BeOfType [uint64]
183+
$context.InstallationID | Should -BeGreaterThan 0
184+
$context.Permissions | Should -BeOfType [PSCustomObject]
185+
$context.Events | Should -BeOfType 'string'
186+
$context.InstallationType | Should -Be $ownertype
187+
$context.InstallationName | Should -Be $owner
188+
$context.ID | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner"
189+
$context.Name | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner"
190+
$context.DisplayName | Should -Be $githubApp.Name
191+
$context.Type | Should -Be 'Installation'
192+
$context.HostName | Should -Be $config.HostName
193+
$context.ApiBaseUri | Should -Be $config.ApiBaseUri
194+
$context.ApiVersion | Should -Be $config.ApiVersion
195+
$context.AuthType | Should -Be 'IAT'
196+
$context.NodeID | Should -Not -BeNullOrEmpty
197+
$context.DatabaseID | Should -Not -BeNullOrEmpty
198+
$context.UserName | Should -Be $githubApp.Slug
199+
$context.Token | Should -BeOfType [System.Security.SecureString]
200+
$context.TokenType | Should -Be 'ghs'
201+
$context.HttpVersion | Should -Be $config.HttpVersion
202+
$context.PerPage | Should -Be $config.PerPage
203+
}
204+
205+
It 'Revoked GitHub App token should fail on API call' -Skip:($TokenType -eq 'GITHUB_TOKEN') {
206+
$org = Get-GitHubOrganization -Name PSModule -Context $context
207+
$org | Should -Not -BeNullOrEmpty
208+
$context | Disconnect-GitHub
209+
210+
{
211+
Invoke-RestMethod -Method Get -Uri "$($context.ApiBaseUri)/orgs/PSModule" -Authentication Bearer -Token $context.token
212+
} | Should -Throw
192213
}
193-
$context | Should -BeOfType 'InstallationGitHubContext'
194-
$context.ClientID | Should -Be $githubApp.ClientID
195-
$context.TokenExpirationDate | Should -BeOfType [datetime]
196-
$context.InstallationID | Should -BeOfType [uint64]
197-
$context.InstallationID | Should -BeGreaterThan 0
198-
$context.Permissions | Should -BeOfType [PSCustomObject]
199-
$context.Events | Should -BeOfType 'string'
200-
$context.InstallationType | Should -Be $ownertype
201-
$context.InstallationName | Should -Be $owner
202-
$context.ID | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner"
203-
$context.Name | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner"
204-
$context.DisplayName | Should -Be $githubApp.Name
205-
$context.Type | Should -Be 'Installation'
206-
$context.HostName | Should -Be $config.HostName
207-
$context.ApiBaseUri | Should -Be $config.ApiBaseUri
208-
$context.ApiVersion | Should -Be $config.ApiVersion
209-
$context.AuthType | Should -Be 'IAT'
210-
$context.NodeID | Should -Not -BeNullOrEmpty
211-
$context.DatabaseID | Should -Not -BeNullOrEmpty
212-
$context.UserName | Should -Be $githubApp.Slug
213-
$context.Token | Should -BeOfType [System.Security.SecureString]
214-
$context.TokenType | Should -Be 'ghs'
215-
$context.HttpVersion | Should -Be $config.HttpVersion
216-
$context.PerPage | Should -Be $config.PerPage
217214
}
218215
}
219216

0 commit comments

Comments
 (0)