import jwt import bcrypt from datetime import datetime, timedelta, timezone from app.core.settings import settings def hash_password(password: str) -> str: """ Hashes a plain-text password using bcrypt. Bcrypt has a 72-byte limit for the password. Directly using bcrypt avoids passlib compatibility issues. """ pwd_bytes = password.encode('utf-8')[:72] salt = bcrypt.gensalt() hashed = bcrypt.hashpw(pwd_bytes, salt) return hashed.decode('utf-8') def verify_password(plain_password: str, hashed_password: str) -> bool: """ Verifies a plain-text password against a hashed one. """ try: pwd_bytes = plain_password.encode('utf-8')[:72] hashed_bytes = hashed_password.encode('utf-8') return bcrypt.checkpw(pwd_bytes, hashed_bytes) except Exception: return False def create_access_token(data: dict, expires_delta: timedelta = None) -> str: """ Creates a JWT access token. """ to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM) return encoded_jwt def decode_access_token(token: str) -> dict: """ Decodes a JWT access token. Returns the payload if valid, else None or raises? Usually returns dict. """ try: payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]) return payload except Exception: return None