6.3 KiB
6.3 KiB
Task 02: Client Identity for Rate Limiting Report
Date: 2026-08-21
Base Commit SHA: 92f6d1922500791ef221cc11ed63f606afc01b53
Status: IMPLEMENTED / PASS
Assigned Agent: Antigravity (Implementation Orchestrator)
1. Executive Summary
Устранена проблема совместного использования одного общего bucket ('direct-client') в механизме rate limiting при неизвестном IP (req.ip пуст и TRUST_PROXY !== 'true'), из-за которой в дефолтной конфигурации self-hosted next start один пользователь мог заблокировать всех остальных организаторов (выдав 429 Too Many Requests).
Реализована модель User-Scoped & Role-Isolated Rate Limiting:
- Для аутентифицированных маршрутов (
/api/giveaways*) ключ лимита формируется строго на основе доверенного серверного идентификатора организатораsessionUser.id:draw-execute:${sessionUser.id}:${id}snapshot-lock:${sessionUser.id}:${id}participants-import:${sessionUser.id}:${id}participants-get:${sessionUser.id}giveaway-get:${sessionUser.id}giveaways-list:${sessionUser.id}giveaway-create:${sessionUser.id}
- Порядок вызовов: аутентификация и проверка владения (
requireAuthenticatedUser/requireGiveawayOwner) теперь выполняются до вызова рейт-лимитера. Неаутентифицированные запросы сразу отклоняются со статусом401 Unauthorizedи не имеют возможности исчерпать или затронуть квоту организатора. - Для гибридного эндпоинта
POST /api/posts/preview: при наличии активной сессии используется ключpost-preview:user:${sessionUser.id}, а для анонимных пользователей —post-preview:anon:${clientIp}. Анонимный спам не блокирует авторизованных пользователей. - В
src/lib/client-ip.tsдобавлен однократный[SECURITY CONFIGURATION WARNING]в production, еслиTRUST_PROXY !== 'true'иreq.ipпуст. Поведение задокументировано вdocs/PRODUCTION_GUARDS.md.
2. Modified Files
| File | Type | Description |
|---|---|---|
src/lib/client-ip.ts |
Security | Добавлено предупреждение в production при отсутствии TRUST_PROXY и пустом req.ip. |
src/app/api/giveaways/[id]/draw/route.ts |
API Route | Перенесён вызов requireGiveawayOwner перед лимитером; ключ лимита draw-execute:${sessionUser.id}:${id}. |
src/app/api/giveaways/[id]/snapshot/route.ts |
API Route | Перенесён вызов requireGiveawayOwner перед лимитером; ключ лимита snapshot-lock:${sessionUser.id}:${id}. |
src/app/api/giveaways/[id]/participants/route.ts |
API Route | Аутентификация перед лимитером; ключи participants-get:${sessionUser.id} и participants-import:${sessionUser.id}:${id}. |
src/app/api/giveaways/[id]/route.ts |
API Route | Аутентификация перед лимитером; ключ giveaway-get:${sessionUser.id}. |
src/app/api/giveaways/route.ts |
API Route | Аутентификация перед лимитером; ключи giveaways-list:${sessionUser.id} и giveaway-create:${sessionUser.id}. |
src/app/api/posts/preview/route.ts |
API Route | Разделение ключей на post-preview:user:${sessionUser.id} и post-preview:anon:${clientIp}. |
docs/PRODUCTION_GUARDS.md |
Docs | Обновлен раздел 2 (Client Identity Scoping Architecture, Trust Proxy Modes). |
tests/rate-limit-identity.test.ts |
Tests (NEW) | Набор тестов изоляции лимитов организаторов, анонимных пользователей и защиты от обхода (5 тестов). |
3. Verification Evidence & Test Gate
Фактически выполненные команды:
npx prisma generate -> EXIT 0 (Prisma Client v5.22.0)
npx tsc --noEmit -> EXIT 0 (Clean TypeScript check, 0 errors)
npm test -> EXIT 0 (51 test files, 300 tests passed, 0 failed)
npm run lint -> EXIT 0 (Next.js ESLint passed clean)
npm run build -> EXIT 0 (Next.js production build compiled successfully)
Summary of New Tests (tests/rate-limit-identity.test.ts):
two distinct organizers with empty req.ip have independent draw rate limits→ PASSorganizer listing rate limit is scoped by sessionUser.id→ PASSexhausting anonymous rate limit on post preview does not block authenticated organizers→ PASSunauthenticated request fails with 401 without affecting organizer rate limit bucket→ PASSuses validated client IP for anonymous endpoints when TRUST_PROXY=true→ PASS
4. Core Invariants & Security
- Randomizer / Audit Proof Invariants: Алгоритмы
HMAC_SHA256_FY_V1,executeDeterministicDrawV1,verifyDrawResultсохранены без изменений. - Fail-Closed Authorization: Любой неаутентифицированный или cross-tenant запрос отклоняется
401/403до изменения счётчиков лимитера. - Proxy Header Integrity: При
TRUST_PROXY !== 'true'клиентские заголовкиX-Forwarded-Forпо-прежнему строго игнорируются во избежание IP-spoofing.
5. UNVERIFIED Assertions & Tech Debt
- UNVERIFIED: Distributed Edge / Redis Rate Limiter:
- В текущей реализации лимитер остаётся in-memory (
SlidingWindowRateLimiter). Для горизонтально масштабируемых кластеров рекомендуется подключение Redis/Valkey или edge-уровня (Cloudflare Rate Limiting).
- В текущей реализации лимитер остаётся in-memory (