From 99a26865321dce7653fbd2b05f4180f213973be2 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 4 Mar 2026 18:17:38 +0300 Subject: [PATCH] chore: adds extra tasks for AI-agent --- AGENT_TASKS.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/AGENT_TASKS.md b/AGENT_TASKS.md index fddc529..00955ed 100644 --- a/AGENT_TASKS.md +++ b/AGENT_TASKS.md @@ -65,10 +65,25 @@ Do G1–G4, commit after each. **Agent H (Testing):** ```text -Implement Agent H tasks from AGENT_TASKS.md. Work in branch feat/testing. +Implement Agent H tasks from AGENT_TASKS.md. Work in branch feat/testing. Do H1–H7, commit after each. Target ≥70% coverage on services. ``` +**Agent A2 (Progressive Login Lockout):** + +```text +Implement Agent A2 task from AGENT_TASKS.md. Work in branch feat/progressive-login-lockout. +Branch from dev. Do task A2-1. Commit with the message from the table. +``` + +**Agent A2-2 (Fix Login Lockout Tests):** + +```text +Implement Agent A2-2 task from AGENT_TASKS.md. Work in branch fix/login-lockout-test-mock. +Branch from dev. Do task A2-2. Commit with the message from the table. +Ensure tests/integration/auth.routes.test.ts passes. +``` + ## Текущее состояние репозитория Часть работы уже выполнена одним агентом: @@ -138,6 +153,69 @@ Do H1–H7, commit after each. Target ≥70% coverage on services. --- +## Agent A2: Progressive Login Lockout + +**Зависимости:** Agent A (redis, rateLimit), Agent C (auth routes). Уже есть Auth и rateLimit. + +**Ветка:** `feat/progressive-login-lockout` + +**Контекст:** Сейчас `POST /auth/login` использует фиксированный лимит через `@fastify/rate-limit` (N попыток на 15 мин). По [security.md](samreshu_docs/principles/security.md) нужен **прогрессивный lockout** — считаются только **неудачные** попытки входа, с нарастающим временем блокировки: + +| Неудачных попыток | Блокировка | +|-------------------|------------| +| 5 за 15 мин | 15 минут | +| 10 за 1 час | 1 час | +| 20 за 24 часа | 24 часа | + +Счётчики в Redis по ключу `lockout:`. При успешном логине — сброс счётчиков. + +**Задача A2-1:** + +1. **Создать `src/utils/loginLockout.ts`** — утилита для работы с Redis: + - `checkBlocked(redis, ip: string)` → `{ blocked: boolean, retryAfter?: number }` — проверяет `lockout:blocked:`; если ключ есть и TTL > 0, возвращает `{ blocked: true, retryAfter }`. + - `recordFailedAttempt(redis, ip: string)` — INCR по ключам `lockout:15m:`, `lockout:1h:`, `lockout:24h:` с TTL 15m/1h/24h; при достижении порогов (5/10/20) устанавливает `lockout:blocked:` с соответствующим TTL. + - `clearOnSuccess(redis, ip: string)` — DEL всех ключей `lockout:*:` и `lockout:blocked:`. + +2. **Обновить `src/plugins/rateLimit.ts`** — убрать `login` из `rateLimitOptions` (больше не используется для login). Остальные endpoints без изменений. + +3. **Обновить `src/routes/auth.ts`** — для `POST /login`: + - Убрать `config: { rateLimit: rateLimitOptions.login }`. + - Добавить `preValidation`: вызвать `checkBlocked(app.redis, req.ip)`; если `blocked` — `reply.status(429).send({ error: { code: 'RATE_LIMIT_EXCEEDED', message: '...', retryAfter } })`. + - В handler: обернуть `authService.login(...)` в try/catch; при успехе — `clearOnSuccess(app.redis, ip)`; при throw (например `unauthorized`) — `recordFailedAttempt(app.redis, ip)`, затем rethrow. + +4. **Опционально** — вынести пороги (5, 10, 20) и окна в `env.ts` или оставить константами в `loginLockout.ts` (документация security.md указывает фиксированные значения). + +**Коммит:** `feat: replace fixed login rate limit with progressive lockout` + +**Итого:** 1 коммит. После — PR в `dev`. + +--- + +## Agent A2-2: Fix Login Lockout Tests (после A2) + +**Зависимости:** Agent A2 выполнен. + +**Ветка:** `fix/login-lockout-test-mock` + +**Проблема:** Auth routes используют `app.redis` для `checkBlocked`, `recordFailedAttempt`, `clearOnSuccess`. В `buildAuthTestApp` Redis не подключён — тесты `POST /auth/login` падают с 500. + +**Задача A2-2:** + +1. **Добавить mock Redis** в `tests/helpers/build-test-app.ts`: + - Создать объект, реализующий минимальный интерфейс ioredis для loginLockout: `ttl`, `setex`, `del`, `eval`. + - `ttl(key)` → -2 (ключ не существует) или -1/положительное значение для тестов. + - `setex`, `del` → no-op или простая in-memory имитация. + - `eval(script, keysCount, ...keys, ...args)` → вернуть `[0, 0, 0]` (счётчики не достигли порога). + - `app.decorate('redis', mockRedis)` перед регистрацией auth routes. + +2. **Обновить `rateLimitOptions`** в buildAuthTestApp — убрать `login` (его больше нет в типе из rateLimit.ts). + +**Коммит:** `test: add mock Redis for auth integration tests with login lockout` + +**Итого:** 1 коммит. Проверить: `npm run test -- tests/integration/auth.routes.test.ts` проходит. + +--- + ## Agent B: Data Model & Drizzle Schema **Зависимости:** Agent A (нужен database plugin). Может стартовать после A4.