Prefer browser PKCE mint over flaky direct HTTP to auth.x.ai.
Logs show curl(28)/ReadTimeout on direct mint while registration browser still works; skip slow requests fallback, shorten HTTP step timeout, and mint via page first when available.
This commit is contained in:
1 parent
8b399277e7
commit
96061cbd78
3 files changed
+68
-55
No files matched your search
+31
-11
@@ -72,7 +72,13 @@ def _mint_tokens(
|
|||||||
log: Callable[[str], None],
|
log: Callable[[str], None],
|
||||||
proxy: str | None,
|
proxy: str | None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""优先 HTTP SSO 授权码;TLS 失败时用注册浏览器 PKCE;可选设备码。"""
|
"""铸造策略(有 page 时优先浏览器,避免直连 auth.x.ai 卡超时):
|
||||||
|
|
||||||
|
1. page+sso 且 cpa_prefer_browser_mint:直接浏览器 PKCE
|
||||||
|
2. 否则 HTTP SSO→OAuth(curl_cffi,短超时)
|
||||||
|
3. HTTP 失败且有 page:浏览器 PKCE
|
||||||
|
4. 可选设备码(通常无 referrer,默认关)
|
||||||
|
"""
|
||||||
from oidc_mint.oauth_code import (
|
from oidc_mint.oauth_code import (
|
||||||
OAuthCodeError,
|
OAuthCodeError,
|
||||||
_is_http_tls_failure,
|
_is_http_tls_failure,
|
||||||
@@ -84,10 +90,31 @@ def _mint_tokens(
|
|||||||
sso_token = normalize_sso_cookie(sso or "")
|
sso_token = normalize_sso_cookie(sso or "")
|
||||||
prefer_sso = bool(cfg.get("cpa_prefer_sso_oauth", True))
|
prefer_sso = bool(cfg.get("cpa_prefer_sso_oauth", True))
|
||||||
allow_browser = bool(cfg.get("cpa_allow_browser_fallback", True))
|
allow_browser = bool(cfg.get("cpa_allow_browser_fallback", True))
|
||||||
|
# 有注册浏览器时默认先浏览器:直连/跨境对 auth.x.ai 经常 curl(28)/ReadTimeout
|
||||||
|
prefer_browser = bool(cfg.get("cpa_prefer_browser_mint", True))
|
||||||
allow_device = bool(cfg.get("cpa_allow_device_fallback", False))
|
allow_device = bool(cfg.get("cpa_allow_device_fallback", False))
|
||||||
timeout = float(cfg.get("mint_timeout_sec", 300) or 300)
|
timeout = float(cfg.get("mint_timeout_sec", 300) or 300)
|
||||||
require_ref = bool(cfg.get("cpa_require_referrer", True))
|
require_ref = bool(cfg.get("cpa_require_referrer", True))
|
||||||
http_err: Exception | None = None
|
http_err: Exception | None = None
|
||||||
|
browser_timeout = min(timeout, float(cfg.get("cpa_browser_mint_timeout_sec", 120) or 120))
|
||||||
|
|
||||||
|
def _browser_mint(reason: str) -> dict[str, Any]:
|
||||||
|
log(f"[cpa] 浏览器 PKCE 铸造({reason})")
|
||||||
|
return mint_from_sso_browser(
|
||||||
|
sso_token,
|
||||||
|
page,
|
||||||
|
log=lambda m: log(f"[Debug] {m}"),
|
||||||
|
require_referrer=require_ref,
|
||||||
|
timeout_sec=browser_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 路径 A:有 page 时优先浏览器(最稳,不依赖 Python→auth.x.ai 直连)
|
||||||
|
if prefer_sso and sso_token and allow_browser and page is not None and prefer_browser:
|
||||||
|
try:
|
||||||
|
return _browser_mint("优先浏览器,绕开 Python 直连 auth.x.ai")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[!] 浏览器 PKCE 优先路径失败: {exc}")
|
||||||
|
log("[cpa] 继续尝试 HTTP SSO→OAuth")
|
||||||
|
|
||||||
if prefer_sso and sso_token:
|
if prefer_sso and sso_token:
|
||||||
log("[cpa] 使用 SSO→OAuth(PKCE, referrer=grok-build)")
|
log("[cpa] 使用 SSO→OAuth(PKCE, referrer=grok-build)")
|
||||||
@@ -105,18 +132,11 @@ def _mint_tokens(
|
|||||||
http_err = exc
|
http_err = exc
|
||||||
log(f"[!] SSO→OAuth 异常: {exc}")
|
log(f"[!] SSO→OAuth 异常: {exc}")
|
||||||
|
|
||||||
# HTTP 失败后:有 page+sso 就优先浏览器 PKCE(Chrome TLS 通常正常,且保留 referrer)
|
# HTTP 失败后:有 page+sso 再试浏览器(若优先路径没走过或当时失败)
|
||||||
if allow_browser and page is not None and sso_token and http_err is not None:
|
if allow_browser and page is not None and sso_token and http_err is not None:
|
||||||
why = "TLS/连接" if _is_http_tls_failure(http_err) else "HTTP"
|
why = "TLS/连接/超时" if _is_http_tls_failure(http_err) else "HTTP"
|
||||||
log(f"[cpa] 回退浏览器 PKCE 铸造({why}失败,绕开 Python TLS)")
|
|
||||||
try:
|
try:
|
||||||
return mint_from_sso_browser(
|
return _browser_mint(f"{why}失败后回退")
|
||||||
sso_token,
|
|
||||||
page,
|
|
||||||
log=lambda m: log(f"[Debug] {m}"),
|
|
||||||
require_referrer=require_ref,
|
|
||||||
timeout_sec=min(timeout, 120.0),
|
|
||||||
)
|
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log(f"[!] 浏览器 PKCE 失败: {exc}")
|
log(f"[!] 浏览器 PKCE 失败: {exc}")
|
||||||
if not allow_device:
|
if not allow_device:
|
||||||
|
|||||||
+17
-13
@@ -85,7 +85,9 @@ DEFAULT_CONFIG = {
|
|||||||
# OIDC:优先 SSO→Authorization Code + referrer=grok-build
|
# OIDC:优先 SSO→Authorization Code + referrer=grok-build
|
||||||
"cpa_prefer_sso_oauth": True, # True=用 sso cookie 走 PKCE(必须带 referrer)
|
"cpa_prefer_sso_oauth": True, # True=用 sso cookie 走 PKCE(必须带 referrer)
|
||||||
"cpa_require_referrer": True, # True=access_token 无 referrer=grok-build 则失败
|
"cpa_require_referrer": True, # True=access_token 无 referrer=grok-build 则失败
|
||||||
"cpa_allow_browser_fallback": True, # True=HTTP 铸造失败时用注册浏览器走 PKCE(绕 Python TLS)
|
"cpa_prefer_browser_mint": True, # True=有注册浏览器时优先浏览器 PKCE(避开 Python 直连超时)
|
||||||
|
"cpa_allow_browser_fallback": True, # True=HTTP 铸造失败时用注册浏览器走 PKCE
|
||||||
|
"cpa_browser_mint_timeout_sec": 120, # 浏览器 PKCE 最长等待
|
||||||
"cpa_allow_device_fallback": False, # True=SSO 失败时回退设备码(通常不可用)
|
"cpa_allow_device_fallback": False, # True=SSO 失败时回退设备码(通常不可用)
|
||||||
# OIDC 铸造代理/超时
|
# OIDC 铸造代理/超时
|
||||||
"mint_proxy": "", # 铸造专用代理;空=复用 proxy
|
"mint_proxy": "", # 铸造专用代理;空=复用 proxy
|
||||||
@@ -2096,6 +2098,7 @@ def update_nsfw_settings(session, log_callback=None):
|
|||||||
|
|
||||||
|
|
||||||
def _is_curl_tls_error(exc) -> bool:
|
def _is_curl_tls_error(exc) -> bool:
|
||||||
|
"""curl_cffi 库损坏 / TLS / 连接超时:可尝试换后端或记失败,勿当业务错误。"""
|
||||||
text = str(exc or "").lower()
|
text = str(exc or "").lower()
|
||||||
return any(
|
return any(
|
||||||
n in text
|
n in text
|
||||||
@@ -2103,9 +2106,16 @@ def _is_curl_tls_error(exc) -> bool:
|
|||||||
"openssl_internal:invalid library",
|
"openssl_internal:invalid library",
|
||||||
"tls connect error",
|
"tls connect error",
|
||||||
"curl: (35)",
|
"curl: (35)",
|
||||||
|
"curl: (28)",
|
||||||
"failed to perform, curl: (35)",
|
"failed to perform, curl: (35)",
|
||||||
|
"failed to perform, curl: (28)",
|
||||||
|
"connection timed out",
|
||||||
"ssl_error_syscall",
|
"ssl_error_syscall",
|
||||||
"ssl connect error",
|
"ssl connect error",
|
||||||
|
"readtimeout",
|
||||||
|
"connecttimeout",
|
||||||
|
"timed out",
|
||||||
|
"timeout",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2166,24 +2176,18 @@ def enable_nsfw_for_token(token, cf_clearance="", log_callback=None):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# 仅用 curl_cffi:本机实测 std requests 对 accounts.x.ai/auth.x.ai 更易 ReadTimeout
|
||||||
try:
|
try:
|
||||||
session = _make_post_reg_session(proxies, prefer="curl")
|
session = _make_post_reg_session(proxies, prefer="curl")
|
||||||
try:
|
try:
|
||||||
return _run(session)
|
return _run(session)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if not _is_curl_tls_error(exc):
|
if _is_curl_tls_error(exc):
|
||||||
return False, f"异常: {exc}"
|
log(f"[Debug] set_tos/nsfw 网络/TLS 失败(不回退 requests): {exc}")
|
||||||
log(f"[Debug] curl TLS 异常,set_tos/nsfw 回退 requests: {exc}")
|
return False, f"网络/TLS: {exc}"
|
||||||
_close(session)
|
return False, f"异常: {exc}"
|
||||||
session = _make_post_reg_session(proxies, prefer="requests")
|
|
||||||
try:
|
|
||||||
return _run(session)
|
|
||||||
finally:
|
|
||||||
_close(session)
|
|
||||||
session = None
|
|
||||||
finally:
|
finally:
|
||||||
if session is not None:
|
_close(session)
|
||||||
_close(session)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"异常: {str(e)}"
|
return False, f"异常: {str(e)}"
|
||||||
|
|
||||||
|
|||||||
+20
-31
@@ -260,6 +260,10 @@ def _final_url(resp: Any) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# HTTP 铸造单步超时:直连/跨境链路差时不要卡 30s,尽快让上层走浏览器
|
||||||
|
HTTP_STEP_TIMEOUT = 12
|
||||||
|
|
||||||
|
|
||||||
def open_authorize_page(session: Any, flow: AuthCodeFlow) -> str:
|
def open_authorize_page(session: Any, flow: AuthCodeFlow) -> str:
|
||||||
params = {
|
params = {
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
@@ -277,7 +281,7 @@ def open_authorize_page(session: Any, flow: AuthCodeFlow) -> str:
|
|||||||
url,
|
url,
|
||||||
headers=_browser_headers("GET", url),
|
headers=_browser_headers("GET", url),
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
timeout=30,
|
timeout=HTTP_STEP_TIMEOUT,
|
||||||
)
|
)
|
||||||
body = resp.text or ""
|
body = resp.text or ""
|
||||||
final = _final_url(resp)
|
final = _final_url(resp)
|
||||||
@@ -355,7 +359,7 @@ def approve_authorization(session: Any, consent_url: str, flow: AuthCodeFlow) ->
|
|||||||
data=body.encode("utf-8"),
|
data=body.encode("utf-8"),
|
||||||
headers=_browser_headers("POST", consent_url, NEXT_ACTION_ID),
|
headers=_browser_headers("POST", consent_url, NEXT_ACTION_ID),
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
timeout=30,
|
timeout=HTTP_STEP_TIMEOUT,
|
||||||
)
|
)
|
||||||
text = resp.text or ""
|
text = resp.text or ""
|
||||||
if resp.status_code < 200 or resp.status_code >= 300:
|
if resp.status_code < 200 or resp.status_code >= 300:
|
||||||
@@ -384,7 +388,7 @@ def exchange_auth_code(session: Any, code: str, flow: AuthCodeFlow) -> TokenResu
|
|||||||
TOKEN_URL,
|
TOKEN_URL,
|
||||||
data=urlencode(form),
|
data=urlencode(form),
|
||||||
headers=_token_headers(),
|
headers=_token_headers(),
|
||||||
timeout=30,
|
timeout=HTTP_STEP_TIMEOUT,
|
||||||
)
|
)
|
||||||
text = resp.text or ""
|
text = resp.text or ""
|
||||||
if resp.status_code < 200 or resp.status_code >= 300:
|
if resp.status_code < 200 or resp.status_code >= 300:
|
||||||
@@ -454,47 +458,27 @@ def sso_to_token(
|
|||||||
log: LogFn | None = None,
|
log: LogFn | None = None,
|
||||||
require_referrer: bool = True,
|
require_referrer: bool = True,
|
||||||
) -> TokenResult:
|
) -> TokenResult:
|
||||||
"""SSO cookie → 带 referrer=grok-build 的 OAuth token。"""
|
"""SSO cookie → 带 referrer=grok-build 的 OAuth token。
|
||||||
|
|
||||||
|
仅用 curl_cffi(Chrome TLS)。不再回退 std requests:
|
||||||
|
实测 requests 访问 auth.x.ai / accounts.x.ai 常 ReadTimeout,比 curl 更差。
|
||||||
|
连接/TLS/超时由上层切到浏览器 PKCE。
|
||||||
|
"""
|
||||||
log = log or _noop_log
|
log = log or _noop_log
|
||||||
sso = normalize_sso_cookie(sso_cookie)
|
sso = normalize_sso_cookie(sso_cookie)
|
||||||
if not sso:
|
if not sso:
|
||||||
raise OAuthCodeError("sso cookie 为空")
|
raise OAuthCodeError("sso cookie 为空")
|
||||||
|
|
||||||
# 先 curl_cffi;若遇到 OpenSSL invalid library / curl(35),自动回退 std requests
|
|
||||||
session = _make_session(proxy, prefer="curl")
|
session = _make_session(proxy, prefer="curl")
|
||||||
try:
|
try:
|
||||||
return _run_sso_flow(
|
return _run_sso_flow(
|
||||||
sso, session=session, log=log, require_referrer=require_referrer
|
sso, session=session, log=log, require_referrer=require_referrer
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001
|
finally:
|
||||||
backend = str(getattr(session, "_cpa_http_backend", "") or "")
|
|
||||||
can_fallback = _is_curl_tls_broken(exc) or (
|
|
||||||
backend.startswith("curl_cffi") and "curl: (35)" in str(exc).lower()
|
|
||||||
)
|
|
||||||
if not can_fallback:
|
|
||||||
raise
|
|
||||||
log(f"curl TLS 异常,回退标准 requests: {_short(str(exc), 160)}")
|
|
||||||
try:
|
try:
|
||||||
session.close()
|
session.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
session = _make_session(proxy, prefer="requests")
|
|
||||||
try:
|
|
||||||
return _run_sso_flow(
|
|
||||||
sso, session=session, log=log, require_referrer=require_referrer
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
session.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
session = None # type: ignore[assignment]
|
|
||||||
finally:
|
|
||||||
if session is not None:
|
|
||||||
try:
|
|
||||||
session.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def mint_from_sso(
|
def mint_from_sso(
|
||||||
@@ -523,7 +507,7 @@ def mint_from_sso(
|
|||||||
|
|
||||||
|
|
||||||
def _is_http_tls_failure(exc: BaseException | str) -> bool:
|
def _is_http_tls_failure(exc: BaseException | str) -> bool:
|
||||||
"""HTTP 层 TLS/连接失败:适合改走浏览器铸造。"""
|
"""HTTP 层 TLS/连接/超时失败:适合改走浏览器铸造。"""
|
||||||
text = str(exc or "").lower()
|
text = str(exc or "").lower()
|
||||||
needles = (
|
needles = (
|
||||||
"unexpected_eof_while_reading",
|
"unexpected_eof_while_reading",
|
||||||
@@ -533,11 +517,16 @@ def _is_http_tls_failure(exc: BaseException | str) -> bool:
|
|||||||
"openssl_internal:invalid library",
|
"openssl_internal:invalid library",
|
||||||
"tls connect error",
|
"tls connect error",
|
||||||
"curl: (35)",
|
"curl: (35)",
|
||||||
|
"curl: (28)",
|
||||||
"failed to perform, curl: (35)",
|
"failed to perform, curl: (35)",
|
||||||
|
"failed to perform, curl: (28)",
|
||||||
|
"connection timed out",
|
||||||
"ssl_error_syscall",
|
"ssl_error_syscall",
|
||||||
"connection reset",
|
"connection reset",
|
||||||
"connection aborted",
|
"connection aborted",
|
||||||
"name resolution",
|
"name resolution",
|
||||||
|
"readtimeout",
|
||||||
|
"connecttimeout",
|
||||||
"timed out",
|
"timed out",
|
||||||
"timeout",
|
"timeout",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user