# auth/security.py # JWT creation/verification and bcrypt password hashing. # Self-contained — no dependencies on existing app code. import os from datetime import datetime, timedelta, timezone from typing import Optional import bcrypt from jose import JWTError, jwt from dotenv import load_dotenv load_dotenv() SECRET_KEY = os.getenv("JWT_SECRET_KEY", "") ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256") EXPIRE_MINUTES = int(os.getenv("JWT_EXPIRE_MINUTES", "10080")) # 7 days if not SECRET_KEY: raise RuntimeError( "JWT_SECRET_KEY is not set in .env — " "run: python -c \"import secrets; print(secrets.token_hex(32))\"" ) def hash_password(plaintext: str) -> str: return bcrypt.hashpw(plaintext.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') def verify_password(plaintext: str, hashed: str) -> bool: try: return bcrypt.checkpw(plaintext.encode('utf-8'), hashed.encode('utf-8')) except Exception: return False def create_access_token(data: dict) -> str: payload = data.copy() payload["exp"] = datetime.now(timezone.utc) + timedelta(minutes=EXPIRE_MINUTES) return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) def decode_token(token: str) -> Optional[dict]: """ Returns the decoded payload dict if token is valid. Returns None if token is expired or invalid. """ try: return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) except JWTError: return None