import jwt from fastapi import Request, HTTPException from fastapi.responses import JSONResponse from config import settings from database import AsyncSessionLocal from models import User, UserRole, Role, RolePermission, Permission from sqlalchemy import select async def rbac_middleware(request: Request, call_next): public_paths = ["/api/auth/login", "/api/auth/wecom", "/health", "/docs", "/openapi.json", "/wecom/callback"] if any(request.url.path.startswith(p) for p in public_paths): return await call_next(request) token = request.headers.get("Authorization", "").replace("Bearer ", "") if not token: return JSONResponse({"code": 401, "message": "未提供认证令牌"}, 401) try: payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) user_id = payload.get("sub") except jwt.PyJWTError: return JSONResponse({"code": 401, "message": "令牌无效或已过期"}, 401) async with AsyncSessionLocal() as db: result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user or user.status != "active": return JSONResponse({"code": 401, "message": "用户不存在或已禁用"}, 401) ur_result = await db.execute( select(Role).join(UserRole).where(UserRole.user_id == user.id) ) roles = ur_result.scalars().all() role_codes = [r.code for r in roles] is_root = "root" in role_codes permissions = [] data_scopes = [] for role in roles: data_scopes.append(role.data_scope) rp_result = await db.execute( select(Permission).join(RolePermission).where(RolePermission.role_id == role.id) ) perms = rp_result.scalars().all() permissions.extend([p.code for p in perms]) unique_perms = list(set(permissions)) if is_root and "*:*" not in unique_perms: unique_perms.insert(0, "*:*") request.state.user = { "id": str(user.id), "username": user.username, "display_name": user.display_name, "department_id": str(user.department_id) if user.department_id else None, "roles": [{"code": r.code, "name": r.name, "data_scope": r.data_scope} for r in roles], "permissions": unique_perms, "is_root": is_root, "data_scope": "all" if is_root or "all" in data_scopes else ( "subordinate_only" if "subordinate_only" in data_scopes else "self_only" ), } return await call_next(request)