feat(sitefinder): «Птица» принимает сессию общего реестра [PR-2c/6] #2635
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2635
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/auth-ptica-session-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Четвёртый PR эпика «единый вход» (#2597 → #2602 → #2608 → #2619). Самая рискованная часть: единственная, где ошибка открывает доступ, а не закрывает.
Прод после мержа работает ровно как сейчас.
AUTH_MODE=legacy— дефолт: соединение с БДauthне открывается, кука не читается, отсутствие настроек не роняет старт. Popup Caddy стоит и снимается последним PR — инвариант «гейт уходит последним» не нарушен.Что делает
У Site Finder не было авторизации вообще:
rbac_guardдоверял заголовкуX-Authenticated-User, который проставляет Caddy из basic_auth. Снять popup сегодня означало бы не оставить у продукта ничего.Теперь guard умеет резолвить сессионную куку общего реестра. Выдавать сессии «Птица» не будет — логин один, у «Меры», а кука host-only на
gendsgn.ruсpath=/и так долетает до обоих продуктов. Меньше кода и меньше мест, где можно ошибиться.Режим трёхзначный, а не булев — прямое следствие ревью
AUTH_MODE: legacy | dual | db_only.При булевом флаге фолбэк «сессия не нашлась → верим заголовку» после снятия popup превращался бы в полный обход аутентификации, и ничто в коде не заставило бы про него вспомнить. В
db_onlyлегаси-ветка недостижима: 401. Забыть переключиться теперь нельзя молча — это отдельное состояние, а не отсутствие флага.Три ревьюера независимо нашли один и тот же high
Sync-запрос к БД в
async def rbac_guardблокирует event loop на каждом non-public запросе — ровно инцидент #1202, который в этом же файле уже лечили. Исправлено: кука разбирается на loop'е, резолв уехал вrun_in_threadpool, в поток передаётся только строка токена. Запрос без куки не платит ни за поток, ни за коннект — то есть весь сегодняшний трафик не платит ничего.Что ещё вскрыло ревью
Срок годности решали часы приложения, а строку переписывали часы БД. Проверка
expires_atшла в Python, а sliding-refresh обновлял строку черезnow()базы. При расхождении часов истёкшая сессия не просто проходила — она продлевалась, то есть воскресала навсегда. Теперьexpires_at > now()в самом SELECT, один источник времени; питоновская проверка оставлена вторым поясом.Fail-fast проверял синтаксис DSN, а не работоспособность. Неверный пароль или хост выглядели бы как «ни у кого нет сессии» — сутками, потому что ошибку ловил бы
exceptвнутри guard'а. Теперь на старте открывается соединение и делаетсяSELECT 1; вlegacyпробы нет вовсе.Тела SQL не были покрыты ничем — доказано мутацией: правка запроса не красила ни одного теста. Добавлены ассерты формы (
FROM/JOIN/WHERE/expires_at > now(), обе колонки UPDATE,CAST(:ttl_hours AS integer)).Плюс
connect_timeoutиstatement_timeoutпо 3с (недоступный хост вешал коннект на минуты) и throttling логов сбоя реестра (иначе шторм событий в GlitchTip на каждый запрос).Ловушка хоста, зеркальная к «Мере»
Дефолт —
postgres. У «Меры» онgendesign-postgres, потому что внутри её стека имяpostgres— чужой контейнер; здесь стек главный, иpostgresиз корневого compose и есть нужный сервер. Алиасgendesign-postgresдефолтом был бы багом: контейнерbeatсостоит только в сетиdefault, и это имя из него не разрезолвится.Сверка с ролевой картой — сделана на живом проде
Агент оставил это долгом (не мог сделать кросс-БД запрос); проверил сам:
auth.usersприсутствуют вauth/roles.yaml→ ни один не получит 403 на всё после включения;admintest,pilottest,analysttest,expiredtest) есть в yaml, но нет в реестре — после снятия popup войти ими через браузер будет нельзя, только внутрисетевым заголовком. Если они нужны для смоука через UI — их надо завести в реестре отдельно;user2=expiredв yaml иtrial_expiredв реестре — согласовано, сессию он получить не может в принципе.Осознанный долг, вписан ⚠️-блоком перед guard'ом
paths/denyизroles.yamlбэкендом не применяются — их энфорсит фронтовыйRouteGuard; guard проверяет только известность username и admin-пути. Это предсуществующее поведение: менять его здесь значило бы изменить и легаси-ветку, то есть нарушить «дефолт = сегодня». Записано как долг с двумя рубильниками отзыва, а не замолчано.Test plan
uv run pytest -q— 4594 passed, 0 failed, 52 skippedruff checkпо изменённым файлам — чистоX-Authenticated-Userдругого пользователя → выигрывает кука, и роут, читающий заголовок напрямую, видит владельца куки. У «Птицы» таких прямых читателей одиннадцать, поэтому перезапись ASGI-scope обязана быть полной, а не skip-if-present (CRITICAL #2552)roles.yamlна живом продеДальше
PR-3 — нейтральная форма входа на обоих фронтах. PR-4 — canary. PR-5 — cutover: снятие popup у Site Finder (
import caddy/users.caddy.snippetна строках 176 и 272, четыреheader_upSet-формы на 180/186/276/282 →-X-Authenticated-User, блок:80→ redir на https). PR-6 — уборка.Отдельно к PR-5:
/api/v1/trade-in/*в бэкенде Site Finder — легаси-поверхность, которую matcher@tradeinне ловит; сегодня она закрыта именно popup'ом и после cutover останется без гейта. Закрыть до снятия.