"""JWT token service for Mantimon TCG. This module provides functions for creating and verifying JWT tokens used in the authentication system. Token Types: - Access tokens: Short-lived (30 min default), used for API authentication - Refresh tokens: Longer-lived (7 days default), used to obtain new access tokens Example: from app.services.jwt_service import create_access_token, verify_token # Create tokens access_token = create_access_token(user_id) refresh_token, jti = create_refresh_token(user_id) # Verify token user_id = verify_token(access_token) if user_id: print(f"Valid token for user {user_id}") """ import uuid from datetime import UTC, datetime, timedelta from jose import JWTError, jwt from app.config import settings from app.schemas.auth import TokenPayload, TokenType def create_access_token(user_id: uuid.UUID) -> str: """Create a short-lived JWT access token. Args: user_id: The user's UUID to encode in the token. Returns: Encoded JWT access token string. Example: token = create_access_token(user.id) # Use token in Authorization header: Bearer {token} """ now = datetime.now(UTC) expires = now + timedelta(minutes=settings.jwt_expire_minutes) payload = { "sub": str(user_id), "exp": expires, "iat": now, "type": TokenType.ACCESS.value, } return jwt.encode( payload, settings.secret_key.get_secret_value(), algorithm=settings.jwt_algorithm, ) def create_refresh_token(user_id: uuid.UUID) -> tuple[str, str]: """Create a longer-lived JWT refresh token. Refresh tokens include a JTI (JWT ID) for tracking and revocation. Args: user_id: The user's UUID to encode in the token. Returns: Tuple of (encoded JWT refresh token, JTI string). The JTI should be stored in Redis for revocation tracking. Example: token, jti = create_refresh_token(user.id) await token_store.store_refresh_token(user.id, jti, expires_at) """ now = datetime.now(UTC) expires = now + timedelta(days=settings.jwt_refresh_expire_days) jti = str(uuid.uuid4()) payload = { "sub": str(user_id), "exp": expires, "iat": now, "type": TokenType.REFRESH.value, "jti": jti, } token = jwt.encode( payload, settings.secret_key.get_secret_value(), algorithm=settings.jwt_algorithm, ) return token, jti def decode_token(token: str) -> TokenPayload | None: """Decode and validate a JWT token. Args: token: The JWT token string to decode. Returns: TokenPayload if valid, None if invalid or expired. Example: payload = decode_token(token) if payload: user_id = UUID(payload.sub) """ try: payload_dict = jwt.decode( token, settings.secret_key.get_secret_value(), algorithms=[settings.jwt_algorithm], ) return TokenPayload(**payload_dict) except JWTError: return None def verify_access_token(token: str) -> uuid.UUID | None: """Verify an access token and extract the user ID. Args: token: The JWT access token to verify. Returns: User UUID if valid access token, None otherwise. Example: user_id = verify_access_token(token) if user_id: user = await user_service.get_user_by_id(db, user_id) """ payload = decode_token(token) if payload is None: return None if payload.type != TokenType.ACCESS: return None try: return uuid.UUID(payload.sub) except ValueError: return None def verify_refresh_token(token: str) -> tuple[uuid.UUID, str] | None: """Verify a refresh token and extract user ID and JTI. The JTI should be checked against the token store to ensure the token hasn't been revoked. Args: token: The JWT refresh token to verify. Returns: Tuple of (user UUID, JTI) if valid refresh token, None otherwise. Example: result = verify_refresh_token(token) if result: user_id, jti = result if await token_store.is_token_valid(user_id, jti): # Issue new access token """ payload = decode_token(token) if payload is None: return None if payload.type != TokenType.REFRESH: return None if payload.jti is None: return None try: user_id = uuid.UUID(payload.sub) return user_id, payload.jti except ValueError: return None def get_token_expiration_seconds() -> int: """Get the access token expiration time in seconds. Useful for the expires_in field in token responses. Returns: Access token lifetime in seconds. """ return settings.jwt_expire_minutes * 60 def get_refresh_token_expiration() -> datetime: """Get the expiration datetime for a new refresh token. Useful for setting TTL when storing in Redis. Returns: Datetime when a refresh token created now would expire. """ return datetime.now(UTC) + timedelta(days=settings.jwt_refresh_expire_days)