fix: admin auth UX, password policy, deps, db pool
This commit is contained in:
@@ -8,7 +8,25 @@ import queue
|
||||
import time
|
||||
from typing import Callable, Optional, Dict, Any
|
||||
import nest_asyncio
|
||||
nest_asyncio.apply()
|
||||
|
||||
_NEST_ASYNCIO_APPLIED = False
|
||||
_NEST_ASYNCIO_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def _apply_nest_asyncio_once() -> None:
|
||||
"""按需应用 nest_asyncio,避免 import 时产生全局副作用。"""
|
||||
global _NEST_ASYNCIO_APPLIED
|
||||
|
||||
if _NEST_ASYNCIO_APPLIED:
|
||||
return
|
||||
with _NEST_ASYNCIO_LOCK:
|
||||
if _NEST_ASYNCIO_APPLIED:
|
||||
return
|
||||
try:
|
||||
nest_asyncio.apply()
|
||||
except Exception:
|
||||
pass
|
||||
_NEST_ASYNCIO_APPLIED = True
|
||||
|
||||
# 安全修复: 将魔法数字提取为可配置常量
|
||||
BROWSER_IDLE_TIMEOUT = int(os.environ.get('BROWSER_IDLE_TIMEOUT', '300')) # 空闲超时(秒),默认5分钟
|
||||
@@ -210,6 +228,8 @@ class BrowserWorkerPool:
|
||||
if self.initialized:
|
||||
return
|
||||
|
||||
_apply_nest_asyncio_once()
|
||||
|
||||
self.log(f"正在初始化工作线程池({self.pool_size}个worker,按需启动浏览器)...")
|
||||
|
||||
for i in range(self.pool_size):
|
||||
|
||||
@@ -111,6 +111,7 @@ class ConnectionPool:
|
||||
if self._pool.qsize() < self.pool_size:
|
||||
try:
|
||||
new_conn = self._create_connection()
|
||||
self._created_connections += 1
|
||||
self._pool.put(new_conn, block=False)
|
||||
except Full:
|
||||
# 在获取锁期间池被填满了,关闭新建的连接
|
||||
@@ -170,6 +171,7 @@ class PooledConnection:
|
||||
|
||||
if self._cursor:
|
||||
self._cursor.close()
|
||||
self._cursor = None
|
||||
except Exception as e:
|
||||
print(f"关闭游标失败: {e}")
|
||||
finally:
|
||||
@@ -180,7 +182,8 @@ class PooledConnection:
|
||||
|
||||
def cursor(self):
|
||||
"""获取游标"""
|
||||
self._cursor = self._conn.cursor()
|
||||
if self._cursor is None:
|
||||
self._cursor = self._conn.cursor()
|
||||
return self._cursor
|
||||
|
||||
def commit(self):
|
||||
|
||||
@@ -3,7 +3,6 @@ flask-socketio==5.3.5
|
||||
flask-login==0.6.3
|
||||
python-socketio==5.10.0
|
||||
playwright==1.40.0
|
||||
eventlet==0.33.3
|
||||
schedule==1.2.0
|
||||
psutil==5.9.6
|
||||
pytz==2024.1
|
||||
|
||||
@@ -43,6 +43,10 @@ def register():
|
||||
if not username or not password:
|
||||
return jsonify({"error": "用户名和密码不能为空"}), 400
|
||||
|
||||
is_valid, error_msg = validate_password(password)
|
||||
if not is_valid:
|
||||
return jsonify({"error": error_msg}), 400
|
||||
|
||||
client_ip = get_client_ip()
|
||||
|
||||
allowed, error_msg = check_ip_rate_limit(client_ip)
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import database
|
||||
import email_service
|
||||
from app_logger import get_logger
|
||||
from app_security import require_ip_not_locked, validate_email
|
||||
from app_security import require_ip_not_locked, validate_email, validate_password
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_login import current_user, login_required
|
||||
from routes.pages import render_app_spa_or_legacy
|
||||
@@ -119,8 +119,9 @@ def change_user_password():
|
||||
if not current_password or not new_password:
|
||||
return jsonify({"error": "请填写完整信息"}), 400
|
||||
|
||||
if len(new_password) < 6:
|
||||
return jsonify({"error": "新密码至少6位"}), 400
|
||||
is_valid, error_msg = validate_password(new_password)
|
||||
if not is_valid:
|
||||
return jsonify({"error": error_msg}), 400
|
||||
|
||||
user = database.get_user_by_id(current_user.id)
|
||||
if not user:
|
||||
@@ -290,4 +291,3 @@ def get_run_stats():
|
||||
"today_attachments": stats.get("total_attachments", 0),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import jsonify, request, session
|
||||
from flask import jsonify, redirect, request, session, url_for
|
||||
|
||||
from services.runtime import get_logger
|
||||
|
||||
@@ -18,9 +18,11 @@ def admin_required(f):
|
||||
logger.debug(f"[admin_required] 检查会话,admin_id存在: {'admin_id' in session}")
|
||||
if "admin_id" not in session:
|
||||
logger.warning(f"[admin_required] 拒绝访问 {request.path} - session中无admin_id")
|
||||
return jsonify({"error": "需要管理员权限"}), 403
|
||||
is_api = request.blueprint == "admin_api" or request.path.startswith("/yuyx/api")
|
||||
if is_api:
|
||||
return jsonify({"error": "需要管理员权限"}), 403
|
||||
return redirect(url_for("pages.admin_login_page"))
|
||||
logger.info(f"[admin_required] 管理员 {session.get('admin_username')} 访问 {request.path}")
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
Reference in New Issue
Block a user