refactor: 修复9项中等架构问题
安全修复: - WebSocket 端点添加认证(cookie/token),防止未授权窃听日志 - SPA serve_spa 添加路径遍历防护(resolve + relative_to 检查) - Token 改用 httpOnly Cookie 存储,移除前端 localStorage token(防 XSS 窃取) - 添加安全响应头中间件(X-Content-Type-Options/X-Frame-Options/Referrer-Policy) - HTTP 请求日志脱敏请求体中的 password/secret/token 等敏感字段 - 权限检查统一使用 user_has_permission(考虑自定义权限,修复 has_permission 忽略 custom_permissions 的缺陷) 性能与稳定性: - cookies.py 列表接口修复 N+1 查询(改为批量查询 Account) - login_service.py run() 结束时关闭 DB Session(防止连接泄漏) - _active_batches/_active_tests 全局字典添加 threading.Lock(防止并发竞态) 配置优化: - CORS 源支持环境变量 CORS_ORIGINS 配置 - Uvicorn reload 支持环境变量 UVICORN_RELOAD 控制(生产环境默认关闭) - Cookie 安全标志支持环境变量 COOKIE_SECURE 配置(HTTPS 部署时启用) - logs.py 权限不足返回 HTTP 403(原来返回 200 + message)
This commit is contained in:
@@ -9,13 +9,14 @@ from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from ..models import User, ProxyConfig as ProxyConfigModel, AuditLog
|
||||
from ..schemas import ProxyConfigOut, ProxyConfigUpdate
|
||||
from ..deps import require_permission
|
||||
from ..deps import require_permission, authenticate_websocket
|
||||
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
|
||||
|
||||
router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"])
|
||||
|
||||
# 运行中的测试: test_id -> {log_queue, loop, result}
|
||||
_active_tests: dict[str, dict] = {}
|
||||
_active_tests_lock = threading.Lock()
|
||||
|
||||
|
||||
def _get_or_create(db: Session) -> ProxyConfigModel:
|
||||
@@ -63,10 +64,17 @@ def update_proxy_config(
|
||||
|
||||
@router.websocket("/ws/test/{test_id}")
|
||||
async def ws_test_logs(websocket: WebSocket, test_id: str):
|
||||
"""WebSocket 推送代理/白名单测试实时日志。"""
|
||||
"""WebSocket 推送代理/白名单测试实时日志(需认证)。"""
|
||||
# 认证:从 cookie 或 token query param 验证用户身份
|
||||
user = authenticate_websocket(websocket)
|
||||
if not user:
|
||||
await websocket.close(code=1008, reason="未授权")
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
test = _active_tests.get(test_id)
|
||||
with _active_tests_lock:
|
||||
test = _active_tests.get(test_id)
|
||||
if not test:
|
||||
await websocket.send_json({"level": "error", "message": "测试任务不存在"})
|
||||
await websocket.close()
|
||||
@@ -88,7 +96,8 @@ async def ws_test_logs(websocket: WebSocket, test_id: str):
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
finally:
|
||||
_active_tests.pop(test_id, None)
|
||||
with _active_tests_lock:
|
||||
_active_tests.pop(test_id, None)
|
||||
|
||||
|
||||
# ---- 异步测试执行 ----
|
||||
@@ -263,7 +272,8 @@ async def test_proxy(
|
||||
log_queue = asyncio.Queue()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
with _active_tests_lock:
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
|
||||
thread = threading.Thread(target=_run_proxy_test, args=(cfg, log_queue, loop), daemon=True)
|
||||
thread.start()
|
||||
@@ -283,7 +293,8 @@ async def test_whitelist(
|
||||
log_queue = asyncio.Queue()
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
with _active_tests_lock:
|
||||
_active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
||||
|
||||
thread = threading.Thread(target=_run_whitelist_test, args=(cfg, log_queue, loop), daemon=True)
|
||||
thread.start()
|
||||
|
||||
Reference in New Issue
Block a user