feat(tradein/support): чат поддержки без входа — экран логина и «доступа нет» #2577
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#2577
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-anon-support"
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?
Проблема (инцидент 2026-07-31)
После cutover'а на свою авторизацию (#2558) единственным каналом в поддержку остался чат за логином, а самая частая причина писать в поддержку — как раз «не могу войти».
Сегодня это выстрелило: «Практика» весь день билась в форму входа (5
login_failedс трёх разных IP, ни одной успешной попытки), при этом в поле «Логин» вводили старый basic_auth-пароль. Сообщить об этом из продукта было нечем — на/loginнет ни чата, ни контакта. В логах ровно этот тупик:GET /support/unread → 401.Backend — 4 ручки
/api/v1/trade-in/support/anon/*Публичные в
rbac_guard(рядом с/auth/login, по той же логике: вызываются до того, как клиент аутентифицирован).token_urlsafe(18), 144 бита) в httpOnly+Secure+SameSite=Lax куке. Тред живёт в тех жеweb_support_threadsпод ключомanon:<token>.tradein_usersдопускает только^[A-Za-z0-9._-]{3,64}$(CHECK миграции 193 + Pydantic), двоеточия там быть не может — аноним никогда не попадёт в чужой тред и не «станет» существующим юзером.thread_idснаружи не принимается ни в каком виде, тред резолвится исключительно из куки. Форма куки валидируется regex'ом — мусор из браузера не становится ключом треда и до storage не доезжает вообще.anon-<6 hex от sha256(токен)>, а не сам bearer — зеркало читают люди и пересылают дальше. Метка[С САЙТА · БЕЗ ВХОДА]: оператору важно, что у обращения нет аккаунта и вероятная причина — невозможность войти._send_limiter, ключи не пересекаются) и per-IP (10/10 мин). Второй ловит обход ротацией куки; без него бюджет обнуляется сбросом куки.sendMessage. Неудачная отправка не закрепляет за посетителем пустой тред.Ответы оператора маршрутизируются без изменений в
bridge.py: реплай резолвится поtopic_message_id→thread_id, кто автор треда — там неважно.Frontend
SupportScope = "auth" | "anon"вuseSupportChat: scope выбирает базовый путь и входит в ключ кэша — иначе после логина в панели висела бы переписка анонима. Дефолт"auth", существующие места монтирования (v2 layout) не меняются.AnonSupportWidget— провайдер + кнопка в анонимном scope. Монтируется на/loginи вNoAccessScreen: обе точки — тупики, из которых пользователю больше некуда идти. На/loginдобавлена подсказка «Не получается войти? …».NoAccessScreen— сознательное расхождение с зеркалом в Site Finder (виджет ходит в trade-in'овый бридж, которого в том бэкенде нет), отмечено в докстринге.Проверено
pytest tests/test_support.py tests/test_rbac.py— 62 passed_USERNAME_RErbac_guard: аноним проходит; соседний авторизованный/support/unreadпо-прежнему 401/403client-фикстура переведена наhttps://testserver— иначе httpx не возвращал бы Secure-куку и тесты «тот же тред» тихо проверяли бы каждый раз нового анонимаruff check+ruff format— чисто; frontendtsc --noEmit+next build— зелёные