Files
MoviePilot/tests/test_mfa_passkey_registration_errors.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

170 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
验证通行密钥注册错误的业务归一与配置修正提示。
"""
from types import SimpleNamespace
from unittest.mock import patch
import pytest
import webauthn
import webauthn.helpers
from webauthn.helpers.exceptions import InvalidRegistrationResponse
from app.api.endpoints import mfa as mfa_endpoint
from app.application.security.passkey import (
PASSKEY_CHALLENGE_TTL_SECONDS,
PasskeyChallengeStore,
PassKeyHelper,
PassKeyRegistrationOriginMismatchError,
PassKeyRegistrationVerificationError,
configure_passkey_challenge_cache,
)
from app.runtime.cache import TTLCache
def setup_function():
"""
为注册错误路径显式装配隔离的 challenge 缓存。
"""
configure_passkey_challenge_cache(TTLCache(
region="passkey_challenge",
maxsize=4096,
ttl=PASSKEY_CHALLENGE_TTL_SECONDS,
))
def _registration_request(user_id: int = 1) -> mfa_endpoint.PassKeyRegistrationFinish:
"""
构造只用于错误路径的注册完成请求。
"""
transaction_token = PasskeyChallengeStore.issue(
challenge="challenge",
purpose="registration",
user_id=user_id,
)
return mfa_endpoint.PassKeyRegistrationFinish(
credential={"id": "credential-id"},
transaction_token=transaction_token,
name="测试通行密钥",
)
def _current_user() -> SimpleNamespace:
"""
构造注册错误路径所需的当前用户契约。
"""
return SimpleNamespace(id=1, name="admin")
def test_passkey_helper_classifies_origin_mismatch():
"""
来源不一致应在 WebAuthn 边界转换为稳定的业务异常。
"""
library_error = InvalidRegistrationResponse(
'Unexpected client data origin "http://localhost:5173", '
'expected "http://localhost:3000"'
)
# passkey 在方法内按需导入 webauthn,替身打在 webauthn 的来源模块上
with patch.object(
webauthn.helpers,
"parse_registration_credential_json",
return_value=object(),
), patch.object(
webauthn,
"verify_registration_response",
side_effect=library_error,
), pytest.raises(PassKeyRegistrationOriginMismatchError) as exc_info:
PassKeyHelper.verify_registration_response(
credential={"id": "credential-id"},
expected_challenge="Y2hhbGxlbmdl",
)
assert exc_info.value.__cause__ is library_error
def test_passkey_helper_classifies_other_verification_failure():
"""
其他注册验证错误不应被误判为来源配置问题。
"""
library_error = InvalidRegistrationResponse(
"Client data challenge was not expected challenge"
)
# passkey 在方法内按需导入 webauthn,替身打在 webauthn 的来源模块上
with patch.object(
webauthn.helpers,
"parse_registration_credential_json",
return_value=object(),
), patch.object(
webauthn,
"verify_registration_response",
side_effect=library_error,
), pytest.raises(PassKeyRegistrationVerificationError) as exc_info:
PassKeyHelper.verify_registration_response(
credential={"id": "credential-id"},
expected_challenge="Y2hhbGxlbmdl",
)
assert exc_info.value.__cause__ is library_error
def test_passkey_register_finish_returns_actionable_origin_message():
"""
来源配置不一致时应告诉管理员如何修正访问地址。
"""
with patch.object(
mfa_endpoint.PassKeyHelper,
"verify_registration_response",
side_effect=PassKeyRegistrationOriginMismatchError(),
):
response = mfa_endpoint.passkey_register_finish(
passkey_req=_registration_request(),
current_user=_current_user(),
)
assert not response.success
assert response.message == "访问域名与系统配置不一致,请使用配置的域名重试"
assert "APP_DOMAIN" not in response.message
assert "Unexpected client data origin" not in response.message
def test_passkey_register_finish_hides_other_verification_details():
"""
其他 WebAuthn 验证细节只记录在服务端,不返回给客户端。
"""
with patch.object(
mfa_endpoint.PassKeyHelper,
"verify_registration_response",
side_effect=PassKeyRegistrationVerificationError(
"Client data challenge was not expected challenge"
),
):
response = mfa_endpoint.passkey_register_finish(
passkey_req=_registration_request(),
current_user=_current_user(),
)
assert not response.success
assert response.message == "通行密钥注册验证失败,请重新发起注册后重试"
assert "challenge" not in response.message
def test_passkey_register_finish_hides_unexpected_error_details():
"""
未知内部异常应返回通用提示,避免泄露实现信息。
"""
with patch.object(
mfa_endpoint.PassKeyHelper,
"verify_registration_response",
side_effect=RuntimeError("database connection details"),
):
response = mfa_endpoint.passkey_register_finish(
passkey_req=_registration_request(),
current_user=_current_user(),
)
assert not response.success
assert response.message == "通行密钥注册失败,请稍后重试"
assert "database" not in response.message