feat: SMS verification через n8n webhook

- Перенесена проверка SMS кода в n8n webhook (N8N_SMS_VERIFY_WEBHOOK)
- Упрощен формат ответа: убран токен, только success/message
- sms-verify.php теперь проксирует запросы на n8n
- Обновлен JS код: убрано использование токена
- Обновлена документация с упрощенным форматом ответа
- Протестировано: верный и неверный коды работают корректно
This commit is contained in:
Fedor
2026-01-15 18:11:18 +03:00
parent 2c516362df
commit ed4270312e
6 changed files with 918 additions and 96 deletions

View File

@@ -353,11 +353,10 @@ try {
], JSON_UNESCAPED_UNICODE);
} elseif ($action === 'verify') {
// Проверка кода
// Проверка кода через n8n webhook
log_message("=== НАЧАЛО ПРОВЕРКИ КОДА ===");
log_message("REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'не установлен'));
log_message("POST данные: " . json_encode($_POST, JSON_UNESCAPED_UNICODE));
log_message("GET данные: " . json_encode($_GET, JSON_UNESCAPED_UNICODE));
$phone = $_POST['phonenumber'] ?? '';
$code = $_POST['code'] ?? '';
@@ -370,108 +369,89 @@ try {
throw new Exception("Номер телефона или код не указаны");
}
$phone_cleaned = clear_phone($phone);
log_message("Номер после очистки: '$phone_cleaned'");
// Получаем URL webhook из .env
$webhook_url = env('N8N_SMS_VERIFY_WEBHOOK', '');
// Проверка rate limiting - пытаемся переподключиться при необходимости
log_message("Попытка подключения к Redis для проверки кода...");
$redis = getRedis(true); // Принудительно пытаемся переподключиться
$stored_code = false;
$stored_code = false;
// Пытаемся получить код из Redis
if ($redis) {
log_message("Redis подключен успешно для проверки кода");
try {
// Проверяем количество попыток
$key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
$attempts = $redis->get($key_attempts) ?: 0;
if ($attempts >= 10) {
log_message("Превышено количество попыток для номера $phone_cleaned");
throw new Exception("Превышено количество попыток. Попробуйте позже.");
}
// Увеличиваем счетчик попыток
$redis->setex($key_attempts, 900, $attempts + 1); // 15 минут
// Проверяем код
$key = "sms:code:$phone_cleaned";
log_message("Проверка кода для номера $phone_cleaned, ключ Redis: $key, введенный код: $code");
$stored_code = $redis->get($key);
if ($stored_code !== null) {
log_message("Код найден в Redis: $stored_code");
}
} catch (Exception $e) {
// Пробрасываем исключение, если это ошибка rate limiting
if (strpos($e->getMessage(), 'Превышено количество попыток') !== false) {
throw $e;
}
log_message("Ошибка при работе с Redis: " . $e->getMessage());
}
if (empty($webhook_url)) {
log_message("Ошибка: не указан N8N_SMS_VERIFY_WEBHOOK в .env");
throw new Exception("Сервис временно недоступен. Попробуйте позже.");
}
// Если код не найден в Redis, пытаемся получить из файла (fallback)
if ($stored_code === null) {
log_message("Код не найден в Redis, проверяем файловое хранилище...");
$stored_code = getCodeFromFile($phone_cleaned);
if ($stored_code !== false) {
log_message("Код найден в файловом хранилище для номера $phone_cleaned");
}
log_message("Отправка запроса на n8n webhook: $webhook_url");
// Отправляем запрос на n8n webhook
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $webhook_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phonenumber' => $phone,
'code' => $code
], JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
$curl_errno = curl_errno($ch);
curl_close($ch);
if ($curl_error) {
log_message("Ошибка CURL при проверке кода через n8n: $curl_error (код: $curl_errno)");
throw new Exception("Ошибка соединения с сервисом. Попробуйте позже.");
}
// Если код все еще не найден, выдаем ошибку
if ($stored_code === null || $stored_code === false) {
log_message("КРИТИЧЕСКАЯ ОШИБКА: Код не найден ни в Redis, ни в файловом хранилище для номера $phone_cleaned");
throw new Exception("Код не найден или истек. Запросите новый код.");
// Логируем ответ от n8n
log_message("Ответ от n8n: HTTP $http_code, ответ: " . substr($response, 0, 200));
// Парсим ответ
$response_data = json_decode($response, true);
if ($response_data === null) {
log_message("Ошибка: не удалось распарсить ответ от n8n: " . substr($response, 0, 200));
throw new Exception("Ошибка обработки ответа. Попробуйте позже.");
}
log_message("Проверка кода: сохраненный=$stored_code, введенный=$code");
if ($stored_code !== $code) {
log_message("Неверный код для номера $phone_cleaned. Введен: $code, ожидался: $stored_code");
throw new Exception("Неверный код");
}
// Код верный - удаляем его из Redis и файла, создаем сессию подтверждения
if ($redis) {
try {
$key = "sms:code:$phone_cleaned";
$redis->del($key);
$key_attempts = "sms:ratelimit:attempts:$phone_cleaned";
$redis->del($key_attempts); // Сбрасываем счетчик попыток
} catch (Exception $e) {
log_message("Ошибка при удалении кода из Redis: " . $e->getMessage());
}
}
deleteCodeFromFile($phone_cleaned); // Удаляем из файла тоже
// Создаем токен подтверждения (действует 1 час) - только если Redis доступен
$verify_token = bin2hex(random_bytes(32));
if ($redis) {
try {
$verify_key = "sms:verified:$phone_cleaned";
$redis->setex($verify_key, 3600, $verify_token);
log_message("Токен верификации сохранен в Redis для номера $phone_cleaned");
} catch (Exception $e) {
log_message("Ошибка при создании токена верификации: " . $e->getMessage());
// Проверяем успешность
if ($http_code >= 200 && $http_code < 300) {
// Успешный ответ от n8n
if (isset($response_data['success']) && $response_data['success'] === true) {
log_message("Код подтвержден через n8n для номера: " . ($phone ?: 'не указан'));
// Возвращаем упрощенный ответ (без токена)
echo json_encode([
'success' => true,
'message' => $response_data['message'] ?? 'Код подтвержден'
], JSON_UNESCAPED_UNICODE);
} else {
// Ошибка от n8n
$error_message = $response_data['message'] ?? 'Неверный код';
log_message("Ошибка проверки кода через n8n: $error_message");
http_response_code(400);
echo json_encode([
'success' => false,
'message' => $error_message
], JSON_UNESCAPED_UNICODE);
}
} else {
log_message("Токен верификации сгенерирован, но не сохранен (Redis недоступен)");
// HTTP ошибка
$error_message = $response_data['message'] ?? "Ошибка сервиса (HTTP $http_code)";
log_message("HTTP ошибка от n8n: $http_code, сообщение: $error_message");
http_response_code($http_code >= 500 ? 500 : 400);
echo json_encode([
'success' => false,
'message' => $error_message
], JSON_UNESCAPED_UNICODE);
}
log_message("Код подтвержден для номера: $phone_cleaned");
echo json_encode([
'success' => true,
'message' => 'Код подтвержден',
'token' => $verify_token // Токен для последующей проверки
], JSON_UNESCAPED_UNICODE);
} elseif ($action === 'check_verified') {
// Проверка статуса верификации (для проверки перед отправкой формы)
$phone = $_POST['phonenumber'] ?? '';