Files

219 lines
9.2 KiB
PHP
Executable File

<?php
namespace App\Services\VK;
use App\Services\VK\Album\VkAlbumService;
use App\Services\VK\Wall\VkWallService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use VK\Client\VKApiClient;
class VkAuthService
{
public function getToken()
{
try {
// Получаем последний токен
$token = DB::table('vk_tokens')->latest()->first();
// Проверяем, существует ли токен и является ли он валидным
if ($token && !$this->isTokenValid($token)) {
// Если токен не валиден, обновляем его
return $this->refresh();
}
return $token;
} catch (\Exception $e) {
// Обработка ошибок, например, логирование
Log::channel('vk')->error('Failed to get VK token', ['error' => $e->getMessage()]);
return null; // Или выбросьте исключение, если это необходимо
}
}
public function redirectToProvider()
{
$state = bin2hex(random_bytes(16)); // Генерирует случайную строку состояния (state) для защиты от CSRF-атак.
session(['vk_state' => $state]); // Сохраняет строку состояния в сессии под ключом 'vk_state'.
$code_verifier = $this->generateCodeVerifier(); // Создает случайную строку code_verifier для протокола PKCE.
$code_challenge = $this->generateCodeChallenge($code_verifier); // Генерирует code_challenge на основе code_verifier (обычно хеш SHA-256).
session(['vk_code_verifier' => $code_verifier]); // Сохраняет code_verifier в сессии под ключом 'vk_code_verifier'.
$url = 'https://id.vk.ru/authorize?' . http_build_query([ // Формирует URL для перенаправления на страницу авторизации VK ID с параметрами:
'response_type' => 'code', // Указывает, что нужен код авторизации.
'client_id' => config('services.vk.app_id'), // ID приложения VK из переменных окружения.
'redirect_uri' => config('services.vk.redirect_uri'), // URI для перенаправления после авторизации.
'state' => $state, // Передает строку состояния для проверки.
'scope' => 'photos wall video docs', // Запрашивает доступ к фото, стене, видео и документам.
'code_challenge' => $code_challenge, // Передает code_challenge для PKCE.
'code_challenge_method' => 's256', // Указывает метод генерации code_challenge (SHA-256).
]);
return redirect($url); // Перенаправляет пользователя на сформированный URL для авторизации.
}
public function handleProviderCallback(Request $request)
{
$this->validateState($request);
$codeVerifier = session('vk_code_verifier');
$tokenData = $this->exchangeCodeForTokens($request, $codeVerifier);
if (isset($tokenData->error)) {
return response()->json(['error' => $tokenData->error_description], 400);
}
return $this->storeTokenData($tokenData, $request);
}
public function refresh()
{
$token = DB::table('vk_tokens')->latest()->first();
$newTokenData = $this->refreshToken($token->refresh_token, bin2hex(random_bytes(16)), $token->device_id);
return $this->storeRefreshTokenData($newTokenData['data']);
}
public function logout()
{
session()->forget('vk_state');
session()->forget('vk_code_verifier');
return redirect('/'); // Перенаправление на главную страницу
}
private function exchangeCodeForTokens(Request $request, $codeVerifier)
{
return Http::asForm()->post('https://id.vk.ru/oauth2/auth', [
'grant_type' => 'authorization_code',
'code_verifier' => $codeVerifier,
'redirect_uri' => config('services.vk.'),
'code' => $request->code,
'client_id' => config('services.vk.app_id'),
'device_id' => $request->device_id,
'state' => $request->state,
'scope' => 'photos wall video docs',
])->object();
}
private function refreshToken($refresh_token, $state, $device_id)
{
$response = Http::asForm()->post('https://id.vk.ru/oauth2/auth', [
'grant_type' => 'refresh_token',
'refresh_token' => $refresh_token,
'client_id' => config('services.vk.app_id'),
'device_id' => $device_id,
'state' => $state,
'scope' => 'photos wall video docs',
]);
if ($response->failed()) {
// Обработка ошибок
return [
'success' => false,
'error' => $response->json(), // Возвращаем детали ошибки
];
}
return [
'success' => true,
'data' => $response->object(), // Успешный ответ
];
}
private function storeTokenData($tokenData, Request $request)
{
try {
DB::table('vk_tokens')->updateOrInsert(
['user_id' => $tokenData->user_id],
[
'user_id' => $tokenData->user_id,
'access_token' => $tokenData->access_token,
'refresh_token' => $tokenData->refresh_token,
'id_token' => $tokenData->id_token,
'state' => $tokenData->state,
'scope' => $tokenData->scope,
'device_id' => $request->device_id,
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
'updated_at' => now(),
]
);
} catch (\Exception $e) {
Log::channel('vk')->error('Failed to store VK token', ['error' => $e->getMessage()]);
return response()->json([
'success' => false,
'message' => 'Произошла ошибка при обновлении или создания токена.',
'error_message' => $e->getMessage()
], 500);
}
return redirect('/'); // Перенаправление после успешной авторизации
}
private function storeRefreshTokenData($tokenData)
{
try {
// Обновляем запись и получаем количество затронутых строк
$updatedRows = DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->update(
[
'access_token' => $tokenData->access_token,
'refresh_token' => $tokenData->refresh_token,
'state' => $tokenData->state,
'scope' => $tokenData->scope,
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
'updated_at' => now(),
]
);
// Если обновление прошло успешно, получаем обновленную запись
if ($updatedRows > 0) {
return DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->first();
}
// Если запись не найдена, можно вернуть null или выбросить исключение
return response()->json([
'success' => false,
'message' => 'Запись не найдена для обновления.',
], 404);
} catch (\Exception $e) {
Log::channel('vk')->error('Failed to refresh VK token', ['error' => $e->getMessage()]);
return response()->json([
'success' => false,
'message' => 'Произошла ошибка при обновлении токена.',
'error_message' => $e->getMessage()
], 500);
}
}
private function generateCodeVerifier($length = 128)
{
return bin2hex(random_bytes($length / 2));
}
private function generateCodeChallenge($code_verifier)
{
return rtrim(strtr(base64_encode(hash('sha256', $code_verifier, true)), '+/', '-_'), '=');
}
private function validateState(Request $request)
{
if ($request->input('state') !== session('vk_state')) {
abort(403, 'Invalid state');
}
}
private function isTokenValid($token)
{
if (Carbon::now() > $token->token_expire) {
return false;
}
return true;
}
}