import logging import time from typing import cast import backoff from aiohttp import ClientError from auth0.authentication import GetToken from auth0.exceptions import Auth0Error, RateLimitError from auth0.management import Auth0 import config from src.errors import UserProcessError __all__ = ['auth0_client'] logger = logging.getLogger('users_cleanup') class Auth0MngClient: """Singleton async auth0 management client.""" def __init__(self) -> None: self._client, self._exp_time = self._get_client() def _get_client(self) -> tuple[Auth0, int]: get_token = GetToken( domain=config.AUTH0_DOMAIN, client_id=config.AUTH0_CLIENT_ID, client_secret=config.AUTH0_CLIENT_SECRET ) token = get_token.client_credentials(f'https://{config.AUTH0_DOMAIN}/api/v2/') exp_time = int(time.time()) + token['expires_in'] client = Auth0(config.AUTH0_DOMAIN, token['access_token']) return client, exp_time @property def _is_token_expired(self) -> bool: return time.time() >= self._exp_time @property def client(self) -> Auth0: if self._is_token_expired: self._client, self._exp_time = self._get_client() return self._client @backoff.on_exception(backoff.expo, (Auth0Error, ClientError), max_tries=3) async def get_id_by_email(self, email: str) -> str: users = await self.client.users_by_email.search_users_by_email_async( email.lower(), fields=['user_id'], include_fields=True ) if len(users) == 0: raise UserProcessError('Auth0 user not found') if len(users) > 1: raise UserProcessError('Found more than one auth0 user') return cast(str, users[0]['user_id']) @backoff.on_exception(backoff.expo, (Auth0Error, ClientError), max_tries=3) @backoff.on_exception( backoff.runtime, RateLimitError, value=lambda e: max(int(e.reset_at - time.time()), 1), max_tries=3, ) async def delete_user(self, user_id: str) -> None: await self.client.users.delete_async(user_id) auth0_client = Auth0MngClient()