feat: Provider 异步删除、可配置密码策略、Hub 超时优化及多项改进

- 新增 Provider 异步删除任务系统,后台分阶段删除子资源并清理残留引用
- 新增可配置密码策略等级(weak/medium/strong),支持系统设置面板调整
- aether-hub 升级至 0.1.4,idle timeout 支持禁用(设为 0),worker 默认超时调整为 120s
- OAuth 手动续期增加 Redis 分布式锁,防止并发刷新冲突
- ProxyNode 心跳检测改为 asyncio.to_thread,避免阻塞事件循环
- 删除 ModelMultiSelect 和 useInvalidModels,MultiSelect 组件通用化
- 明确 allowed_providers/allowed_api_formats 的 NULL 与空数组语义
- 前端 StandaloneKeyFormDialog、UserFormDialog 等多处 UI 优化
- 新增 Alembic 迁移脚本清理 Provider 删除后的残留引用
- 补充相关测试用例
This commit is contained in:
fawney19
2026-03-12 01:11:35 +08:00
parent 0d770d1c4d
commit 6e51a3f45d
55 changed files with 3219 additions and 862 deletions

View File

@@ -3,29 +3,48 @@
包含密码复杂度验证和其他输入验证
"""
from __future__ import annotations
import re
from enum import Enum
class PasswordPolicyLevel(str, Enum):
"""密码策略级别。"""
WEAK = "weak"
MEDIUM = "medium"
STRONG = "strong"
class PasswordValidator:
"""密码复杂度验证器"""
MIN_LENGTH = 6 # 降低到6位
MIN_LENGTH = 6
MEDIUM_MIN_LENGTH = 8
STRONG_MIN_LENGTH = 8
MAX_LENGTH = 128
@classmethod
def validate(cls, password: str) -> tuple[bool, str | None]:
"""
验证密码复杂度
def normalize_policy(cls, policy: PasswordPolicyLevel | str | None) -> PasswordPolicyLevel:
"""规范化密码策略级别,异常值回退为弱策略。"""
if isinstance(policy, PasswordPolicyLevel):
return policy
if isinstance(policy, str):
normalized = policy.strip().lower()
try:
return PasswordPolicyLevel(normalized)
except ValueError:
pass
return PasswordPolicyLevel.WEAK
要求:
- 长度至少6个字符
Args:
password: 待验证的密码
Returns:
(是否通过, 错误消息)
"""
@classmethod
def validate(
cls,
password: str,
policy: PasswordPolicyLevel | str | None = None,
) -> tuple[bool, str | None]:
"""验证密码复杂度。"""
if not password:
return False, "密码不能为空"
@@ -35,22 +54,26 @@ class PasswordValidator:
if len(password) > cls.MAX_LENGTH:
return False, f"密码长度不能超过{cls.MAX_LENGTH}个字符"
# 简化密码复杂度要求 - 只检查长度
# 不再要求大小写字母、数字和特殊字符
policy_level = cls.normalize_policy(policy)
# 检查常见弱密码
weak_passwords = [
"password123",
"admin123",
"12345678",
"qwerty123",
"password@123",
"admin@123",
"Password123!",
"Admin123!",
]
if password.lower() in [p.lower() for p in weak_passwords]:
return False, "密码过于简单,请使用更复杂的密码"
if policy_level == PasswordPolicyLevel.MEDIUM:
if len(password) < cls.MEDIUM_MIN_LENGTH:
return False, f"密码长度至少为{cls.MEDIUM_MIN_LENGTH}个字符"
if not re.search(r"[A-Za-z]", password):
return False, "密码必须包含至少一个字母"
if not re.search(r"\d", password):
return False, "密码必须包含至少一个数字"
elif policy_level == PasswordPolicyLevel.STRONG:
if len(password) < cls.STRONG_MIN_LENGTH:
return False, f"密码长度至少为{cls.STRONG_MIN_LENGTH}个字符"
if not re.search(r"[A-Z]", password):
return False, "密码必须包含至少一个大写字母"
if not re.search(r"[a-z]", password):
return False, "密码必须包含至少一个小写字母"
if not re.search(r"\d", password):
return False, "密码必须包含至少一个数字"
if not re.search(r"[!@#$%^&*()_+\-=\[\]{};:'\",.<>?/\\|`~]", password):
return False, "密码必须包含至少一个特殊字符"
return True, None
@@ -85,7 +108,7 @@ class PasswordValidator:
score += 1
if re.search(r"\d", password):
score += 1
if re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
if re.search(r"[!@#$%^&*()_+\-=\[\]{};:\'\",.<>?/\\|`~]", password):
score += 2
# 额外复杂度评分