""" app/middleware/workspace_guard.py — 多租户隔离中间件 所有业务接口强制注入 workspace_id(基石C)。 读操作信任 JWT;写操作+切换 workspace 查 workspace_members 校验。 """ import logging from typing import Annotated import jwt from fastapi import Depends, Header from sqlalchemy.orm import Session from app.core.database import get_db from app.core.response import raise_forbidden, raise_unauthorized from app.core.security import decode_access_token from app.models.workspace import WorkspaceMember logger = logging.getLogger(__name__) class CurrentUser: """JWT 解码后的请求上下文,注入到每个路由函数。""" def __init__( self, user_id: int, workspace_id: int, role: str, ): self.user_id = user_id self.workspace_id = workspace_id self.role = role def _extract_token(authorization: str | None) -> str: """从 Authorization: Bearer 中提取 token。""" if not authorization or not authorization.startswith("Bearer "): raise_unauthorized("缺少 Authorization header") return authorization.split(" ", 1)[1] async def get_current_user( authorization: Annotated[str | None, Header()] = None, ) -> CurrentUser: """ FastAPI 依赖:解码 JWT,返回 CurrentUser。 所有业务路由 Depends(get_current_user)。 """ token = _extract_token(authorization) try: payload = decode_access_token(token) except jwt.ExpiredSignatureError: raise_unauthorized("Token 已过期") except jwt.PyJWTError: raise_unauthorized("Token 无效") return CurrentUser( user_id=int(payload["user_id"]), workspace_id=int(payload["current_workspace_id"]), role=payload["role"], ) def require_write_permission( current_user: Annotated[CurrentUser, Depends(get_current_user)], db: Annotated[Session, Depends(get_db)], ) -> CurrentUser: """ 写操作依赖:查 workspace_members 校验当前用户确实属于此 workspace。 读操作用 get_current_user 即可(JWT 加速)。 """ member = ( db.query(WorkspaceMember) .filter( WorkspaceMember.workspace_id == current_user.workspace_id, WorkspaceMember.user_id == current_user.user_id, ) .first() ) if not member: logger.warning( "workspace permission denied: user=%s workspace=%s", current_user.user_id, current_user.workspace_id, ) raise_forbidden("无权限访问此 workspace") return current_user def require_admin( current_user: Annotated[CurrentUser, Depends(require_write_permission)], ) -> CurrentUser: """仅管理员可访问的路由依赖。""" if current_user.role != "admin": raise_forbidden("需要管理员权限") return current_user def require_supervisor_or_above( current_user: Annotated[CurrentUser, Depends(require_write_permission)], ) -> CurrentUser: """组长及以上(supervisor/admin)。""" if current_user.role not in ("supervisor", "admin"): raise_forbidden("需要组长或管理员权限") return current_user