"""认证服务模块 - 密码哈希、令牌管理、会话管理、默认管理员创建""" import hashlib import logging import secrets from datetime import datetime, timedelta from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.models.user import Session, User logger = logging.getLogger(__name__) def hash_password(password: str) -> str: """使用 SHA256 + 随机盐 对密码进行哈希。""" salt = secrets.token_hex(16) hashed = hashlib.sha256(f"{salt}{password}".encode()).hexdigest() return f"{salt}:{hashed}" def verify_password(password: str, password_hash: str) -> bool: """验证密码是否与哈希值匹配。 兼容两种分隔符格式: - 新版: salt:hash(冒号分隔) - 旧版: salt$hash(美元符号分隔,从原版迁移的数据) """ # 尝试冒号分隔(新版) if ":" in password_hash: salt, expected = password_hash.split(":", 1) elif "$" in password_hash: # 美元符号分隔(旧版迁移数据) salt, expected = password_hash.split("$", 1) else: return False actual = hashlib.sha256(f"{salt}{password}".encode()).hexdigest() return secrets.compare_digest(actual, expected) async def create_token(user_id: int, db: AsyncSession) -> str: """为用户创建会话令牌并持久化。 Returns: 生成的令牌字符串。 """ token = secrets.token_urlsafe(32) session = Session( user_id=user_id, token=token, expires_at=datetime.utcnow() + timedelta(hours=settings.jwt_expire_hours), is_valid=True, ) db.add(session) await db.commit() return token async def verify_token(token: str, db: AsyncSession) -> User | None: """验证令牌有效性,返回对应的 User 对象或 None。""" stmt = select(Session).where( Session.token == token, Session.is_valid == True, # noqa: E712 Session.expires_at > datetime.utcnow(), ) result = await db.execute(stmt) session = result.scalar_one_or_none() if not session: return None stmt_user = select(User).where(User.id == session.user_id) result_user = await db.execute(stmt_user) user = result_user.scalar_one_or_none() if not user or not user.is_active: return None return user async def authenticate_user( db: AsyncSession, username: str, password: str ) -> User | None: """验证用户名和密码,返回 User 对象或 None。""" stmt = select(User).where(User.username == username) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user or not verify_password(password, user.password_hash): return None return user async def invalidate_session(token: str, db: AsyncSession) -> None: """使指定令牌失效。""" stmt = select(Session).where(Session.token == token) result = await db.execute(stmt) session = result.scalar_one_or_none() if session: session.is_valid = False await db.flush() async def update_last_login(user: User, db: AsyncSession) -> None: """更新用户最后登录时间。""" user.last_login = datetime.utcnow() await db.flush() async def create_default_admin(db: AsyncSession) -> None: """如果数据库中不存在管理员账户 lxy_root,则创建默认管理员。""" stmt = select(User).where(User.username == "lxy_root") result = await db.execute(stmt) admin = result.scalar_one_or_none() if not admin: user = User( username="lxy_root", password_hash=hash_password("admin123"), email="admin@system.local", role="admin", is_active=True, ) db.add(user) await db.commit() logger.info("默认管理员账户已创建: lxy_root / admin123")