diff --git a/AGENTS.md b/AGENTS.md index 2369ee7..a9d0fe4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo - **Stack**: Node.js 20 + Express, PostgreSQL 16, Redis 7, Docker Compose, S3-совместимое хранилище файлов, Tailscale (Serve/Funnel) - **Architecture**: Single Express server (`server.js`) + storage abstraction (`storage.js`) + cache/pub-sub abstraction (`redis.js`) + static frontend in `public/` - **Deployment**: Docker Compose (app + db + s3 + tailscale), bind-mounted uploads, named volumes for Postgres and S3 data -- **Auth**: Admin-only via `X-Admin-Token` header (value = `ADMIN_PASSWORD` env var). No user sessions. +- **Auth**: сессии в БД. `POST /api/auth/login` (bcrypt) → токен в заголовке `X-Auth-Token`. Роли: `admin` и не-admin, ограниченные филиалами (`user_branches`). `ADMIN_PASSWORD` используется **только** для автосоздания первого админа в пустой БД — это не механизм авторизации API --- @@ -63,7 +63,8 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo - **Секреты**: пароль только в `REDIS_URL` / `REDIS_PASSWORD`, порт 6379 публикуется лишь на `127.0.0.1` ### 4. API Patterns -- **Admin routes**: `requireAdmin` middleware (checks `X-Admin-Token`) +- **Middleware**: `requireAuth` — читает `X-Auth-Token`, 401 без валидной активной сессии. `requireAdmin` — самодостаточный (внутри вызывает `requireAuth`, если `req.user` ещё нет), 403 при `role !== 'admin'`. `optionalAuth` — для публичных страниц с персонализацией +- **Филиалы**: `branchScope(user)` / `branchWhere(user, alias)` — для не-admin `user.branch_ids` (из `user_branches`) ограничивают выборку; у `admin` `ids = null` и фильтр не добавляется - **Public routes**: `apiLimiter` (300/15min), `entryLimiter` (10/15min), `fileLimiter` (300/15min) — все на `cache.rateLimitStore(...)`, не на `MemoryStore` - **Responses**: JSON, `{ error: 'message' }` on failure, data directly on success - **Pagination**: `limit` / `offset` query params, return `{ items, total }` or `{ entries, total }` @@ -72,7 +73,7 @@ This document defines how AI agents should work with the WhatIDo codebase. Follo ### 5. Frontend (public/) - Vanilla HTML/CSS/JS, no build step - Each page = single HTML file + shared `admin.js` / `admin.css` -- API calls via `fetch` with `X-Admin-Token` from `localStorage` +- API calls via `fetch` with `X-Auth-Token` (токен из `localStorage`); `X-Admin-Token` больше не используется и не работает - Share pages (`share.html`, `links.html`) work without auth ### 6. Docker / Compose @@ -150,8 +151,13 @@ docker compose up -d --build # Check logs docker compose logs -f app -# Test API (replace TOKEN) -curl -H "X-Admin-Token: $ADMIN_PASSWORD" http://localhost:3003/api/groups +# Test API (replace LOGIN/PASS; X-Admin-Token больше не работает) +TOKEN=$(curl -s -X POST http://localhost:3003/api/auth/login \ + -H 'Content-Type: application/json' \ + -d "{\"username\":\"$LOGIN\",\"password\":\"$PASS\"}" | sed -E 's/.*"token":"([a-f0-9]+)".*/\1/') +curl -H "X-Auth-Token: $TOKEN" http://localhost:3003/api/auth/me +# /api/groups — публичный (optionalAuth), 200 даже без токена: +# для проверки авторизации берите /api/auth/me или /api/users # Run backup/restore scripts ./scripts/backup.sh diff --git a/README.md b/README.md index 884cdc8..c4c08df 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,8 @@ docker compose exec app md5sum /app/server.js # совпадает с md5sum | Переменная | По умолчанию | Назначение | |------------------|--------------------|-------------------------------------| -| `ADMIN_PASSWORD` | — (обязательно) | Пароль администратора (X-Admin-Token). Без него сервер не стартует | +| `ADMIN_PASSWORD` | — (обязательно) | Пароль первого администратора, создаётся в пустой БД. Не является способом авторизации в API | +| `ADMIN_USERNAME` | `admin` | Логин первого администратора | | `DB_PASSWORD` | — (обязательно) | Пароль пользователя `app` в PostgreSQL | | `REDIS_PASSWORD` | — (обязательно) | Пароль Redis (`--requirepass`) | | `REDIS_PREFIX` | `whatido` | Префикс ключей Redis — свой для каждого инстанса | @@ -391,7 +392,7 @@ node api.smoketest.js # сквозная проверка API (нужен ## Безопасность -- **Пароль администратора** обязателен (`ADMIN_PASSWORD`); фолбэка на `admin` нет. +- **Пароль администратора** обязателен (`ADMIN_PASSWORD`) — он создаёт первого админа в пустой БД; фолбэка на `admin` нет. Самостоятельной роли в API не даёт: доступ только по сессиям. - **CORS отключён** — кросс-доменные запросы к API запрещены. - **Rate limiting** по IP на публичные роуты: `POST /api/entries` — 10 запросов / 15 мин, загрузка файлов и share-ссылки — 300 / 15 мин. - **Загрузки** ограничены: 30 МБ суммарно на запись, 10 МБ на файл; заблокированы опасные расширения (`.html`, `.js`, `.svg`, `.xml`, `.exe` и др.); SVG не отдаётся inline. @@ -419,7 +420,26 @@ node api.smoketest.js # сквозная проверка API (нужен | `POST` | `/api/restore` | Восстановить из бэкапа | | `GET` | `/api/dashboard`, `/api/stats` | Статистика | -Защищённые админ-маршруты требуют заголовок `X-Admin-Token` с `ADMIN_PASSWORD`. +Авторизация — по сессиям, не по статическому токену: + +1. `POST /api/auth/login` с `username` и `password` возвращает `{ token, expires_at }`. +2. Токен передаётся в заголовке `X-Auth-Token` во все защищённые запросы; `POST /api/auth/logout` удаляет сессию. +3. `GET /api/auth/me` — текущий пользователь (`id`, `username`, `role`, `is_active`, `branch_ids`). + +Заголовок `X-Admin-Token` больше не поддерживается. Маршруты помечены `requireAuth` (любой активный пользователь) или `requireAdmin` (только `role = admin`); филиалы не-admin ограничены его `user_branches`. + +Защищённые маршруты: + +| Метод | Путь | Доступ | +|---|---|---| +| `GET/POST/PUT/DELETE` | `/api/users`, `/api/users/:id` | admin | +| `GET/POST/DELETE` | `/api/bans` | admin | +| `GET/POST/PUT/DELETE` | `/api/branches`, `/api/branches/:id` | admin (список — любой активный) | +| `GET/POST/DELETE` | `/api/settings` | admin | +| `GET` | `/api/audit` | admin | +| `GET` | `/api/backup`, `POST /api/restore` | admin | + +Сессия хранится в таблице `sessions` (срок 30 дней) и кэшируется в Redis на 30 секунд. ## Структура проекта