feat(tradein): Cian browser auto-login — POST /admin/scrape/cian/auto-login (#639) #917
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#917
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/639-cian-auto-login"
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?
Что
Variant B для #639: headless-браузер (
tradein-browser/camoufox) логинится в cian.ru по email+паролю (без SMS), извлекает auth-cookies →verify_session→save_session. Разблокируетcian_history_backfillбез ручного DevTools-экспорта cookies.On-demand endpoint:
Архитектура
browser/server.py— новый genericPOST /login(url/selectors/success_cookie в body, как/fetch). Браузер-сервис остаётся site-agnostic.browser_fetcher.py— методlogin()→ POST /login (отдельный 60s timeout, 502-диагностика).config.py— вся Cian-специфика (URL + селекторы) вcian_login_*, env-overridable → тюнинг DOM-дрифта через.env.runtimeбез редеплоя кода.admin.py— endpoint, зеркалитupload_cian_cookies(verify→save, те же статусы).Флоу провалидирован вживую (2026-05-31, тестовый аккаунт)
Прогнал реальный логин через браузер end-to-end — email+пароль работает, SMS/капчи нет. Селекторы не угаданы, а сняты с живой формы:
https://ekb.cian.ru/[data-name="LoginButton"][data-name="AnotherAccountBtn"]— на fresh headless отсутствует, скипается[data-name="SwitchToEmailAuthBtn"](«Другой способ»)input[name="username"]input[name="password"]button[data-name="ContinueAuthBtn"]DMIR_AUTH2-шаговость: после первого сабмита возможен экран «Введите пароль» → код делает повторный ввод пароля + submit (retry ×2).
Безопасность
Активация на проде (после merge)
tradein-mvp/backend/.env.runtime:CIAN_LOGIN_EMAIL=...,CIAN_LOGIN_PASSWORD=...(тестовый scraper-аккаунт).SCRAPER_FETCH_MODEне требуется — login всегда идёт черезtradein-browser.POST /admin/scrape/cian/auto-login→ проверить{"ok": true, ...}.CIAN_LOGIN_*_SELECTORв env, рестарт backend.Scope / follow-up
trigger_cian_backfill_run— приverify_session → Noneвызвать auto-login до скипа). Сознательно вынесено: сначала ручная валидация логина на проде, потом автоматизация по таймеру.Review
Pre-merge
code-reviewer: ⚠️ MINOR, без блокеров, creds-leak нет. M1 (page_url в 502) и M4 (lazy import) — исправлены.Closes #639.
✅ APPROVE — security-sensitive PR (auth/creds), проверил creds-leak независимо. Чисто. Мержу.
Security verification (не доверял PR-телу, проверял сам)
admin.py:cian_auto_login— логирует толькоtype(exc).__name__; HTTP detail'ы generic (503/502/401). ✓browser_fetcher.py::_post_login— логируетcookie_count,has_screenshot(bool),page_url;RuntimeErrorнесётerror+page_url, не креды. ✓server.py::_login_once/login_handler—{exc}в логе/payload: Playwrightpage.fill(selector, value)при ошибке репортит селектор+timeout, не значение → email/password не попадают в текст исключения. ✓config.pyдефолтыcian_login_email/password = None. Нет 40-hex/API-key/JWT. ✓url=settings.cian_login_url(не из body); browser/login— только internal Docker. ✓admin.py:54) гейтится только Caddy basic_auth (задокументированоadmin.py:3«application layer открыт») — это существующая архитектура, новый endpoint консистентен со всеми siblings (upload_cian_cookies,test_cian_auth). PR posture не ослабляет. ✓Sound
_browser/_fetch_lock/_page_counter/BROWSER_*/_relaunch_browser/_is_browser_crash) существуют в server.py. ✓range(2)— без бесконечного цикла; cookie-wait 20×500ms. ✓page.close()в success и except; screenshot/url try-guarded; page recycle после login. ✓CIAN_LOGIN_*не выставлены. Безопасный merge.🟡 Advisory (не блокеры, follow-up не завожу)
_login_oncescreenshot в 502-payload может содержать email в plaintext (password-поле masked). Толькоhas_screenshot=boolлогируется, байты не персистятся, internal-сеть → остаточный риск низкий._post_loginна 502 делаетdata = resp.json()— если 502-тело не JSON, бросит JSONDecodeError (есть один retry вlogin()). Edge, browser-сервис всегда отдаёт JSON.browser/server.pyстроки 395/399 >100 — ноbrowser/не покрыт ruff в CI (линтуется толькоbackend/, line-length=100), консистентно с pre-existing_fetch-путём.Squash-merge + delete branch.
Closes #639закроет issue — отдельным комментом отмечу ручную prod-активацию (creds + реальный логин = needs-human, у qa-бота нет cian-аккаунта).