2025-05-19 21:00:24 +00:00
|
|
|
|
from datetime import datetime, timezone, timedelta
|
2023-10-30 21:00:55 +00:00
|
|
|
|
|
2023-10-26 20:38:31 +00:00
|
|
|
|
import jwt
|
2024-11-01 12:06:21 +00:00
|
|
|
|
from pydantic import BaseModel
|
2025-05-19 21:00:24 +00:00
|
|
|
|
from typing import Optional
|
|
|
|
|
from utils.logger import root_logger as logger
|
2023-10-30 21:00:55 +00:00
|
|
|
|
|
2024-11-01 12:06:21 +00:00
|
|
|
|
from auth.exceptions import ExpiredToken, InvalidToken
|
2023-10-26 21:07:35 +00:00
|
|
|
|
from settings import JWT_ALGORITHM, JWT_SECRET_KEY
|
2024-11-01 12:06:21 +00:00
|
|
|
|
|
|
|
|
|
class TokenPayload(BaseModel):
|
|
|
|
|
user_id: str
|
|
|
|
|
username: str
|
2025-05-19 21:00:24 +00:00
|
|
|
|
exp: Optional[datetime] = None
|
2024-11-01 12:06:21 +00:00
|
|
|
|
iat: datetime
|
|
|
|
|
iss: str
|
2023-10-26 20:38:31 +00:00
|
|
|
|
|
2021-06-28 09:08:09 +00:00
|
|
|
|
|
2022-07-21 11:58:50 +00:00
|
|
|
|
class JWTCodec:
|
2022-09-03 10:50:14 +00:00
|
|
|
|
@staticmethod
|
2025-05-19 21:00:24 +00:00
|
|
|
|
def encode(user, exp: Optional[datetime] = None) -> str:
|
|
|
|
|
# Поддержка как объектов, так и словарей
|
|
|
|
|
if isinstance(user, dict):
|
|
|
|
|
# В SessionManager.create_session передается словарь {"id": user_id, "email": username}
|
|
|
|
|
user_id = str(user.get("id", ""))
|
|
|
|
|
username = user.get("email", "") or user.get("username", "")
|
|
|
|
|
else:
|
|
|
|
|
# Для объектов с атрибутами
|
|
|
|
|
user_id = str(getattr(user, "id", ""))
|
|
|
|
|
username = getattr(user, "slug", "") or getattr(user, "email", "") or getattr(user, "phone", "") or ""
|
|
|
|
|
|
|
|
|
|
logger.debug(f"[JWTCodec.encode] Кодирование токена для user_id={user_id}, username={username}")
|
|
|
|
|
|
|
|
|
|
# Если время истечения не указано, установим срок годности на 30 дней
|
|
|
|
|
if exp is None:
|
|
|
|
|
exp = datetime.now(tz=timezone.utc) + timedelta(days=30)
|
|
|
|
|
logger.debug(f"[JWTCodec.encode] Время истечения не указано, устанавливаем срок: {exp}")
|
|
|
|
|
|
|
|
|
|
# Важно: убедимся, что exp всегда является либо datetime, либо целым числом от timestamp
|
|
|
|
|
if isinstance(exp, datetime):
|
|
|
|
|
# Преобразуем datetime в timestamp чтобы гарантировать правильный формат
|
|
|
|
|
exp_timestamp = int(exp.timestamp())
|
|
|
|
|
else:
|
|
|
|
|
# Если передано что-то другое, установим значение по умолчанию
|
|
|
|
|
logger.warning(f"[JWTCodec.encode] Некорректный формат exp: {exp}, используем значение по умолчанию")
|
|
|
|
|
exp_timestamp = int((datetime.now(tz=timezone.utc) + timedelta(days=30)).timestamp())
|
|
|
|
|
|
2022-09-03 10:50:14 +00:00
|
|
|
|
payload = {
|
2025-05-19 21:00:24 +00:00
|
|
|
|
"user_id": user_id,
|
|
|
|
|
"username": username,
|
|
|
|
|
"exp": exp_timestamp, # Используем timestamp вместо datetime
|
2022-11-24 14:31:52 +00:00
|
|
|
|
"iat": datetime.now(tz=timezone.utc),
|
2023-10-30 21:00:55 +00:00
|
|
|
|
"iss": "discours",
|
2022-09-03 10:50:14 +00:00
|
|
|
|
}
|
2025-05-19 21:00:24 +00:00
|
|
|
|
|
|
|
|
|
logger.debug(f"[JWTCodec.encode] Сформирован payload: {payload}")
|
|
|
|
|
|
2022-10-23 09:33:28 +00:00
|
|
|
|
try:
|
2025-05-19 21:00:24 +00:00
|
|
|
|
token = jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM)
|
|
|
|
|
logger.debug(f"[JWTCodec.encode] Токен успешно создан, длина: {len(token) if token else 0}")
|
|
|
|
|
return token
|
2022-10-23 09:33:28 +00:00
|
|
|
|
except Exception as e:
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.error(f"[JWTCodec.encode] Ошибка при кодировании JWT: {e}")
|
|
|
|
|
raise
|
2021-06-28 09:08:09 +00:00
|
|
|
|
|
2022-09-03 10:50:14 +00:00
|
|
|
|
@staticmethod
|
2024-11-01 12:06:21 +00:00
|
|
|
|
def decode(token: str, verify_exp: bool = True):
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.debug(f"[JWTCodec.decode] Начало декодирования токена длиной {len(token) if token else 0}")
|
2025-05-21 15:29:32 +00:00
|
|
|
|
|
|
|
|
|
if not token:
|
|
|
|
|
logger.error("[JWTCodec.decode] Пустой токен")
|
|
|
|
|
return None
|
|
|
|
|
|
2022-10-23 09:33:28 +00:00
|
|
|
|
try:
|
|
|
|
|
payload = jwt.decode(
|
|
|
|
|
token,
|
|
|
|
|
key=JWT_SECRET_KEY,
|
2022-10-31 19:53:48 +00:00
|
|
|
|
options={
|
|
|
|
|
"verify_exp": verify_exp,
|
2022-10-31 21:05:10 +00:00
|
|
|
|
# "verify_signature": False
|
2022-10-31 19:53:48 +00:00
|
|
|
|
},
|
2022-10-23 09:33:28 +00:00
|
|
|
|
algorithms=[JWT_ALGORITHM],
|
2023-10-30 21:00:55 +00:00
|
|
|
|
issuer="discours",
|
2022-10-23 09:33:28 +00:00
|
|
|
|
)
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.debug(f"[JWTCodec.decode] Декодирован payload: {payload}")
|
|
|
|
|
|
|
|
|
|
# Убедимся, что exp существует (добавим обработку если exp отсутствует)
|
|
|
|
|
if "exp" not in payload:
|
|
|
|
|
logger.warning(f"[JWTCodec.decode] В токене отсутствует поле exp")
|
|
|
|
|
# Добавим exp по умолчанию, чтобы избежать ошибки при создании TokenPayload
|
|
|
|
|
payload["exp"] = int((datetime.now(tz=timezone.utc) + timedelta(days=30)).timestamp())
|
|
|
|
|
|
2025-05-21 15:29:32 +00:00
|
|
|
|
try:
|
|
|
|
|
r = TokenPayload(**payload)
|
|
|
|
|
logger.debug(f"[JWTCodec.decode] Создан объект TokenPayload: user_id={r.user_id}, username={r.username}")
|
|
|
|
|
return r
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"[JWTCodec.decode] Ошибка при создании TokenPayload: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
2022-11-13 23:38:06 +00:00
|
|
|
|
except jwt.InvalidIssuedAtError:
|
2025-05-21 15:29:32 +00:00
|
|
|
|
logger.error("[JWTCodec.decode] Недействительное время выпуска токена")
|
|
|
|
|
return None
|
2022-10-31 21:05:10 +00:00
|
|
|
|
except jwt.ExpiredSignatureError:
|
2025-05-21 15:29:32 +00:00
|
|
|
|
logger.error("[JWTCodec.decode] Истек срок действия токена")
|
|
|
|
|
return None
|
2022-10-31 21:17:00 +00:00
|
|
|
|
except jwt.InvalidSignatureError:
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.error("[JWTCodec.decode] Недействительная подпись токена")
|
2025-05-21 15:29:32 +00:00
|
|
|
|
return None
|
2025-05-16 06:23:48 +00:00
|
|
|
|
except jwt.InvalidTokenError:
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.error("[JWTCodec.decode] Недействительный токен")
|
2025-05-21 15:29:32 +00:00
|
|
|
|
return None
|
2025-05-16 06:23:48 +00:00
|
|
|
|
except jwt.InvalidKeyError:
|
2025-05-19 21:00:24 +00:00
|
|
|
|
logger.error("[JWTCodec.decode] Недействительный ключ")
|
2025-05-21 15:29:32 +00:00
|
|
|
|
return None
|
2025-05-19 21:00:24 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"[JWTCodec.decode] Неожиданная ошибка при декодировании: {e}")
|
2025-05-21 15:29:32 +00:00
|
|
|
|
return None
|