from typing import Annotated, AsyncGenerator, Callable, Iterable import uuid from fastapi import Depends, HTTPException, status from pydantic import ValidationError from sqlalchemy.ext.asyncio import AsyncSession from app.core.exceptions import AppException from app.core.security import decode_token, oauth2_scheme from app.crud import user as user_crud from app.crud import member as member_crud from app.db.session import SessionLocal from app.schemas.user import TokenPayload async def get_db_session() -> AsyncGenerator[AsyncSession, None]: async with SessionLocal() as session: yield session async def get_current_user( token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[AsyncSession, Depends(get_db_session)], ): payload = decode_token(token) try: token_data = TokenPayload(**payload) except ValidationError as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) from exc user = await user_crud.get_by_id(db, uuid.UUID(str(token_data.sub))) if not user or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive or missing user", headers={"WWW-Authenticate": "Bearer"}, ) return user def require_roles(roles: Iterable[str]) -> Callable: roles_set = set(roles) async def dependency(current_user=Depends(get_current_user)): if current_user.role not in roles_set: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions", ) return current_user return dependency async def get_study_member( study_id: uuid.UUID, current_user=Depends(get_current_user), db: AsyncSession = Depends(get_db_session), ): if current_user.role == "ADMIN": return None return await member_crud.get_member(db, study_id, current_user.id) def require_study_member(): async def dependency( study_id: uuid.UUID, current_user=Depends(get_current_user), db: AsyncSession = Depends(get_db_session), ): if current_user.role == "ADMIN": return current_user membership = await member_crud.get_member(db, study_id, current_user.id) if not membership or not membership.is_active: raise AppException( code="FORBIDDEN", message="Not a member of this study", status_code=status.HTTP_403_FORBIDDEN, ) return current_user return dependency def require_study_roles(roles: Iterable[str]): roles_set = set(roles) async def dependency( study_id: uuid.UUID, current_user=Depends(get_current_user), db: AsyncSession = Depends(get_db_session), ): if current_user.role == "ADMIN": return current_user membership = await member_crud.get_member(db, study_id, current_user.id) if not membership or not membership.is_active or membership.role_in_study not in roles_set: raise AppException( code="FORBIDDEN", message="Insufficient study permissions", status_code=status.HTTP_403_FORBIDDEN, ) return current_user return dependency