mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor: 将路由层同步 DB 操作移至线程池执行,统一认证工具函数
- 管理端和用户端路由中的同步数据库操作提取为独立函数,通过 run_in_threadpool 在线程池中执行,避免阻塞事件循环(涉及 api_keys、payments、users、wallets、 provider_oauth、system、user_me、wallet 等模块) - 抽取 authenticate_user_from_bearer_token 统一 token 验证逻辑,支持 ManagementToken 和 JWT 两种认证方式,消除多处重复代码 - key_command_service 的 CRUD 操作改为线程池执行 - maintenance_scheduler 定时任务中的数据库操作改用 asyncio.to_thread - Dockerfile 中 aether-hub 增加 --worker-idle-timeout 0 防止空闲断连 - 新增 test_api_auth_conventions 和 test_auth_utils 单元测试
This commit is contained in:
25
tests/unit/test_api_auth_conventions.py
Normal file
25
tests/unit/test_api_auth_conventions.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_no_direct_access_token_verification_outside_pipeline() -> None:
|
||||
api_root = Path("src/api")
|
||||
allowed = {
|
||||
Path("src/api/base/pipeline.py"),
|
||||
Path("src/api/auth/routes.py"),
|
||||
}
|
||||
offenders: list[str] = []
|
||||
|
||||
for path in api_root.rglob("*.py"):
|
||||
if path in allowed:
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if "verify_token(" in text and 'token_type="access"' in text:
|
||||
offenders.append(str(path))
|
||||
|
||||
assert (
|
||||
offenders == []
|
||||
), "这些 API 文件仍在手写 access token 校验,应改为走 pipeline 或 auth_utils 统一入口: " + ", ".join(
|
||||
sorted(offenders)
|
||||
)
|
||||
87
tests/unit/test_auth_utils.py
Normal file
87
tests/unit/test_auth_utils.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
|
||||
from src.models.database import UserRole
|
||||
from src.utils.auth_utils import get_current_user, get_current_user_from_header
|
||||
|
||||
|
||||
class TestAuthUtilsManagementToken:
|
||||
@staticmethod
|
||||
def _make_request() -> Any:
|
||||
return MagicMock(
|
||||
headers={},
|
||||
client=MagicMock(host="127.0.0.1"),
|
||||
state=MagicMock(spec=[]),
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_accepts_management_token(self) -> None:
|
||||
request = self._make_request()
|
||||
credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials="ae_valid_token")
|
||||
db = MagicMock()
|
||||
user = MagicMock()
|
||||
user.id = "admin-123"
|
||||
user.role = UserRole.ADMIN
|
||||
management_token = MagicMock()
|
||||
management_token.id = "mt-123"
|
||||
|
||||
with patch(
|
||||
"src.utils.auth_utils.AuthService.authenticate_management_token",
|
||||
new_callable=AsyncMock,
|
||||
return_value=(user, management_token),
|
||||
) as mock_authenticate:
|
||||
result = await get_current_user(request, credentials, db)
|
||||
|
||||
assert result == user
|
||||
assert request.state.user_id == "admin-123"
|
||||
assert request.state.management_token_id == "mt-123"
|
||||
mock_authenticate.assert_awaited_once_with(db, "ae_valid_token", "127.0.0.1")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_from_header_accepts_management_token(self) -> None:
|
||||
request = self._make_request()
|
||||
db = MagicMock()
|
||||
user = MagicMock()
|
||||
user.id = "admin-123"
|
||||
user.role = UserRole.ADMIN
|
||||
management_token = MagicMock()
|
||||
management_token.id = "mt-123"
|
||||
|
||||
with patch(
|
||||
"src.utils.auth_utils.AuthService.authenticate_management_token",
|
||||
new_callable=AsyncMock,
|
||||
return_value=(user, management_token),
|
||||
) as mock_authenticate:
|
||||
result = await get_current_user_from_header(
|
||||
request,
|
||||
authorization="Bearer ae_valid_token",
|
||||
db=db,
|
||||
)
|
||||
|
||||
assert result == user
|
||||
assert request.state.user_id == "admin-123"
|
||||
assert request.state.management_token_id == "mt-123"
|
||||
mock_authenticate.assert_awaited_once_with(db, "ae_valid_token", "127.0.0.1")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_invalid_management_token_raises_401(self) -> None:
|
||||
request = self._make_request()
|
||||
credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials="ae_invalid_token")
|
||||
db = MagicMock()
|
||||
|
||||
with patch(
|
||||
"src.utils.auth_utils.AuthService.authenticate_management_token",
|
||||
new_callable=AsyncMock,
|
||||
return_value=None,
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, credentials, db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "无效的Token"
|
||||
Reference in New Issue
Block a user