feat: implement Guzzle facade and update JwtMiddleware to use it (#22)
All checks were successful
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m59s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m55s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 3m9s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Successful in 3m5s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 3m51s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Successful in 3m11s

Reviewed-on: #22
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
This commit was merged in pull request #22.
This commit is contained in:
2025-11-25 16:51:45 +00:00
committed by Siteworx Pro Gitea
parent a9a5cb6216
commit 721008bdfc
5 changed files with 202 additions and 22 deletions

View File

@@ -6,7 +6,6 @@ namespace Siteworxpro\App\Http\Middleware;
use Carbon\Carbon;
use Carbon\WrapperClock;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Hmac\Sha256 as Hmac256;
@@ -28,6 +27,7 @@ use Siteworxpro\App\Attributes\Guards\Jwt;
use Siteworxpro\App\Controllers\Controller;
use Siteworxpro\App\Http\JsonResponseFactory;
use Siteworxpro\App\Services\Facades\Config;
use Siteworxpro\App\Services\Facades\Guzzle;
use Siteworxpro\App\Services\Facades\Redis;
use Siteworxpro\HttpStatus\CodesEnum;
@@ -133,21 +133,21 @@ class JwtMiddleware extends Middleware
}
return JsonResponseFactory::createJsonResponse([
'status_code' => 401,
'status_code' => CodesEnum::UNAUTHORIZED->value,
'message' => 'Unauthorized: Invalid token',
'errors' => $violations
], CodesEnum::UNAUTHORIZED);
} catch (InvalidTokenStructure) {
// Token could not be parsed due to malformed structure.
return JsonResponseFactory::createJsonResponse([
'status_code' => 401,
'status_code' => CodesEnum::UNAUTHORIZED->value,
'message' => 'Unauthorized: Invalid token',
], CodesEnum::UNAUTHORIZED);
} catch (GuzzleException) {
} catch (GuzzleException | \RuntimeException) {
return JsonResponseFactory::createJsonResponse([
'status_code' => 501,
'message' => 'Token validation service unavailable',
], CodesEnum::UNAUTHORIZED);
'status_code' => CodesEnum::INTERNAL_SERVER_ERROR->value,
'message' => 'Token validation service unavailable or unknown error',
], CodesEnum::INTERNAL_SERVER_ERROR);
}
// Expose all token claims as request attributes for downstream consumers.
@@ -170,7 +170,6 @@ class JwtMiddleware extends Middleware
* @return SignedWith Signature constraint used during JWT parsing.
*
* @throws \RuntimeException When no signing key is configured.
* @throws GuzzleException On JWKS key retrieval issues.
* @throws \JsonException
*/
private function getSignedWith(string $token): SignedWith
@@ -205,9 +204,6 @@ class JwtMiddleware extends Middleware
return new SignedWith(new Hmac256(), $key);
}
/**
* @throws GuzzleException
*/
private function getJwksKey(string $url, string $keyId): Key
{
$cached = Redis::get('jwks_key_' . $keyId);
@@ -215,15 +211,14 @@ class JwtMiddleware extends Middleware
return InMemory::plainText($cached);
}
$client = new Client();
$openIdConfig = $client->get($url);
$openIdConfig = Guzzle::get($url);
$body = json_decode($openIdConfig->getBody()->getContents(), true, JSON_THROW_ON_ERROR);
$jwksUri = $body['jwks_uri'] ?? '';
if (empty($jwksUri)) {
throw new \RuntimeException('JWKS URI not found in OpenID configuration.');
}
$jwksResponse = $client->get($jwksUri);
$jwksResponse = Guzzle::get($jwksUri);
$jwksBody = json_decode(
$jwksResponse->getBody()->getContents(),
true,
@@ -234,7 +229,7 @@ class JwtMiddleware extends Middleware
$firstKey = array_filter(
$jwksBody['keys'],
fn($key) => $key['kid'] === $keyId
)[0] ?? null;
)[0] ?? $jwksBody['keys'][0] ?? null;
if (empty($firstKey)) {
throw new \RuntimeException('No matching key found in JWKS for key ID: ' . $keyId);

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Services\Facades;
use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
use Siteworxpro\App\Services\Facade;
/**
* @method static Response get(string $uri, array $options = [])
* @method static Response post(string $uri, array $options = [])
* @method static Response put(string $uri, array $options = [])
* @method static Response delete(string $uri, array $options = [])
* @method static Response patch(string $uri, array $options = [])
* @method static Response head(string $uri, array $options = [])
* @method static PromiseInterface sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = [])
* @method static PromiseInterface requestAsync(string $method, string $uri, array $options = [])
*/
class Guzzle extends Facade
{
protected static function getFacadeAccessor(): string
{
return Client::class;
}
}