feat(sitefinder): «Птица» принимает сессию общего реестра [PR-2c/6] #2635
Merged
lekss361
merged 1 commit from 2026-08-02 14:18:24 +00:00
feat/auth-ptica-session-guard into main
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 7be07efe70 |
feat(sitefinder): «Птица» принимает сессию общего реестра [PR-2c/6]
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 15m13s
Дефолт AUTH_MODE=legacy — сегодняшнее поведение байт-в-байт: соединение с БД auth не открывается, кука не читается, отсутствие настроек не роняет старт. Popup Caddy стоит и снимается последним PR эпика — инвариант «гейт уходит последним» не нарушен. У Site Finder не было авторизации вообще: rbac_guard доверял заголовку X-Authenticated-User от Caddy. Теперь он умеет резолвить сессионную куку общего реестра. ВЫДАВАТЬ сессии «Птица» не будет — логин один, у «Меры», а кука host-only на gendsgn.ru с path=/ и так долетает до обоих продуктов. Меньше кода и меньше мест, где можно ошибиться. Режим трёхзначный, а не булев: legacy | dual | db_only. Это прямое следствие ревью. При булевом флаге фолбэк «сессия не нашлась → верим заголовку» после снятия popup превращался бы в полный обход аутентификации, и ничто в коде не заставило бы про него вспомнить. В db_only легаси-ветка недостижима: 401. Резолв уехал в threadpool. Три независимых ревьюера нашли одно и то же: sync-запрос к БД в async-guard блокирует event loop на каждом non-public запросе — ровно инцидент #1202, который в этом же файле уже лечили. Кука разбирается на loop'е, в поток уезжает только строка токена; запрос без куки не платит ни за поток, ни за коннект. Срок годности сессии считают часы БД, а не приложения. Раньше проверка шла в Python, а sliding-refresh переписывал строку через now() базы — при расхождении часов истёкшая сессия не просто проходила, а продлевалась заново, то есть воскресала навсегда. Теперь `expires_at > now()` в самом SELECT; питоновская проверка оставлена вторым поясом. Fail-fast на старте проверяет не синтаксис DSN, а живое соединение: SELECT 1. Иначе неверный пароль или хост выглядели бы как «ни у кого нет сессии» — сутками, потому что ошибку ловил бы except в guard'е. Ещё из ревью: connect_timeout и statement_timeout по 3с (недоступный хост вешал коннект на минуты); throttling логов сбоя реестра (иначе шторм в GlitchTip на каждый запрос); тела SQL закреплены ассертами формы — мутация любого фрагмента теперь красит тесты, до этого не красила ничего. Дефолт хоста БД — postgres, и это зеркально «Мере». У неё gendesign-postgres, потому что внутри её стека `postgres` — чужой контейнер; здесь стек главный, и `postgres` из корневого compose и есть нужный сервер. Алиас gendesign-postgres дефолтом был бы багом: контейнер beat состоит только в сети default и это имя из него не разрезолвится. Сверка реестра с ролевой картой сделана на живом проде: все 13 юзеров auth.users присутствуют в auth/roles.yaml, ни один не получит 403 на всё. Четыре QA-фикстуры (admintest, pilottest, analysttest, expiredtest) есть в yaml, но не в реестре — после снятия popup войти ими через браузер будет нельзя, только внутрисетевым заголовком. Осознанный долг, вписан ⚠️-блоком перед guard'ом: paths/deny из roles.yaml бэкендом не применяются (их энфорсит фронтовый RouteGuard), guard проверяет только известность username и admin-пути. Это предсуществующее поведение; менять его здесь значило бы изменить и легаси-ветку, то есть нарушить «дефолт = сегодня». Тесты: 4594 passed, 0 failed. Главный — подделка: валидная кука плюс присланный клиентом X-Authenticated-User другого пользователя, выигрывает кука, и роут, читающий заголовок напрямую, видит владельца куки. У «Птицы» таких прямых читателей одиннадцать, поэтому перезапись ASGI-scope обязана быть полной, а не skip-if-present (CRITICAL #2552). |