Commit Graph

2 Commits

Author SHA1 Message Date
浅念 eebff9f786 feat: RSA login encryption + confirmed wechat type codes
Replace the Phase-0 placeholder password encryption with real RSA
(PKCS#1 v1.5). encrypt_password now builds an RSA public key from the
getSecureKey modulus/exponent (RSAPublicNumbers, e=int(hex,16),
n=int(hex,16)), encrypts the UTF-8 password, and returns hex (documented
assumption -- base64 is the one-line fallback noted in the docstring).
get_secure_key now returns the inner data dict to match the new contract;
pc_login keeps {account, password} with a comment on the username/password
and account/pwd fallbacks if pcLogin rejects.

Extend normalize_wechat_type with the Phase-0 confirmed codes:
2 -> image, 3 -> image_legacy, 4 -> video, 10 -> system (1 -> text and
-2 -> divider unchanged; 6/9/13/15/21/22 stay unknown until confirmed).

Tests:
- encrypt_password round-trip: generate an RSA keypair, build secure_key
  from its public numbers, encrypt, then decrypt with the private key and
  assert the original password comes back. Pins 512-hex output for a
  2048-bit key. No network.
- Parser parametrize table extended for the new codes (and a downstream
  message-fixture assertion updated from unknown -> image_legacy).

Suite: 87 -> 97 passing. ruff check clean.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 13:37:42 +08:00
浅念 8243e9c6d2 feat: session login + authed client
Task 1.4 — 会话层. Adds the authenticated HTTP client that every yunke
scraper will use to call the CRM API.

- app/session/login.py: get_secure_key + pc_login wrappers, LoginResult
  dataclass, and a clearly-marked Phase-0 swappable encrypt_password
  placeholder (real RSA per getSecureKey is a TODO; drop-in contract
  documented in the docstring).
- app/session/yunke_client.py: YunkeClient wrapping httpx.AsyncClient.
  Constructor takes either (username+password) for auto-login or
  (userid+company[+cookies]) for an existing session. Injects the 5
  observed auth headers (userid/company/source=yunkepc/channel=4/
  content-type=application/json) on every request, auto-logs-in on first
  request, and on HTTP 401 (or body code 401) re-logs-in once and
  retries — a persistent 401 raises after the single retry so there is
  no infinite loop.
- Tests (respx): login populates LoginResult + httpx cookie jar; every
  request carries all 5 auth headers + cookie; 401 triggers exactly one
  re-login + retry and returns 200; persistent 401 raises after one
  retry; first request with no session auto-logs-in.

Session is held in-memory only for now; persistence to encrypted
YkAccount.session_blob_enc is left as a later concern (TODO in
yunke_client.py).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 11:22:47 +08:00