"""JWT token handling for cabinet authentication.""" from datetime import datetime, timedelta from typing import Any import jwt from app.config import settings JWT_ALGORITHM = 'HS256' def create_access_token(user_id: int, telegram_id: int | None = None) -> str: """ Create a short-lived access token. Args: user_id: Database user ID telegram_id: Telegram user ID (optional for email-only users) Returns: Encoded JWT access token """ expire_minutes = settings.get_cabinet_access_token_expire_minutes() expires = datetime.utcnow() + timedelta(minutes=expire_minutes) payload = { 'sub': str(user_id), 'type': 'access', 'exp': expires, 'iat': datetime.utcnow(), } # Добавляем telegram_id только если он есть if telegram_id is not None: payload['telegram_id'] = telegram_id secret = settings.get_cabinet_jwt_secret() return jwt.encode(payload, secret, algorithm=JWT_ALGORITHM) def create_refresh_token(user_id: int) -> str: """ Create a long-lived refresh token. Args: user_id: Database user ID Returns: Encoded JWT refresh token """ expire_days = settings.get_cabinet_refresh_token_expire_days() expires = datetime.utcnow() + timedelta(days=expire_days) payload = { 'sub': str(user_id), 'type': 'refresh', 'exp': expires, 'iat': datetime.utcnow(), } secret = settings.get_cabinet_jwt_secret() return jwt.encode(payload, secret, algorithm=JWT_ALGORITHM) def decode_token(token: str) -> dict[str, Any] | None: """ Decode and validate a JWT token. Args: token: JWT token string Returns: Decoded payload dict or None if invalid/expired """ try: secret = settings.get_cabinet_jwt_secret() return jwt.decode(token, secret, algorithms=[JWT_ALGORITHM]) except jwt.ExpiredSignatureError: return None except jwt.InvalidTokenError: return None def get_token_payload(token: str, expected_type: str = 'access') -> dict[str, Any] | None: """ Decode token and verify its type. Args: token: JWT token string expected_type: Expected token type ("access" or "refresh") Returns: Decoded payload dict or None if invalid/expired/wrong type """ payload = decode_token(token) if not payload: return None if payload.get('type') != expected_type: return None return payload def get_refresh_token_expires_at() -> datetime: """Get the expiration datetime for a new refresh token.""" expire_days = settings.get_cabinet_refresh_token_expire_days() return datetime.utcnow() + timedelta(days=expire_days)