from datetime import timedelta, datetime, timezone from typing import TypedDict, Literal, TypeAlias import bcrypt import jwt from app.config.env import env BCRYPT_MAX_BYTES = 72 AccessTokenType: TypeAlias = Literal["access", "refresh", "verify", "api"] class TokenInfo(TypedDict): username: str exp: datetime type: AccessTokenType def _safe_password_bytes(password: str) -> bytes: data = password.encode("utf-8") if len(data) > BCRYPT_MAX_BYTES: data = data[:BCRYPT_MAX_BYTES] return data class CryptUtils: @staticmethod def get_password_hash(password: str): return bcrypt.hashpw(_safe_password_bytes(password), bcrypt.gensalt()).decode( "utf-8" ) @staticmethod def verify_password(plain_password: str, hashed_password: str): try: return bcrypt.checkpw( _safe_password_bytes(plain_password), hashed_password.encode("utf-8") ) except ValueError: return False except Exception: return False @staticmethod def create_token(username: str, type: AccessTokenType, expires_delta: timedelta): data: TokenInfo = { "username": username, "type": type, "exp": datetime.now(timezone.utc) + expires_delta, } return jwt.encode(data, env.jwt_secret_key, env.jwt_algorithm) @staticmethod def get_token_info(token: str) -> TokenInfo: data = jwt.decode(token, env.jwt_secret_key, algorithms=[env.jwt_algorithm]) return data