179 lines
5.6 KiB
PHP
179 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\GoogleOAuthConnection;
|
|
use App\Enums\GoogleService;
|
|
use Google_Client;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class GoogleOAuthService
|
|
{
|
|
private const TOKEN_CACHE_TTL = 300;
|
|
private const MAX_RETRIES = 2;
|
|
|
|
public function buildClient(): Google_Client
|
|
{
|
|
$client = new Google_Client();
|
|
$client->setClientId(config('services.google.client_id'));
|
|
$client->setClientSecret(config('services.google.client_secret'));
|
|
$client->setRedirectUri(route('google-oauth.callback'));
|
|
$client->setAccessType('offline');
|
|
$client->setPrompt('consent');
|
|
$client->setIncludeGrantedScopes(true);
|
|
|
|
return $client;
|
|
}
|
|
|
|
public function getAuthUrl(string $service): string
|
|
{
|
|
$serviceEnum = GoogleService::from($service);
|
|
$client = $this->buildClient();
|
|
$client->setScopes($serviceEnum->scopes());
|
|
$client->setState($service);
|
|
|
|
return $client->createAuthUrl();
|
|
}
|
|
|
|
public function handleCallback(string $authorizationCode, GoogleService $service): GoogleOAuthConnection
|
|
{
|
|
$client = $this->buildClient();
|
|
$client->setScopes($service->scopes());
|
|
$client->fetchAccessTokenWithAuthCode($authorizationCode);
|
|
|
|
$tokenData = $client->getAccessToken();
|
|
|
|
if (isset($tokenData['error'])) {
|
|
throw new \RuntimeException('Google OAuth error: ' . ($tokenData['error_description'] ?? $tokenData['error']));
|
|
}
|
|
|
|
$token = $tokenData['access_token'];
|
|
$refreshToken = $tokenData['refresh_token'] ?? null;
|
|
$expiresAt = now()->addSeconds($tokenData['expires_in']);
|
|
|
|
$email = $this->getEmailFromToken($token);
|
|
|
|
$connection = GoogleOAuthConnection::updateOrCreate([
|
|
'user_id' => auth()->id(),
|
|
'service' => $service->value,
|
|
'email' => $email,
|
|
], [
|
|
'access_token' => $token,
|
|
'refresh_token' => $refreshToken,
|
|
'expires_at' => $expiresAt,
|
|
]);
|
|
|
|
return $connection;
|
|
}
|
|
|
|
public function getAccessToken(GoogleOAuthConnection $connection): string
|
|
{
|
|
$cacheKey = "google_oauth_token_{$connection->id}";
|
|
|
|
return Cache::remember($cacheKey, self::TOKEN_CACHE_TTL, function () use ($connection) {
|
|
if ($connection->isExpired()) {
|
|
$this->refreshToken($connection);
|
|
}
|
|
return $connection->access_token;
|
|
});
|
|
}
|
|
|
|
public function refreshToken(GoogleOAuthConnection $connection): void
|
|
{
|
|
if (!$connection->refresh_token) {
|
|
throw new \RuntimeException('Impossibile rinnovare: nessun refresh token');
|
|
}
|
|
|
|
$client = $this->buildClient();
|
|
$client->fetchAccessTokenWithRefreshToken($connection->refresh_token);
|
|
|
|
$tokenData = $client->getAccessToken();
|
|
|
|
if (isset($tokenData['error'])) {
|
|
$connection->delete();
|
|
Cache::forget("google_oauth_token_{$connection->id}");
|
|
|
|
throw new \RuntimeException(
|
|
'Refresh token scaduto o revocato per ' . $connection->email
|
|
);
|
|
}
|
|
|
|
$connection->access_token = $tokenData['access_token'];
|
|
$connection->expires_at = now()->addSeconds($tokenData['expires_in']);
|
|
|
|
if (isset($tokenData['refresh_token'])) {
|
|
$connection->refresh_token = $tokenData['refresh_token'];
|
|
}
|
|
|
|
$connection->save();
|
|
Cache::forget("google_oauth_token_{$connection->id}");
|
|
}
|
|
|
|
public function revoke(GoogleOAuthConnection $connection): void
|
|
{
|
|
$client = $this->buildClient();
|
|
$client->revokeToken($connection->access_token);
|
|
|
|
$connection->delete();
|
|
Cache::forget("google_oauth_token_{$connection->id}");
|
|
}
|
|
|
|
public function createXoAuth2SmtpTransport(
|
|
string $email,
|
|
string $accessToken
|
|
): \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport {
|
|
$transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
|
|
'smtp.gmail.com',
|
|
587,
|
|
true,
|
|
);
|
|
|
|
$transport->setUsername($email);
|
|
$transport->setPassword($accessToken);
|
|
$transport->setAuthenticators([
|
|
new \Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator(),
|
|
]);
|
|
|
|
return $transport;
|
|
}
|
|
|
|
private function getEmailFromToken(string $accessToken): string
|
|
{
|
|
$client = $this->buildClient();
|
|
$payload = $client->verifyIdToken($accessToken);
|
|
|
|
if ($payload && isset($payload['email'])) {
|
|
return $payload['email'];
|
|
}
|
|
|
|
$http = new \GuzzleHttp\Client();
|
|
$response = $http->get('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', [
|
|
'headers' => ['Authorization' => "Bearer $accessToken"],
|
|
]);
|
|
$data = json_decode((string) $response->getBody(), true);
|
|
|
|
return $data['email'];
|
|
}
|
|
|
|
public function getConnectionStatus(): array
|
|
{
|
|
$connections = GoogleOAuthConnection::where('user_id', auth()->id())->get();
|
|
$status = [];
|
|
|
|
foreach (GoogleService::cases() as $service) {
|
|
$connection = $connections->firstWhere('service', $service->value);
|
|
$status[$service->value] = [
|
|
'connected' => $connection !== null,
|
|
'connection' => $connection,
|
|
'email' => $connection?->email,
|
|
'expired' => $connection?->isExpired() ?? false,
|
|
];
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
}
|