""" Handles password hashing and JWT issuing/validation for user login. """ from datetime import datetime, timedelta, timezone from typing import Optional import bcrypt from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from config import settings from database import users_collection oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth/login") # bcrypt has a hard 72-byte limit on the input; longer passwords are truncated # before hashing (this matches bcrypt's own behavior, so it's safe either way). _MAX_PASSWORD_BYTES = 72 def hash_password(password: str) -> str: pw_bytes = password.encode("utf-8")[:_MAX_PASSWORD_BYTES] hashed = bcrypt.hashpw(pw_bytes, bcrypt.gensalt()) return hashed.decode("utf-8") def verify_password(plain: str, hashed: str) -> bool: pw_bytes = plain.encode("utf-8")[:_MAX_PASSWORD_BYTES] return bcrypt.checkpw(pw_bytes, hashed.encode("utf-8")) def create_access_token(subject: str) -> str: expire = datetime.now(timezone.utc) + timedelta(minutes=settings.JWT_EXPIRE_MINUTES) payload = {"sub": subject, "exp": expire} return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM) async def get_current_user(token: str = Depends(oauth2_scheme)) -> dict: credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) user_email: Optional[str] = payload.get("sub") if user_email is None: raise credentials_exception except JWTError: raise credentials_exception user = await users_collection().find_one({"email": user_email}) if user is None: raise credentials_exception return user