[fix] callback url validation (#2959)

This commit is contained in:
Vlad Stan
2025-02-13 15:11:46 +02:00
committed by GitHub
parent b76d8b5458
commit bfa23568e3
14 changed files with 139 additions and 5 deletions
+4 -1
View File
@@ -13,7 +13,7 @@ from lnbits.decorators import (
WalletTypeInfo,
require_admin_key,
)
from lnbits.helpers import url_for
from lnbits.helpers import check_callback_url, url_for
from lnbits.lnurl import LnurlErrorResponse
from lnbits.lnurl import decode as decode_lnurl
from lnbits.settings import settings
@@ -37,6 +37,7 @@ async def redeem_lnurl_withdraw(
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers) as client:
lnurl = decode_lnurl(lnurl_request)
check_callback_url(str(lnurl))
r = await client.get(str(lnurl))
res = r.json()
@@ -72,6 +73,7 @@ async def redeem_lnurl_withdraw(
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers) as client:
try:
check_callback_url(res["callback"])
await client.get(res["callback"], params=params)
except Exception:
pass
@@ -135,6 +137,7 @@ async def perform_lnurlauth(
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers) as client:
assert key.verifying_key, "LNURLauth verifying_key does not exist"
check_callback_url(callback)
r = await client.get(
callback,
params={
+6 -1
View File
@@ -6,6 +6,7 @@ from py_vapid.utils import b64urlencode
from lnbits.db import dict_to_model
from lnbits.settings import (
EditableSettings,
UpdateSettings,
readonly_variables,
settings,
)
@@ -37,8 +38,12 @@ async def check_webpush_settings():
logger.info(f"Pubkey: {settings.lnbits_webpush_pubkey}")
def dict_to_settings(sets_dict: dict) -> UpdateSettings:
return dict_to_model(sets_dict, UpdateSettings)
def update_cached_settings(sets_dict: dict):
editable_settings = dict_to_model(sets_dict, EditableSettings)
editable_settings = dict_to_settings(sets_dict)
for key in sets_dict.keys():
if key in readonly_variables:
continue
+2
View File
@@ -30,6 +30,7 @@ from lnbits.core.services.notifications import (
process_next_notification,
)
from lnbits.db import Filters
from lnbits.helpers import check_callback_url
from lnbits.settings import settings
from lnbits.tasks import create_unique_task, send_push_notification
from lnbits.utils.exchange_rates import btc_rates
@@ -117,6 +118,7 @@ async def dispatch_webhook(payment: Payment):
async with httpx.AsyncClient(headers=headers) as client:
data = payment.dict()
try:
check_callback_url(payment.webhook)
r = await client.post(payment.webhook, json=data, timeout=40)
r.raise_for_status()
await mark_webhook_sent(payment.payment_hash, r.status_code)
@@ -318,6 +318,42 @@
</div>
</div>
</div>
<div class="col-12 col-md-12">
<p v-text="$t('callback_url_rules')"></p>
<div class="row q-col-gutter-md">
<div class="col-12">
<q-input
filled
v-model="formCallbackUrlRule"
@keydown.enter="addCallbackUrlRule"
type="text"
:label="$t('enter_callback_url_rule')"
:hint="$t('callback_url_rule_hint')"
>
<q-btn
@click="addCallbackUrlRule"
dense
flat
icon="add"
></q-btn>
</q-input>
<div>
<q-chip
v-for="rule in formData.lnbits_callback_url_rules"
:key="rule"
removable
@remove="removeCallbackUrlRule(rule)"
color="primary"
text-color="white"
:label="rule"
class="ellipsis"
></q-chip>
</div>
<br />
</div>
</div>
</div>
</div>
</div>
</q-card-section>
+10
View File
@@ -16,6 +16,7 @@ from lnbits.core.services import (
get_balance_delta,
update_cached_settings,
)
from lnbits.core.services.settings import dict_to_settings
from lnbits.decorators import check_admin, check_super_user
from lnbits.server import server_restart
from lnbits.settings import AdminSettings, Settings, UpdateSettings, settings
@@ -71,6 +72,15 @@ async def api_update_settings(data: UpdateSettings, user: User = Depends(check_a
return {"status": "Success"}
@admin_router.patch(
"/api/v1/settings",
status_code=HTTPStatus.OK,
)
async def api_update_settings_partial(data: dict, user: User = Depends(check_admin)):
updatable_settings = dict_to_settings({**settings.dict(), **data})
return await api_update_settings(updatable_settings, user)
@admin_router.get(
"/api/v1/settings/default",
status_code=HTTPStatus.OK,
+2
View File
@@ -29,6 +29,7 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
from lnbits.helpers import check_callback_url
from lnbits.lnurl import decode as lnurl_decode
from lnbits.settings import settings
from lnbits.utils.exchange_rates import (
@@ -128,6 +129,7 @@ async def api_lnurlscan(
else:
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:
check_callback_url(url)
r = await client.get(url, timeout=5)
r.raise_for_status()
if r.is_error:
+2 -1
View File
@@ -17,7 +17,7 @@ from lnbits.core.models.extensions import ExtensionMeta, InstallableExtension
from lnbits.core.services import create_invoice, create_user_account
from lnbits.core.services.extensions import get_valid_extensions
from lnbits.decorators import check_admin, check_user_exists
from lnbits.helpers import template_renderer
from lnbits.helpers import check_callback_url, template_renderer
from lnbits.settings import settings
from lnbits.wallets import get_funding_source
@@ -443,6 +443,7 @@ async def lnurlwallet(request: Request, lightning: str = ""):
lnurl = lnurl_decode(lightning)
async with httpx.AsyncClient() as client:
check_callback_url(lnurl)
res1 = await client.get(lnurl, timeout=2)
res1.raise_for_status()
data1 = res1.json()
+8 -1
View File
@@ -46,7 +46,11 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
from lnbits.helpers import filter_dict_keys, generate_filter_params_openapi
from lnbits.helpers import (
check_callback_url,
filter_dict_keys,
generate_filter_params_openapi,
)
from lnbits.lnurl import decode as lnurl_decode
from lnbits.settings import settings
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
@@ -225,6 +229,7 @@ async def _api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers) as client:
try:
check_callback_url(data.lnurl_callback)
r = await client.get(
data.lnurl_callback,
params={"pr": payment.bolt11},
@@ -337,6 +342,7 @@ async def api_payments_pay_lnurl(
amount_msat = ceil(amount_msat // 1000) * 1000
else:
amount_msat = data.amount
check_callback_url(data.callback)
r = await client.get(
data.callback,
params={"amount": amount_msat, "comment": data.comment},
@@ -461,6 +467,7 @@ async def api_payment_pay_with_nfc(
headers = {"User-Agent": settings.user_agent}
async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:
try:
check_callback_url(url)
lnurl_req = await client.get(url, timeout=10)
if lnurl_req.is_error:
return JSONResponse(