Skip to content

Commit 82939b5

Browse files
🩹 [Patch]: Add TokenExpiresAt property and update expiration logic for GitHubAppContext (#494)
## Description This pull request improves how token expiry information is handled and displayed for GitHub authentication contexts in both code and documentation. The main changes introduce automatic token renewal for GitHub Apps, add new properties to track token expiry, update formatting and type definitions to support these properties, and enhance tests to validate the new behavior. **Authentication and token management improvements:** * Added documentation in `README.md` explaining that short-lived tokens (for GitHub Apps) are automatically renewed by the module, clarifying the difference from long-lived tokens. * Removed outdated/duplicated token renewal documentation and examples from `README.md` to streamline the explanation. **Code and formatting updates:** * Added `TokenExpiresAt` property to `GitHubContext` and `GitHubAppContext` classes, and implemented a `TokenExpiresIn` script property for `GitHubAppContext` to calculate remaining token time. * Updated `GitHubContext.Format.ps1xml` to display `TokenExpiresAt` and `TokenExpiresIn` in relevant views, adjusted expiry logic (`-le 0` instead of `-lt 0`), and added special handling for token types (e.g., 10-minute expiry for APP tokens). **Testing enhancements:** * Extended tests in `GitHub.Tests.ps1` to verify that authentication contexts include valid `TokenExpiresAt` and `TokenExpiresIn` properties. ## Type of change <!-- Use the check-boxes [x] on the options that are relevant. --> - [ ] 📖 [Docs] - [ ] 🪲 [Fix] - [x] 🩹 [Patch] - [ ] ⚠️ [Security fix] - [ ] 🚀 [Feature] - [ ] 🌟 [Breaking change] ## Checklist <!-- Use the check-boxes [x] on the options that are relevant. --> - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent 05728fd commit 82939b5

File tree

11 files changed

+125
-99
lines changed

11 files changed

+125
-99
lines changed

README.md

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,34 +70,7 @@ Press Enter to open github.com in your browser...: #-> Press enter and paste th
7070
After this you will need to install the GitHub App on the repos you want to manage. You can do this by visiting the
7171
[PowerShell for GitHub](https://github.com/apps/powershell-for-github) app page.
7272

73-
> Info: We will be looking to include this as a check in the module in the future. So it becomes a part of the regular sign in process.
7473

75-
Consecutive runs of the `Connect-GitHubAccount` will not require you to paste the code again unless you revoke the token
76-
or you change the type of authentication you want to use. Instead, it checks the remaining duration of the access token and
77-
uses the refresh token to get a new access token if its less than 4 hours remaining.
78-
79-
```powershell
80-
Connect-GitHubAccount
81-
✓ Access token is still valid for 05:30:41 ...
82-
✓ Logged in as octocat!
83-
```
84-
85-
This is also happening automatically when you run a command that requires authentication. The validity of the token is checked before the command is executed.
86-
If it is no longer valid, the token is refreshed and the command is executed.
87-
88-
```powershell
89-
Connect-GitHubAccount
90-
⚠ Access token remaining validity 01:22:31. Refreshing access token...
91-
✓ Logged in as octocat!
92-
```
93-
94-
If the timer has gone out, we still have your back. It will just refresh as long as the refresh token is valid.
95-
96-
```powershell
97-
Connect-GitHubAccount
98-
⚠ Access token expired. Refreshing access token...
99-
✓ Logged in as octocat!
100-
```
10174

10275
#### Personal authentication - User access tokens with OAuth app
10376

@@ -223,6 +196,15 @@ Connect-GitHubAccount -Host 'https://msx.ghe.com' -ClientID 'lv123456789'
223196
✓ Logged in as octocat!
224197
```
225198

199+
#### Automatic token renewal
200+
201+
The module automatically manages short‑lived tokens for GitHub Apps:
202+
203+
- User access tokens (when you authenticate via a GitHub App) are short‑lived and include a refresh token. The module refreshes them automatically before/when they expire—no extra steps are required.
204+
- App JWTs (when the context is a GitHub App) are generated and rotated automatically per call as needed. You never need to create or renew the JWT yourself.
205+
206+
Note: Long‑lived tokens like classic/fine‑grained PATs and provided installation tokens (GH_TOKEN/GITHUB_TOKEN) are not refreshed by the module.
207+
226208
### Command Exploration
227209

228210
Familiarize yourself with the available cmdlets using the module's comprehensive documentation or inline help.

src/classes/public/Config/GitHubConfig.ps1

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
# The default value for retry interval in seconds.
3636
[System.Nullable[int]] $RetryInterval
3737

38-
# The tolerance time in seconds for JWT token validation.
39-
[System.Nullable[int]] $JwtTimeTolerance
40-
4138
# The environment type, which is used to determine the context of the GitHub API calls.
4239
[string] $EnvironmentType
4340

src/classes/public/Context/GitHubContext.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
$this.UserName = $Object.UserName
8383
$this.Token = $Object.Token
8484
$this.TokenType = $Object.TokenType
85+
$this.TokenExpiresAt = $Object.TokenExpiresAt
8586
$this.Enterprise = $Object.Enterprise
8687
$this.Owner = $Object.Owner
8788
$this.Repository = $Object.Repository

src/formats/GitHubConfig.Format.ps1xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@
3131
<ListItem>
3232
<PropertyName>AccessTokenGracePeriodInHours</PropertyName>
3333
</ListItem>
34-
<ListItem>
35-
<PropertyName>JwtTimeTolerance</PropertyName>
36-
</ListItem>
3734
<ListItem>
3835
<PropertyName>ApiVersion</PropertyName>
3936
</ListItem>

src/formats/GitHubContext.Format.ps1xml

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
return
5959
}
6060

61-
if ($_.TokenExpiresIn -lt 0) {
61+
if ($_.TokenExpiresIn -le 0) {
6262
$text = 'Expired'
6363
} else {
6464
$text = $_.TokenExpiresIn.ToString('hh\:mm\:ss')
@@ -73,6 +73,9 @@
7373
'IAT' {
7474
$maxValue = [TimeSpan]::FromHours(1)
7575
}
76+
'APP' {
77+
$maxValue = [TimeSpan]::FromMinutes(10)
78+
}
7679
}
7780
$ratio = [Math]::Min(($_.TokenExpiresIn / $maxValue), 1)
7881
[GitHubFormatter]::FormatColorByRatio($ratio, $text)
@@ -172,6 +175,12 @@
172175
<ListItem>
173176
<PropertyName>TokenType</PropertyName>
174177
</ListItem>
178+
<ListItem>
179+
<PropertyName>TokenExpiresAt</PropertyName>
180+
</ListItem>
181+
<ListItem>
182+
<PropertyName>TokenExpiresIn</PropertyName>
183+
</ListItem>
175184
<ListItem>
176185
<PropertyName>HostName</PropertyName>
177186
</ListItem>
@@ -238,22 +247,22 @@
238247
<PropertyName>TokenType</PropertyName>
239248
</ListItem>
240249
<ListItem>
241-
<PropertyName>HostName</PropertyName>
250+
<PropertyName>TokenExpiresAt</PropertyName>
242251
</ListItem>
243252
<ListItem>
244-
<PropertyName>UserName</PropertyName>
253+
<PropertyName>TokenExpiresIn</PropertyName>
245254
</ListItem>
246255
<ListItem>
247-
<PropertyName>ClientID</PropertyName>
256+
<PropertyName>HostName</PropertyName>
248257
</ListItem>
249258
<ListItem>
250-
<PropertyName>InstallationID</PropertyName>
259+
<PropertyName>UserName</PropertyName>
251260
</ListItem>
252261
<ListItem>
253-
<PropertyName>TokenExpiresAt</PropertyName>
262+
<PropertyName>ClientID</PropertyName>
254263
</ListItem>
255264
<ListItem>
256-
<PropertyName>TokenExpiresIn</PropertyName>
265+
<PropertyName>InstallationID</PropertyName>
257266
</ListItem>
258267
<ListItem>
259268
<PropertyName>InstallationType</PropertyName>
@@ -311,6 +320,12 @@
311320
<ListItem>
312321
<PropertyName>TokenType</PropertyName>
313322
</ListItem>
323+
<ListItem>
324+
<PropertyName>TokenExpiresAt</PropertyName>
325+
</ListItem>
326+
<ListItem>
327+
<PropertyName>TokenExpiresIn</PropertyName>
328+
</ListItem>
314329
<ListItem>
315330
<PropertyName>HostName</PropertyName>
316331
</ListItem>
@@ -326,12 +341,6 @@
326341
<ListItem>
327342
<PropertyName>Scope</PropertyName>
328343
</ListItem>
329-
<ListItem>
330-
<PropertyName>TokenExpiresAt</PropertyName>
331-
</ListItem>
332-
<ListItem>
333-
<PropertyName>TokenExpiresIn</PropertyName>
334-
</ListItem>
335344
<ListItem>
336345
<PropertyName>RefreshTokenExpiresAt</PropertyName>
337346
</ListItem>

src/functions/private/Apps/GitHub Apps/New-GitHubUnsignedJWT.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
}
4646
)
4747
$now = [System.DateTimeOffset]::UtcNow
48-
$iat = $now.AddSeconds(-$script:GitHub.Config.JwtTimeTolerance)
49-
$exp = $now.AddSeconds($script:GitHub.Config.JwtTimeTolerance)
48+
$iat = $now.AddMinutes(-10)
49+
$exp = $now.AddMinutes(10)
5050
$payload = [GitHubJWTComponent]::ToBase64UrlString(
5151
@{
5252
iat = $iat.ToUnixTimeSeconds()

src/functions/private/Apps/GitHub Apps/Test-GitHubJWTRefreshRequired.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function Test-GitHubJWTRefreshRequired {
3131

3232
process {
3333
try {
34-
($Context.TokenExpiresAt - [datetime]::Now).TotalSeconds -le ($script:GitHub.Config.JwtTimeTolerance / 2)
34+
($Context.TokenExpiresAt - [datetime]::Now).TotalSeconds -le 60
3535
} catch {
3636
return $true
3737
}

src/types/GitHubContext.Types.ps1xml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ScriptProperty>
77
<Name>TokenExpiresIn</Name>
88
<GetScriptBlock>
9-
if ($null -eq $this.TokenExpiresAt) { return }
9+
if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero }
1010
$timeRemaining = $this.TokenExpiresAt - [DateTime]::Now
1111
if ($timeRemaining.TotalSeconds -lt 0) {
1212
return [TimeSpan]::Zero
@@ -17,7 +17,7 @@
1717
<ScriptProperty>
1818
<Name>RefreshTokenExpiresIn</Name>
1919
<GetScriptBlock>
20-
if ($null -eq $this.RefreshTokenExpiresAt) { return }
20+
if ($null -eq $this.RefreshTokenExpiresAt) { return [TimeSpan]::Zero }
2121
$timeRemaining = $this.RefreshTokenExpiresAt - [DateTime]::Now
2222
if ($timeRemaining.TotalSeconds -lt 0) {
2323
return [TimeSpan]::Zero
@@ -29,6 +29,22 @@
2929
</Type>
3030
<Type>
3131
<Name>GitHubAppInstallationContext</Name>
32+
<Members>
33+
<ScriptProperty>
34+
<Name>TokenExpiresIn</Name>
35+
<GetScriptBlock>
36+
if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero }
37+
$timeRemaining = $this.TokenExpiresAt - [DateTime]::Now
38+
if ($timeRemaining.TotalSeconds -lt 0) {
39+
return [TimeSpan]::Zero
40+
}
41+
return $timeRemaining
42+
</GetScriptBlock>
43+
</ScriptProperty>
44+
</Members>
45+
</Type>
46+
<Type>
47+
<Name>GitHubAppContext</Name>
3248
<Members>
3349
<ScriptProperty>
3450
<Name>TokenExpiresIn</Name>

src/variables/private/GitHub.ps1

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ $script:GitHub = [pscustomobject]@{
1818
PerPage = 100
1919
RetryCount = 0
2020
RetryInterval = 1
21-
JwtTimeTolerance = 300
2221
EnvironmentType = Get-GitHubEnvironmentType
2322
}
2423
Config = $null

tests/Emojis.Tests.ps1

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' }
2+
3+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
4+
'PSUseDeclaredVarsMoreThanAssignments', '',
5+
Justification = 'Pester grouping syntax: known issue.'
6+
)]
7+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
8+
'PSAvoidUsingConvertToSecureStringWithPlainText', '',
9+
Justification = 'Used to create a secure string for testing.'
10+
)]
11+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
12+
'PSAvoidUsingWriteHost', '',
13+
Justification = 'Log outputs to GitHub Actions logs.'
14+
)]
15+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
16+
'PSAvoidLongLines', '',
17+
Justification = 'Long test descriptions and skip switches'
18+
)]
19+
[CmdletBinding()]
20+
param()
21+
22+
Describe 'Emojis' {
23+
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
24+
25+
Context 'As <Type> using <Case> on <Target>' -ForEach $authCases {
26+
BeforeAll {
27+
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
28+
LogGroup 'Context' {
29+
Write-Host ($context | Format-List | Out-String)
30+
}
31+
}
32+
AfterAll {
33+
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
34+
Write-Host ('-' * 60)
35+
}
36+
37+
# Tests for APP goes here
38+
if ($AuthType -eq 'APP') {
39+
It 'Connect-GitHubApp - Connects as a GitHub App to <Owner>' {
40+
$context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent
41+
LogGroup 'Context' {
42+
Write-Host ($context | Format-List | Out-String)
43+
}
44+
}
45+
}
46+
47+
# Tests for runners goes here
48+
if ($Type -eq 'GitHub Actions') {}
49+
50+
# Tests for IAT UAT and PAT goes here
51+
It 'Get-GitHubEmoji - Gets a list of all emojis' {
52+
$emojis = Get-GitHubEmoji
53+
LogGroup 'emojis' {
54+
Write-Host ($emojis | Format-Table | Out-String)
55+
}
56+
$emojis | Should -Not -BeNullOrEmpty
57+
}
58+
It 'Get-GitHubEmoji - Downloads all emojis' {
59+
Get-GitHubEmoji -Path $Home
60+
LogGroup 'emojis' {
61+
$emojis = Get-ChildItem -Path $Home -File
62+
Write-Host ($emojis | Format-Table | Out-String)
63+
}
64+
$emojis | Should -Not -BeNullOrEmpty
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)