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; } }