Replace repository with grok-reg-protocol-cpa project
Overwrite previous grok-keygen contents with the local registration/CPA protocol codebase (secrets and runtime data excluded).
This commit is contained in:
commit
6f7c949681
33 files changed
+11125
No files matched your search
@@ -0,0 +1,281 @@
|
||||
"""Pure-HTTP SSO cookie → OIDC tokens (device flow, no browser).
|
||||
|
||||
Uses curl_cffi (Chrome TLS fingerprint) + SSO cookie to:
|
||||
1. Validate session on accounts.x.ai
|
||||
2. Request device code (stdlib / oauth_device)
|
||||
3. GET verification_uri_complete
|
||||
4. POST /oauth2/device/verify
|
||||
5. POST /oauth2/device/approve
|
||||
6. Poll token endpoint
|
||||
|
||||
On any failure raise ProtocolMintError so callers can fall back to browser mint.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .oauth_device import (
|
||||
CLIENT_ID,
|
||||
ISSUER,
|
||||
OAuthDeviceError,
|
||||
SCOPE,
|
||||
poll_device_token,
|
||||
request_device_code,
|
||||
)
|
||||
from .proxyutil import proxy_log_label, resolve_proxy, set_runtime_proxy
|
||||
|
||||
LogFn = Callable[[str], None]
|
||||
|
||||
VERIFY_URL = f"{ISSUER}/oauth2/device/verify"
|
||||
APPROVE_URL = f"{ISSUER}/oauth2/device/approve"
|
||||
|
||||
|
||||
class ProtocolMintError(RuntimeError):
|
||||
"""Protocol path failed; caller may fall back to browser mint."""
|
||||
|
||||
|
||||
def _noop_log(_: str) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def extract_sso_from_cookies(cookies: Any) -> str:
|
||||
"""Pull sso / sso-rw value from a cookie list/dict."""
|
||||
if not cookies:
|
||||
return ""
|
||||
if isinstance(cookies, str):
|
||||
return cookies.strip()
|
||||
if isinstance(cookies, dict):
|
||||
for name in ("sso", "sso-rw"):
|
||||
v = cookies.get(name)
|
||||
if v:
|
||||
return str(v).strip()
|
||||
return ""
|
||||
if isinstance(cookies, (list, tuple)):
|
||||
# Prefer bare "sso" over "sso-rw"
|
||||
found_rw = ""
|
||||
for c in cookies:
|
||||
if not isinstance(c, dict):
|
||||
continue
|
||||
name = str(c.get("name") or c.get("Name") or "")
|
||||
value = c.get("value") if "value" in c else c.get("Value")
|
||||
if not value:
|
||||
continue
|
||||
if name == "sso":
|
||||
return str(value).strip()
|
||||
if name == "sso-rw" and not found_rw:
|
||||
found_rw = str(value).strip()
|
||||
return found_rw
|
||||
return ""
|
||||
|
||||
|
||||
def _session(proxy: str | None, log: LogFn):
|
||||
try:
|
||||
from curl_cffi import requests as cf_requests
|
||||
except ImportError as e:
|
||||
raise ProtocolMintError(
|
||||
"curl_cffi not installed; cannot run protocol mint"
|
||||
) from e
|
||||
|
||||
s = cf_requests.Session()
|
||||
resolved = resolve_proxy(proxy)
|
||||
if resolved:
|
||||
s.proxies = {"http": resolved, "https": resolved}
|
||||
log(f"protocol proxy={proxy_log_label(resolved)}")
|
||||
return s
|
||||
|
||||
|
||||
def _set_sso_cookie(session: Any, sso_cookie: str) -> None:
|
||||
sso_cookie = (sso_cookie or "").strip()
|
||||
if not sso_cookie:
|
||||
raise ProtocolMintError("empty sso cookie")
|
||||
for domain in (".x.ai", "accounts.x.ai", "auth.x.ai", ".accounts.x.ai"):
|
||||
try:
|
||||
session.cookies.set("sso", sso_cookie, domain=domain)
|
||||
except Exception:
|
||||
try:
|
||||
session.cookies.set("sso", sso_cookie, domain=domain, path="/")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
session.cookies.set("sso-rw", sso_cookie, domain=domain)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _url_path(url: str) -> str:
|
||||
try:
|
||||
return urlparse(url or "").path or ""
|
||||
except Exception:
|
||||
return url or ""
|
||||
|
||||
|
||||
def mint_with_sso_protocol(
|
||||
*,
|
||||
sso_cookie: str,
|
||||
email: str = "",
|
||||
proxy: str | None = None,
|
||||
timeout: float = 30.0,
|
||||
poll_timeout_sec: float = 90.0,
|
||||
log: LogFn | None = None,
|
||||
cancel: Callable[[], bool] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""SSO cookie → OIDC token dict (access/refresh/id/expires_in).
|
||||
|
||||
Raises ProtocolMintError on failure.
|
||||
"""
|
||||
log = log or _noop_log
|
||||
sso_cookie = (sso_cookie or "").strip()
|
||||
if not sso_cookie:
|
||||
raise ProtocolMintError("missing sso cookie")
|
||||
|
||||
resolved = resolve_proxy(proxy)
|
||||
set_runtime_proxy(resolved or None)
|
||||
|
||||
session = _session(resolved or None, log)
|
||||
_set_sso_cookie(session, sso_cookie)
|
||||
|
||||
# 1) Validate SSO
|
||||
try:
|
||||
r = session.get(
|
||||
"https://accounts.x.ai/",
|
||||
impersonate="chrome",
|
||||
timeout=timeout,
|
||||
allow_redirects=True,
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"accounts.x.ai network error: {e}") from e
|
||||
|
||||
final_url = getattr(r, "url", "") or ""
|
||||
if "sign-in" in final_url or "sign-up" in final_url:
|
||||
raise ProtocolMintError(f"sso invalid (landed {final_url[:120]})")
|
||||
log(f"protocol sso valid url={final_url[:120]}")
|
||||
|
||||
# 2) Device code
|
||||
try:
|
||||
sess = request_device_code(proxy=resolved or None, timeout=timeout)
|
||||
except OAuthDeviceError as e:
|
||||
raise ProtocolMintError(f"device code: {e}") from e
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"device code: {e}") from e
|
||||
log(f"protocol user_code={sess.user_code}")
|
||||
|
||||
if cancel and cancel():
|
||||
raise ProtocolMintError("cancelled")
|
||||
|
||||
# 3) Open verification URI (sets session state / CSRF)
|
||||
try:
|
||||
r = session.get(
|
||||
sess.verification_uri_complete,
|
||||
impersonate="chrome",
|
||||
timeout=timeout,
|
||||
allow_redirects=True,
|
||||
)
|
||||
log(f"protocol verify-uri status={getattr(r, 'status_code', '?')} url={getattr(r, 'url', '')[:140]}")
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"verification_uri get failed: {e}") from e
|
||||
|
||||
if cancel and cancel():
|
||||
raise ProtocolMintError("cancelled")
|
||||
|
||||
# 4) POST device/verify
|
||||
try:
|
||||
r = session.post(
|
||||
VERIFY_URL,
|
||||
data={"user_code": sess.user_code},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
impersonate="chrome",
|
||||
timeout=timeout,
|
||||
allow_redirects=True,
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"device/verify exception: {e}") from e
|
||||
|
||||
verify_url = getattr(r, "url", "") or ""
|
||||
status = getattr(r, "status_code", 0)
|
||||
path = _url_path(verify_url)
|
||||
body_snip = ""
|
||||
try:
|
||||
body_snip = (r.text or "")[:200]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "consent" not in verify_url and "consent" not in path:
|
||||
# Some flows land on consent via relative redirect body; accept soft signals
|
||||
soft_ok = (
|
||||
"consent" in (body_snip or "").lower()
|
||||
or "authorize grok build" in (body_snip or "").lower()
|
||||
or "授权 grok build" in (body_snip or "").lower()
|
||||
)
|
||||
if not soft_ok:
|
||||
raise ProtocolMintError(
|
||||
f"device/verify failed status={status} url={verify_url[:160]}"
|
||||
)
|
||||
log(f"protocol verify ok status={status} url={verify_url[:140]}")
|
||||
|
||||
if cancel and cancel():
|
||||
raise ProtocolMintError("cancelled")
|
||||
|
||||
# 5) POST device/approve
|
||||
try:
|
||||
r = session.post(
|
||||
APPROVE_URL,
|
||||
data={
|
||||
"user_code": sess.user_code,
|
||||
"action": "allow",
|
||||
"principal_type": "User",
|
||||
"principal_id": "",
|
||||
},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
impersonate="chrome",
|
||||
timeout=timeout,
|
||||
allow_redirects=True,
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"device/approve exception: {e}") from e
|
||||
|
||||
approve_url = getattr(r, "url", "") or ""
|
||||
status = getattr(r, "status_code", 0)
|
||||
if "done" not in approve_url and "device/done" not in _url_path(approve_url):
|
||||
# Accept success page markers in body
|
||||
try:
|
||||
text = r.text or ""
|
||||
except Exception:
|
||||
text = ""
|
||||
if "设备已授权" not in text and "device authorized" not in text.lower() and "done" not in text.lower():
|
||||
raise ProtocolMintError(
|
||||
f"device/approve failed status={status} url={approve_url[:160]}"
|
||||
)
|
||||
log(f"protocol approve ok status={status} url={approve_url[:140]}")
|
||||
|
||||
# 6) Poll tokens
|
||||
poll_expires = min(int(sess.expires_in), max(int(poll_timeout_sec), 30))
|
||||
try:
|
||||
tr = poll_device_token(
|
||||
sess.device_code,
|
||||
interval=max(int(sess.interval), 2),
|
||||
expires_in=poll_expires,
|
||||
timeout=timeout,
|
||||
log=log,
|
||||
cancel=cancel,
|
||||
proxy=resolved or None,
|
||||
)
|
||||
except OAuthDeviceError as e:
|
||||
raise ProtocolMintError(f"token poll: {e}") from e
|
||||
except Exception as e: # noqa: BLE001
|
||||
raise ProtocolMintError(f"token poll: {e}") from e
|
||||
|
||||
log(
|
||||
f"protocol token ok expires_in={tr.expires_in}"
|
||||
+ (f" email={email}" if email else "")
|
||||
)
|
||||
return {
|
||||
"access_token": tr.access_token,
|
||||
"refresh_token": tr.refresh_token,
|
||||
"id_token": tr.id_token,
|
||||
"token_type": tr.token_type,
|
||||
"expires_in": tr.expires_in,
|
||||
"user_code": sess.user_code,
|
||||
"mint_method": "protocol",
|
||||
}
|
||||
Reference in new issue
Block a user