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.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 HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not a member of this study", ) 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 HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient study permissions", ) return current_user return dependency