diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
index 4e37e7d008..9a04c0ac94 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
@@ -79,13 +79,13 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
IGitHubService gitHubService;
if (instanceAuthenticated)
{
- authenticatedGitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
+ authenticatedGitHubService = await gitHubServiceFactory.CreateService(repositorySettings.AccessToken!, cancellationToken);
gitHubService = authenticatedGitHubService;
}
else
{
authenticatedGitHubService = null;
- gitHubService = gitHubServiceFactory.CreateService();
+ gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
}
var repoOwner = remoteInformation.RemoteRepositoryOwner!;
@@ -175,8 +175,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
}
var gitHubService = repositorySettings.AccessToken != null
- ? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
- : gitHubServiceFactory.CreateService();
+ ? await gitHubServiceFactory.CreateService(repositorySettings.AccessToken, cancellationToken)
+ : await gitHubServiceFactory.CreateService(cancellationToken);
var tasks = revisionInformation
.ActiveTestMerges
@@ -255,7 +255,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
int testMergeNumber,
CancellationToken cancellationToken)
{
- var gitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
+ var gitHubService = await gitHubServiceFactory.CreateService(repositorySettings.AccessToken!, cancellationToken);
try
{
@@ -343,7 +343,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
return;
}
- var gitHubService = gitHubServiceFactory.CreateService(gitHubAccessToken);
+ var gitHubService = await gitHubServiceFactory.CreateService(gitHubAccessToken, cancellationToken);
try
{
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
index 6109f490bb..69289bb5d4 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
@@ -49,8 +49,8 @@ namespace Tgstation.Server.Host.Components.Repository
CancellationToken cancellationToken)
{
var gitHubService = repositorySettings.AccessToken != null
- ? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
- : gitHubServiceFactory.CreateService();
+ ? await gitHubServiceFactory.CreateService(repositorySettings.AccessToken, cancellationToken)
+ : await gitHubServiceFactory.CreateService(cancellationToken);
PullRequest? pr = null;
ApiException? exception = null;
diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index d4c1335df7..c41113069d 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Controllers
Uri? repoUrl = null;
try
{
- var gitHubService = gitHubServiceFactory.CreateService();
+ var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
var releases = await gitHubService.GetTgsReleases(cancellationToken);
diff --git a/src/Tgstation.Server.Host/Core/ServerUpdater.cs b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
index 8c9d968168..1170537a12 100644
--- a/src/Tgstation.Server.Host/Core/ServerUpdater.cs
+++ b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
@@ -288,7 +288,7 @@ namespace Tgstation.Server.Host.Core
{
logger.LogDebug("Looking for GitHub releases version {version}...", newVersion);
- var gitHubService = gitHubServiceFactory.CreateService();
+ var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
var releases = await gitHubService.GetTgsReleases(cancellationToken);
foreach (var kvp in releases)
{
diff --git a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
index 3f3180f9da..d535d97884 100644
--- a/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
+++ b/src/Tgstation.Server.Host/Security/OAuth/GitHubOAuthValidator.cs
@@ -60,12 +60,12 @@ namespace Tgstation.Server.Host.Security.OAuth
{
logger.LogTrace("Validating response code...");
- var gitHubService = gitHubServiceFactory.CreateService();
+ var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
var token = await gitHubService.CreateOAuthAccessToken(oAuthConfiguration, code, cancellationToken);
if (token == null)
return null;
- var authenticatedClient = gitHubServiceFactory.CreateService(token);
+ var authenticatedClient = await gitHubServiceFactory.CreateService(token, cancellationToken);
logger.LogTrace("Getting user details...");
var userId = await authenticatedClient.GetCurrentUserId(cancellationToken);
diff --git a/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs
index e479388b79..4cf31a5d28 100644
--- a/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHub/GitHubClientFactory.cs
@@ -1,9 +1,16 @@
using System;
using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+
using Octokit;
using Tgstation.Server.Host.Configuration;
@@ -12,7 +19,7 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Utils.GitHub
{
///
- sealed class GitHubClientFactory : IGitHubClientFactory
+ sealed class GitHubClientFactory : IGitHubClientFactory, IDisposable
{
///
/// Limit to the amount of days a can live in the .
@@ -45,6 +52,11 @@ namespace Tgstation.Server.Host.Utils.GitHub
///
readonly Dictionary clientCache;
+ ///
+ /// The used to guard access to .
+ ///
+ readonly SemaphoreSlim clientCacheSemaphore;
+
///
/// Initializes a new instance of the class.
///
@@ -61,50 +73,139 @@ namespace Tgstation.Server.Host.Utils.GitHub
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
clientCache = new Dictionary();
+ clientCacheSemaphore = new SemaphoreSlim(1, 1);
}
///
- public IGitHubClient CreateClient() => GetOrCreateClient(generalConfiguration.GitHubAccessToken);
+ public void Dispose() => clientCacheSemaphore.Dispose();
///
- public IGitHubClient CreateClient(string accessToken)
- => GetOrCreateClient(
- accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
+ public async ValueTask CreateClient(CancellationToken cancellationToken)
+ => (await GetOrCreateClient(
+ generalConfiguration.GitHubAccessToken,
+ null,
+ cancellationToken))!;
+
+ ///
+ public async ValueTask CreateClient(string accessToken, CancellationToken cancellationToken)
+ => (await GetOrCreateClient(
+ accessToken ?? throw new ArgumentNullException(nameof(accessToken)),
+ null,
+ cancellationToken))!;
+
+ ///
+ public ValueTask CreateInstallationClient(string serializedPem, long repositoryId, CancellationToken cancellationToken)
+ => GetOrCreateClient(serializedPem, repositoryId, cancellationToken);
///
- /// Retrieve a from the or add a new one based on a given .
+ /// Retrieve a from the or add a new one based on a given .
///
- /// Optional access token to use as credentials.
- /// The for the given .
- GitHubClient GetOrCreateClient(string? accessToken)
+ /// Optional access token to use as credentials or GitHub App private key. If using a private key, must be set.
+ /// Setting this specifies is a private key and a GitHub App installation authenticated client will be returned.
+ /// The for the operation.
+ /// A resulting in the for the given or if authentication failed.
+#pragma warning disable CA1506 // TODO: Decomplexify
+ async ValueTask GetOrCreateClient(string? accessTokenOrSerializedPem, long? installationRepositoryId, CancellationToken cancellationToken)
+#pragma warning restore CA1506
{
GitHubClient client;
bool cacheHit;
DateTimeOffset? lastUsed;
- lock (clientCache)
+ using (await SemaphoreSlimContext.Lock(clientCacheSemaphore, cancellationToken))
{
string cacheKey;
- if (String.IsNullOrWhiteSpace(accessToken))
+ if (String.IsNullOrWhiteSpace(accessTokenOrSerializedPem))
{
- accessToken = null;
+ accessTokenOrSerializedPem = null;
cacheKey = DefaultCacheKey;
}
else
- cacheKey = accessToken;
+ cacheKey = accessTokenOrSerializedPem;
cacheHit = clientCache.TryGetValue(cacheKey, out var tuple);
var now = DateTimeOffset.UtcNow;
if (!cacheHit)
{
+ logger.LogTrace("Creating new GitHubClient...");
var product = assemblyInformationProvider.ProductInfoHeaderValue.Product!;
client = new GitHubClient(
new ProductHeaderValue(
product.Name,
product.Version));
- if (accessToken != null)
- client.Credentials = new Credentials(accessToken);
+ if (accessTokenOrSerializedPem != null)
+ {
+ if (installationRepositoryId.HasValue)
+ {
+ logger.LogTrace("Performing GitHub App authentication for installation on repository {installationRepositoryId}", installationRepositoryId.Value);
+ var splits = accessTokenOrSerializedPem.Split(':');
+ if (splits.Length != 2)
+ {
+ logger.LogError("Failed to parse serialized Client ID & PEM! Expected 2 chunks, got {chunkCount}", splits.Length);
+ return null;
+ }
+
+ byte[] pemBytes;
+ try
+ {
+ pemBytes = Convert.FromBase64String(splits[1]);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to parse supposed base64 PEM!");
+ return null;
+ }
+
+ var pem = Encoding.UTF8.GetString(pemBytes);
+
+ using var rsa = RSA.Create();
+ rsa.ImportFromPem(pem);
+
+ var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
+ var jwtSecurityTokenHandler = new JwtSecurityTokenHandler { SetDefaultTimesOnTokenCreation = false };
+
+ var nowDateTime = DateTime.UtcNow;
+
+ var jwt = jwtSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor
+ {
+ Issuer = splits[0],
+ Expires = nowDateTime.AddMinutes(10),
+ IssuedAt = nowDateTime,
+ SigningCredentials = signingCredentials,
+ });
+
+ var jwtStr = jwtSecurityTokenHandler.WriteToken(jwt);
+
+ client.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer);
+
+ Installation installation;
+ try
+ {
+ installation = await client.GitHubApps.GetRepositoryInstallationForCurrent(installationRepositoryId.Value);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to perform app authentication!");
+ return null;
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+ try
+ {
+ var installToken = await client.GitHubApps.CreateInstallationToken(installation.Id);
+
+ client.Credentials = new Credentials(installToken.Token);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to perform installation authentication!");
+ return null;
+ }
+ }
+ else
+ client.Credentials = new Credentials(accessTokenOrSerializedPem);
+ }
clientCache.Add(cacheKey, (Client: client, LastUsed: now));
lastUsed = null;
diff --git a/src/Tgstation.Server.Host/Utils/GitHub/GitHubServiceFactory.cs b/src/Tgstation.Server.Host/Utils/GitHub/GitHubServiceFactory.cs
index efa08d13bb..a38b84b271 100644
--- a/src/Tgstation.Server.Host/Utils/GitHub/GitHubServiceFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHub/GitHubServiceFactory.cs
@@ -1,4 +1,6 @@
using System;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -44,13 +46,16 @@ namespace Tgstation.Server.Host.Utils.GitHub
}
///
- public IGitHubService CreateService() => CreateServiceImpl(gitHubClientFactory.CreateClient());
+ public async ValueTask CreateService(CancellationToken cancellationToken)
+ => CreateServiceImpl(
+ await gitHubClientFactory.CreateClient(cancellationToken));
///
- public IAuthenticatedGitHubService CreateService(string accessToken)
+ public async ValueTask CreateService(string accessToken, CancellationToken cancellationToken)
=> CreateServiceImpl(
- gitHubClientFactory.CreateClient(
- accessToken ?? throw new ArgumentNullException(nameof(accessToken))));
+ await gitHubClientFactory.CreateClient(
+ accessToken ?? throw new ArgumentNullException(nameof(accessToken)),
+ cancellationToken));
///
/// Create a .
diff --git a/src/Tgstation.Server.Host/Utils/GitHub/IGitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/GitHub/IGitHubClientFactory.cs
index ec95fd16ab..1808b4ca3c 100644
--- a/src/Tgstation.Server.Host/Utils/GitHub/IGitHubClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHub/IGitHubClientFactory.cs
@@ -1,4 +1,7 @@
-using Octokit;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Octokit;
namespace Tgstation.Server.Host.Utils.GitHub
{
@@ -10,14 +13,25 @@ namespace Tgstation.Server.Host.Utils.GitHub
///
/// Create a client. Low rate limit unless the server's GitHubAccessToken is set to bypass it.
///
+ /// The for the operation.
/// A new .
- IGitHubClient CreateClient();
+ ValueTask CreateClient(CancellationToken cancellationToken);
///
/// Create a client with authentication using a personal access token.
///
/// The GitHub personal access token.
+ /// The for the operation.
/// A new .
- IGitHubClient CreateClient(string accessToken);
+ ValueTask CreateClient(string accessToken, CancellationToken cancellationToken);
+
+ ///
+ /// Creates a GitHub App client for an installation.
+ ///
+ /// The private key .
+ /// The GitHub repository ID.
+ /// The for the operation.
+ /// A resulting in a new for the given or if authentication failed.
+ ValueTask CreateInstallationClient(string pem, long repositoryId, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Utils/GitHub/IGitHubServiceFactory.cs b/src/Tgstation.Server.Host/Utils/GitHub/IGitHubServiceFactory.cs
index f4e7be70f5..adb0ec88eb 100644
--- a/src/Tgstation.Server.Host/Utils/GitHub/IGitHubServiceFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHub/IGitHubServiceFactory.cs
@@ -1,4 +1,7 @@
-namespace Tgstation.Server.Host.Utils.GitHub
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Utils.GitHub
{
///
/// Factory for s.
@@ -8,14 +11,16 @@
///
/// Create a .
///
- /// A new .
- public IGitHubService CreateService();
+ /// The for the operation.
+ /// A resulting in a new .
+ public ValueTask CreateService(CancellationToken cancellationToken);
///
/// Create an .
///
/// The access token to use for communication with GitHub.
- /// A new .
- public IAuthenticatedGitHubService CreateService(string accessToken);
+ /// The for the operation.
+ /// A resulting in a new .
+ public ValueTask CreateService(string accessToken, CancellationToken cancellationToken);
}
}
diff --git a/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubClientFactory.cs b/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubClientFactory.cs
index 6f4fac0ae2..33058be75f 100644
--- a/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubClientFactory.cs
+++ b/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubClientFactory.cs
@@ -1,5 +1,6 @@
using System;
using System.Net.Http.Headers;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -57,14 +58,14 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
mockOptions.SetupGet(x => x.Value).Returns(gc);
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger(), mockOptions.Object);
- var client = factory.CreateClient();
+ var client = await factory.CreateClient(CancellationToken.None);
Assert.IsNotNull(client);
var credentials = await client.Connection.CredentialStore.GetCredentials();
Assert.AreEqual(AuthenticationType.Anonymous, credentials.AuthenticationType);
gc.GitHubAccessToken = "asdfasdfasdfasdfasdfasdf";
- client = factory.CreateClient();
+ client = await factory.CreateClient(CancellationToken.None);
Assert.IsNotNull(client);
credentials = await client.Connection.CredentialStore.GetCredentials();
@@ -83,9 +84,9 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger(), mockOptions.Object);
- Assert.ThrowsException(() => factory.CreateClient(null));
+ await Assert.ThrowsExceptionAsync(() => factory.CreateClient(null, CancellationToken.None).AsTask());
- var client = factory.CreateClient("asdf");
+ var client = await factory.CreateClient("asdf", CancellationToken.None);
Assert.IsNotNull(client);
var credentials = await client.Connection.CredentialStore.GetCredentials();
@@ -96,7 +97,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
}
[TestMethod]
- public void TestClientCaching()
+ public async Task TestClientCaching()
{
var mockApp = new Mock();
mockApp.SetupGet(x => x.ProductInfoHeaderValue).Returns(new ProductInfoHeaderValue("TGSTests", "1.2.3")).Verifiable();
@@ -105,10 +106,10 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger(), mockOptions.Object);
- var client1 = factory.CreateClient();
- var client2 = factory.CreateClient("asdf");
- var client3 = factory.CreateClient();
- var client4 = factory.CreateClient("asdf");
+ var client1 = await factory.CreateClient(CancellationToken.None);
+ var client2 = await factory.CreateClient("asdf", CancellationToken.None);
+ var client3 = await factory.CreateClient(CancellationToken.None);
+ var client4 = await factory.CreateClient("asdf", CancellationToken.None);
Assert.ReferenceEquals(client1, client3);
Assert.ReferenceEquals(client2, client4);
}
diff --git a/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubServiceFactory.cs b/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubServiceFactory.cs
index daa9afdd4c..24212eaa93 100644
--- a/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubServiceFactory.cs
+++ b/tests/Tgstation.Server.Host.Tests/Utils/GitHub/TestGitHubServiceFactory.cs
@@ -1,4 +1,6 @@
using System;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -28,27 +30,29 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
}
[TestMethod]
- public void TestCreateService()
+ public async Task TestCreateService()
{
var mockFactory = new Mock();
- mockFactory.Setup(x => x.CreateClient()).Returns(Mock.Of()).Verifiable();
+#pragma warning disable CA2012 // Use ValueTasks correctly
+ mockFactory.Setup(x => x.CreateClient(It.IsAny())).Returns(ValueTask.FromResult(Mock.Of())).Verifiable();
var mockToken = "asdf";
- mockFactory.Setup(x => x.CreateClient(mockToken)).Returns(Mock.Of()).Verifiable();
+ mockFactory.Setup(x => x.CreateClient(mockToken, It.IsAny())).Returns(ValueTask.FromResult(Mock.Of())).Verifiable();
+#pragma warning restore CA2012 // Use ValueTasks correctly
var mockOptions = new Mock>();
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
var factory = new GitHubServiceFactory(mockFactory.Object, Mock.Of(), mockOptions.Object);
- Assert.ThrowsException(() => factory.CreateService(null));
+ await Assert.ThrowsExceptionAsync(() => factory.CreateService(null, CancellationToken.None).AsTask());
Assert.AreEqual(0, mockFactory.Invocations.Count);
- var result1 = factory.CreateService();
+ var result1 = await factory.CreateService(CancellationToken.None);
Assert.IsNotNull(result1);
- var result2 = factory.CreateService(mockToken);
+ var result2 = factory.CreateService(mockToken, CancellationToken.None);
Assert.IsNotNull(result2);
mockFactory.VerifyAll();
diff --git a/tests/Tgstation.Server.Tests/Live/DummyGitHubServiceFactory.cs b/tests/Tgstation.Server.Tests/Live/DummyGitHubServiceFactory.cs
index 2f65757782..d05faed9a6 100644
--- a/tests/Tgstation.Server.Tests/Live/DummyGitHubServiceFactory.cs
+++ b/tests/Tgstation.Server.Tests/Live/DummyGitHubServiceFactory.cs
@@ -1,4 +1,6 @@
using System;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -18,13 +20,13 @@ namespace Tgstation.Server.Tests.Live
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
- public IGitHubService CreateService() => CreateDummyService();
+ public ValueTask CreateService(CancellationToken cancellationToken) => ValueTask.FromResult(CreateDummyService());
- public IAuthenticatedGitHubService CreateService(string accessToken)
+ public ValueTask CreateService(string accessToken, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(accessToken);
- return CreateDummyService();
+ return ValueTask.FromResult(CreateDummyService());
}
TestingGitHubService CreateDummyService() => new TestingGitHubService(cryptographySuite, logger);
diff --git a/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs b/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs
index d104dbd6d3..585dd56999 100644
--- a/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs
+++ b/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Tests.Live
});
var gitHubClientFactory = new GitHubClientFactory(new AssemblyInformationProvider(), Mock.Of>(), mockOptions.Object);
- RealClient = gitHubClientFactory.CreateClient();
+ RealClient = gitHubClientFactory.CreateClient(CancellationToken.None).GetAwaiter().GetResult();
}
public static async Task InitializeAndInject(CancellationToken cancellationToken)