复查修复(四): 独立审查发现的问题
- S1/S2: 会话锁改 RLock(持锁可重入调 _save_sessions);cookie 时间戳改整数+解析兼容(会话创建/验证往返已实测) - M1: api_login 接入 verify_user(账号锁定/失败计数生效),DB 无用户时才回退 config 凭据 - M3+L5: handler _do_auth 统一入口加 IP 白名单检查;未知 auth_type 返回 401 - M4+L10: metrics 与无版本 /api/admin/ 加入受保护端点 - M6: debug 日志敏感头脱敏;main.py 不再打印 token 前缀 - M7: auth_token.txt / auth_sessions.json chmod 600 - M9: verify_user 统一错误消息防用户枚举 - M10: AdminAPI 复用共享 APIAuthManager(修复会话状态分裂) - L7: check_auth 大小写不敏感匹配(防 /API/.. 大写绕过) - L8: token_expires_at 显式 is not None 判断 - L11: verify_password 对非 bcrypt 哈希回退 PBKDF2(重写,修复 ValueError 分支不落回退的问题)
This commit is contained in:
+707
-698
File diff suppressed because it is too large
Load Diff
@@ -20,23 +20,6 @@ def get_resource_path(relative_path: str) -> str:
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), relative_path)
|
||||
|
||||
# 打包模式
|
||||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||
# 外部目录:与 exe 同级
|
||||
external_path = os.path.join(os.path.dirname(sys.executable), relative_path)
|
||||
if os.path.exists(external_path):
|
||||
return external_path
|
||||
|
||||
# 打包后的资源路径(_MEIPASS)
|
||||
bundled_path = os.path.join(sys._MEIPASS, relative_path)
|
||||
if os.path.exists(bundled_path):
|
||||
return bundled_path
|
||||
|
||||
return external_path
|
||||
|
||||
# 开发模式
|
||||
return os.path.join(project_root, relative_path)
|
||||
|
||||
|
||||
def deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
+26
-18
@@ -1258,7 +1258,8 @@ class DatabaseManager:
|
||||
user = session.query(UserRecord).filter_by(username=username).first()
|
||||
|
||||
if not user:
|
||||
return {'valid': False, 'reason': '用户不存在'}
|
||||
# 统一错误消息,防止用户枚举
|
||||
return {'valid': False, 'reason': '用户名或密码错误'}
|
||||
|
||||
# 检查是否被锁定
|
||||
if user.locked_until and user.locked_until > time.time():
|
||||
@@ -1341,26 +1342,33 @@ class DatabaseManager:
|
||||
f"${base64.b64encode(dk).decode()}")
|
||||
|
||||
def verify_password(self, password: str, password_hash: str) -> bool:
|
||||
"""验证密码是否正确"""
|
||||
"""验证密码是否正确(bcrypt 优先;非 bcrypt 哈希或 bcrypt 缺失时回退 PBKDF2,绝不接受明文)"""
|
||||
import hashlib
|
||||
import base64
|
||||
import hmac
|
||||
|
||||
# 1) 优先 bcrypt;ValueError(哈希不是 bcrypt 格式)与 ImportError 都继续走回退
|
||||
try:
|
||||
import bcrypt
|
||||
return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
|
||||
except ImportError:
|
||||
# 回退: 只接受自描述 PBKDF2 格式,绝不接受明文
|
||||
import hashlib
|
||||
import base64
|
||||
import hmac
|
||||
if not password_hash.startswith('$pbkdf2-sha256$'):
|
||||
return False
|
||||
try:
|
||||
_, algo, iters_s, salt_b64, hash_b64 = password_hash.split('$')
|
||||
iterations = int(iters_s)
|
||||
salt = base64.b64decode(salt_b64)
|
||||
expected = base64.b64decode(hash_b64)
|
||||
dk = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, iterations)
|
||||
return hmac.compare_digest(dk, expected)
|
||||
except Exception:
|
||||
return False
|
||||
return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
|
||||
except ValueError:
|
||||
pass
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# 2) 回退: 只接受自描述 PBKDF2 格式
|
||||
if not password_hash.startswith('$pbkdf2-sha256$'):
|
||||
return False
|
||||
try:
|
||||
_, algo, iters_s, salt_b64, hash_b64 = password_hash.split('$')
|
||||
iterations = int(iters_s)
|
||||
salt = base64.b64decode(salt_b64)
|
||||
expected = base64.b64decode(hash_b64)
|
||||
dk = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, iterations)
|
||||
return hmac.compare_digest(dk, expected)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_user_by_token(self, token: str) -> dict:
|
||||
"""通过 token 获取用户信息"""
|
||||
|
||||
Reference in New Issue
Block a user