Багатошаровий захист Next.js 16 від ботів: proxy.ts, reCAPTCHA v3, Zod та відкладені повідомлення

Автор: Muntai

Багатошаровий захист Next.js 16 від ботів: proxy.ts, reCAPTCHA v3, Zod та відкладені повідомлення

Бот-апокаліпсис 2026: Як ми зупинили масовану атаку на Next.js реєстрацію (і чому ваш Telegram-бот від цього страждає)

Знайома ситуація? Прокидаєшся, береш телефон, відкриваєш Telegram - а там 500 сповіщень про реєстрацію нових юзерів. Імена на кшталт adflkj123, пошта - sdfg@mail.ru, і все це за одну ніч. Твій бот розривається, серце починає битися швидше, бо ти розумієш: проект під "атакою".

Це не просто спам. Це "брудний" трафік, який забиває базу даних, вбиває твою репутацію в email-сервісах і змушує твої серверні ресурси працювати на "сміттєві" запити. У 2026 році ці "дикі" бот-мережі стали набагато розумнішими за середньостатистичного джуна. Вони вміють рендерити JS, обходити базові перевірки та атакувати ваш API з сотень різних IP-адрес одночасно.

Коли це сталось на моїх проектах, я зрозумів: стандартні методи захисту померли. Проста перевірка "чи заповнені поля" - це як намагатися зупинити поїзд картонною стіною.

Чому ваш "захист" більше не працює?

Раніше ми думали, що достатньо перевірити honeypot або поставити базову валідацію. Але сучасні боти використовують LLM для обходу капчі та реальні проксі-мережі для обходу rate-лімітів. Вони не просто "стукають" у ваші двері - вони намагаються винести їх з петлями.

Коли ваш Telegram-бот сповіщає про реєстрацію, він стає не просто помічником, а індикатором "агонії" вашої системи. Ігнорувати це - означає дати ботам виснажити ваші ресурси та зіпсувати статистику, яку ви так старанно збираєте для свого next.js проєкту.

Наша стратегія: Багатошаровий щит

Ми не стали будувати один складний захист. Ми побудували "багатошаровий щит", де кожен рівень відсікає свій тип загрози:

  1. Honeypot-захист: Відсікаємо тупих скриптових ботів ще на рівні форми.

  2. Google reCAPTCHA v3: Використовуємо оцінку поведінки юзера (score), щоб зрозуміти, чи це людина, чи автоматизований скрипт, навіть не показуючи капчу користувачу.

  3. Edge Rate Limiting (proxy.ts): Блокуємо підозрілі IP ще до того, як запит дійде до вашого основного Node.js сервера.

  4. Backend Verification: Перевірка через Prisma та Server Actions - останній рубіж, де ми блокуємо реєстрації навіть якщо "щось" прорвалося крізь захист.

У цій статті я покажу, як ми перетворили реєстрацію з "прохідного двору" на фортецю, яку навіть професійні спам-ферми не змогли пробити. Ніяких зайвих слів - тільки архітектура захисту, яку можна впровадити за один вечір.

Фільтрація ботів на рівні Server Actions (register.ts)

Middleware (proxy.ts) - це наш перший ешелон оборони. Але другий ешелон - це Server Actions. Саме тут ми зупиняємо тих ботів, які вміють імітувати запити до API. Ми не просто приймаємо дані, ми проводимо їх через "сито" валідації.

Основний біль багатьох розробників - довіра до FormData. Ми ж використовуємо Zod для суворої валідації та reCAPTCHA v3 для оцінки поведінки.

// src/lib/actions/register.ts
'use server'

import { z } from 'zod'
// ... (імпорти)

const signupSchema = z.object({
  username: z.string().min(2).max(50).regex(/^[a-zA-Z0-9_]+$/),
  email: z.string().email(),
  password1: z.string().min(6),
})

export const register = async (values: FormData) => {
  // 1. Honeypot: Якщо заповнене приховане поле, це бот
  const honeypot = values.get('website')
  if (honeypot) return { success: '...' } // мовчимо, щоб не давати підказку

  // 2. reCAPTCHA v3: Якщо score < 0.7 - блокуємо
  const recaptchaToken = values.get('recaptchaToken') as string
  const recaptchaRes = await fetch(`.../siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${recaptchaToken}`, { method: 'POST' })
  const recaptchaData = await recaptchaRes.json()
  if (!recaptchaData.success || recaptchaData.score < 0.7) return { error: 'Bot detected!' }

  // 3. Zod-валідація (відсікаємо ін'єкції та невірні формати)
  // ... (логіка перевірки через Zod)

  // 4. Реєстрація тільки після проходження всіх фільтрів
  await auth.api.signUpEmail({ ... })
  
  // 5. Тільки ТУТ ми генеруємо токен і відправляємо лист
  const verificationToken = await generateVerificationToken(email)
  await sendVerificationEmail(email, verificationToken.value, name || '')

  return { success: '...' }
}

Telegram-бот як індикатор, а не "смітник"

