From 3c8ba40d2dc18b2f20ec3504fa779b1f519bcb02 Mon Sep 17 00:00:00 2001 From: Mison Date: Tue, 24 Mar 2026 09:15:02 +0800 Subject: [PATCH] fix: prevent secondary OTP from hanging --- src/core/register.py | 7 ++-- src/services/tempmail.py | 7 ++-- tests/test_registration_otp_phase.py | 15 +++++---- tests/test_tempmail_service.py | 48 ++++++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/core/register.py b/src/core/register.py index 81a5a43..74fbe7c 100644 --- a/src/core/register.py +++ b/src/core/register.py @@ -1073,12 +1073,11 @@ class RegistrationEngine: self._log("未能重新进入登录流程", "warning") return None, None - password_ok, _ = self._submit_login_password_step_and_get_continue_url() - if not password_ok: - return None, None - self._otp_sent_at = time.time() + if not self._submit_login_password_step(): + return None, None + code = self._get_verification_code() if not code: self._log("登录流程获取验证码失败", "warning") diff --git a/src/services/tempmail.py b/src/services/tempmail.py index a3cc87b..aef3350 100644 --- a/src/services/tempmail.py +++ b/src/services/tempmail.py @@ -15,6 +15,8 @@ from ..config.constants import OTP_CODE_PATTERN logger = logging.getLogger(__name__) +OTP_SENT_AT_TOLERANCE_SECONDS = 2 + class TempmailService(BaseEmailService): """ @@ -184,7 +186,7 @@ class TempmailService(BaseEmailService): email_id: 邮箱 token(如果不提供,从缓存中查找) timeout: 超时时间(秒) pattern: 验证码正则表达式 - otp_sent_at: OTP 发送时间戳,只允许使用严格晚于该锚点的邮件 + otp_sent_at: OTP 发送时间戳,只允许使用严格晚于该锚点减去容差后的邮件 Returns: 验证码字符串,如果超时或未找到返回 None @@ -241,7 +243,8 @@ class TempmailService(BaseEmailService): msg_timestamp = self._get_received_timestamp(msg) if otp_sent_at is not None: - if msg_timestamp is None or msg_timestamp <= otp_sent_at: + min_allowed_timestamp = otp_sent_at - OTP_SENT_AT_TOLERANCE_SECONDS + if msg_timestamp is None or msg_timestamp <= min_allowed_timestamp: continue message_id = str( diff --git a/tests/test_registration_otp_phase.py b/tests/test_registration_otp_phase.py index d5ea0b9..2e747bc 100644 --- a/tests/test_registration_otp_phase.py +++ b/tests/test_registration_otp_phase.py @@ -71,7 +71,7 @@ def test_phase_otp_secondary_returns_dedicated_timeout_error_code(monkeypatch): assert engine.phase_history[0].error_code == ERROR_OTP_TIMEOUT_SECONDARY -def test_advance_login_authorization_refreshes_otp_anchor_after_password_submit(monkeypatch): +def test_advance_login_authorization_sets_otp_anchor_before_password_submit(monkeypatch): email_service = FakeEmailService(code=None) engine = _build_engine(monkeypatch, email_service) engine.oauth_start = object() @@ -82,14 +82,15 @@ def test_advance_login_authorization_refreshes_otp_anchor_after_password_submit( monkeypatch.setattr(engine, "_start_oauth", lambda: True) monkeypatch.setattr(engine, "_get_device_id", lambda: True) monkeypatch.setattr(engine, "_try_reenter_login_flow", lambda: True) - monkeypatch.setattr( - engine, - "_submit_login_password_step_and_get_continue_url", - lambda: (True, "https://continue.example.test"), - ) seen_anchors = [] + def fake_submit_login_password_step(): + seen_anchors.append(engine._otp_sent_at) + return True + + monkeypatch.setattr(engine, "_submit_login_password_step", fake_submit_login_password_step) + def fake_get_verification_code(): seen_anchors.append(engine._otp_sent_at) return None @@ -101,4 +102,4 @@ def test_advance_login_authorization_refreshes_otp_anchor_after_password_submit( assert workspace_id is None assert callback_url is None assert engine._otp_sent_at == 456.0 - assert seen_anchors == [456.0] + assert seen_anchors == [456.0, 456.0] diff --git a/tests/test_tempmail_service.py b/tests/test_tempmail_service.py index c9c0e27..c9a2d46 100644 --- a/tests/test_tempmail_service.py +++ b/tests/test_tempmail_service.py @@ -23,7 +23,7 @@ class FakeHTTPClient: return self.responses.pop(0) -def test_get_verification_code_ignores_messages_not_newer_than_otp_anchor(monkeypatch): +def test_get_verification_code_ignores_messages_older_than_tolerance_window(monkeypatch): service = TempmailService({ "base_url": "https://api.tempmail.test/v2", "timeout": 1, @@ -43,7 +43,7 @@ def test_get_verification_code_ignores_messages_not_newer_than_otp_anchor(monkey "from": "noreply@openai.com", "subject": "Old verification code", "body": "111111", - "received_at": 1999, + "received_at": 1998, }, { "id": "new-mail", @@ -74,3 +74,47 @@ def test_get_verification_code_ignores_messages_not_newer_than_otp_anchor(monkey }, } ] + + +def test_get_verification_code_allows_two_second_anchor_tolerance(monkeypatch): + service = TempmailService({ + "base_url": "https://api.tempmail.test/v2", + "timeout": 1, + "max_retries": 1, + }) + service._email_cache["tester@example.com"] = { + "email": "tester@example.com", + "token": "token-1", + } + service.http_client = FakeHTTPClient([ + FakeResponse( + status_code=200, + payload={ + "emails": [ + { + "id": "too-old-mail", + "from": "noreply@openai.com", + "subject": "Too old verification code", + "body": "111111", + "received_at": 1998, + }, + { + "id": "tolerated-mail", + "from": "noreply@openai.com", + "subject": "Tolerated verification code", + "body": "654321", + "received_at": 1999, + }, + ] + }, + ) + ]) + monkeypatch.setattr(tempmail_module.time, "sleep", lambda _: None) + + code = service.get_verification_code( + email="tester@example.com", + timeout=1, + otp_sent_at=2000, + ) + + assert code == "654321"