Skip to content

feat(auth): wordstat login/logout — transfer Yandex session via cookies - #61

Open
axisrow wants to merge 4 commits into
mainfrom
feat/auth-session-transfer
Open

axisrow wants to merge 4 commits into
mainfrom
feat/auth-session-transfer

Conversation

@axisrow

@axisrow axisrow commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Что сделано

  • wordstat login — перенос существующей Yandex-сессии в подключённый Chrome: куки читаются из локального профиля Chrome (SQLite, ?mode=ro&immutable=1, без лока живого браузера), расшифровываются (v10, AES-128-CBC, ключ из Keychain «Chrome Safe Storage») и ставятся через корневой CDP Storage.setCookies с верификацией и проверкой «Выйти».
  • wordstat logout — зеркальное удаление только Yandex-кук (перезапись истёкшим expires: Storage.deleteCookies на browser-endpoint этого Chrome отсутствует), с верификацией результата.
  • wordstat login --check — только проверка авторизации цели, exit 0/1.
  • dom.py — единый источник DOM-знаний Вордстата (URL, селектор, JS-проба) для collector.py и auth.py.
  • Дефолт CDP: 9222 → 9223 (обоснование — в wordstat.toml); chrome_profile настраивается там же.
  • SessionImportError (отдельная доменная ошибка); cryptography объявлена прямой зависимостью; ленивый импорт auth в CLI — collect не платит за cryptography.
  • 22 юнит-теста в tests/test_auth.py (синтетические векторы шифрования, без keychain и реальных кук); обновлены README, CLAUDE.md, docs/LIVE_TEST_CHECKLIST.md.

Инварианты: значения кук никогда не печатаются/не логируются/не пишутся на диск (отчёт — только счётчики); clearCookies не вызывается никогда.

Проверка

  • pytest — 230 passed (<2s); ruff check . чисто.
  • Живой цикл на 9223: logout (удалено 115 кук, «Выйти» исчез) → login (перенос, авторизация подтверждена) → login --check exit 0 → контрольный collect.

🤖 Generated with Claude Code

axisrow and others added 2 commits September 3, 2026 15:23
wordstat login pointwise-imports an existing Yandex session into the attached Chrome: cookies for Yandex domains are read from a local Chrome profile (SQLite, read-only/immutable), decrypted (v10, AES-128-CBC, key derived from the Chrome Safe Storage Keychain item) and installed via the root CDP Storage.setCookies. The mirror wordstat logout deletes only Yandex cookies (expired-overwrite; Storage.deleteCookies is absent on the browser endpoint), and login --check verifies authorization with exit 0/1.

Shared Wordstat DOM knowledge (URL, query selector, auth probe JS) moves to dom.py, consumed by both collector.py and auth.py. The default CDP endpoint becomes port 9223 with the rationale documented in wordstat.toml, and the source Chrome profile is configurable there. Failures surface as the new SessionImportError; cryptography becomes a direct dependency; the CLI imports auth lazily so collect never pays for it. Cookie values are never printed, logged, or written to disk, and clearCookies is never called.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@axisrow

axisrow commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

🔍 Local review (cycle 1) — round ec3cc5ff-b9f7-4761-a8af-a2742018d0dd

Reviewed locally (/review + Codex companion), no bots pinged.

Note: локальный /review (Claude) недоступен в этой сессии — фоновый агент падает с детерминированной ошибкой API маршрутизации; фолбэк на облачный пинг отклонён владельцем. Ревью этого раунда — только Codex.

Verdict Reviewer Finding Location
FIX codex Импорт теряет CHIPS-партиционирование кук: 267 строк боевого профиля расширяются до глобальных, 6 групп коллизируют по свёрнутому ключу; logout не удаляет партиционированные src/wordstat/auth.py:246
FIX codex Открытие живой базы кук Chrome в immutable-режиме: ретрай покрывает только connect, SELECT без ретрая — транзиентный сбой роняет импорт целиком src/wordstat/auth.py:231

axisrow and others added 2 commits September 3, 2026 16:11
Codex review of #61, round 1 (both findings FIX, verified against the live
profile before fixing):

1. Chrome rows are keyed by (host_key, top_frame_site_key,
   has_cross_site_ancestor, name, path, source_scheme, source_port); the
   importer collapsed them to (name, domain, path). On the real profile that
   broadened 267 CHIPS-partitioned analytics cookies into globally visible
   ones and left 6 collision groups to be resolved by arbitrary apply order,
   and logout counted partitioned cookies as deleted while its unpartitioned
   expired-overwrite never touched them. Partitioned rows are now skipped
   with their own counter (the Yandex session is unpartitioned — live check:
   369 rows total, 267 partitioned, import still authorizes), a genuine
   reduced-key collision among the remaining rows fails loudly, the
   mismatched_after_set verification ignores stored partitioned cookies (a
   shared reduced triple would mask a failed unpartitioned write), and
   logout deletes only unpartitioned cookies, reporting what it deliberately
   leaves (this Chrome Storage.setCookies rejects every partitionKey shape —
   live-probed — so per-partition deletion is not possible at all).

2. immutable=1 lied about the source (a running Chrome keeps writing) and
   the retry loop covered only connect, not the SELECT: one transient
   busy/locked moment failed the whole import. Now mode=ro +
   PRAGMA busy_timeout with the retry wrapping connect and query together.

Live cycle after the fix: logout 110/0/4 → login 102 imported (267
partitioned skipped), 0 mismatched → login --check exit 0.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…rwrite

/code-review findings on #61, all three verified and applied:

- `--from-chrome-profile` no longer carries an exists-checked click default:
  click type-converts defaults at startup, so on a machine without the
  everyday-Chrome path even `login --check` (which never reads the profile)
  aborted with exit 2. The default now resolves inside the command, after
  the --check branch, with a clear error naming --from-chrome-profile.
- The live-DB retry narrows to genuine lock contention ("locked"/"busy"
  OperationalError); any other OperationalError (missing table, corrupt
  schema) fails immediately as an unreadable source instead of burning two
  retries and surfacing as a misleading "Chrome was writing it".
- The logout expired-overwrite echoes the stored secure/httpOnly flags, so
  Chrome name-prefix rules (__Secure-/__Host-) cannot reject the delete for
  any future prefixed cookie.

Live cycle re-verified: logout 102/0/4, login 102 imported / 0 mismatched.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant