<?php
declare(strict_types=1);
namespace GNexus\GAuth\Runtime;
use GNexus\GAuth\Config\GAuthConfig;
use GNexus\GAuth\Contract\RuntimeUserProviderInterface;
use GNexus\GAuth\DTO\AuthenticatedUser;
use GNexus\GAuth\DTO\ClientAccess;
use GNexus\GAuth\Exception\RuntimeApiException;
use GNexus\GAuth\Exception\TransportException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Log\LoggerInterface;
final readonly class HttpRuntimeUserProvider implements RuntimeUserProviderInterface
{
public function __construct(
private GAuthConfig $config,
private ClientInterface $httpClient,
private RequestFactoryInterface $requestFactory,
private ?LoggerInterface $logger = null,
) {
}
public function fetchUser(string $accessToken): AuthenticatedUser
{
$request = $this->requestFactory->createRequest('GET', $this->config->userInfoUrl())
->withHeader('Accept', 'application/json')
->withHeader('Authorization', sprintf('Bearer %s', $accessToken));
if ($this->config->userAgent() !== null) {
$request = $request->withHeader('User-Agent', $this->config->userAgent());
}
try {
$response = $this->httpClient->sendRequest($request);
} catch (ClientExceptionInterface $exception) {
$this->logger?->error('gnexus-auth runtime transport failure', [
'message' => $exception->getMessage(),
]);
throw new TransportException('Request to gnexus-auth runtime API failed.', 0, $exception);
}
if ($response->getStatusCode() >= 400) {
throw new RuntimeApiException('gnexus-auth runtime API returned an error response.');
}
$payload = json_decode((string) $response->getBody(), true);
if (! is_array($payload)) {
throw new RuntimeApiException('gnexus-auth runtime API returned malformed JSON.');
}
$clientAccessList = [];
$client = $payload['client'] ?? null;
if (is_array($client) && isset($client['client_id']) && is_string($client['client_id'])) {
$clientAccessList[] = new ClientAccess(
clientId: $client['client_id'],
accessStatus: 'granted',
roleIds: $this->stringList($client['roles'] ?? []),
permissionIds: $this->stringList($client['permissions'] ?? []),
);
}
return new AuthenticatedUser(
userId: (string) ($payload['sub'] ?? $payload['id'] ?? ''),
email: (string) ($payload['email'] ?? ''),
emailVerified: (bool) ($payload['email_verified'] ?? false),
systemRole: isset($payload['system_role']) ? (string) $payload['system_role'] : null,
status: isset($payload['status']) ? (string) $payload['status'] : null,
profile: is_array($payload['profile'] ?? null) ? $payload['profile'] : [],
clientAccessList: $clientAccessList,
rawPayload: $payload,
);
}
/**
* @param mixed $value
* @return array<int, string>
*/
private function stringList(mixed $value): array
{
if (! is_array($value)) {
return [];
}
return array_values(array_map(static fn ($item) => (string) $item, $value));
}
}