Newer
Older
gnexus-auth-client-php / src / Client / GAuthClient.php
@Eugene Sukhodolskiy Eugene Sukhodolskiy 12 hours ago 4 KB Initial auth client package scaffold
<?php

declare(strict_types=1);

namespace GNexus\GAuth\Client;

use GNexus\GAuth\Config\GAuthConfig;
use GNexus\GAuth\Contract\ClockInterface;
use GNexus\GAuth\Contract\PkceStoreInterface;
use GNexus\GAuth\Contract\RuntimeUserProviderInterface;
use GNexus\GAuth\Contract\StateStoreInterface;
use GNexus\GAuth\Contract\TokenEndpointInterface;
use GNexus\GAuth\Contract\WebhookParserInterface;
use GNexus\GAuth\Contract\WebhookVerifierInterface;
use GNexus\GAuth\DTO\AuthenticatedUser;
use GNexus\GAuth\DTO\AuthorizationRequest;
use GNexus\GAuth\DTO\TokenSet;
use GNexus\GAuth\DTO\VerifiedWebhook;
use GNexus\GAuth\DTO\WebhookEvent;
use GNexus\GAuth\Exception\PkceException;
use GNexus\GAuth\Exception\StateValidationException;
use GNexus\GAuth\OAuth\AuthorizationUrlBuilder;
use GNexus\GAuth\OAuth\PkceGenerator;
use GNexus\GAuth\Support\SystemClock;

final class GAuthClient
{
    private readonly AuthorizationUrlBuilder $authorizationUrlBuilder;
    private readonly ClockInterface $clock;

    public function __construct(
        private readonly GAuthConfig $config,
        private readonly TokenEndpointInterface $tokenEndpoint,
        private readonly RuntimeUserProviderInterface $runtimeUserProvider,
        private readonly WebhookVerifierInterface $webhookVerifier,
        private readonly WebhookParserInterface $webhookParser,
        private readonly StateStoreInterface $stateStore,
        private readonly PkceStoreInterface $pkceStore,
        ?ClockInterface $clock = null,
        ?AuthorizationUrlBuilder $authorizationUrlBuilder = null,
    ) {
        $this->clock = $clock ?? new SystemClock();
        $this->authorizationUrlBuilder = $authorizationUrlBuilder ?? new AuthorizationUrlBuilder($config);
    }

    public function buildAuthorizationRequest(?string $returnTo = null, array $scopes = []): AuthorizationRequest
    {
        $state = PkceGenerator::generateState();
        $verifier = PkceGenerator::generateVerifier();
        $challenge = PkceGenerator::generateChallenge($verifier);
        $expiresAt = $this->clock->now()->modify(sprintf('+%d seconds', $this->config->stateTtlSeconds()));

        $this->stateStore->put($state, $expiresAt, [
            'return_to' => $returnTo,
            'scopes' => array_values($scopes),
        ]);

        $this->pkceStore->put($state, $verifier, $expiresAt);

        $url = $this->authorizationUrlBuilder->build(
            state: $state,
            pkceChallenge: $challenge,
            returnTo: $returnTo,
            scopes: $scopes,
        );

        return new AuthorizationRequest(
            authorizationUrl: $url,
            state: $state,
            pkceVerifier: $verifier,
            pkceChallenge: $challenge,
            scopes: array_values($scopes),
            returnTo: $returnTo,
        );
    }

    public function exchangeAuthorizationCode(string $code, string $state): TokenSet
    {
        if (! $this->stateStore->has($state)) {
            throw new StateValidationException('Unknown or expired authorization state.');
        }

        $verifier = $this->pkceStore->get($state);

        if ($verifier === null || $verifier === '') {
            throw new PkceException('Missing PKCE verifier for authorization callback.');
        }

        $tokenSet = $this->tokenEndpoint->exchangeAuthorizationCode($code, $verifier);

        $this->stateStore->forget($state);
        $this->pkceStore->forget($state);

        return $tokenSet;
    }

    public function refreshToken(string $refreshToken): TokenSet
    {
        return $this->tokenEndpoint->refreshToken($refreshToken);
    }

    public function revokeToken(string $token, ?string $tokenTypeHint = null): void
    {
        $this->tokenEndpoint->revokeToken($token, $tokenTypeHint);
    }

    public function fetchUser(string $accessToken): AuthenticatedUser
    {
        return $this->runtimeUserProvider->fetchUser($accessToken);
    }

    public function verifyWebhook(string $rawBody, array $headers, string $secret): VerifiedWebhook
    {
        return $this->webhookVerifier->verify($rawBody, $headers, $secret);
    }

    public function parseWebhook(string $rawBody): WebhookEvent
    {
        return $this->webhookParser->parse($rawBody);
    }

    public function verifyAndParseWebhook(string $rawBody, array $headers, string $secret): WebhookEvent
    {
        $this->verifyWebhook($rawBody, $headers, $secret);

        return $this->parseWebhook($rawBody);
    }
}