Prefer SSO OAuth PKCE for CPA minting and sync latest accounts.
Switch CPA export to SSO→Authorization Code with referrer=grok-build, keep device-code as optional fallback, and capture new register/auth artifacts.
This commit is contained in:
1 parent
8b3664a2d5
commit
ee151343e0
455 files changed
+9051
-59
No files matched your search
@@ -6,6 +6,9 @@ from .schema import (
|
||||
CLI_BASE_URL,
|
||||
CLIENT_ID,
|
||||
DEFAULT_BASE_URL,
|
||||
DEFAULT_HEADERS,
|
||||
GROK_TOKEN_UA,
|
||||
GROK_VERSION,
|
||||
REDIRECT_URI,
|
||||
TOKEN_ENDPOINT,
|
||||
build_cpa_xai_auth,
|
||||
@@ -19,6 +22,9 @@ __all__ = [
|
||||
"CLI_BASE_URL",
|
||||
"CLIENT_ID",
|
||||
"DEFAULT_BASE_URL",
|
||||
"DEFAULT_HEADERS",
|
||||
"GROK_TOKEN_UA",
|
||||
"GROK_VERSION",
|
||||
"REDIRECT_URI",
|
||||
"TOKEN_ENDPOINT",
|
||||
"CpaPushError",
|
||||
|
||||
+45
-6
@@ -4,6 +4,9 @@ internal/auth/xai/token.go 的 TokenStorage 结构。
|
||||
生成的 xai-<email>.json 可被 CLIProxyAPI 直接加载;CLIProxyAPI 请求 grok 时会
|
||||
自带 x-grok-client-version 头(xai_executor.go 硬编码 0.2.93),免费 Build 账号
|
||||
不会 426。
|
||||
|
||||
2026-07:AccessToken 必须含 referrer=grok-build,否则 cli-chat-proxy 拒用。
|
||||
铸造请走 oidc_mint.oauth_code(SSO→Authorization Code + PKCE)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -24,6 +27,18 @@ CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1"
|
||||
API_BASE_URL = "https://api.x.ai/v1"
|
||||
DEFAULT_BASE_URL = CLI_BASE_URL
|
||||
|
||||
GROK_VERSION = "0.2.93"
|
||||
GROK_TOKEN_UA = (
|
||||
f"grok-pager/{GROK_VERSION} grok-shell/{GROK_VERSION} (linux; x86_64)"
|
||||
)
|
||||
DEFAULT_HEADERS = {
|
||||
"x-grok-client-version": GROK_VERSION,
|
||||
"x-xai-token-auth": "xai-grok-cli",
|
||||
"x-authenticateresponse": "authenticate-response",
|
||||
"x-grok-client-identifier": "grok-pager",
|
||||
"User-Agent": GROK_TOKEN_UA,
|
||||
}
|
||||
|
||||
|
||||
def _sanitize_file_segment(value: str) -> str:
|
||||
"""对齐 CPA CredentialFileName 的清洗规则。"""
|
||||
@@ -65,14 +80,15 @@ def _jwt_payload(token: str) -> dict[str, Any]:
|
||||
return json.loads(base64.urlsafe_b64decode(seg))
|
||||
|
||||
|
||||
def expired_from_access_token(access_token: str) -> tuple[str, int, str]:
|
||||
"""从 access_token 解析 (expired_rfc3339, expires_in, sub)。"""
|
||||
def expired_from_access_token(access_token: str) -> tuple[str, int, str, str]:
|
||||
"""从 access_token 解析 (expired_rfc3339, expires_in, sub, referrer)。"""
|
||||
pl = _jwt_payload(access_token)
|
||||
exp = int(pl["exp"])
|
||||
iat = int(pl["iat"]) if pl.get("iat") is not None else exp - 21600
|
||||
expired = datetime.fromtimestamp(exp, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
sub = str(pl.get("sub") or pl.get("principal_id") or "").strip()
|
||||
return expired, max(exp - iat, 0), sub
|
||||
referrer = str(pl.get("referrer") or "").strip()
|
||||
return expired, max(exp - iat, 0), sub, referrer
|
||||
|
||||
|
||||
def build_cpa_xai_auth(
|
||||
@@ -88,6 +104,9 @@ def build_cpa_xai_auth(
|
||||
base_url: str = DEFAULT_BASE_URL,
|
||||
token_endpoint: str = TOKEN_ENDPOINT,
|
||||
redirect_uri: str = REDIRECT_URI,
|
||||
sso: str | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
disabled: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""组装一个 CLIProxyAPI 可导入的 xai auth 对象(TokenStorage 字段)。"""
|
||||
access_token = (access_token or "").strip()
|
||||
@@ -97,10 +116,11 @@ def build_cpa_xai_auth(
|
||||
if not refresh_token:
|
||||
raise ValueError("refresh_token is required (CPA 无法在缺 refresh_token 时续期)")
|
||||
|
||||
referrer = ""
|
||||
try:
|
||||
exp_s, exp_in, sub_jwt = expired_from_access_token(access_token)
|
||||
exp_s, exp_in, sub_jwt, referrer = expired_from_access_token(access_token)
|
||||
except Exception:
|
||||
exp_s, exp_in, sub_jwt = "", 21600, ""
|
||||
exp_s, exp_in, sub_jwt, referrer = "", 21600, "", ""
|
||||
|
||||
if not expired:
|
||||
expired = exp_s
|
||||
@@ -111,10 +131,23 @@ def build_cpa_xai_auth(
|
||||
if not last_refresh:
|
||||
last_refresh = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# 从 id_token 补 email
|
||||
email = (email or "").strip()
|
||||
if not email and id_token:
|
||||
try:
|
||||
idp = _jwt_payload(id_token)
|
||||
email = str(idp.get("email") or "").strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
||||
if not re.search(r"/v1$", base_url) and base_url.endswith("cli-chat-proxy.grok.com"):
|
||||
base_url = base_url + "/v1"
|
||||
|
||||
hdrs = dict(DEFAULT_HEADERS)
|
||||
if headers:
|
||||
hdrs.update({str(k): str(v) for k, v in headers.items() if k and v is not None})
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"type": "xai",
|
||||
"auth_kind": "oauth",
|
||||
@@ -124,12 +157,18 @@ def build_cpa_xai_auth(
|
||||
"expires_in": int(expires_in),
|
||||
"expired": expired,
|
||||
"last_refresh": last_refresh,
|
||||
"email": (email or "").strip(),
|
||||
"email": email,
|
||||
"sub": (sub or "").strip(),
|
||||
"base_url": base_url,
|
||||
"token_endpoint": token_endpoint,
|
||||
"redirect_uri": redirect_uri,
|
||||
"disabled": bool(disabled),
|
||||
"headers": hdrs,
|
||||
}
|
||||
if id_token:
|
||||
payload["id_token"] = id_token.strip()
|
||||
if sso:
|
||||
payload["sso"] = str(sso).strip()
|
||||
if referrer:
|
||||
payload["referrer"] = referrer
|
||||
return payload
|
||||
Reference in new issue
Block a user