Найважливіший момент, який врятував мій спокій - відкладене сповіщення. Раніше мій Telegram-бот "вибухав" відразу після запиту на реєстрацію. Тепер ми викликаємо сповіщення sendTelegramNotification тільки в момент верифікації пошти у функції verifyEmail.

Якщо бот створив акаунт, але не підтвердив пошту (а він не підтвердить, бо пошта фейкова) - ви не отримаєте жодного повідомлення. Ваш Telegram-канал стане чистим інструментом для моніторингу реальних клієнтів.

// src/actions/auth.ts (фрагмент verifyEmail)

export const verifyEmail = async (token: string) => {
  // ... (пошук токена та юзера)

  const isAlreadyVerified = existingUser.emailVerified !== null

  await prisma.user.update({ ... })

  // 🟢 Тільки тут, коли людина клікнула по посиланню в листі:
  if (!isAlreadyVerified) {
    try {
      await sendTelegramNotification(existingUser.name || '', existingUser.email || '')
    } catch (e) {
      console.error(e)
    }
  }

  await prisma.verificationToken.delete({ ... })
  return { success: 'Email confirmed!' }
}

Важливий фінальний штрих: конфігурація середовища (.env)

Щоб це запрацювало у вас на VPS, не забудьте винести всі ключі захисту у змінні оточення. Це наш "запобіжник" для ботів.

Створіть або оновіть ваш .env файл на сервері:

# 🟢 Захист від ботів
NEXT_PUBLIC_RECAPTCHA_SITE_KEY="6Ld_ВАШ_КЛЮЧ_ДЛЯ_ФРОНТЕНДУ"
RECAPTCHA_SECRET_KEY="6Ld_ВАШ_СЕКРЕТНИЙ_КЛЮЧ_ДЛЯ_БЕКЕНДУ"

# 🟢 Telegram
TELEGRAM_BOT_TOKEN="ваш токен бота"
TELEGRAM_ADMIN_IDS="ваш id адмін чату (якщо у вас декілька адмінів - тоді перечисляємо їх через кому)"
TELEGRAM_CHAT_ID=""ваш id адмін чату (якщо у вас декілька адмінів - тоді перечисляємо їх через кому)"

Чому це спрацювало?

  • Honeypot вбиває найпримітивніших ботів, які просто заповнюють усі поля форми.

  • reCAPTCHA v3 вбиває "розумніших" ботів, які намагаються імітувати поведінку користувача, але не проходять аналіз Google.

  • Відкладена нотифікація - це головна зброя проти "бот-спаму" в Telegram. Ви отримуєте повідомлення лише тоді, коли юзер реально підтвердив email. Це дає вам 100% якісну базу контактів, а не мертві душі.

Верифікація як фінальний рубіж (Verification Page)

Часто розробники забувають про стан Loading та обробку помилок під час верифікації пошти. Але боти обожнюють "ламати" ці сторінки, надсилаючи запити з випадковими токенами. Твій код сторінки верифікації (new-verification/page.tsx) - це еталон того, як має виглядати надійна Frontend-логіка.

Використання Suspense для useSearchParams - це must-have у Next.js 16 для уникнення проблем з SSR, а useRef для контролю стану верифікації гарантує, що запит до API виконається рівно один раз, навіть при суворому режимі React (Strict Mode).

// src/app/[locale]/auth/new-verification/page.tsx
'use client'

// ... (імпорти)

function VerificationContent() {
  const t = useTranslations('Auth')
  const searchParams = useSearchParams()
  const token = searchParams.get('token')
  const router = useRouter()

  const [error, setError] = useState<string | undefined>(!token ? t('error_title') : undefined)
  const [success, setSuccess] = useState<string | undefined>()
  const [timer, setTimer] = useState(7)

  // 🟢 Блокуємо подвійний виклик (double-tap safety)
  const verificationStarted = useRef(false)

  useEffect(() => {
    if (!token || verificationStarted.current || success || error) return

    verificationStarted.current = true

    const runVerification = async () => {
      try {
        const data = await verifyEmail(token)
        if (data.success) {
          setSuccess(data.success)
        } else {
          setError(data.error)
        }
      } catch (err) {
        setError(t('error_generic'))
      }
    }
    runVerification()
  }, [token, success, error, t])

  // ... (логіка таймера та рендер)
}

Чому це круто:

  1. Double-call safety: verificationStarted гарантує, що ми не відправимо два запити до БД, що критично для продуктивності при великому навантаженні.

  2. User Flow: Користувач не просто бачить білий екран, він бачить Spinner, потім чіткий статус (успіх/помилка) і таймер автоматичного редиректу. Це перетворює "технічний процес" на приємний UX.

Чистий код та reCAPTCHA Hook

Ми не хочемо засмічувати наші компоненти логікою window.grecaptcha. Це поганий тон, який ускладнює тестування та підтримуваність коду. Винесення reCAPTCHA у кастомний хук useRecaptchaToken.ts - це ознака Senior-архітектури.

