复查修复(二): 同步模块与安全遗留

- 修复 remove_sync_source 引入的 stop_sync KeyError 回归(容错 get)
- stop_sync 不再删除存活线程条目(防双 worker);FTP 子目录递归传真名(不再 KeyError)
- 远程文件名统一 _safe_remote_name 校验(FTP/SFTP/HTTP 防路径穿越)
- temp 任务名加随机后缀防碰撞;temp 状态/线程完成后清理(防 sync_state.json 膨胀)
- save_sync_state 快照+原子写(临时文件+os.replace),持锁调用不死锁
- URL 打印脱敏(user:pass@ -> ***@)
- _need_sync_http size=0 不再全量重下(仅按存在性)
- cron 同一分钟去重;PyPI 进度只更新当前源
- v2 start_sync 不再把 bool 当 task_id
- UserRecord.to_dict 脱敏(不返回 password_hash/token),新增 to_dict_private/get_user_with_password
- config_hotreload 单配置源(set+persist 与热重载一致);server.py 用 get_all()
- 会话创建时顺带清理过期项;JSON stats/历史读改写加锁
- 解压目标目录先校验;FTP RETR 命令注入防护;rsync --delete 目标保护
- git/urlopen 补超时;cleanup_completed_tasks 删旧留新
This commit is contained in:
HYC Fixer
2026-09-02 00:39:12 +08:00
parent fc72759a92
commit 82875b710a
10 changed files with 957 additions and 810 deletions
+17 -3
View File
@@ -369,11 +369,10 @@ class UserRecord(Base):
enabled = Column(Boolean, default=True)
def to_dict(self) -> dict:
"""安全序列化: 不返回密码哈希与 token(防止泄露给前端/日志)"""
return {
'id': self.id,
'username': self.username,
'password_hash': self.password_hash,
'token': self.token,
'token_expires_at': self.token_expires_at,
'role': self.role,
'email': self.email,
@@ -387,6 +386,13 @@ class UserRecord(Base):
'enabled': self.enabled
}
def to_dict_private(self) -> dict:
"""内部序列化: 含密码哈希与 token,仅供认证内部逻辑使用"""
data = self.to_dict()
data['password_hash'] = self.password_hash
data['token'] = self.token
return data
class LoginLogRecord(Base):
"""登录日志记录表"""
@@ -1212,13 +1218,21 @@ class DatabaseManager:
return {'success': True, 'user_id': user.id}
def get_user(self, username: str) -> dict:
"""获取用户信息"""
"""获取用户信息(安全版,不含 password_hash/token)"""
with self.session() as session:
user = session.query(UserRecord).filter_by(username=username).first()
if user:
return user.to_dict()
return None
def get_user_with_password(self, username: str) -> dict:
"""获取用户信息(含密码哈希,仅供密码验证内部使用)"""
with self.session() as session:
user = session.query(UserRecord).filter_by(username=username).first()
if user:
return user.to_dict_private()
return None
def get_user_by_id(self, user_id: int) -> dict:
"""通过ID获取用户信息"""
with self.session() as session: