Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async def create_pay_link(data: CreatePayLinkData) -> PayLink:
created_at=now,
updated_at=now,
disposable=data.disposable if data.disposable is not None else True,
verify=data.verify if data.verify is not None else False,
)

await db.insert("lnurlp.pay_links", link)
Expand Down
6 changes: 6 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,9 @@ async def m012_add_disposable(db: Connection):
await db.execute(
"ALTER TABLE lnurlp.pay_links ADD COLUMN disposable BOOLEAN DEFAULT TRUE"
)


async def m013_add_verify(db: Connection):
await db.execute(
"ALTER TABLE lnurlp.pay_links ADD COLUMN verify BOOLEAN DEFAULT FALSE"
)
2 changes: 2 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class CreatePayLinkData(BaseModel):
username: str | None = Query(None)
zaps: bool | None = Query(False)
disposable: bool | None = Query(True)
verify: bool | None = Query(False)


class PayLink(BaseModel):
Expand Down Expand Up @@ -69,6 +70,7 @@ class PayLink(BaseModel):
fiat_base_multiplier: int | None = None

disposable: bool
verify: bool

# TODO deprecated, unused in the code, should be deleted from db.
domain: str | None = None
1 change: 1 addition & 0 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ window.app = Vue.createApp({
fixedAmount: true,
data: {
disposable: true,
verify: false,
zaps: false
}
},
Expand Down
13 changes: 13 additions & 0 deletions templates/lnurlp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ <h5 class="text-caption q-mt-sm q-mb-none">
/>
</div>
</div>
<h5 class="text-caption q-mt-sm q-mb-none">
LUD-21: verify base spec.
</h5>
<div class="row">
<div class="col-12">
<q-checkbox
dense
:toggle-indeterminate="false"
v-model="formDialog.data.verify"
label="If enabled, a verify URL will be added to LnurlPayActionResponse."
/>
</div>
</div>
<h5 class="text-caption q-mt-sm q-mb-none">LNURL</h5>
<div class="row">
<div class="col-12">
Expand Down
35 changes: 33 additions & 2 deletions views_lnurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from http import HTTPStatus

from fastapi import APIRouter, HTTPException, Query, Request
from lnbits.core.crud import get_standalone_payment
from lnbits.core.services import create_invoice
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
from lnurl import (
Expand All @@ -12,6 +13,7 @@
LnurlPayMetadata,
LnurlPayResponse,
LnurlPaySuccessActionTag,
LnurlPayVerifyResponse,
Max144Str,
MessageAction,
MilliSatoshi,
Expand Down Expand Up @@ -120,13 +122,21 @@ async def api_lnurl_callback(
)
invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11))

verify: CallbackUrl | None = None

if link.verify:
verify_url = request.url_for(
"lnurlp.api_lnurl_verify", payment_hash=payment.payment_hash
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LUD does not specify that the check value must be the payment hash.
Can't this in fact be a vulnerability? Anyone that sees the QR code can check the payment status (and obtain the pre-image).

Suggestion:

  • use verify_secret random value
  • introduce extra column (not to have such a large number of migrations)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nack

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the payment_hash is actually inside the bolt11 invoice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason for adding anything extra, the verify link is sent with the bolt11 invoice which contains the payment_hash already, so there is no additional information leaked.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@motorina0 can you re-evaluate the NACK?

)
verify = parse_obj_as(CallbackUrl, str(verify_url))

if link.success_url:
url = parse_obj_as(CallbackUrl, str(link.success_url))
text = link.success_text or f"Link to {link.success_url}"
desc = parse_obj_as(Max144Str, text)
action = UrlAction(tag=LnurlPaySuccessActionTag.url, url=url, description=desc)
return LnurlPayActionResponse(
pr=invoice, successAction=action, disposable=link.disposable
pr=invoice, successAction=action, disposable=link.disposable, verify=verify
)

if link.success_text:
Expand All @@ -135,9 +145,10 @@ async def api_lnurl_callback(
pr=invoice,
successAction=MessageAction(message=message),
disposable=link.disposable,
verify=verify,
)

return LnurlPayActionResponse(pr=invoice, disposable=link.disposable)
return LnurlPayActionResponse(pr=invoice, disposable=link.disposable, verify=verify)


@lnurlp_lnurl_router.get(
Expand Down Expand Up @@ -206,3 +217,23 @@ async def lnaddress(
if not address_data:
return LnurlErrorResponse(reason="Lightning address not found.")
return await api_lnurl_response(request, address_data.id)


# LUD-21. verify base spec. https://github.com/lnurl/luds/blob/luds/21.md
@lnurlp_lnurl_router.get(
"/api/v1/lnurl/verify/{payment_hash}",
name="lnurlp.api_lnurl_verify",
)
async def api_lnurl_verify(
payment_hash: str,
) -> LnurlErrorResponse | LnurlPayVerifyResponse:
payment = await get_standalone_payment(payment_hash, incoming=True)
if not payment:
return LnurlErrorResponse(reason="Not found")
invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11))
if payment.success:
return LnurlPayVerifyResponse(
settled=True, preimage=payment.preimage, pr=invoice
)
else:
return LnurlPayVerifyResponse(settled=False, pr=invoice)