# Самокритика v0.4 (рабочая админка с RBAC) **Дата:** 2026-05-13 **Кто проверял:** Claude (сам автор) — поэтому проверка не объективная, но конкретная. **Что проверял:** https://obmenka.felix.nohumaninside.me с пятью разными ролями. ## Что работает (короткое подтверждение) - ✅ Логин / логаут / редирект анонимов на /login - ✅ 5 ролей: admin / manager / accountant / cashier / viewer — каждая со своими паролями в `pass` - ✅ Access matrix: ``` PATH admin manager accnt cashier viewer / 200 200 200 200 200 /operations 200 200 200 200 200 /reconciliation 200 200 200 403 403 /branches 200 200 200 403 403 /wallets 200 200 200 403 403 /managers 200 200 403 403 403 /handbook 200 200 200 200 200 /audit 200 403 403 403 403 ``` - ✅ Cashier видит только свою кассу (Москва · Арбат) в операциях - ✅ Cashier видит только свой раздел handbook (`role=cashier`), admin — все 5 - ✅ 143 тестовые операции (10 за сегодня), 10 disputed, 11 unmatched, 3 TG-флага - ✅ Дашборд: оборот 11.78 млн ₽ сегодня, график за 7 дней с разбивкой по отделениям ## Что плохо — критика по убыванию важности ### Бизнес-логика **1. ⚠ Балансы наличных уходят в минус.** Сейчас остаток = `sum(cash_in) − sum(cash_out)` без начального капитала. По seed-данным три из трёх физических отделений в минусе по EUR (−18k / −9.5k / −32.5k). Это не баг seed'а — это дефект модели. В реальной кассе всегда есть **открытие смены** (начальный остаток) и **пересчёт** (фактическая денежка). Без этого балансы не имеют смысла. - **Чинить:** добавить таблицу `cash_count` (snapshot пересчёта) и/или операцию `shift_open`. Балансы считать от последнего пересчёта плюс delta операций. **2. ⚠ Маржа считается как ровно 2% от оборота.** Это плейсхолдер, не настоящая маржа. Настоящая = `(курс_клиенту − рыночный_курс) × объём`. У операций сейчас нет поля `applied_fx_rate`. - **Чинить:** добавить `applied_fx_rate` в `operations`, подключить источник рыночного курса (Binance API), пересчитать. **3. ⚠ Кнопки «связать вручную» и «Добавить операцию» не функциональны.** В сверке и в operations они отрисованы, но не реагируют. Это вводит в заблуждение. - **Чинить:** на этом этапе либо `disabled` с tooltip «появится на E1+», либо вообще убрать. Не показывать «функционал», которого нет. **4. ⚠ Сверка показывает две колонки, но связь между парой визуально не сделана.** В operations виден `matched_with_id`, в reconciliation — только надпись «связано с op #N», без подсветки/линии/hover. На 30 записях с двух сторон искать пару — глаза устают. - **Чинить:** при hover на cash подсвечивать парную crypto, или явные пары рядом. **5. ⚠ Сессии не invalidate при logout.** Логаут просто удаляет cookie у клиента (это signed JWT-подобный токен, не серверная сессия). Если кто-то перехватил cookie — он валиден 12 часов даже после нажатия «Выйти». - **Чинить:** server-side sessions (таблица `sessions` + revocation) или хотя бы короткий TTL. ### Безопасность **6. ⚠ Cookie без `Secure` флага.** `set-cookie: ...; HttpOnly; SameSite=lax` — нет `Secure`. На HTTPS обязателен. - **Чинить:** одна строка в `main.py`: `secure=True`. **7. ⚠ Нет rate-limit на /login.** 5 неправильных попыток за 1.5 сек ответили без задержки. Brute-force открыт. - **Чинить:** Caddy `rate_limit` matcher или `slowapi` на роуте. **8. ⚠ Нет CSRF tokens на POST формах.** /login и /logout уязвимы к CSRF (правда, SameSite=lax многое закрывает, но не всё). - **Чинить:** добавить CSRF middleware или `fastapi-csrf-protect`. **9. ⚠ Cashier через дашборд узнаёт сколько флагов у менеджеров (KPI-карточка).** Хотя `/managers` ему 403. Маленькая информационная утечка. - **Чинить:** скрывать KPI-карту «Флаги менеджеров» если `can_see(managers)` = False. ### Технические **10. `@app.on_event("startup")` deprecated** в FastAPI ≥0.100. Должен быть `lifespan` handler. **11. N+1 в шаблонах.** `o.branch.name`, `o.matched_with_id` тянутся лениво. 200 операций → потенциально 200+ запросов. Сейчас отвечает за 50ms — терпимо, на росте — `joinedload`. **12. `init_db()` запускается при каждом старте сервиса.** Это `create_all()`, идемпотентно, но без миграций — любое изменение схемы потребует ручного DROP. Нужен Alembic. **13. Audit log не пишется автоматически.** Я наполнил его seed-данными, но реальные действия (логин, матчинг) в него не пишутся. Нет middleware. **14. Пагинации нет.** Operations лимит 200 — дальше обрезается. На росте сломается. ### UI/UX **15. Нет селектора периода на дашборде.** В preview была кнопка «7д/30д/90д», в рабочей версии — нет. Хардкод «сегодня + 7 дней». **16. Сайдбар не сжимается на мобильном.** `grid-cols-[240px_1fr]` ломает layout на узких экранах. **17. Колонка «расхождение onchain vs db» в wallets всегда показывает «✓ совпадает».** Потому что seed заполнил оба поля одинаково. Без E2 эта колонка бесполезна. Лучше пометить «нет данных» до подключения сетей. **18. Handbook рендерит markdown полу-ручным regexp в шаблоне** (в admin-preview HTML-моке) — это плохо. В рабочей версии используется `python-markdown`, всё ок, но на фронте `