Pratham200Rajbhar
first commit
78013c4
Raw
History Blame Contribute Delete
5.97 kB
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Request, status
from prisma.models import User
from app.api.dependencies import get_current_user, reusable_oauth2
from app.core.config import settings
from app.core.logging_config import get_logger
from app.core.security import decode_access_token
from app.db.client import db
from app.db.redis import get_redis
from app.repositories.student_repo import StudentRepository
from app.schemas.auth import Token, UserLogin, UserProfileResponse, DeviceChangeRequestCreate
from app.schemas.student import StudentCreate, StudentResponse
from app.schemas.teacher import TeacherCreate, TeacherResponse
from app.services.auth_service import AuthService
from app.services.device_change_service import DeviceChangeService
logger = get_logger("app.api.auth")
router = APIRouter(prefix="/auth", tags=["Authentication"])
_RATE_LIMIT_WINDOW = 60
_RATE_LIMIT_MAX = 10
async def _rate_limit(request: Request) -> None:
if settings.ENVIRONMENT == "development":
return
forwarded = request.headers.get("X-Forwarded-For")
ip = forwarded.split(",")[0].strip() if forwarded else (request.client.host if request.client else "unknown")
key = f"ratelimit:auth:{ip}"
r = await get_redis()
count = await r.incr(key)
if count == 1:
await r.expire(key, _RATE_LIMIT_WINDOW)
if count > _RATE_LIMIT_MAX:
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests. Please try again later.")
@router.post("/login", response_model=Token)
async def login(login_data: UserLogin, request: Request, auth_service: AuthService = Depends()) -> Token:
await _rate_limit(request)
token = await auth_service.authenticate(login_data)
if not token:
logger.warning("Failed login: %s", login_data.email)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password")
return token
@router.post("/register/student", response_model=StudentResponse, status_code=status.HTTP_201_CREATED)
async def register_student(data: StudentCreate, request: Request, auth_service: AuthService = Depends()) -> StudentResponse:
await _rate_limit(request)
student = await auth_service.register_student(data)
if not student:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="User with this email is already registered")
return student
@router.post("/register/teacher", response_model=TeacherResponse, status_code=status.HTTP_201_CREATED)
async def register_teacher(data: TeacherCreate, request: Request, auth_service: AuthService = Depends()) -> TeacherResponse:
await _rate_limit(request)
teacher = await auth_service.register_teacher(data)
if not teacher:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="User with this email is already registered")
return teacher
@router.get("/me", response_model=UserProfileResponse)
async def get_me(current_user: User = Depends(get_current_user)) -> UserProfileResponse:
student_profile = None
teacher_profile = None
if current_user.role == "STUDENT":
student = await db.student.find_unique(where={"userId": current_user.id})
if student:
embedding = await StudentRepository().get_face_embedding(student.id)
student_profile = {
"id": student.id,
"enrollment_number": student.enrollmentNumber,
"first_name": student.firstName,
"last_name": student.lastName,
"face_registered": embedding is not None and len(embedding) > 0,
}
elif current_user.role == "TEACHER":
teacher = await db.teacher.find_unique(where={"userId": current_user.id})
if teacher:
teacher_profile = {
"id": teacher.id,
"department": teacher.department.name if teacher.department else "",
"designation": teacher.designation.name if teacher.designation else "",
"employee_id": teacher.employeeId,
"first_name": teacher.firstName,
"last_name": teacher.lastName,
}
return UserProfileResponse(
id=current_user.id,
email=current_user.email,
role=current_user.role,
is_active=current_user.isActive,
student_profile=student_profile,
teacher_profile=teacher_profile,
)
@router.post("/logout", status_code=status.HTTP_200_OK)
async def logout(token: str = Depends(reusable_oauth2)) -> dict:
payload = decode_access_token(token)
if payload:
exp = payload.get("exp")
if exp:
ttl = exp - int(datetime.now(timezone.utc).timestamp())
if ttl > 0:
try:
await get_redis().setex(f"denylist:{token}", ttl, "revoked")
logger.info("Token revoked: user=%s", payload.get("sub"))
except Exception as cache_err:
logger.warning("Failed to add token to Redis denylist: %s", cache_err)
return {"status": "success", "message": "Successfully logged out."}
@router.post("/request-device-change", status_code=status.HTTP_200_OK)
async def request_device_change(
data: DeviceChangeRequestCreate,
request: Request,
device_change_service: DeviceChangeService = Depends(),
) -> dict:
await _rate_limit(request)
await device_change_service.request_device_change(data)
return {"status": "success", "message": "Device change request submitted successfully."}
# --- System Config (Public) ---
from app.schemas.system_config import SystemConfigResponse
from app.services.system_config_service import SystemConfigService
@router.get("/config", response_model=SystemConfigResponse)
async def get_public_system_config(config_service: SystemConfigService = Depends()):
return await config_service.get_config()