feat: 添加安全模块 + Dockerfile添加curl支持健康检查
主要更新: - 新增 security/ 安全模块 (风险评估、威胁检测、蜜罐等) - Dockerfile 添加 curl 以支持 Docker 健康检查 - 前端页面更新 (管理后台、用户端) - 数据库迁移和 schema 更新 - 新增 kdocs 上传服务 - 添加安全相关测试用例 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
173
db/admin.py
173
db/admin.py
@@ -172,6 +172,17 @@ def get_system_config_raw() -> dict:
|
||||
"auto_approve_enabled": 0,
|
||||
"auto_approve_hourly_limit": 10,
|
||||
"auto_approve_vip_days": 7,
|
||||
"kdocs_enabled": 0,
|
||||
"kdocs_doc_url": "",
|
||||
"kdocs_default_unit": "",
|
||||
"kdocs_sheet_name": "",
|
||||
"kdocs_sheet_index": 0,
|
||||
"kdocs_unit_column": "A",
|
||||
"kdocs_image_column": "D",
|
||||
"kdocs_admin_notify_enabled": 0,
|
||||
"kdocs_admin_notify_email": "",
|
||||
"kdocs_row_start": 0,
|
||||
"kdocs_row_end": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -184,12 +195,24 @@ def update_system_config(
|
||||
schedule_weekdays=None,
|
||||
max_concurrent_per_account=None,
|
||||
max_screenshot_concurrent=None,
|
||||
enable_screenshot=None,
|
||||
proxy_enabled=None,
|
||||
proxy_api_url=None,
|
||||
proxy_expire_minutes=None,
|
||||
auto_approve_enabled=None,
|
||||
auto_approve_hourly_limit=None,
|
||||
auto_approve_vip_days=None,
|
||||
kdocs_enabled=None,
|
||||
kdocs_doc_url=None,
|
||||
kdocs_default_unit=None,
|
||||
kdocs_sheet_name=None,
|
||||
kdocs_sheet_index=None,
|
||||
kdocs_unit_column=None,
|
||||
kdocs_image_column=None,
|
||||
kdocs_admin_notify_enabled=None,
|
||||
kdocs_admin_notify_email=None,
|
||||
kdocs_row_start=None,
|
||||
kdocs_row_end=None,
|
||||
) -> bool:
|
||||
"""更新系统配置(仅更新DB,不做缓存处理)。"""
|
||||
allowed_fields = {
|
||||
@@ -200,12 +223,24 @@ def update_system_config(
|
||||
"schedule_weekdays",
|
||||
"max_concurrent_per_account",
|
||||
"max_screenshot_concurrent",
|
||||
"enable_screenshot",
|
||||
"proxy_enabled",
|
||||
"proxy_api_url",
|
||||
"proxy_expire_minutes",
|
||||
"auto_approve_enabled",
|
||||
"auto_approve_hourly_limit",
|
||||
"auto_approve_vip_days",
|
||||
"kdocs_enabled",
|
||||
"kdocs_doc_url",
|
||||
"kdocs_default_unit",
|
||||
"kdocs_sheet_name",
|
||||
"kdocs_sheet_index",
|
||||
"kdocs_unit_column",
|
||||
"kdocs_image_column",
|
||||
"kdocs_admin_notify_enabled",
|
||||
"kdocs_admin_notify_email",
|
||||
"kdocs_row_start",
|
||||
"kdocs_row_end",
|
||||
"updated_at",
|
||||
}
|
||||
|
||||
@@ -232,6 +267,9 @@ def update_system_config(
|
||||
if max_screenshot_concurrent is not None:
|
||||
updates.append("max_screenshot_concurrent = ?")
|
||||
params.append(max_screenshot_concurrent)
|
||||
if enable_screenshot is not None:
|
||||
updates.append("enable_screenshot = ?")
|
||||
params.append(enable_screenshot)
|
||||
if schedule_weekdays is not None:
|
||||
updates.append("schedule_weekdays = ?")
|
||||
params.append(schedule_weekdays)
|
||||
@@ -253,6 +291,39 @@ def update_system_config(
|
||||
if auto_approve_vip_days is not None:
|
||||
updates.append("auto_approve_vip_days = ?")
|
||||
params.append(auto_approve_vip_days)
|
||||
if kdocs_enabled is not None:
|
||||
updates.append("kdocs_enabled = ?")
|
||||
params.append(kdocs_enabled)
|
||||
if kdocs_doc_url is not None:
|
||||
updates.append("kdocs_doc_url = ?")
|
||||
params.append(kdocs_doc_url)
|
||||
if kdocs_default_unit is not None:
|
||||
updates.append("kdocs_default_unit = ?")
|
||||
params.append(kdocs_default_unit)
|
||||
if kdocs_sheet_name is not None:
|
||||
updates.append("kdocs_sheet_name = ?")
|
||||
params.append(kdocs_sheet_name)
|
||||
if kdocs_sheet_index is not None:
|
||||
updates.append("kdocs_sheet_index = ?")
|
||||
params.append(kdocs_sheet_index)
|
||||
if kdocs_unit_column is not None:
|
||||
updates.append("kdocs_unit_column = ?")
|
||||
params.append(kdocs_unit_column)
|
||||
if kdocs_image_column is not None:
|
||||
updates.append("kdocs_image_column = ?")
|
||||
params.append(kdocs_image_column)
|
||||
if kdocs_admin_notify_enabled is not None:
|
||||
updates.append("kdocs_admin_notify_enabled = ?")
|
||||
params.append(kdocs_admin_notify_enabled)
|
||||
if kdocs_admin_notify_email is not None:
|
||||
updates.append("kdocs_admin_notify_email = ?")
|
||||
params.append(kdocs_admin_notify_email)
|
||||
if kdocs_row_start is not None:
|
||||
updates.append("kdocs_row_start = ?")
|
||||
params.append(kdocs_row_start)
|
||||
if kdocs_row_end is not None:
|
||||
updates.append("kdocs_row_end = ?")
|
||||
params.append(kdocs_row_end)
|
||||
|
||||
if not updates:
|
||||
return False
|
||||
@@ -287,108 +358,6 @@ def get_hourly_registration_count() -> int:
|
||||
# ==================== 密码重置(管理员) ====================
|
||||
|
||||
|
||||
def create_password_reset_request(user_id: int, new_password: str):
|
||||
"""创建密码重置申请(存储哈希)"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
password_hash = hash_password_bcrypt(new_password)
|
||||
cst_time = get_cst_now_str()
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO password_reset_requests (user_id, new_password_hash, status, created_at)
|
||||
VALUES (?, ?, 'pending', ?)
|
||||
""",
|
||||
(user_id, password_hash, cst_time),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
except Exception as e:
|
||||
print(f"创建密码重置申请失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_pending_password_resets():
|
||||
"""获取待审核的密码重置申请列表"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT r.id, r.user_id, r.created_at, r.status,
|
||||
u.username, u.email
|
||||
FROM password_reset_requests r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
WHERE r.status = 'pending'
|
||||
ORDER BY r.created_at DESC
|
||||
"""
|
||||
)
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
|
||||
def approve_password_reset(request_id: int) -> bool:
|
||||
"""批准密码重置申请"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cst_time = get_cst_now_str()
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT user_id, new_password_hash
|
||||
FROM password_reset_requests
|
||||
WHERE id = ? AND status = 'pending'
|
||||
""",
|
||||
(request_id,),
|
||||
)
|
||||
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
return False
|
||||
|
||||
user_id = result["user_id"]
|
||||
new_password_hash = result["new_password_hash"]
|
||||
|
||||
cursor.execute("UPDATE users SET password_hash = ? WHERE id = ?", (new_password_hash, user_id))
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE password_reset_requests
|
||||
SET status = 'approved', processed_at = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(cst_time, request_id),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"批准密码重置失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def reject_password_reset(request_id: int) -> bool:
|
||||
"""拒绝密码重置申请"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cst_time = get_cst_now_str()
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE password_reset_requests
|
||||
SET status = 'rejected', processed_at = ?
|
||||
WHERE id = ? AND status = 'pending'
|
||||
""",
|
||||
(cst_time, request_id),
|
||||
)
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
print(f"拒绝密码重置失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def admin_reset_user_password(user_id: int, new_password: str) -> bool:
|
||||
"""管理员直接重置用户密码"""
|
||||
with db_pool.get_db() as conn:
|
||||
|
||||
Reference in New Issue
Block a user