Цей хук не тільки абстрагує роботу з API Google, але й вирішує конфлікти завантаження скриптів, про які часто забувають.

// src/hooks/useRecaptchaToken.ts
export const useRecaptchaToken = (action: string): RecaptchaHook => {
  const [isReady, setIsReady] = useState(false)
  const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? ''

  useEffect(() => {
    const checkRecaptcha = () => {
      if (window.grecaptcha?.ready) {
        window.grecaptcha.ready(() => { setIsReady(true) })
        return true
      }
      return false
    }

    if (!checkRecaptcha()) {
      const intervalId = setInterval(() => { if (checkRecaptcha()) clearInterval(intervalId) }, 300)
      return () => clearInterval(intervalId)
    }
  }, [])

  const getToken = useCallback(async () => {
    // 🟢 Використовуємо опціональні ланцюжки (?.), щоб уникнути помилок на сервері чи при недовантаженні скрипта
    if (!isReady || !window.grecaptcha || !siteKey) return null

    return new Promise<string | null>((resolve) => {
      window.grecaptcha.ready(async () => {
        try {
          const token = await window.grecaptcha.execute(siteKey, { action })
          resolve(token)
        } catch { resolve(null) }
      })
    })
  }, [isReady, siteKey, action])

  return { executeRecaptcha: getToken, isReady }
}

Фінальний архітектурний висновок

Тепер наша система виглядає як монолітний захист:

  1. На рівні Proxy: Блокуємо IP-шторми (Rate Limiting).

  2. На рівні UI: Використовуємо reCAPTCHA v3 для поведінкового аналізу ботів (через useRecaptchaToken).

  3. На рівні Server Actions: Валідуємо дані через Zod, виключаємо ботів через Honeypot.

  4. На рівні БД: Лише валідні, підтверджені юзери отримують доступ, а наш Telegram-бот мовчить доти, доки не з'явиться справжня людина.

🛠 Фінальний чекліст: Як впровадити цей захист за один вечір

Щоб не загубитися в коді, ось покроковий план впровадження цієї архітектури у ваш проект на Next.js 16. Пройдіться по ньому, як по списку покупок:

  • Крок 1: Оновіть змінні оточення (.env) Отримайте ключі для Google reCAPTCHA v3, налаштуйте токен для Telegram-бота та пароль додатку (App Password) для Gmail. Без цього система не запрацює.

  • Крок 2: Оновіть схему бази даних (Prisma 7) Додайте правильну модель User та VerificationToken. Не забудьте запустити pnpm prisma generate та накотити міграції.

  • Крок 3: Налаштуйте "Щит" на Edge-рівні (proxy.ts) Скопіюйте логіку Map для Rate Limiting. Це відразу зріже 80% тупого спаму та DDoS-атак на ваші API-маршрути.

  • Крок 4: Створіть хук useRecaptchaToken Винесіть логіку роботи з Google reCAPTCHA в окремий клієнтський хук. Це збереже ваш UI-код чистим і врятує від помилок завантаження скриптів (hydration/hydration errors).

  • Крок 5: Додайте капчу та Honeypot у форму реєстрації Налаштуйте стан (Spinner) та блокування кнопки "Зареєструватися", поки reCAPTCHA не поверне валідний токен.

  • Крок 6: Напишіть залізобетонні Server Actions (register.ts) Підключіть Zod для суворої валідації полів. Зробіть так, щоб сервер перевіряв Score від reCAPTCHA (має бути >= 0.7). Тільки після цього реєструйте юзера та генеруйте токен.

  • Крок 7: Налаштуйте ручну відправку Email (mail.ts) Вимкніть автоматичну відправку листів у BetterAuth. Використовуйте nodemailer, щоб контролювати процес через await і гарантовано знати, чи пішов лист.

  • Крок 8: Перенесіть Telegram-сповіщення у функцію verifyEmail Найголовніше: відв'яжіть бота від форми реєстрації! Бот повинен реагувати тільки на успішне підтвердження пошти (клік по посиланню з листа).

  • Крок 9: Створіть куленепробивну сторінку верифікації Використовуйте useRef, щоб уникнути подвійного відправлення запиту при Strict Mode в React. Додайте таймер автоматичного редиректу на сторінку логіну для ідеального UX.

Висновок

Боти еволюціонують, і старі методи захисту більше не працюють. Але впровадивши цю багатошарову архітектуру, ви перетворите свій Next.js проект на справжню фортецю. Ви збережете чисту базу даних, високий рейтинг доставки email-листів і, що найголовніше, свої нерви - бо ваш Telegram-бот більше ніколи не розбудить вас о третій ночі через атаку китайських спамерів.

Будуйте надійно. Будуйте як архітектори!

Коментарі (0)

Залишити коментар

Напишіть мені
Будь ласка, заповніть форму нижче, щоб розпочати спілкування зі мною.

Цей сайт захищено reCAPTCHA. Застосовуються Політика конфіденційності та Умови використання Google.