""" app/services/auth_service.py — 认证 service 密码哈希校验、用户查找、响应格式化。 路由层不含业务逻辑,全在此。 """ import logging from passlib.context import CryptContext from sqlalchemy.orm import Session from app.core.response import raise_unauthorized from app.middleware.workspace_guard import CurrentUser from app.models.user import User from app.models.workspace import WorkspaceMember logger = logging.getLogger(__name__) pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(plain: str) -> str: return pwd_context.hash(plain) def verify_password(plain: str, hashed: str) -> bool: return pwd_context.verify(plain, hashed) def authenticate_user( db: Session, username: str, password: str ) -> tuple[User, int, str]: """ 验证用户名+密码,返回 (user, workspace_id, role)。 失败抛 CloverHTTPException 40101。 """ user = db.query(User).filter( User.username == username, User.is_active == True ).first() if not user or not verify_password(password, user.hashed_password): raise_unauthorized("用户名或密码错误") # 取用户所在的第一个 workspace(手动建账号场景只有一个) member = ( db.query(WorkspaceMember) .filter(WorkspaceMember.user_id == user.id) .first() ) if not member: raise_unauthorized("用户未加入任何 workspace,请联系管理员") # 记录登录 try: from app.models.user import LoginRecord db.add(LoginRecord(user_id=user.id)) db.commit() except Exception: logger.warning("Failed to write login_record for user=%s", user.id) db.rollback() return user, member.workspace_id, member.role def build_user_response(user: User, workspace_id: int, role: str) -> dict: """格式化用户响应体(契约§4 DTO)。""" return { "id": user.id, "username": user.username, "email": user.email, "current_workspace_id": workspace_id, "role": role, }