<?php
declare(strict_types=1);
namespace SHServ\Integrations\GAuth;
use GNexus\GAuth\Client\GAuthClient;
use GNexus\GAuth\Config\GAuthConfig;
use GNexus\GAuth\DTO\AuthenticatedUser;
use GNexus\GAuth\DTO\TokenSet;
use GNexus\GAuth\OAuth\HttpTokenEndpoint;
use GNexus\GAuth\Runtime\HttpRuntimeUserProvider;
use GNexus\GAuth\Webhook\HmacWebhookVerifier;
use GNexus\GAuth\Webhook\JsonWebhookParser;
use SHServ\Integrations\GAuth\Http\CurlHttpClient;
use SHServ\Integrations\GAuth\Http\Psr7Factory;
use SHServ\Integrations\GAuth\Store\DbTokenStore;
use SHServ\Integrations\GAuth\Store\SessionPkceStore;
use SHServ\Integrations\GAuth\Store\SessionStateStore;
final class AuthService
{
private GAuthClient $client;
private GAuthConfig $config;
public function __construct()
{
$cfg = FCONF['gauth'];
$this->config = new GAuthConfig(
baseUrl: $cfg['base_url'],
clientId: $cfg['client_id'],
clientSecret: $cfg['client_secret'],
redirectUri: $cfg['redirect_uri'],
userAgent: 'shserv/' . (FCONF['version'] ?? '0.4'),
);
$httpClient = new CurlHttpClient();
$factory = new Psr7Factory();
$this->client = new GAuthClient(
config: $this->config,
tokenEndpoint: new HttpTokenEndpoint($this->config, $httpClient, $factory, $factory),
runtimeUserProvider: new HttpRuntimeUserProvider($this->config, $httpClient, $factory),
webhookVerifier: new HmacWebhookVerifier($this->config),
webhookParser: new JsonWebhookParser(),
stateStore: new SessionStateStore(),
pkceStore: new SessionPkceStore(),
);
}
public function getClient(): GAuthClient
{
return $this->client;
}
public function getConfig(): GAuthConfig
{
return $this->config;
}
/**
* Build authorization URL and redirect user to gnexus-auth.
*/
public function buildLoginUrl(?string $returnTo = null): string
{
$authRequest = $this->client->buildAuthorizationRequest(
returnTo: $returnTo,
scopes: ['openid', 'email', 'profile', 'roles', 'permissions'],
);
return $authRequest->authorizationUrl;
}
/**
* Exchange authorization code for tokens and fetch user info.
*/
public function handleCallback(string $code, string $state): AuthenticatedUser
{
$tokenSet = $this->client->exchangeAuthorizationCode($code, $state);
$user = $this->client->fetchUser($tokenSet->accessToken);
// Persist tokens in session (DB persistence happens after user resolution)
$sessionToken = bin2hex(random_bytes(32));
$_SESSION['shserv_auth_token'] = $sessionToken;
$_SESSION['shserv_access_token'] = $tokenSet->accessToken;
$_SESSION['shserv_user_id'] = $user->userId;
// Store token set temporarily for callback controller to persist to DB
$_SESSION['shserv_last_token_set'] = [
'access_token' => $tokenSet->accessToken,
'refresh_token' => $tokenSet->refreshToken,
'token_type' => $tokenSet->tokenType,
'expires_in' => $tokenSet->expiresIn,
'expires_at' => $tokenSet->expiresAt ? $tokenSet->expiresAt->format('Y-m-d H:i:s') : null,
];
return $user;
}
/**
* Logout: revoke token + clear local session.
*/
public function logout(): void
{
$sessionToken = $_SESSION['shserv_auth_token'] ?? null;
if ($sessionToken) {
$dbStore = new DbTokenStore(app()->thin_builder);
$tokenSet = $dbStore->get($sessionToken);
if ($tokenSet && $tokenSet->refreshToken) {
try {
$this->client->revokeToken($tokenSet->refreshToken, 'refresh_token');
} catch (\Throwable $e) {
// ignore revoke failures during logout
}
}
$dbStore->forget($sessionToken);
}
unset(
$_SESSION['shserv_auth_token'],
$_SESSION['shserv_access_token'],
$_SESSION['shserv_user_id'],
$_SESSION['gauth_state'],
$_SESSION['gauth_pkce']
);
}
/**
* Refresh access token using stored refresh token.
*/
public function refreshAccessToken(string $sessionToken): ?TokenSet
{
$dbStore = new DbTokenStore(app()->thin_builder);
$tokenSet = $dbStore->get($sessionToken);
if (!$tokenSet || !$tokenSet->refreshToken) {
return null;
}
try {
$newTokenSet = $this->client->refreshToken($tokenSet->refreshToken);
$dbStore->put($sessionToken, $newTokenSet);
$_SESSION['shserv_access_token'] = $newTokenSet->accessToken;
return $newTokenSet;
} catch (\Throwable $e) {
return null;
}
}
/**
* Get current authenticated user from access token in session.
*/
public function getCurrentUser(): ?AuthenticatedUser
{
$accessToken = $_SESSION['shserv_access_token'] ?? null;
if (!$accessToken) {
return null;
}
try {
return $this->client->fetchUser($accessToken);
} catch (\Throwable $e) {
return null;
}
}
/**
* Verify webhook signature and parse payload.
*/
public function verifyWebhook(string $rawBody, array $headers): void
{
$secret = FCONF['gauth']['webhook_secret'] ?? '';
if ($secret === '') {
throw new \RuntimeException('Webhook secret not configured.');
}
$this->client->verifyWebhook($rawBody, $headers, $secret);
}
public function parseWebhook(string $rawBody): \GNexus\GAuth\DTO\WebhookEvent
{
return $this->client->parseWebhook($rawBody);
}
}