diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 4fe64ec41..81db8e8d1 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -385,7 +385,8 @@ async def redeem_lnurl_withdraw( res = {} - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: lnurl = decode_lnurl(lnurl_request) r = await client.get(str(lnurl)) res = r.json() @@ -419,7 +420,8 @@ async def redeem_lnurl_withdraw( except Exception: pass - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: try: await client.get(res["callback"], params=params) except Exception: @@ -482,7 +484,8 @@ async def perform_lnurlauth( sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der) - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: assert key.verifying_key, "LNURLauth verifying_key does not exist" r = await client.get( callback, diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 08f7a1e96..85aa60087 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -120,7 +120,8 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): # dispatch balance_notify url = await get_balance_notify(payment.wallet_id) if url: - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: try: r = await client.post(url, timeout=4) await mark_webhook_sent(payment, r.status_code) @@ -152,7 +153,8 @@ async def dispatch_webhook(payment: Payment): if not payment.webhook: return await mark_webhook_sent(payment, -1) - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: data = payment.dict() try: r = await client.post(payment.webhook, json=data, timeout=40) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 89bf5e2aa..6699392f3 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -297,7 +297,8 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet): if data.lnurl_balance_check is not None: await save_balance_check(wallet.id, data.lnurl_balance_check) - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: try: r = await client.get( data.lnurl_callback, @@ -412,7 +413,8 @@ async def api_payments_pay_lnurl( ): domain = urlparse(data.callback).netloc - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: try: r = await client.get( data.callback, @@ -594,7 +596,8 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type assert lnurlauth_key.verifying_key params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) else: - async with httpx.AsyncClient(follow_redirects=True) as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client: r = await client.get(url, timeout=5) r.raise_for_status() if r.is_error: diff --git a/lnbits/core/views/node_api.py b/lnbits/core/views/node_api.py index b9acc9e30..42555b039 100644 --- a/lnbits/core/views/node_api.py +++ b/lnbits/core/views/node_api.py @@ -179,7 +179,8 @@ class NodeRank(BaseModel): ) async def api_get_1ml_stats(node: Node = Depends(require_node)) -> Optional[NodeRank]: node_id = await node.get_id() - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: r = await client.get(url=f"https://1ml.com/node/{node_id}/json", timeout=15) try: r.raise_for_status() diff --git a/lnbits/extension_manager.py b/lnbits/extension_manager.py index ed5f38a09..c4e1739f0 100644 --- a/lnbits/extension_manager.py +++ b/lnbits/extension_manager.py @@ -144,16 +144,11 @@ async def fetch_github_release_config( async def github_api_get(url: str, error_msg: Optional[str]) -> Any: - async with httpx.AsyncClient() as client: - headers = ( - {"Authorization": "Bearer " + settings.lnbits_ext_github_token} - if settings.lnbits_ext_github_token - else None - ) - resp = await client.get( - url, - headers=headers, - ) + headers = {"User-Agent": settings.user_agent} + if settings.lnbits_ext_github_token: + headers["Authorization"] = f"Bearer {settings.lnbits_ext_github_token}" + async with httpx.AsyncClient(headers=headers) as client: + resp = await client.get(url) if resp.status_code != 200: logger.warning(f"{error_msg} ({url}): {resp.text}") resp.raise_for_status() diff --git a/lnbits/settings.py b/lnbits/settings.py index 6800f8cf1..42f462ba4 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -297,6 +297,7 @@ class EnvSettings(LNbitsSettings): lnbits_extensions_path: str = Field(default="lnbits") super_user: str = Field(default="") version: str = Field(default="0.0.0") + user_agent: str = Field(default="") enable_log_to_file: bool = Field(default=True) log_rotation: str = Field(default="100 MB") log_retention: str = Field(default="3 months") @@ -432,6 +433,9 @@ settings.lnbits_path = str(path.dirname(path.realpath(__file__))) settings.version = importlib.metadata.version("lnbits") +if not settings.user_agent: + settings.user_agent = f"LNbits/{settings.version}" + # printing environment variable for debugging if not settings.lnbits_admin_ui: logger.debug("Environment Settings:") diff --git a/lnbits/utils/exchange_rates.py b/lnbits/utils/exchange_rates.py index 89567535d..523903ac6 100644 --- a/lnbits/utils/exchange_rates.py +++ b/lnbits/utils/exchange_rates.py @@ -4,6 +4,7 @@ from typing import Callable, NamedTuple import httpx from loguru import logger +from lnbits.settings import settings from lnbits.utils.cache import cache currencies = { @@ -246,7 +247,8 @@ async def btc_price(currency: str) -> float: async def fetch_price(provider: Provider): url = provider.api_url.format(**replacements) try: - async with httpx.AsyncClient() as client: + headers = {"User-Agent": settings.user_agent} + async with httpx.AsyncClient(headers=headers) as client: r = await client.get(url, timeout=0.5) r.raise_for_status() data = r.json() diff --git a/lnbits/wallets/alby.py b/lnbits/wallets/alby.py index fd1bf140d..e0eaab41b 100644 --- a/lnbits/wallets/alby.py +++ b/lnbits/wallets/alby.py @@ -28,7 +28,7 @@ class AlbyWallet(Wallet): self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.auth = { "Authorization": "Bearer " + settings.alby_access_token, - "User-Agent": f"LNbits/{settings.version}", + "User-Agent": settings.user_agent, } self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth) diff --git a/lnbits/wallets/corelightningrest.py b/lnbits/wallets/corelightningrest.py index 316d21b6a..461ba726e 100644 --- a/lnbits/wallets/corelightningrest.py +++ b/lnbits/wallets/corelightningrest.py @@ -38,14 +38,15 @@ class CoreLightningRestWallet(Wallet): self.url = ( f"https://{self.url}" if not self.url.startswith("http") else self.url ) - self.auth = { + headers = { "macaroon": self.macaroon, "encodingtype": "hex", "accept": "application/json", + "User-Agent": settings.user_agent, } self.cert = settings.corelightning_rest_cert or False - self.client = httpx.AsyncClient(verify=self.cert, headers=self.auth) + self.client = httpx.AsyncClient(verify=self.cert, headers=headers) self.last_pay_index = 0 self.statuses = { "paid": True, diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index ac6c5e42b..b96ee043b 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -40,8 +40,11 @@ class EclairWallet(Wallet): encodedAuth = base64.b64encode(f":{passw}".encode()) auth = str(encodedAuth, "utf-8") - self.auth = {"Authorization": f"Basic {auth}"} - self.client = httpx.AsyncClient(base_url=self.url, headers=self.auth) + self.headers = { + "Authorization": f"Basic {auth}", + "User-Agent": settings.user_agent, + } + self.client = httpx.AsyncClient(base_url=self.url, headers=self.headers) async def cleanup(self): try: @@ -214,7 +217,7 @@ class EclairWallet(Wallet): try: async with connect( self.ws_url, - extra_headers=[("Authorization", self.auth["Authorization"])], + extra_headers=[("Authorization", self.headers["Authorization"])], ) as ws: while True: message = await ws.recv() diff --git a/lnbits/wallets/lnbits.py b/lnbits/wallets/lnbits.py index 0bc47d06c..7e05526f7 100644 --- a/lnbits/wallets/lnbits.py +++ b/lnbits/wallets/lnbits.py @@ -28,8 +28,8 @@ class LNbitsWallet(Wallet): ) if not self.endpoint or not key: raise Exception("cannot initialize lnbits wallet") - self.key = {"X-Api-Key": key} - self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.key) + self.headers = {"X-Api-Key": key, "User-Agent": settings.user_agent} + self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers) async def cleanup(self): try: @@ -136,7 +136,9 @@ class LNbitsWallet(Wallet): while True: try: - async with httpx.AsyncClient(timeout=None, headers=self.key) as client: + async with httpx.AsyncClient( + timeout=None, headers=self.headers + ) as client: del client.headers[ "accept-encoding" ] # we have to disable compression for SSEs diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index 757ec813f..ee9b1d6af 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -67,9 +67,12 @@ class LndRestWallet(Wallet): # even on startup self.cert = cert or True - self.auth = {"Grpc-Metadata-macaroon": self.macaroon} + headers = { + "Grpc-Metadata-macaroon": self.macaroon, + "User-Agent": settings.user_agent, + } self.client = httpx.AsyncClient( - base_url=self.endpoint, headers=self.auth, verify=self.cert + base_url=self.endpoint, headers=headers, verify=self.cert ) async def cleanup(self): diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py index 9c7e3a0e7..7640668ba 100644 --- a/lnbits/wallets/lnpay.py +++ b/lnbits/wallets/lnpay.py @@ -28,8 +28,11 @@ class LNPayWallet(Wallet): self.wallet_key = wallet_key self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.auth = {"X-Api-Key": settings.lnpay_api_key} - self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth) + headers = { + "X-Api-Key": settings.lnpay_api_key, + "User-Agent": settings.user_agent, + } + self.client = httpx.AsyncClient(base_url=self.endpoint, headers=headers) async def cleanup(self): try: diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py index 048727061..9760162f1 100644 --- a/lnbits/wallets/lntips.py +++ b/lnbits/wallets/lntips.py @@ -29,8 +29,11 @@ class LnTipsWallet(Wallet): if not endpoint or not key: raise Exception("cannot initialize lntxbod") self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.auth = {"Authorization": f"Basic {key}"} - self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth) + headers = { + "Authorization": f"Basic {key}", + "User-Agent": settings.user_agent, + } + self.client = httpx.AsyncClient(base_url=self.endpoint, headers=headers) async def cleanup(self): try: diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index b922a3358..f0be68382 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -21,17 +21,20 @@ class OpenNodeWallet(Wallet): def __init__(self): endpoint = settings.opennode_api_endpoint - key = ( + self.key = ( settings.opennode_key or settings.opennode_admin_key or settings.opennode_invoice_key ) - if not endpoint or not key: + if not endpoint or not self.key: raise Exception("cannot initialize opennode") self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.auth = {"Authorization": key} - self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth) + headers = { + "Authorization": self.key, + "User-Agent": settings.user_agent, + } + self.client = httpx.AsyncClient(base_url=self.endpoint, headers=headers) async def cleanup(self): try: @@ -142,7 +145,7 @@ class OpenNodeWallet(Wallet): # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) # charge_id = data["id"] - # x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256") + # x = hmac.new(self.key.encode("ascii"), digestmod="sha256") # x.update(charge_id.encode("ascii")) # if x.hexdigest() != data["hashed_order"]: # logger.error("invalid webhook, not from opennode") diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index a7c51ce42..09002be14 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -32,9 +32,8 @@ class SparkWallet(Wallet): self.url = settings.spark_url.replace("/rpc", "") self.token = settings.spark_token assert self.token, "spark wallet token does not exist" - self.client = httpx.AsyncClient( - base_url=self.url, headers={"X-Access": self.token} - ) + headers = {"X-Access": self.token, "User-Agent": settings.user_agent} + self.client = httpx.AsyncClient(base_url=self.url, headers=headers) async def cleanup(self): try: