Spaces:
Running
Running
Upload 8 files
Browse files- router_posts.py +3 -3
- router_prompts.py +3 -3
- router_tasks.py +4 -4
- router_users_auth.py +17 -4
- workflow_download_service.py +2 -2
- 安全认证.py +11 -5
router_posts.py
CHANGED
|
@@ -515,14 +515,14 @@ async def tip_post(request: Request, post_id: str, amount: int, is_anon: bool =
|
|
| 515 |
db_session.commit()
|
| 516 |
|
| 517 |
# 📝 审计日志
|
| 518 |
-
logger.info(
|
| 519 |
|
| 520 |
except HTTPException:
|
| 521 |
db_session.rollback()
|
| 522 |
raise
|
| 523 |
except Exception as e:
|
| 524 |
db_session.rollback()
|
| 525 |
-
logger.error(
|
| 526 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 527 |
|
| 528 |
def notify_tip():
|
|
@@ -564,7 +564,7 @@ async def tip_post(request: Request, post_id: str, amount: int, is_anon: bool =
|
|
| 564 |
try:
|
| 565 |
db.atomic_update("posts.json", board_updater, default_data=[])
|
| 566 |
except Exception as e:
|
| 567 |
-
logger.error(
|
| 568 |
|
| 569 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 570 |
|
|
|
|
| 515 |
db_session.commit()
|
| 516 |
|
| 517 |
# 📝 审计日志
|
| 518 |
+
logger.info("POST_TIP")
|
| 519 |
|
| 520 |
except HTTPException:
|
| 521 |
db_session.rollback()
|
| 522 |
raise
|
| 523 |
except Exception as e:
|
| 524 |
db_session.rollback()
|
| 525 |
+
logger.error("POST_TIP_ERROR | error_type=%s", type(e).__name__)
|
| 526 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 527 |
|
| 528 |
def notify_tip():
|
|
|
|
| 564 |
try:
|
| 565 |
db.atomic_update("posts.json", board_updater, default_data=[])
|
| 566 |
except Exception as e:
|
| 567 |
+
logger.error("POST_TIP_BOARD_ERROR | error_type=%s", type(e).__name__)
|
| 568 |
|
| 569 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 570 |
|
router_prompts.py
CHANGED
|
@@ -744,14 +744,14 @@ async def tip_prompt(request: Request, prompt_id: str, amount: int, is_anon: boo
|
|
| 744 |
db_session.commit()
|
| 745 |
|
| 746 |
# 📝 审计日志
|
| 747 |
-
logger.info(
|
| 748 |
|
| 749 |
except HTTPException:
|
| 750 |
db_session.rollback()
|
| 751 |
raise
|
| 752 |
except Exception as e:
|
| 753 |
db_session.rollback()
|
| 754 |
-
logger.error(
|
| 755 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 756 |
|
| 757 |
def notify_tip():
|
|
@@ -793,7 +793,7 @@ async def tip_prompt(request: Request, prompt_id: str, amount: int, is_anon: boo
|
|
| 793 |
try:
|
| 794 |
db.atomic_update("prompts.json", board_updater, default_data=[])
|
| 795 |
except Exception as e:
|
| 796 |
-
logger.error(
|
| 797 |
|
| 798 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 799 |
|
|
|
|
| 744 |
db_session.commit()
|
| 745 |
|
| 746 |
# 📝 审计日志
|
| 747 |
+
logger.info("PROMPT_TIP")
|
| 748 |
|
| 749 |
except HTTPException:
|
| 750 |
db_session.rollback()
|
| 751 |
raise
|
| 752 |
except Exception as e:
|
| 753 |
db_session.rollback()
|
| 754 |
+
logger.error("PROMPT_TIP_ERROR | error_type=%s", type(e).__name__)
|
| 755 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 756 |
|
| 757 |
def notify_tip():
|
|
|
|
| 793 |
try:
|
| 794 |
db.atomic_update("prompts.json", board_updater, default_data=[])
|
| 795 |
except Exception as e:
|
| 796 |
+
logger.error("PROMPT_TIP_BOARD_ERROR | error_type=%s", type(e).__name__)
|
| 797 |
|
| 798 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 799 |
|
router_tasks.py
CHANGED
|
@@ -93,7 +93,7 @@ def create_task_transaction(db_session: Session, account: str, tx_type: str, amo
|
|
| 93 |
)
|
| 94 |
db_session.add(new_tx)
|
| 95 |
|
| 96 |
-
logger.info(
|
| 97 |
return tx_id
|
| 98 |
|
| 99 |
# ==========================================
|
|
@@ -1029,14 +1029,14 @@ async def tip_task(request: Request, task_id: str, amount: int, is_anon: bool =
|
|
| 1029 |
db_session.commit()
|
| 1030 |
|
| 1031 |
# 📝 审计日志
|
| 1032 |
-
logger.info(
|
| 1033 |
|
| 1034 |
except HTTPException:
|
| 1035 |
db_session.rollback()
|
| 1036 |
raise
|
| 1037 |
except Exception as e:
|
| 1038 |
db_session.rollback()
|
| 1039 |
-
logger.error(
|
| 1040 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 1041 |
|
| 1042 |
def notify_tip():
|
|
@@ -1078,7 +1078,7 @@ async def tip_task(request: Request, task_id: str, amount: int, is_anon: bool =
|
|
| 1078 |
try:
|
| 1079 |
db.atomic_update("tasks.json", board_updater, default_data=[])
|
| 1080 |
except Exception as e:
|
| 1081 |
-
logger.error(
|
| 1082 |
|
| 1083 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 1084 |
|
|
|
|
| 93 |
)
|
| 94 |
db_session.add(new_tx)
|
| 95 |
|
| 96 |
+
logger.info("TASK_TX")
|
| 97 |
return tx_id
|
| 98 |
|
| 99 |
# ==========================================
|
|
|
|
| 1029 |
db_session.commit()
|
| 1030 |
|
| 1031 |
# 📝 审计日志
|
| 1032 |
+
logger.info("TASK_TIP")
|
| 1033 |
|
| 1034 |
except HTTPException:
|
| 1035 |
db_session.rollback()
|
| 1036 |
raise
|
| 1037 |
except Exception as e:
|
| 1038 |
db_session.rollback()
|
| 1039 |
+
logger.error("TASK_TIP_ERROR | error_type=%s", type(e).__name__)
|
| 1040 |
raise HTTPException(status_code=500, detail="打赏处理失败,请稍后重试")
|
| 1041 |
|
| 1042 |
def notify_tip():
|
|
|
|
| 1078 |
try:
|
| 1079 |
db.atomic_update("tasks.json", board_updater, default_data=[])
|
| 1080 |
except Exception as e:
|
| 1081 |
+
logger.error("TASK_TIP_BOARD_ERROR | error_type=%s", type(e).__name__)
|
| 1082 |
|
| 1083 |
return {"status": "success", "message": f"成功打赏 {amount} 积分"}
|
| 1084 |
|
router_users_auth.py
CHANGED
|
@@ -23,7 +23,7 @@ from models import UserRegister, UserLogin, SendCodeRequest
|
|
| 23 |
from verify_code_engine import send_email_code, send_sms_code, cleanup_expired_codes, check_send_cooldown
|
| 24 |
|
| 25 |
# 🔒 P0安全增强:导入密码哈希和 JWT 工具
|
| 26 |
-
from 安全认证 import hash_password, verify_password, create_token, require_password_match
|
| 27 |
|
| 28 |
from user_store import read_user, check_registration, create_user, finish_login, reset_user_password, get_password_version
|
| 29 |
|
|
@@ -52,7 +52,20 @@ def _find_contact_account(contact, contact_type):
|
|
| 52 |
raise HTTPException(status_code=503, detail="用户数据暂不可用") from None
|
| 53 |
|
| 54 |
|
| 55 |
-
async def _check_code_contact(req):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
if req.action_type == "recover":
|
| 57 |
account, _ = await asyncio.to_thread(_find_contact_account, req.contact, req.contact_type)
|
| 58 |
return account is not None
|
|
@@ -83,7 +96,7 @@ async def send_verify_code(request: Request, req: SendCodeRequest, bg_tasks: Bac
|
|
| 83 |
- action_type: "register" 注册 / "reset" 重置密码 / "recover" 找回账号
|
| 84 |
- account: (仅重置密码时必填) 用户账号
|
| 85 |
"""
|
| 86 |
-
if not await _check_code_contact(req):
|
| 87 |
return {"status": "success", "message": "如果该联系方式已绑定账号,验证码已发送"}
|
| 88 |
|
| 89 |
# 🔒 检查发送频率限制(同一联系方式60秒内只能发送1条)
|
|
@@ -121,7 +134,7 @@ async def send_verify_code(request: Request, req: SendCodeRequest, bg_tasks: Bac
|
|
| 121 |
@shared_limit(5) # 🔒 安全限流:与异步版对齐,每分钟最多5次
|
| 122 |
async def send_code_api(request: Request, req: SendCodeRequest):
|
| 123 |
"""发送验证码接口(同步版本,直接等待发送结果)"""
|
| 124 |
-
if not await _check_code_contact(req):
|
| 125 |
return {"status": "success", "message": "如果该联系方式已绑定账号,验证码已发送"}
|
| 126 |
|
| 127 |
# 🔒 检查发送频率限制(同一联系方式60秒内只能发送1条)
|
|
|
|
| 23 |
from verify_code_engine import send_email_code, send_sms_code, cleanup_expired_codes, check_send_cooldown
|
| 24 |
|
| 25 |
# 🔒 P0安全增强:导入密码哈希和 JWT 工具
|
| 26 |
+
from 安全认证 import hash_password, verify_password, create_token, require_password_match, require_auth
|
| 27 |
|
| 28 |
from user_store import read_user, check_registration, create_user, finish_login, reset_user_password, get_password_version
|
| 29 |
|
|
|
|
| 52 |
raise HTTPException(status_code=503, detail="用户数据暂不可用") from None
|
| 53 |
|
| 54 |
|
| 55 |
+
async def _check_code_contact(req, request=None):
|
| 56 |
+
if req.action_type == "withdraw":
|
| 57 |
+
if request is None:
|
| 58 |
+
raise HTTPException(status_code=401, detail="请先登录")
|
| 59 |
+
account = await require_auth(request.headers.get("Authorization"))
|
| 60 |
+
if req.account is not None and req.account != account:
|
| 61 |
+
raise HTTPException(status_code=403, detail="无权为其他账号请求提现验证码")
|
| 62 |
+
user = await asyncio.to_thread(read_user, account)
|
| 63 |
+
contact = user.get("email") or user.get("phone", "")
|
| 64 |
+
contact_type = "email" if user.get("email") else "phone"
|
| 65 |
+
if (not isinstance(contact, str) or not contact.strip() or
|
| 66 |
+
req.contact_type != contact_type or req.contact != contact):
|
| 67 |
+
raise HTTPException(status_code=400, detail="联系方式与当前账号绑定信息不匹配")
|
| 68 |
+
return True
|
| 69 |
if req.action_type == "recover":
|
| 70 |
account, _ = await asyncio.to_thread(_find_contact_account, req.contact, req.contact_type)
|
| 71 |
return account is not None
|
|
|
|
| 96 |
- action_type: "register" 注册 / "reset" 重置密码 / "recover" 找回账号
|
| 97 |
- account: (仅重置密码时必填) 用户账号
|
| 98 |
"""
|
| 99 |
+
if not await _check_code_contact(req, request):
|
| 100 |
return {"status": "success", "message": "如果该联系方式已绑定账号,验证码已发送"}
|
| 101 |
|
| 102 |
# 🔒 检查发送频率限制(同一联系方式60秒内只能发送1条)
|
|
|
|
| 134 |
@shared_limit(5) # 🔒 安全限流:与异步版对齐,每分钟最多5次
|
| 135 |
async def send_code_api(request: Request, req: SendCodeRequest):
|
| 136 |
"""发送验证码接口(同步版本,直接等待发送结果)"""
|
| 137 |
+
if not await _check_code_contact(req, request):
|
| 138 |
return {"status": "success", "message": "如果该联系方式已绑定账号,验证码已发送"}
|
| 139 |
|
| 140 |
# 🔒 检查发送频率限制(同一联系方式60秒内只能发送1条)
|
workflow_download_service.py
CHANGED
|
@@ -40,7 +40,7 @@ async def prepare_workflow_download(item, item_id, *, store, source_authorizer,
|
|
| 40 |
close_owned_resources = resources.close
|
| 41 |
|
| 42 |
try:
|
| 43 |
-
print(
|
| 44 |
for redirect_count in range(6):
|
| 45 |
host = await validate_target(target_url)
|
| 46 |
headers = {"User-Agent": "ComfyUI-Ranking-SaaS", "Accept-Encoding": "identity"}
|
|
@@ -107,5 +107,5 @@ async def prepare_workflow_download(item, item_id, *, store, source_authorizer,
|
|
| 107 |
return error_response(content={"error": "下载超时,请稍后重试"}, status_code=504)
|
| 108 |
except Exception as e:
|
| 109 |
await close_owned_resources()
|
| 110 |
-
print(f"
|
| 111 |
return error_response(content={"error": "代理下载失败,请检查资源链接后重试"}, status_code=502)
|
|
|
|
| 40 |
close_owned_resources = resources.close
|
| 41 |
|
| 42 |
try:
|
| 43 |
+
print("WORKFLOW_DOWNLOAD_STARTED")
|
| 44 |
for redirect_count in range(6):
|
| 45 |
host = await validate_target(target_url)
|
| 46 |
headers = {"User-Agent": "ComfyUI-Ranking-SaaS", "Accept-Encoding": "identity"}
|
|
|
|
| 107 |
return error_response(content={"error": "下载超时,请稍后重试"}, status_code=504)
|
| 108 |
except Exception as e:
|
| 109 |
await close_owned_resources()
|
| 110 |
+
print(f"WORKFLOW_DOWNLOAD_FAILED | error_type={type(e).__name__}")
|
| 111 |
return error_response(content={"error": "代理下载失败,请检查资源链接后重试"}, status_code=502)
|
安全认证.py
CHANGED
|
@@ -34,15 +34,21 @@ import bcrypt
|
|
| 34 |
_DEFAULT_JWT_SECRET = "ComfyUI-Ranking-JWT-Secret-v2-beta-2026"
|
| 35 |
_DEFAULT_PASSWORD_SALT = "ComfyUI-Ranking-Salt-v1"
|
| 36 |
|
| 37 |
-
|
|
|
|
| 38 |
PASSWORD_SALT = os.environ.get("PASSWORD_SALT") or _DEFAULT_PASSWORD_SALT
|
| 39 |
|
| 40 |
# 🔒 生产环境必须使用部署密钥:公开默认值只允许本地开发使用。
|
| 41 |
# HF Spaces 或显式生产部署缺少密钥时拒绝启动,避免继续签发可伪造 Token。
|
| 42 |
-
_IS_PRODUCTION = bool(os.environ.get("SPACE_ID")) or os.environ.get("APP_ENV", "").lower() == "production"
|
| 43 |
-
if _IS_PRODUCTION and
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
print("⚠️ [安全提示] 未设置 JWT_SECRET,当前使用仅供本地开发的默认密钥")
|
| 47 |
|
| 48 |
# Token 有效期配置(单位:秒)
|
|
|
|
| 34 |
_DEFAULT_JWT_SECRET = "ComfyUI-Ranking-JWT-Secret-v2-beta-2026"
|
| 35 |
_DEFAULT_PASSWORD_SALT = "ComfyUI-Ranking-Salt-v1"
|
| 36 |
|
| 37 |
+
_CONFIGURED_JWT_SECRET = os.environ.get("JWT_SECRET")
|
| 38 |
+
JWT_SECRET = _CONFIGURED_JWT_SECRET or _DEFAULT_JWT_SECRET
|
| 39 |
PASSWORD_SALT = os.environ.get("PASSWORD_SALT") or _DEFAULT_PASSWORD_SALT
|
| 40 |
|
| 41 |
# 🔒 生产环境必须使用部署密钥:公开默认值只允许本地开发使用。
|
| 42 |
# HF Spaces 或显式生产部署缺少密钥时拒绝启动,避免继续签发可伪造 Token。
|
| 43 |
+
_IS_PRODUCTION = bool(os.environ.get("SPACE_ID")) or os.environ.get("APP_ENV", "").strip().lower() == "production"
|
| 44 |
+
if _IS_PRODUCTION and (
|
| 45 |
+
not _CONFIGURED_JWT_SECRET
|
| 46 |
+
or not _CONFIGURED_JWT_SECRET.strip()
|
| 47 |
+
or _CONFIGURED_JWT_SECRET.strip() == _DEFAULT_JWT_SECRET
|
| 48 |
+
or len(_CONFIGURED_JWT_SECRET.encode("utf-8")) < 32
|
| 49 |
+
):
|
| 50 |
+
raise RuntimeError("生产环境 JWT_SECRET 无效:必须使用至少32字节的独立随机密钥,禁止公开开发密钥或空白值")
|
| 51 |
+
if not _CONFIGURED_JWT_SECRET:
|
| 52 |
print("⚠️ [安全提示] 未设置 JWT_SECRET,当前使用仅供本地开发的默认密钥")
|
| 53 |
|
| 54 |
# Token 有效期配置(单位:秒)
|