From de006129657ce3dac2b1f2fc0ab1b91e23e44241 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 21 Mar 2026 03:58:06 +0300 Subject: [PATCH 1/4] fix: retry Remnawave API calls without externalSquadUuid on A039 FK violation When a tariff has a stale external_squad_uuid that no longer exists in the Remnawave panel, PATCH/POST /api/users fails with A039 (P2003 FK constraint violation). This caused subscriptions to not sync with the panel even though balance was already charged. Now both update_user() and create_user() catch A039 errors and automatically retry without externalSquadUuid, logging a warning about the stale UUID. The subscription sync succeeds without the external squad assignment rather than failing entirely. --- app/external/remnawave_api.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app/external/remnawave_api.py b/app/external/remnawave_api.py index 154c1ed7..f5e588b4 100644 --- a/app/external/remnawave_api.py +++ b/app/external/remnawave_api.py @@ -469,7 +469,21 @@ class RemnaWaveAPI: hwidDeviceLimit=data.get('hwidDeviceLimit'), status=data.get('status'), ) - response = await self._make_request('POST', '/api/users', data) + try: + response = await self._make_request('POST', '/api/users', data) + except RemnaWaveAPIError as e: + # A039 = FK violation on externalSquadUuid — retry without it + error_code = (e.response_data or {}).get('errorCode', '') + if error_code == 'A039' and 'externalSquadUuid' in data: + stale_uuid = data.pop('externalSquadUuid') + logger.warning( + 'A039 FK violation on externalSquadUuid, retrying without it', + stale_uuid=stale_uuid, + username=data.get('username'), + ) + response = await self._make_request('POST', '/api/users', data) + else: + raise user = self._parse_user(response['response']) logger.info( 'POST /api/users response', @@ -570,10 +584,20 @@ class RemnaWaveAPI: try: response = await self._make_request('PATCH', '/api/users', data) - except Exception: - # Логируем полный payload при ошибке для диагностики A039 - logger.error('PATCH /api/users FAILED — full payload', payload=data) - raise + except RemnaWaveAPIError as e: + # A039 = FK violation on externalSquadUuid — retry without it + error_code = (e.response_data or {}).get('errorCode', '') + if error_code == 'A039' and 'externalSquadUuid' in data: + stale_uuid = data.pop('externalSquadUuid') + logger.warning( + 'A039 FK violation on externalSquadUuid, retrying without it', + stale_uuid=stale_uuid, + uuid=uuid, + ) + response = await self._make_request('PATCH', '/api/users', data) + else: + logger.error('PATCH /api/users FAILED — full payload', payload=data) + raise user = self._parse_user(response['response']) logger.info( 'PATCH /api/users response', From 4990ddf9e46495b65fc3638ea8d6bed0cbe6b857 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 21 Mar 2026 04:08:18 +0300 Subject: [PATCH 2/4] fix: add diagnostic payload logging in create_user error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consistent with update_user — log full payload before re-raising non-A039 errors to aid debugging. --- app/external/remnawave_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/external/remnawave_api.py b/app/external/remnawave_api.py index f5e588b4..dcc61132 100644 --- a/app/external/remnawave_api.py +++ b/app/external/remnawave_api.py @@ -483,6 +483,7 @@ class RemnaWaveAPI: ) response = await self._make_request('POST', '/api/users', data) else: + logger.error('POST /api/users FAILED — full payload', payload=data) raise user = self._parse_user(response['response']) logger.info( From 3c5bf4fa22d1cdf144269f4e6ab32a4523c8f1f3 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 21 Mar 2026 04:21:11 +0300 Subject: [PATCH 3/4] feat: add SOCKS proxy support for nalogo (tax service) module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route all nalog.ru API traffic through SOCKS proxy. Uses NALOGO_PROXY_URL env var (falls back to PROXY_URL if not set). Adds httpx[socks] dependency. - Thread proxy_url through Client → AuthProviderImpl + AsyncHTTPClient - Extract mask_proxy_url() and sanitize_proxy_error() utilities - Add socks5h:// scheme support for remote DNS resolution - Sanitize proxy credentials in error messages - Log masked proxy URL at startup and service init --- .env.example | 1 + app/bot.py | 12 ++++++++---- app/config.py | 18 ++++++++++++----- app/lib/nalogo/_http.py | 4 +++- app/lib/nalogo/auth.py | 10 ++++++---- app/lib/nalogo/client.py | 4 ++++ app/services/nalogo_service.py | 15 +++++++++++++-- app/utils/proxy.py | 35 ++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + uv.lock | 18 ++++++++++++++++- 10 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 app/utils/proxy.py diff --git a/.env.example b/.env.example index 682db9a7..67124ae5 100644 --- a/.env.example +++ b/.env.example @@ -522,6 +522,7 @@ NALOGO_STORAGE_PATH=./nalogo_tokens.json # Путь к файлу с токен NALOGO_QUEUE_CHECK_INTERVAL=300 # Интервал проверки очереди чеков (секунды) NALOGO_QUEUE_RECEIPT_DELAY=3 # Задержка между отправкой чеков (секунды) NALOGO_QUEUE_MAX_ATTEMPTS=10 # Максимум попыток отправки одного чека +# NALOGO_PROXY_URL=socks5://127.0.0.1:1080 # SOCKS прокси для nalog.ru (если не задан — используется PROXY_URL) # ===== НАСТРОЙКИ ОПИСАНИЙ ПЛАТЕЖЕЙ ===== # Эти настройки позволяют изменить описания платежей, diff --git a/app/bot.py b/app/bot.py index 3d656be7..66594c38 100644 --- a/app/bot.py +++ b/app/bot.py @@ -102,11 +102,15 @@ async def setup_bot() -> tuple[Bot, Dispatcher]: proxy_url = settings.get_proxy_url() if proxy_url: - from urllib.parse import urlparse + from app.utils.proxy import mask_proxy_url - parsed = urlparse(proxy_url) - masked = f'{parsed.scheme}://***@{parsed.hostname}:{parsed.port}' if parsed.username else proxy_url - logger.info('Proxy configured', proxy_url=masked) + logger.info('Proxy configured', proxy_url=mask_proxy_url(proxy_url)) + + nalogo_proxy_url = settings.get_nalogo_proxy_url() + if nalogo_proxy_url: + from app.utils.proxy import mask_proxy_url + + logger.info('Nalogo proxy configured', proxy_url=mask_proxy_url(nalogo_proxy_url)) maintenance_service.set_bot(bot) logger.info('Бот установлен в maintenance_service') diff --git a/app/config.py b/app/config.py index a50e286f..9afd1a2d 100644 --- a/app/config.py +++ b/app/config.py @@ -376,6 +376,7 @@ class Settings(BaseSettings): NALOGO_PASSWORD: str | None = None NALOGO_DEVICE_ID: str | None = None NALOGO_STORAGE_PATH: str = './nalogo_tokens.json' + NALOGO_PROXY_URL: str | None = None # SOCKS proxy for nalog.ru; falls back to PROXY_URL if not set AUTO_PURCHASE_AFTER_TOPUP_ENABLED: bool = False @@ -807,7 +808,7 @@ class Settings(BaseSettings): # Format: socks5://user:password@host:port or socks5://host:port PROXY_URL: str | None = None - @field_validator('PROXY_URL', mode='before') + @field_validator('PROXY_URL', 'NALOGO_PROXY_URL', mode='before') @classmethod def validate_proxy_url(cls, value: str | None) -> str | None: if not value: @@ -815,13 +816,13 @@ class Settings(BaseSettings): from urllib.parse import urlparse parsed = urlparse(value) - if parsed.scheme not in ('socks5', 'socks4'): + if parsed.scheme not in ('socks5', 'socks5h', 'socks4'): raise ValueError( - f'PROXY_URL must use socks5:// or socks4:// scheme, got: {parsed.scheme!r}. ' - 'HTTP proxies are not supported for security reasons (bot token would be exposed).' + f'Proxy URL must use socks5://, socks5h://, or socks4:// scheme, got: {parsed.scheme!r}. ' + 'HTTP proxies are not supported for security reasons.' ) if not parsed.hostname: - raise ValueError('PROXY_URL must contain a hostname') + raise ValueError('Proxy URL must contain a hostname') return value @field_validator('MAIN_MENU_MODE', mode='before') @@ -954,6 +955,13 @@ class Settings(BaseSettings): """Return SOCKS5 proxy URL or None.""" return self.PROXY_URL if self.PROXY_URL else None + def get_nalogo_proxy_url(self) -> str | None: + """Return SOCKS proxy URL for nalogo or None. + + Uses NALOGO_PROXY_URL if set, otherwise falls back to PROXY_URL. + """ + return self.NALOGO_PROXY_URL or self.PROXY_URL + def is_admin(self, telegram_id: int | None = None, email: str | None = None) -> bool: """ Check if user is admin by telegram_id or email. diff --git a/app/lib/nalogo/_http.py b/app/lib/nalogo/_http.py index 34062fcf..73716d76 100644 --- a/app/lib/nalogo/_http.py +++ b/app/lib/nalogo/_http.py @@ -41,11 +41,13 @@ class AsyncHTTPClient: auth_provider: AuthProvider, default_headers: dict[str, str] | None = None, timeout: float = 10.0, + proxy_url: str | None = None, ): self.base_url = base_url self.auth_provider = auth_provider self.default_headers = default_headers or {} self.timeout = timeout + self.proxy_url = proxy_url self._refresh_lock = asyncio.Lock() self.max_retries = 2 # Same as PHP AuthenticationPlugin::RETRY_LIMIT @@ -124,7 +126,7 @@ class AsyncHTTPClient: if json_data is not None: request_kwargs['json'] = json_data - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(proxy=self.proxy_url) as client: # Initial request response = await client.request(**request_kwargs) diff --git a/app/lib/nalogo/auth.py b/app/lib/nalogo/auth.py index ad6311ac..d07127a8 100644 --- a/app/lib/nalogo/auth.py +++ b/app/lib/nalogo/auth.py @@ -40,6 +40,7 @@ class AuthProviderImpl(AuthProvider): base_url: str = 'https://lknpd.nalog.ru/api', storage_path: str | None = None, device_id: str | None = None, + proxy_url: str | None = None, ): self.base_url_v1 = f'{base_url}/v1' self.base_url_v2 = f'{base_url}/v2' @@ -47,6 +48,7 @@ class AuthProviderImpl(AuthProvider): self.device_id = device_id or generate_device_id() self.device_info = DeviceInfo(sourceDeviceId=self.device_id) self._token_data: dict[str, Any] | None = None + self.proxy_url = proxy_url # Default headers similar to PHP Authenticator self.default_headers = { @@ -130,7 +132,7 @@ class AuthProviderImpl(AuthProvider): 'deviceInfo': self.device_info.model_dump(), } - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(proxy=self.proxy_url) as client: response = await client.post( f'{self.base_url_v1}/auth/lkfl', json=request_data, @@ -165,7 +167,7 @@ class AuthProviderImpl(AuthProvider): 'requireTpToBeActive': True, } - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(proxy=self.proxy_url) as client: response = await client.post( f'{self.base_url_v2}/auth/challenge/sms/start', json=request_data, @@ -200,7 +202,7 @@ class AuthProviderImpl(AuthProvider): 'deviceInfo': self.device_info.model_dump(), } - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(proxy=self.proxy_url) as client: response = await client.post( f'{self.base_url_v1}/auth/challenge/sms/verify', json=request_data, @@ -233,7 +235,7 @@ class AuthProviderImpl(AuthProvider): } try: - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(proxy=self.proxy_url) as client: response = await client.post( f'{self.base_url_v1}/auth/token', json=request_data, diff --git a/app/lib/nalogo/client.py b/app/lib/nalogo/client.py index 648e2492..d6300d72 100644 --- a/app/lib/nalogo/client.py +++ b/app/lib/nalogo/client.py @@ -36,6 +36,7 @@ class Client: storage_path: str | None = None, device_id: str | None = None, timeout: float = 10.0, + proxy_url: str | None = None, ): """ Initialize Moy Nalog API client. @@ -45,6 +46,7 @@ class Client: storage_path: Optional file path for token storage device_id: Optional device ID (auto-generated if not provided) timeout: HTTP request timeout in seconds + proxy_url: Optional SOCKS proxy URL for routing traffic """ self.base_url = base_url self.timeout = timeout @@ -54,6 +56,7 @@ class Client: base_url=base_url, storage_path=storage_path, device_id=device_id, + proxy_url=proxy_url, ) # Initialize HTTP client with auth middleware @@ -67,6 +70,7 @@ class Client: 'Referrer': 'https://lknpd.nalog.ru/auth/login', }, timeout=timeout, + proxy_url=proxy_url, ) # User profile data (for receipt operations) diff --git a/app/services/nalogo_service.py b/app/services/nalogo_service.py index d937b816..e06adaaf 100644 --- a/app/services/nalogo_service.py +++ b/app/services/nalogo_service.py @@ -41,18 +41,29 @@ class NaloGoService: try: # Таймаут 30 секунд — nalog.ru иногда отвечает медленно timeout = getattr(settings, 'NALOGO_TIMEOUT', 30.0) + proxy_url = settings.get_nalogo_proxy_url() self.client = Client( base_url='https://lknpd.nalog.ru/api', storage_path=storage_path, device_id=device_id or 'bot-device-123', timeout=timeout, + proxy_url=proxy_url, ) self.inn = inn self.password = password self.configured = True - logger.info('NaloGO клиент инициализирован для ИНН: ...', inn=inn[:5]) + if proxy_url: + from app.utils.proxy import mask_proxy_url + + logger.info( + 'NaloGO клиент инициализирован с прокси', inn=inn[:5], proxy_url=mask_proxy_url(proxy_url) + ) + else: + logger.info('NaloGO клиент инициализирован для ИНН: ...', inn=inn[:5]) except Exception as error: - logger.error('Ошибка инициализации NaloGO клиента', error=error, exc_info=True) + from app.utils.proxy import sanitize_proxy_error + + logger.error('Ошибка инициализации NaloGO клиента', error=sanitize_proxy_error(error)) self.configured = False @staticmethod diff --git a/app/utils/proxy.py b/app/utils/proxy.py new file mode 100644 index 00000000..eb92c9d8 --- /dev/null +++ b/app/utils/proxy.py @@ -0,0 +1,35 @@ +"""Proxy URL utilities for safe logging and error handling.""" + +import re +from urllib.parse import urlparse + + +def mask_proxy_url(proxy_url: str) -> str: + """Mask credentials in a proxy URL for safe logging. + + Handles edge cases: + - No credentials: returns URL as-is + - Username + password: masks both with *** + - Password-only: masks as well + - No explicit port: omits :port part + """ + parsed = urlparse(proxy_url) + if not parsed.username and not parsed.password: + return proxy_url + host = parsed.hostname or 'unknown' + port_part = f':{parsed.port}' if parsed.port else '' + return f'{parsed.scheme}://***@{host}{port_part}' + + +_PROXY_CRED_RE = re.compile(r'socks[45h]*://[^@\s]+@', re.IGNORECASE) + + +def sanitize_proxy_error(error: Exception) -> str: + """Strip proxy credentials from exception messages. + + httpx/socksio may include the full proxy URL (with credentials) + in connection error messages and tracebacks. This function removes + credentials from the error string. + """ + msg = str(error) + return _PROXY_CRED_RE.sub('socks5://***@', msg) diff --git a/pyproject.toml b/pyproject.toml index e3f882d0..d77ecb43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ 'structlog>=25.1.0,<26', 'rich>=14.0', 'aiohttp-socks>=0.10.1', + 'httpx[socks]>=0.27.0', ] [dependency-groups] diff --git a/uv.lock b/uv.lock index b04db498..c85d0f8f 100644 --- a/uv.lock +++ b/uv.lock @@ -623,6 +623,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[package.optional-dependencies] +socks = [ + { name = "socksio" }, +] + [[package]] name = "idna" version = "3.11" @@ -1137,7 +1142,7 @@ wheels = [ [[package]] name = "remnawave-bedolaga-telegram-bot" -version = "3.36.0" +version = "3.36.1" source = { virtual = "." } dependencies = [ { name = "aiogram" }, @@ -1148,6 +1153,7 @@ dependencies = [ { name = "bcrypt" }, { name = "cryptography" }, { name = "fastapi", extra = ["standard"] }, + { name = "httpx", extra = ["socks"] }, { name = "packaging" }, { name = "pyjwt" }, { name = "python-dateutil" }, @@ -1179,6 +1185,7 @@ requires-dist = [ { name = "bcrypt", specifier = ">=5.0.0" }, { name = "cryptography", specifier = ">=44.0.1" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.129.0" }, + { name = "httpx", extras = ["socks"], specifier = ">=0.27.0" }, { name = "packaging", specifier = ">=26.0" }, { name = "pyjwt", specifier = ">=2.11.0" }, { name = "python-dateutil", specifier = ">=2.9.0.post0" }, @@ -1322,6 +1329,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.46" From 3bf31055e71ff64e6a6d94486bb7f7775ac7dc91 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 21 Mar 2026 04:27:15 +0300 Subject: [PATCH 4/4] fix: sanitize proxy credentials in all nalogo error paths - Apply sanitize_proxy_error() to all 8 error handlers in nalogo_service - Remove exc_info=True from error paths that could expose proxy creds - Fix regex backreference to preserve original SOCKS scheme - Consolidate proxy utility imports to module level - Add source indicator (NALOGO_PROXY_URL vs fallback) to startup log --- app/bot.py | 14 +++++++------- app/services/nalogo_service.py | 23 ++++++++++------------- app/utils/proxy.py | 6 +++--- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/bot.py b/app/bot.py index 66594c38..0b7ce98d 100644 --- a/app/bot.py +++ b/app/bot.py @@ -101,16 +101,16 @@ async def setup_bot() -> tuple[Bot, Dispatcher]: bot = create_bot() proxy_url = settings.get_proxy_url() - if proxy_url: - from app.utils.proxy import mask_proxy_url - - logger.info('Proxy configured', proxy_url=mask_proxy_url(proxy_url)) - nalogo_proxy_url = settings.get_nalogo_proxy_url() - if nalogo_proxy_url: + + if proxy_url or nalogo_proxy_url: from app.utils.proxy import mask_proxy_url - logger.info('Nalogo proxy configured', proxy_url=mask_proxy_url(nalogo_proxy_url)) + if proxy_url: + logger.info('Proxy configured', proxy_url=mask_proxy_url(proxy_url)) + if nalogo_proxy_url: + source = 'NALOGO_PROXY_URL' if settings.NALOGO_PROXY_URL else 'PROXY_URL (fallback)' + logger.info('Nalogo proxy configured', proxy_url=mask_proxy_url(nalogo_proxy_url), source=source) maintenance_service.set_bot(bot) logger.info('Бот установлен в maintenance_service') diff --git a/app/services/nalogo_service.py b/app/services/nalogo_service.py index e06adaaf..9aa97d4a 100644 --- a/app/services/nalogo_service.py +++ b/app/services/nalogo_service.py @@ -10,6 +10,7 @@ from app.config import settings from app.lib.nalogo import Client from app.lib.nalogo.dto.income import IncomeClient, IncomeType from app.utils.cache import cache +from app.utils.proxy import mask_proxy_url, sanitize_proxy_error logger = structlog.get_logger(__name__) @@ -53,16 +54,12 @@ class NaloGoService: self.password = password self.configured = True if proxy_url: - from app.utils.proxy import mask_proxy_url - logger.info( 'NaloGO клиент инициализирован с прокси', inn=inn[:5], proxy_url=mask_proxy_url(proxy_url) ) else: logger.info('NaloGO клиент инициализирован для ИНН: ...', inn=inn[:5]) except Exception as error: - from app.utils.proxy import sanitize_proxy_error - logger.error('Ошибка инициализации NaloGO клиента', error=sanitize_proxy_error(error)) self.configured = False @@ -292,9 +289,9 @@ class NaloGoService: return True except Exception as error: if self._is_service_unavailable(error): - logger.warning('NaloGO временно недоступен (техработы)', error=str(error)[:200]) + logger.warning('NaloGO временно недоступен (техработы)', error=sanitize_proxy_error(error)) else: - logger.error('Ошибка аутентификации в NaloGO', error=error, exc_info=True) + logger.error('Ошибка аутентификации в NaloGO', error=sanitize_proxy_error(error)) return False async def create_receipt( @@ -366,7 +363,7 @@ class NaloGoService: name, amount, quantity, client_info, payment_id, telegram_user_id, amount_kopeks ) else: - logger.error('Ошибка аутентификации NaloGO', auth_error=auth_error, exc_info=True) + logger.error('Ошибка аутентификации NaloGO', auth_error=sanitize_proxy_error(auth_error)) return None # ЭТАП 2: Создание чека @@ -411,9 +408,9 @@ class NaloGoService: # ВАЖНО: Аутентификация была успешной, запрос на создание чека УШЁЛ # При таймауте чек МОГ быть создан на сервере — НЕ добавляем в очередь! if self._is_service_unavailable(error): - error_msg = str(error)[:200] + error_msg = sanitize_proxy_error(error)[:200] logger.error( - '⚠️ ТАЙМАУТ после успешной аутентификации! Чек МОГ быть создан! (payment_id=, сумма=₽). Сохраняем в очередь проверки. Проверьте lknpd.nalog.ru', + 'ТАЙМАУТ после успешной аутентификации! Чек МОГ быть создан!', payment_id=payment_id, amount=amount, ) @@ -429,7 +426,7 @@ class NaloGoService: error_message=error_msg, ) else: - logger.error('Ошибка создания чека в NaloGO', error=error, exc_info=True) + logger.error('Ошибка создания чека в NaloGO', error=sanitize_proxy_error(error)) return None async def get_queue_length(self) -> int: @@ -522,7 +519,7 @@ class NaloGoService: return None except Exception as error: - logger.warning('Ошибка проверки дубликата чека', error=error) + logger.warning('Ошибка проверки дубликата чека', error=sanitize_proxy_error(error)) return None async def get_incomes( @@ -566,7 +563,7 @@ class NaloGoService: except Exception as error: if self._is_service_unavailable(error): - logger.warning('NaloGO временно недоступен', error=error) + logger.warning('NaloGO временно недоступен', error=sanitize_proxy_error(error)) else: - logger.error('Ошибка получения списка доходов', error=error, exc_info=True) + logger.error('Ошибка получения списка доходов', error=sanitize_proxy_error(error)) return None # None = ошибка, [] = нет чеков diff --git a/app/utils/proxy.py b/app/utils/proxy.py index eb92c9d8..19a71707 100644 --- a/app/utils/proxy.py +++ b/app/utils/proxy.py @@ -21,7 +21,7 @@ def mask_proxy_url(proxy_url: str) -> str: return f'{parsed.scheme}://***@{host}{port_part}' -_PROXY_CRED_RE = re.compile(r'socks[45h]*://[^@\s]+@', re.IGNORECASE) +_PROXY_CRED_RE = re.compile(r'(socks[45h]*://)([^@\s]+@)', re.IGNORECASE) def sanitize_proxy_error(error: Exception) -> str: @@ -29,7 +29,7 @@ def sanitize_proxy_error(error: Exception) -> str: httpx/socksio may include the full proxy URL (with credentials) in connection error messages and tracebacks. This function removes - credentials from the error string. + credentials from the error string while preserving the original scheme. """ msg = str(error) - return _PROXY_CRED_RE.sub('socks5://***@', msg) + return _PROXY_CRED_RE.sub(r'\1***@', msg)