smart-attendance-api / backend /app /api /dependencies.py
Pratham200Rajbhar
first commit
78013c4
Raw
History Blame Contribute Delete
4.13 kB
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