Spaces:
Configuration error
Configuration error
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import OAuth2PasswordBearer | |
| from prisma.models import User, Student, Teacher | |
| from app.core.logging_config import get_logger | |
| from app.core.security import decode_access_token | |
| from app.repositories.user_repo import UserRepository | |
| from app.repositories.student_repo import StudentRepository | |
| from app.repositories.teacher_repo import TeacherRepository | |
| logger = get_logger("app.auth") | |
| reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") | |
| _UNAUTHORIZED = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) | |
| _FORBIDDEN = {"STUDENT": "Access forbidden: Students only", "TEACHER": "Access forbidden: Teachers only"} | |
| _INACTIVE = "User account is inactive or disabled" | |
| async def _check_token_revoked(token: str) -> None: | |
| try: | |
| from app.db.redis import get_redis | |
| if await get_redis().get(f"denylist:{token}"): | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Token has been revoked", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| except HTTPException: | |
| raise | |
| except Exception: | |
| pass | |
| async def _validate_token_payload(token: str) -> dict: | |
| await _check_token_revoked(token) | |
| payload = decode_access_token(token) | |
| if not payload: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Could not validate credentials", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| user_id = payload.get("sub") | |
| if not user_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Subject not found in token", | |
| ) | |
| return payload | |
| async def get_current_user(token: str = Depends(reusable_oauth2)) -> User: | |
| payload = await _validate_token_payload(token) | |
| user_id = payload.get("sub") | |
| user = await UserRepository().get_by_id(user_id) | |
| if not user or not user.isActive: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail=_INACTIVE, | |
| ) | |
| return user | |
| async def get_current_student(current_user: User = Depends(get_current_user)) -> Student: | |
| if current_user.role != "STUDENT": | |
| raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=_FORBIDDEN["STUDENT"]) | |
| student = await StudentRepository().get_by_user_id(current_user.id) | |
| if not student: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Student profile not found") | |
| return student | |
| async def get_current_teacher(current_user: User = Depends(get_current_user)) -> Teacher: | |
| if current_user.role != "TEACHER": | |
| raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=_FORBIDDEN["TEACHER"]) | |
| teacher = await TeacherRepository().get_by_user_id(current_user.id) | |
| if not teacher: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Teacher profile not found") | |
| return teacher | |
| class RoleChecker: | |
| def __init__(self, allowed_roles: list[str]) -> None: | |
| self.allowed_roles = allowed_roles | |
| def __call__(self, current_user: User = Depends(get_current_user)) -> User: | |
| if current_user.role not in self.allowed_roles: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Access denied: Insufficient permissions", | |
| ) | |
| return current_user | |
| async def get_current_user_from_token(token: str) -> User: | |
| await _check_token_revoked(token) | |
| payload = decode_access_token(token) | |
| if not payload: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials") | |
| user_id = payload.get("sub") | |
| if not user_id: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Subject not found in token") | |
| user = await UserRepository().get_by_id(user_id) | |
| if not user or not user.isActive: | |
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=_INACTIVE) | |
| return user | |