From 4d34d689b0420e2b4cde02d90886d00cffdfdef3 Mon Sep 17 00:00:00 2001
From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Date: Mon, 6 Jan 2025 16:27:18 +0100
Subject: [PATCH] refactor: github.php
- Rename functions
- Consolidate Code
- Fix: timing issues with JWT tokens
- Clearer error handling
---
bootstrap/helpers/github.php | 107 ++++++++++++++++++++---------------
1 file changed, 61 insertions(+), 46 deletions(-)
diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php
index 529ac82b1..cd36845a1 100644
--- a/bootstrap/helpers/github.php
+++ b/bootstrap/helpers/github.php
@@ -12,77 +12,91 @@ use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Builder;
-function generate_github_installation_token(GithubApp $source)
+function generateGithubToken(GithubApp $source, string $type)
{
$signingKey = InMemory::plainText($source->privateKey->private_key);
$algorithm = new Sha256;
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
- $now = CarbonImmutable::now();
- $now = $now->setTime($now->format('H'), $now->format('i'));
- $issuedToken = $tokenBuilder
- ->issuedBy($source->app_id)
- ->issuedAt($now)
- ->expiresAt($now->modify('+10 minutes'))
- ->getToken($algorithm, $signingKey)
- ->toString();
- $token = Http::withHeaders([
- 'Authorization' => "Bearer $issuedToken",
- 'Accept' => 'application/vnd.github.machine-man-preview+json',
- ])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
- if ($token->failed()) {
- throw new RuntimeException('Failed to get access token for '.$source->name.' with error: '.data_get($token->json(), 'message', 'no error message found'));
- }
+ $now = CarbonImmutable::now()->setTimezone('UTC');
- return $token->json()['token'];
-}
-
-function generate_github_jwt_token(GithubApp $source)
-{
- $signingKey = InMemory::plainText($source->privateKey->private_key);
- $algorithm = new Sha256;
- $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
- $now = CarbonImmutable::now();
- $now = $now->setTime($now->format('H'), $now->format('i'));
-
- return $tokenBuilder
+ $jwt = $tokenBuilder
->issuedBy($source->app_id)
->issuedAt($now->modify('-1 minute'))
- ->expiresAt($now->modify('+10 minutes'))
+ ->expiresAt($now->modify('+8 minutes'))
->getToken($algorithm, $signingKey)
->toString();
+
+ return match ($type) {
+ 'jwt' => $jwt,
+ 'installation' => (function () use ($source, $jwt) {
+ $response = Http::withHeaders([
+ 'Authorization' => "Bearer $jwt",
+ 'Accept' => 'application/vnd.github.machine-man-preview+json',
+ ])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
+
+ if (! $response->successful()) {
+ throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".data_get($response->json(), 'message', 'no error message found'));
+ }
+
+ return $response->json()['token'];
+ })(),
+ default => throw new \InvalidArgumentException("Unsupported token type: {$type}")
+ };
+}
+
+function generateGithubInstallationToken(GithubApp $source)
+{
+ return generateGithubToken($source, 'installation');
+}
+
+function generateGithubJwt(GithubApp $source)
+{
+ return generateGithubToken($source, 'jwt');
}
function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true)
{
if (is_null($source)) {
- throw new \Exception('Not implemented yet.');
+ throw new \Exception('Source is required for API calls');
}
- if ($source->getMorphClass() === \App\Models\GithubApp::class) {
- if ($source->is_public) {
- $response = Http::github($source->api_url)->$method($endpoint);
+
+ if ($source->getMorphClass() !== GithubApp::class) {
+ throw new \InvalidArgumentException("Unsupported source type: {$source->getMorphClass()}");
+ }
+
+ if ($source->is_public) {
+ $response = Http::GitHub($source->api_url)->$method($endpoint);
+ } else {
+ $token = generateGithubInstallationToken($source);
+ if ($data && in_array(strtolower($method), ['post', 'patch', 'put'])) {
+ $response = Http::GitHub($source->api_url, $token)->$method($endpoint, $data);
} else {
- $github_access_token = generate_github_installation_token($source);
- if ($data && ($method === 'post' || $method === 'patch' || $method === 'put')) {
- $response = Http::github($source->api_url, $github_access_token)->$method($endpoint, $data);
- } else {
- $response = Http::github($source->api_url, $github_access_token)->$method($endpoint);
- }
+ $response = Http::GitHub($source->api_url, $token)->$method($endpoint);
}
}
- $json = $response->json();
- if ($response->failed() && $throwError) {
- ray($json);
- throw new \Exception("Failed to get data from {$source->name} with error:
".$json['message'].'
Rate Limit resets at: '.Carbon::parse((int) $response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s').'UTC');
+
+ if (! $response->successful() && $throwError) {
+ $resetTime = Carbon::parse((int) $response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s');
+ $errorMessage = data_get($response->json(), 'message', 'no error message found');
+ $remainingCalls = $response->header('X-RateLimit-Remaining', '0');
+
+ throw new \Exception(
+ "GitHub API call failed:\n".
+ "Error: {$errorMessage}\n".
+ "Rate Limit Status:\n".
+ "- Remaining Calls: {$remainingCalls}\n".
+ "- Reset Time: {$resetTime} UTC"
+ );
}
return [
'rate_limit_remaining' => $response->header('X-RateLimit-Remaining'),
'rate_limit_reset' => $response->header('X-RateLimit-Reset'),
- 'data' => collect($json),
+ 'data' => collect($response->json()),
];
}
-function get_installation_path(GithubApp $source)
+function getInstallationPath(GithubApp $source)
{
$github = GithubApp::where('uuid', $source->uuid)->first();
$name = str(Str::kebab($github->name));
@@ -90,7 +104,8 @@ function get_installation_path(GithubApp $source)
return "$github->html_url/$installation_path/$name/installations/new";
}
-function get_permissions_path(GithubApp $source)
+
+function getPermissionsPath(GithubApp $source)
{
$github = GithubApp::where('uuid', $source->uuid)->first();
$name = str(Str::kebab($github->name));