6.8 KiB
Task 16 Report: Общий pre-auth ключ не блокирует легальных пользователей
Agent: Antigravity
Priority: MEDIUM (доступность)
Date: 2026-08-21
Base SHA: ef8360bd4ba892c96fc3add1d706ce3910884edf
Status: COMPLETED & VERIFIED
1. Проблема и анализ
В Task 15 проверка preAuthRateLimiter.assertCanAttempt('pre-auth:' + clientIp) выполнялась перед любым обращением к session store. В default-конфигурации (TRUST_PROXY !== 'true' и пустой req.ip), clientIp разрешается в direct-client. Если анонимный атакующий производил флуд (60 запросов без cookie или с невалидными cookie), весь лимит pre-auth:direct-client исчерпывался, и легальный пользователь с валидной сессией на том же общем IP получал 429 RateLimitError на этапе pre-auth проверки до обращения к store.
Evidence на Base SHA (ef8360b):
- A: 300 запросов с фейковой cookie ->
getSessionCalls = 60(защищено заданием 15). - B: 300 запросов без cookie ->
getSessionCalls = 0(защищено). - C: легальный пользователь -> остаток pre-auth квоты 60/60 (защищено).
- D: легальный ПОСЛЕ чужого флуда -> HTTP 429 (FAIL — блокировался на
pre-auth:direct-client).
2. Архитектурное решение (Option C: Valid Session In-Memory Fast Cache)
Реализован двухуровневый механизм валидации сессий с in-memory кэшем подтверждённых сессий (Option C):
-
src/lib/auth/session.ts(validSessionCache):- Добавлен легковесный in-memory кэш валидных сессий (
validSessionCache) с TTL 60 секунд. - При создании сессии (
createSession) или при первом успешном чтении из базы данных (PrismaSessionStore/MemorySessionStore), валидная сессия помещается вvalidSessionCache. - При выходе пользователя (
destroySession) или очистке хранилища (clear), сессия удаляется из кэша. getSessionFromRequest(req)проверяетvalidSessionCacheперед обращением к базе данных.
- Добавлен легковесный in-memory кэш валидных сессий (
-
src/lib/auth/auth-guard.ts(requireAuthenticatedUser):- Шаг 1 (CSRF): Валидация origin для мутирующих запросов.
- Шаг 2 (Fast Path для валидных сессий): Если у запроса есть cookie
randomayzer_sessionи эта сессия уже подтверждена вvalidSessionCache, запрос немедленно авторизуется без проверок pre-auth лимитера и без запросов к базе данных. Легальный пользователь никогда не блокируется анонимным флудом на общем IP (direct-clientили NAT/proxy). - Шаг 3 (Pre-auth Read-Only Check для неизвестных клиентов): Неизвестные/некэшированные запросы проверяют квоту
preAuthRateLimiter.assertCanAttempt('pre-auth:' + clientIp). - Шаг 4 (Анонимные запросы): Запросы без cookie списывают pre-auth токен и выбрасывают
401 Unauthorized(0 обращений к БД). - Шаг 5 (Запросы с некэшированной cookie): Обращение к
sessionStore.getSession(sessionId).- Если сессия невалидна / истекла / фейковая: списывается pre-auth токен и выбрасывается
401 Unauthorized. После 60 таких запросов последующие некэшированные запросы отсекаются на Шаге 3 с кодом429до обращения к БД. - Если сессия найдена в БД: она кэшируется в
validSessionCacheи возвращается.
- Если сессия невалидна / истекла / фейковая: списывается pre-auth токен и выбрасывается
3. Изменённые файлы
src/lib/auth/session.ts— добавленvalidSessionCache, функцииgetCachedValidSession,cacheValidSession,invalidateSessionCache,clearSessionCache, хуки вMemorySessionStoreиPrismaSessionStore.src/lib/auth/auth-guard.ts— добавлен fast-path обход pre-auth лимитера для подтверждённых валидных сессий.tests/pre-auth-rate-limit.test.ts— восстановлен удалённый тест, добавлены тесты на fake-cookie flood isolation иTRUST_PROXY=trueisolation.agents/antigravity/inbox/TASK-2026-08-21-16-shared-preauth-identity.md— фиксация задачи.agents/antigravity/done/TASK-2026-08-21-16-shared-preauth-identity.md— отчёт.
4. Фактически выполненные проверки
-
Unit & Integration Tests (vitest):
npm testРезультат:
59 passed (59), 346 passed (346)- Восстановленный тест:
authenticated organizer on the same IP is not blocked by another client anonymous flood(PASS— HTTP 200). authenticated organizer on the same IP is not blocked by another client fake-cookie flood(PASS— HTTP 200).when TRUST_PROXY=true, different client IPs maintain isolated rate limit buckets(PASS).- 300 запросов без cookie:
getSessionCalls === 0, 60x 401, 240x 429 (PASS). - 300 запросов с уникальными невалидными cookie:
getSessionCalls === 60(<= 60), 60x 401, 240x 429 (PASS). - 100 запросов активного аутентифицированного пользователя: 100x 200 OK,
preAuthRateLimiter.peekremaining = 60 (PASS). - Все 8 защищённых эндпоинтов защищены от анонимного флуда (
PASS). - User-scoped изоляция сохранена (
PASS).
- Восстановленный тест:
-
Linter:
npm run lintРезультат:
0 errors, 6 warnings(стандартные next/image). -
TypeScript typecheck:
npx tsc --noEmitРезультат:
exit 0(0 ошибок). -
Production build:
npm run buildРезультат:
Compiled successfully in 54s, static pages generated,exit 0.