Harden auth, CSRF, and email log UX

This commit is contained in:
2025-12-26 19:05:20 +08:00
parent 3214cbbd91
commit f90b0a4f11
47 changed files with 583 additions and 198 deletions

View File

@@ -11,7 +11,13 @@ import database
import email_service
import requests
from app_logger import get_logger
from app_security import require_ip_not_locked, validate_password
from app_security import (
get_rate_limit_ip,
is_safe_outbound_url,
require_ip_not_locked,
validate_email,
validate_password,
)
from flask import current_app, jsonify, redirect, request, session, url_for
from routes.admin_api import admin_api_bp
from routes.decorators import admin_required
@@ -26,6 +32,10 @@ from services.state import (
safe_iter_task_status_items,
safe_remove_user_accounts,
safe_verify_and_consume_captcha,
check_ip_request_rate,
check_login_captcha_required,
clear_login_failures,
record_login_failure,
)
from services.tasks import get_task_scheduler, submit_account_task
from services.time_utils import BEIJING_TZ, get_beijing_now
@@ -62,7 +72,7 @@ def debug_config():
def admin_login():
"""管理员登录支持JSON和form-data两种格式"""
if request.is_json:
data = request.json
data = request.json or {}
else:
data = request.form
@@ -72,15 +82,29 @@ def admin_login():
captcha_code = data.get("captcha", "").strip()
need_captcha = data.get("need_captcha", False)
if need_captcha:
client_ip = get_rate_limit_ip()
allowed, error_msg = check_ip_request_rate(client_ip, "login")
if not allowed:
if request.is_json:
return jsonify({"error": error_msg, "need_captcha": True}), 429
return redirect(url_for("pages.admin_login_page"))
captcha_required = check_login_captcha_required(client_ip) or bool(need_captcha)
if captcha_required:
if not captcha_session or not captcha_code:
if request.is_json:
return jsonify({"error": "请填写验证码", "need_captcha": True}), 400
return redirect(url_for("pages.admin_login_page"))
success, message = safe_verify_and_consume_captcha(captcha_session, captcha_code)
if not success:
record_login_failure(client_ip)
if request.is_json:
return jsonify({"error": message}), 400
return jsonify({"error": message, "need_captcha": True}), 400
return redirect(url_for("pages.admin_login_page"))
admin = database.verify_admin(username, password)
if admin:
clear_login_failures(client_ip)
session.pop("admin_id", None)
session.pop("admin_username", None)
session["admin_id"] = admin["id"]
@@ -94,9 +118,10 @@ def admin_login():
return jsonify({"success": True, "redirect": "/yuyx/admin"})
return redirect(url_for("pages.admin_page"))
record_login_failure(client_ip)
logger.warning(f"[admin_login] 管理员 {username} 登录失败 - 用户名或密码错误")
if request.is_json:
return jsonify({"error": "管理员用户名或密码错误", "need_captcha": True}), 401
return jsonify({"error": "管理员用户名或密码错误", "need_captcha": check_login_captcha_required(client_ip)}), 401
return redirect(url_for("pages.admin_login_page"))
@@ -565,6 +590,9 @@ def test_proxy_api():
if not api_url:
return jsonify({"error": "请提供API地址"}), 400
if not is_safe_outbound_url(api_url):
return jsonify({"error": "API地址不可用或不安全"}), 400
try:
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
@@ -1071,10 +1099,9 @@ def test_smtp_config_api(config_id):
if not test_email:
return jsonify({"error": "请提供测试邮箱"}), 400
import re
if not re.match(r"^[^\s@]+@[^\s@]+\.[^\s@]+$", test_email):
return jsonify({"error": "邮箱格式不正确"}), 400
is_valid, error_msg = validate_email(test_email)
if not is_valid:
return jsonify({"error": error_msg}), 400
result = email_service.test_smtp_config(config_id, test_email)
return jsonify(result)