Revert "fix: extend RFC1918 LNURL carve-out to the HTTP-views path"

This reverts commit 40dce41.

Going with TLS termination on the dev LNbits instead, so the
RFC1918 carve-out becomes unnecessary. The lnbits-core
`/api/v1/lnurlscan` consumer-side validator applies the same
HTTPS-required rule python-lnurl enforces; carving the producer
side out only got greg's LNURL generated, not redeemed.
This commit is contained in:
Padreug 2026-06-01 21:43:37 +02:00
commit 0e06ab2087
2 changed files with 38 additions and 51 deletions

View file

@ -1,9 +1,9 @@
import ipaddress import ipaddress
from dataclasses import dataclass
from urllib.parse import urlparse from urllib.parse import urlparse
from fastapi import Request from fastapi import Request
from lnbits.settings import settings from lnbits.settings import settings
from lnurl import Lnurl
from lnurl import encode as lnurl_encode from lnurl import encode as lnurl_encode
from lnurl.helpers import url_encode from lnurl.helpers import url_encode
from shortuuid import uuid from shortuuid import uuid
@ -11,18 +11,26 @@ from shortuuid import uuid
from .models import WithdrawLink from .models import WithdrawLink
@dataclass(frozen=True) def create_lnurl(link: WithdrawLink, req: Request) -> Lnurl:
class _EncodedLnurl: if link.is_unique:
"""Minimal duck-typed stand-in for `lnurl.Lnurl` exposing just the usescssv = link.usescsv.split(",")
`.bech32` and `.url` attributes the callers (views.py, views_api.py, tohash = link.id + link.unique_hash + usescssv[link.number]
transport_rpcs.py) read. We can't always return a real `Lnurl` — multihash = uuid(name=tohash)
its `__new__` re-runs python-lnurl's HTTPS-required host check on url = req.url_for(
bech32-decode, which rejects RFC1918/loopback HTTP URLs even after "withdraw.api_lnurl_multi_response",
we've intentionally encoded one. See #2. unique_hash=link.unique_hash,
""" id_unique_hash=multihash,
)
else:
url = req.url_for("withdraw.api_lnurl_response", unique_hash=link.unique_hash)
bech32: str try:
url: str return lnurl_encode(str(url))
except Exception as e:
raise ValueError(
f"Error creating LNURL with url: `{url!s}`, "
"check your webserver proxy configuration."
) from e
def _is_private_network_http(url: str) -> bool: def _is_private_network_http(url: str) -> bool:
@ -49,46 +57,17 @@ def _is_private_network_http(url: str) -> bool:
return ip.is_loopback or ip.is_private return ip.is_loopback or ip.is_private
def _encode_lnurl(url: str, error_hint: str) -> _EncodedLnurl: def create_lnurl_from_baseurl(link: WithdrawLink) -> tuple[str, str]:
"""Bech32-encode `url` as an LNURL. LAN-local HTTP URLs (loopback /
RFC1918) skip python-lnurl's HTTPS-required host validation via the
public `lnurl.helpers.url_encode` helper. Everything else goes
through the validated `lnurl_encode` path. See #2.
"""
if _is_private_network_http(url):
return _EncodedLnurl(bech32=url_encode(url), url=url)
try:
encoded = lnurl_encode(url)
except Exception as e:
raise ValueError(
f"Error creating LNURL with url: `{url!s}`, {error_hint}"
) from e
return _EncodedLnurl(bech32=str(encoded.bech32), url=str(encoded.url))
def create_lnurl(link: WithdrawLink, req: Request) -> _EncodedLnurl:
if link.is_unique:
usescssv = link.usescsv.split(",")
tohash = link.id + link.unique_hash + usescssv[link.number]
multihash = uuid(name=tohash)
url = req.url_for(
"withdraw.api_lnurl_multi_response",
unique_hash=link.unique_hash,
id_unique_hash=multihash,
)
else:
url = req.url_for("withdraw.api_lnurl_response", unique_hash=link.unique_hash)
return _encode_lnurl(str(url), "check your webserver proxy configuration.")
def create_lnurl_from_baseurl(link: WithdrawLink) -> _EncodedLnurl:
""" """
Same shape as `create_lnurl`, but composes the callback URL from Same shape as `create_lnurl`, but composes the callback URL from
`settings.lnbits_baseurl` instead of a FastAPI `Request`. Used by `settings.lnbits_baseurl` instead of a FastAPI `Request`. Used by
the nostr-transport RPC handlers, which have no HTTP request to the nostr-transport RPC handlers, which have no HTTP request to
derive a base URL from. derive a base URL from.
Returns `(bech32, url)` the two fields `_populate_lnurl` writes
onto `WithdrawLink.lnurl` / `lnurl_url`. LAN-local HTTP URLs
(loopback / RFC1918) skip python-lnurl's HTTPS-required check via
the public `lnurl.helpers.url_encode` helper; see #2.
""" """
base = settings.lnbits_baseurl.rstrip("/") base = settings.lnbits_baseurl.rstrip("/")
if link.is_unique: if link.is_unique:
@ -99,4 +78,14 @@ def create_lnurl_from_baseurl(link: WithdrawLink) -> _EncodedLnurl:
else: else:
url = f"{base}/withdraw/api/v1/lnurl/{link.unique_hash}" url = f"{base}/withdraw/api/v1/lnurl/{link.unique_hash}"
return _encode_lnurl(url, "check your `LNBITS_BASEURL` configuration.") if _is_private_network_http(url):
return url_encode(url), url
try:
encoded = lnurl_encode(url)
except Exception as e:
raise ValueError(
f"Error creating LNURL with url: `{url!s}`, "
"check your `LNBITS_BASEURL` configuration."
) from e
return str(encoded.bech32), str(encoded.url)

View file

@ -211,9 +211,7 @@ def _populate_lnurl(link: WithdrawLink) -> WithdrawLink:
duplicating state LNbits already knows. See aiolabs/withdraw#1. duplicating state LNbits already knows. See aiolabs/withdraw#1.
""" """
try: try:
encoded = create_lnurl_from_baseurl(link) link.lnurl, link.lnurl_url = create_lnurl_from_baseurl(link)
link.lnurl = encoded.bech32
link.lnurl_url = encoded.url
except ValueError: except ValueError:
pass pass
return link return link