diff --git a/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs index 2a1554f0e9..00a99c6330 100644 --- a/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs +++ b/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs @@ -30,6 +30,11 @@ namespace Tgstation.Server.Host.Utils.GitHub /// Set to app installation token lifetime, which is the lowest. See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app. const uint ClientCacheHours = 1; + /// + /// Minutes before tokens expire before not using them. + /// + const uint AppTokenExpiryGraceMinutes = 15; + /// /// The used in place of when accessing a configuration-based client with no token set in . /// @@ -56,9 +61,9 @@ namespace Tgstation.Server.Host.Utils.GitHub readonly GeneralConfiguration generalConfiguration; /// - /// Cache of created s and last used times, keyed by access token. + /// Cache of created s and last used/expiry times, keyed by access token. /// - readonly Dictionary clientCache; + readonly Dictionary clientCache; /// /// The used to guard access to . @@ -83,7 +88,7 @@ namespace Tgstation.Server.Host.Utils.GitHub this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); - clientCache = new Dictionary(); + clientCache = new Dictionary(); clientCacheSemaphore = new SemaphoreSlim(1, 1); } @@ -137,13 +142,20 @@ namespace Tgstation.Server.Host.Utils.GitHub else cacheKey = accessString; - cacheHit = clientCache.TryGetValue(cacheKey, out var tuple); - var now = DateTimeOffset.UtcNow; - if (!cacheHit) + cacheHit = clientCache.TryGetValue(cacheKey, out var tuple); + var tokenValid = cacheHit && (!tuple.Expiry.HasValue || tuple.Expiry.Value <= now); + if (!tokenValid) { + if (cacheHit) + { + logger.LogDebug("Previously cached GitHub token has expired!"); + clientCache.Remove(cacheKey); + } + logger.LogTrace("Creating new GitHubClient..."); + DateTimeOffset? expiry = null; if (accessString != null) { if (accessString.StartsWith(RepositorySettings.TgsAppPrivateKeyPrefix)) @@ -177,6 +189,7 @@ namespace Tgstation.Server.Host.Utils.GitHub var installToken = await client.GitHubApps.CreateInstallationToken(installation.Id); client.Credentials = new Credentials(installToken.Token); + expiry = installToken.ExpiresAt.AddMinutes(-AppTokenExpiryGraceMinutes); } catch (Exception ex) { @@ -193,7 +206,7 @@ namespace Tgstation.Server.Host.Utils.GitHub else client = CreateUnauthenticatedClient(); - clientCache.Add(cacheKey, (Client: client, LastUsed: now)); + clientCache.Add(cacheKey, (Client: client, LastUsed: now, Expiry: expiry)); lastUsed = null; } else @@ -213,7 +226,7 @@ namespace Tgstation.Server.Host.Utils.GitHub continue; // save the hash lookup tuple = clientCache[key]; - if (tuple.LastUsed <= purgeAfter) + if (tuple.LastUsed <= purgeAfter || (tuple.Expiry.HasValue && tuple.Expiry.Value <= now)) { clientCache.Remove(key); ++purgeCount;