createClient($service); $client->setRedirectUri(self::getRedirectUri()); $client->setState($service->value); $client->setAccessType('offline'); $client->setPrompt('consent'); $client->setIncludeGrantedScopes(true); return $client->createAuthUrl(); } public function handleCallback(string $code, GoogleService $service): GoogleOAuthConnection { $client = $this->createClient($service); $client->setRedirectUri(self::getRedirectUri()); $token = $client->fetchAccessTokenWithAuthCode($code); if (isset($token['error'])) { throw new \RuntimeException('Google OAuth error: ' . ($token['error_description'] ?? $token['error'])); } if (!isset($token['refresh_token'])) { throw new \RuntimeException('No refresh token returned. Ensure access_type=offline and prompt=consent are set.'); } $clientId = $client->getClientId(); $clientSecret = $client->getClientSecret(); return GoogleOAuthConnection::updateOrCreate( ['service' => $service->value], [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'refresh_token' => $token['refresh_token'], ] ); } public function revoke(GoogleService $service): void { $connection = $this->getConnection($service); if (!$connection) { return; } $client = $this->createClient($service); $client->revokeToken(); $connection->delete(); } public function getAccessToken(GoogleService $service): ?string { $connection = $this->getConnection($service); if (!$connection) { return null; } try { $client = $this->createClient($service); $client->setAccessToken([ 'access_token' => '', 'refresh_token' => $connection->refresh_token, 'client_id' => $connection->client_id, 'client_secret' => $connection->client_secret, ]); if ($client->isAccessTokenExpired()) { $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); $newToken = $client->getAccessToken(); if (isset($newToken['refresh_token'])) { $connection->refresh_token = $newToken['refresh_token']; $connection->save(); } } $accessToken = $client->getAccessToken(); return $accessToken['access_token'] ?? null; } catch (\Exception $e) { Log::error('Google OAuth token refresh failed for ' . $service->value . ': ' . $e->getMessage()); return null; } } public function isAuthorized(GoogleService $service): bool { return $this->getConnection($service) !== null; } public function getConnection(GoogleService $service): ?GoogleOAuthConnection { return GoogleOAuthConnection::where('service', $service->value)->first(); } public function buildClient(GoogleService $service): Client { $client = $this->createClient($service); $connection = $this->getConnection($service); if ($connection) { $client->setAccessToken([ 'access_token' => '', 'refresh_token' => $connection->refresh_token, 'client_id' => $connection->client_id, 'client_secret' => $connection->client_secret, ]); if ($client->isAccessTokenExpired()) { $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); } } return $client; } private function createClient(GoogleService $service): Client { $client = new Client(); $client->setApplicationName(config('app.name')); $client->setScopes([$service->scope()]); $client->setClientId(config('services.google.client_id')); $client->setClientSecret(config('services.google.client_secret')); $client->setRedirectUri(self::getRedirectUri()); return $client; } }