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

63 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```text
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)
```