From 392c575647bd4a7960d7390e2491ecafcd6e8e66 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:30:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Worker=20=E5=B9=B3=E5=8F=B0=20MVP?= =?UTF-8?q?=20v1.0.0=20-=20=E5=A4=9A=E7=A7=9F=E6=88=B7/=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86/AI=20Worker/=E4=BB=BB=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E6=8E=92/HITL=E5=AE=A1=E6=A0=B8/=E6=88=90=E6=9C=AC=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/deps.py | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 backend/app/api/deps.py diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 0000000..9ac9816 --- /dev/null +++ b/backend/app/api/deps.py @@ -0,0 +1,76 @@ +"""FastAPI dependencies: auth, tenant context, permissions.""" +from fastapi import Depends, HTTPException, status, Request +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from sqlalchemy.orm import Session +from app.database import get_db +from app.core.security import decode_access_token +from app.core.permissions import Role, has_role +from app.models.user import User +from typing import Set, Optional + +security_scheme = HTTPBearer(auto_error=False) + + +async def get_current_user( + request: Request, + credentials: HTTPAuthorizationCredentials = Depends(security_scheme), + db: Session = Depends(get_db), +) -> User: + """Extract and validate the JWT token, return current user.""" + if not credentials: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + payload = decode_access_token(credentials.credentials) + user_id = int(payload["sub"]) + tenant_id = payload["tid"] + role = payload["role"] + except Exception: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + user = db.query(User).filter(User.id == user_id).first() + if not user or not user.is_active: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User not found or inactive", + ) + + # Set tenant context on request state + request.state.tenant_id = user.tenant_id + request.state.user_id = user.id + request.state.user_role = user.role + + return user + + +def require_roles(*roles: Role): + """Dependency factory: require one of the specified roles.""" + role_set: Set[Role] = set(roles) + + async def _check(user: User = Depends(get_current_user)) -> User: + if not has_role(user.role, role_set): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"Requires role: {', '.join(r.value for r in role_set)}", + ) + return user + + return _check + + +def get_tenant_id(user: User = Depends(get_current_user)) -> int: + """Extract tenant_id from current user.""" + return user.tenant_id + + +# Common dependency aliases +CurrentUser = Depends(get_current_user) +TenantId = Depends(get_tenant_id)