randomayzer/agents/antigravity/done/TASK-2026-08-21-12-pre-auth-rate-limit.md

6.1 KiB
Raw Blame History

Task 12: Rate limit до аутентификации Report

Date: 2026-08-21
Base Commit SHA: 906148813ee66f6f52d7c291f335c1ebdca2a057
Status: COMPLETED / PASS
Assigned Agent: Antigravity (Implementation Orchestrator)


1. Executive Summary

Устранена уязвимость неограниченной нагрузки на Session Store / базу данных при неаутентифицированном флуде:

  1. Pre-Authentication Rate Limiter (src/lib/rate-limiter.ts & src/lib/auth/auth-guard.ts):
    • Экспортирован preAuthRateLimiter со скользящим окном 60 запросов / 60 секунд на клиентский IP (resolveClientIp(req)).
    • В requireAuthenticatedUser(req) проверка лимитера вынесена до обращения к Session Store (prisma.session):
      • Запросы без сессионной куки сразу проверяются через preAuthRateLimiter.assertAllowed('pre-auth:' + clientIp) без единого обращения к Session Store / БД (0 запросов в БД). При превышении лимита возвращается 429 RATE_LIMIT_EXCEEDED вместо 401.
      • Запросы с недействительными/поддельными куками после проверки в Session Store списывают попытку из preAuthRateLimiter, ограничивая максимальное число недействительных запросов к БД числом 60 в минуту.
  2. Защита аутентифицированных пользователей на общем ключе (direct-client / NAT):
    • При наличии валидной сессионной куки запрос успешно аутентифицируется и не попадает под штрафной лимит pre-auth.
    • Аутентифицированный пользователь подчиняется исключительно своему изолированному user-scoped лимиту (sessionUser.id), что полностью исключает возможность DoS легальных пользователей через анонимный флуд с того же IP / direct-client.
  3. Оптимизация в POST /api/posts/preview (src/app/api/posts/preview/route.ts):
    • Поиск сессии в getSessionFromRequest теперь вызывается только при наличии заголовка Cookie: randomayzer_session=..., исключая холостые вызовы при анонимных превью.
  4. Тестирование (tests/pre-auth-rate-limit.test.ts):
    • Добавлено 5 всесторонних тестов, проверяющих:
      • 60 запросов без куки -> 61-й запрос возвращает 429 с кодом RATE_LIMIT_EXCEEDED, 0 вызовов sessionStore.getSession;
      • 60 запросов с фейковыми куками -> ровно 60 вызовов sessionStore.getSession, затем блокировка 429;
      • Аутентифицированный пользователь на том же direct-client успешно выполняет запросы (200 OK) при исчерпанном анонимном лимите;
      • Защита всех 8 защищенных эндпоинтов (/api/giveaways, /api/giveaways/[id], /api/giveaways/[id]/participants, /api/giveaways/[id]/draw, /api/giveaways/[id]/snapshot, /api/giveaways/[id]/unlock);
      • Сохранение изоляции user-scoped лимитов.

2. Архитектурные решения

Почему requireAuthenticatedUser вместо middleware.ts?

  1. Совместимость с Next.js 16 и тестами: В Next.js 16 middleware по умолчанию работает в Edge runtime, в то время как Vitest и интеграционные тесты запускают route handlers напрямую как функции. Размещение pre-auth guard внутри requireAuthenticatedUser гарантирует 100% покрытие во всех тестах, одинаковое поведение в dev/test/production и отсутствие накладных расходов на сериализацию между runtime-слоями.
  2. Детерминированность: Все защищенные маршруты используют requireAuthenticatedUser или requireGiveawayOwner (который внутри вызывает requireAuthenticatedUser), что обеспечивает единую точку входа и невозможность обойти лимитер.

3. Modified & Created Files

File Status Description
src/lib/rate-limiter.ts MODIFIED Экспортирован preAuthRateLimiter (60 req / 60s).
src/lib/auth/auth-guard.ts MODIFIED Добавлена проверка preAuthRateLimiter до Session Store и для failed auth.
src/app/api/posts/preview/route.ts MODIFIED Пропуск getSessionFromRequest при отсутствии куки SESSION_COOKIE_NAME.
tests/pre-auth-rate-limit.test.ts NEW 5 тестов покрытия pre-auth лимитирования и защиты Session Store.
tests/rate-limit-identity.test.ts MODIFIED Сброс preAuthRateLimiter в beforeEach.

4. Verification Evidence

npx prisma generate  -> EXIT 0 (Prisma Client v5.22.0)
npx tsc --noEmit     -> EXIT 0 (0 ошибок типизации во всех 58 тестовых файлах и кодовой базе)
npm test             -> EXIT 0 (58 сьютов, 338 тестов пройдены успешно без БД)
npm run lint         -> EXIT 0 (0 ошибок, 6 warnings на no-img-element)
npm run build        -> EXIT 0 (Все 17 маршрутов скомпилированы успешно в Next.js 16.3.2)
npm audit --omit=dev -> EXIT 0 (0 vulnerabilities)