fix(register): align live sentinel flow with successful HAR

This commit is contained in:
Logic
2026-04-07 13:39:01 +08:00
parent ba02799b18
commit 3c6fce8d57
13 changed files with 1784 additions and 51 deletions

0
tests/__init__.py Normal file
View File

View File

@@ -0,0 +1,129 @@
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
class FakeResponse:
def __init__(self, *, status_code=200, text="", json_data=None, headers=None):
self.status_code = status_code
self.text = text
self._json_data = json_data
self.headers = headers or {"content-type": "application/json"}
def json(self):
return self._json_data
class RegisterLiveSentinelTests(unittest.TestCase):
def test_register_authorize_continue_and_so_token_are_used(self):
from src.chatgpt_register_http_reverse import ChatGPTRegisterHTTPReverse
flow_history: list[str] = []
def build_token(flow: str) -> str:
flow_history.append(flow)
return f"{flow}-token"
def build_session_observer_token(flow: str) -> str:
flow_history.append("session_observer")
return "session-observer-token"
fake_http = SimpleNamespace(
request=Mock(return_value=FakeResponse(json_data={"accessToken": "at", "user": {"id": "uid"}})),
session=SimpleNamespace(cookies=SimpleNamespace(jar=[])),
)
fake_mail = SimpleNamespace(
create_mailbox=Mock(return_value={"address": "user@example.com", "id": "box-1", "password": ""}),
wait_for_otp=Mock(return_value="123456"),
)
solver = SimpleNamespace(
build_token=Mock(side_effect=build_token),
build_session_observer_token=Mock(side_effect=build_session_observer_token),
)
register = ChatGPTRegisterHTTPReverse(fake_http, fake_mail, sentinel_solver=solver)
order: list[str] = []
create_call_args: dict[str, str | None] = {}
def fake_authorize_continue(*args, **kwargs):
order.append("authorize_continue")
return "https://auth.openai.com/create-account/password"
def fake_attempt_register(*args, **kwargs):
order.append("attempt_register")
return FakeResponse(status_code=200), {"continue_url": "continue-1"}
def fake_attempt_create_account(*args, sentinel=None, sentinel_so=None, **kwargs):
order.append("attempt_create")
create_call_args["sentinel"] = sentinel
create_call_args["sentinel_so"] = sentinel_so
return FakeResponse(status_code=200), {"continue_url": "continue-3"}
with patch.object(register, "_bootstrap_chatgpt", return_value="csrf"), \
patch.object(register, "_signin_auth0", return_value="auth-url"), \
patch.object(register, "_follow_auth_redirects", return_value="https://auth.openai.com/create-account/password"), \
patch.object(register, "_accounts_api_base", return_value="https://auth.openai.com/api/accounts"), \
patch.object(register, "_register_endpoint", return_value="https://auth.openai.com/api/accounts/user/register"), \
patch.object(register, "_authorize_continue", side_effect=fake_authorize_continue, create=True) as authorize_continue, \
patch.object(register, "_open_continue_url", side_effect=["email-url", "about-you-url"]), \
patch.object(register, "_validate_otp", return_value="continue-2"), \
patch.object(register, "_finalize_auth_callback"), \
patch.object(register, "_attempt_register", side_effect=fake_attempt_register) as attempt_register, \
patch.object(register, "_attempt_create_account", side_effect=fake_attempt_create_account) as attempt_create:
result = register.register("Passw0rd!", "Jane Doe")
self.assertEqual(result["email"], "user@example.com")
self.assertEqual(
flow_history,
[
"authorize_continue",
"username_password_create",
"oauth_create_account",
"session_observer",
],
)
self.assertTrue(authorize_continue.called)
self.assertEqual(authorize_continue.call_args.args[1], "https://auth.openai.com/log-in-or-create-account?usernameKind=email")
self.assertLess(order.index("authorize_continue"), order.index("attempt_register"))
self.assertEqual(attempt_register.call_args.args[1], "https://auth.openai.com/create-account/password")
self.assertEqual(attempt_register.call_args.kwargs["sentinel"], "username_password_create-token")
self.assertEqual(create_call_args["sentinel"], "oauth_create_account-token")
self.assertEqual(create_call_args["sentinel_so"], "session-observer-token")
solver.build_session_observer_token.assert_called_once_with("oauth_create_account")
def test_authorize_continue_posts_screen_hint_and_json_headers(self):
from src.chatgpt_register_http_reverse import ChatGPTRegisterHTTPReverse
captured = {}
def fake_request(method, url, **kwargs):
captured['method'] = method
captured['url'] = url
captured['kwargs'] = kwargs
return FakeResponse(status_code=200, json_data={"continue_url": "https://auth.openai.com/create-account/password"})
fake_http = SimpleNamespace(
request=fake_request,
session=SimpleNamespace(cookies=SimpleNamespace(jar=[])),
)
fake_mail = SimpleNamespace()
register = ChatGPTRegisterHTTPReverse(fake_http, fake_mail, sentinel_solver=SimpleNamespace())
with patch.object(register, '_open_continue_url', return_value='https://auth.openai.com/create-account/password'):
result = register._authorize_continue(
'https://auth.openai.com/api/accounts',
'https://auth.openai.com/log-in-or-create-account?usernameKind=email',
'user@example.com',
sentinel='authorize-token',
)
self.assertEqual(result, 'https://auth.openai.com/create-account/password')
self.assertEqual(captured['method'], 'POST')
self.assertEqual(captured['kwargs']['json']['screen_hint'], 'login_or_signup')
self.assertEqual(captured['kwargs']['headers']['Accept'], 'application/json')
self.assertEqual(captured['kwargs']['headers']['openai-sentinel-token'], 'authorize-token')
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,146 @@
import json
import unittest
from types import SimpleNamespace
from unittest.mock import patch
class FakeResponse:
def __init__(self, *, text="", status_code=200, json_data=None, headers=None):
self.text = text
self.status_code = status_code
self._json_data = json_data
self.headers = headers or {}
def json(self):
if self._json_data is None:
raise ValueError("no json")
return self._json_data
class FakeHTTP:
def __init__(self):
self.calls = []
self.device_id = "device-fallback-id"
self.session = SimpleNamespace(
headers={
"User-Agent": "Mozilla/5.0 Test",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
},
cookies=SimpleNamespace(
jar=[SimpleNamespace(name="oai-did", value="cookie-did", domain="chatgpt.com")]
),
)
self.req_payloads = []
def request(self, method, url, **kwargs):
self.calls.append((method, url, kwargs))
if url.endswith("/backend-api/sentinel/sdk.js"):
return FakeResponse(
text=(
"window.SentinelSDK = {};"
"script.src = 'https://sentinel.openai.com/sentinel/20260219f9f6/sdk.js';"
)
)
if "/backend-api/sentinel/frame.html?sv=" in url:
return FakeResponse(text="<html></html>")
if url.endswith("/backend-api/sentinel/req"):
data = kwargs.get("data")
if data:
payload = json.loads(data)
else:
payload = {}
self.req_payloads.append(payload)
return FakeResponse(
json_data={
"token": "collector-token",
"proofofwork": {"required": True, "seed": "seed-1", "difficulty": "0"},
"turnstile": {"required": True, "dx": "encoded-dx"},
"so": {"required": True, "snapshot_dx": "snapshot-dx"},
}
)
raise AssertionError(f"unexpected request: {method} {url}")
class SentinelSolverTests(unittest.TestCase):
@patch("subprocess.run")
def test_build_token_uses_enforcement_p_not_prepare_p(self, run_mock):
from src.sentinel_solver import SentinelSolver
def fake_run(command, **kwargs):
mode = command[-1]
payload = json.loads(kwargs["input"])
if mode == "prepare":
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "prepare-proof"}), stderr="")
if mode == "turnstile":
self.assertEqual(payload["p"], "prepare-proof")
self.assertEqual(payload["dx"], "encoded-dx")
return SimpleNamespace(returncode=0, stdout=json.dumps({"t": "wire-turnstile"}), stderr="")
if mode == "enforcement":
self.assertEqual(payload["chat_req"]["proofofwork"]["seed"], "seed-1")
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "enforcement-proof"}), stderr="")
raise AssertionError(command)
run_mock.side_effect = fake_run
http = FakeHTTP()
solver = SentinelSolver(http)
token = solver.build_token("username_password_create")
parsed = json.loads(token)
self.assertEqual(
parsed,
{
"p": "enforcement-proof",
"t": "wire-turnstile",
"c": "collector-token",
"id": "cookie-did",
"flow": "username_password_create",
},
)
self.assertEqual(http.calls[0][0:2], ("GET", "https://sentinel.openai.com/backend-api/sentinel/sdk.js"))
self.assertTrue(http.calls[1][1].startswith("https://sentinel.openai.com/backend-api/sentinel/frame.html?sv="))
self.assertEqual(http.calls[2][0:2], ("POST", "https://sentinel.openai.com/backend-api/sentinel/req"))
self.assertEqual(http.req_payloads[-1]["p"], "prepare-proof")
self.assertEqual(http.req_payloads[-1]["flow"], "username_password_create")
@patch("subprocess.run")
def test_build_session_observer_token_uses_cached_chat_req_snapshot(self, run_mock):
from src.sentinel_solver import SentinelSolver
def fake_run(command, **kwargs):
mode = command[-1]
payload = json.loads(kwargs["input"])
if mode == "prepare":
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "prepare-proof"}), stderr="")
if mode == "turnstile":
return SimpleNamespace(returncode=0, stdout=json.dumps({"t": "wire-turnstile"}), stderr="")
if mode == "enforcement":
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "enforcement-proof"}), stderr="")
if mode == "session-observer":
self.assertEqual(payload["flow"], "oauth_create_account")
self.assertEqual(payload["c"], "collector-token")
self.assertEqual(payload["snapshot_dx"], "snapshot-dx")
return SimpleNamespace(returncode=0, stdout=json.dumps({"so": "wire-so"}), stderr="")
raise AssertionError(command)
run_mock.side_effect = fake_run
http = FakeHTTP()
solver = SentinelSolver(http)
so_token = solver.build_session_observer_token("oauth_create_account")
self.assertEqual(
json.loads(so_token),
{
"so": "wire-so",
"c": "collector-token",
"id": "cookie-did",
"flow": "oauth_create_account",
},
)
self.assertEqual(http.req_payloads[-1]["flow"], "oauth_create_account")
self.assertEqual(http.req_payloads[-1]["p"], "prepare-proof")
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,40 @@
import unittest
from types import SimpleNamespace
from unittest.mock import patch
class YYDSMailClientDomainTests(unittest.TestCase):
@patch('src.vmail_client.httpx.post')
def test_create_mailbox_sends_configured_domain(self, post_mock):
from src.vmail_client import YYDSMailClient, settings
captured = {}
def fake_post(url, headers=None, json=None, timeout=None):
captured['url'] = url
captured['headers'] = headers
captured['json'] = json
captured['timeout'] = timeout
return SimpleNamespace(
status_code=201,
json=lambda: {
'data': {
'id': 'box-1',
'address': 'chosen@20001028.xyz',
'token': 'temp-token',
}
},
)
post_mock.side_effect = fake_post
with patch.object(settings, 'yyds_mail_domain', '20001028.xyz', create=True):
client = YYDSMailClient()
mailbox = client.create_mailbox()
self.assertEqual(mailbox['address'], 'chosen@20001028.xyz')
self.assertEqual(captured['json'], {'domain': '20001028.xyz'})
if __name__ == '__main__':
unittest.main()