6.1 KiB
6.1 KiB
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 / базу данных при неаутентифицированном флуде:
- 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 в минуту.
- Запросы без сессионной куки сразу проверяются через
- Экспортирован
- Защита аутентифицированных пользователей на общем ключе (
direct-client/ NAT):- При наличии валидной сессионной куки запрос успешно аутентифицируется и не попадает под штрафной лимит
pre-auth. - Аутентифицированный пользователь подчиняется исключительно своему изолированному
user-scopedлимиту (sessionUser.id), что полностью исключает возможность DoS легальных пользователей через анонимный флуд с того же IP /direct-client.
- При наличии валидной сессионной куки запрос успешно аутентифицируется и не попадает под штрафной лимит
- Оптимизация в
POST /api/posts/preview(src/app/api/posts/preview/route.ts):- Поиск сессии в
getSessionFromRequestтеперь вызывается только при наличии заголовкаCookie: randomayzer_session=..., исключая холостые вызовы при анонимных превью.
- Поиск сессии в
- Тестирование (
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 лимитов.
- 60 запросов без куки -> 61-й запрос возвращает
- Добавлено 5 всесторонних тестов, проверяющих:
2. Архитектурные решения
Почему requireAuthenticatedUser вместо middleware.ts?
- Совместимость с Next.js 16 и тестами: В Next.js 16 middleware по умолчанию работает в Edge runtime, в то время как Vitest и интеграционные тесты запускают route handlers напрямую как функции. Размещение pre-auth guard внутри
requireAuthenticatedUserгарантирует 100% покрытие во всех тестах, одинаковое поведение в dev/test/production и отсутствие накладных расходов на сериализацию между runtime-слоями. - Детерминированность: Все защищенные маршруты используют
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)