from jose import jwt, JOSEError from pydantic import BaseModel from datetime import timedelta, datetime from typing import ClassVar import logging from fooder.settings import settings from fooder.exc import Unauthorized class Token(BaseModel): exp: datetime sub: int secret_key: ClassVar[str] expire_delta: ClassVar[timedelta] @classmethod def calculate_exp(cls, now: datetime) -> datetime: return now + cls.expire_delta @classmethod def decode(cls, jwt_token: str | bytes) -> "Token": try: data = jwt.decode( jwt_token, cls.secret_key, algorithms=[settings.ALGORITHM] ) except JOSEError as e: logging.warning(e) raise Unauthorized() return cls(**data) def encode(self) -> str: data = self.model_dump() data["sub"] = str(data["sub"]) return jwt.encode(data, self.secret_key, settings.ALGORITHM) class AccessToken(Token): secret_key = settings.SECRET_KEY expire_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) class RefreshToken(Token): secret_key = settings.REFRESH_SECRET_KEY expire_delta = timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) def generate_token_pair( entity_id: int, now: datetime ) -> tuple[AccessToken, RefreshToken]: return ( AccessToken(exp=AccessToken.calculate_exp(now), sub=entity_id), RefreshToken(exp=RefreshToken.calculate_exp(now), sub=entity_id), )