Merge remote-tracking branch 'origin/FastAPI' into FastAPI

This commit is contained in:
Ben Arc 2021-12-15 00:18:24 +00:00
commit bb1b452134
5 changed files with 88 additions and 45 deletions

View File

@ -1,9 +1,10 @@
import base64
import hashlib
from http import HTTPStatus
from typing import Optional
from fastapi import Request
from fastapi.param_functions import Query
from lnurl import LnurlPayActionResponse, LnurlPayResponse # type: ignore
from starlette.exceptions import HTTPException
from lnbits.core.services import create_invoice
@ -28,53 +29,101 @@ async def lnurl_response(
nonce: str = Query(None),
pos_id: str = Query(None),
payload: str = Query(None),
):
return await handle_lnurl_firstrequest(
request, pos_id, nonce, payload, verify_checksum=False
)
@lnurlpos_ext.get(
"/api/v2/lnurl/{pos_id}",
status_code=HTTPStatus.OK,
name="lnurlpos.lnurl_v2_params",
)
async def lnurl_v2_params(
request: Request,
pos_id: str = Query(None),
n: str = Query(None),
p: str = Query(None),
):
return await handle_lnurl_firstrequest(request, pos_id, n, p, verify_checksum=True)
async def handle_lnurl_firstrequest(
request: Request, pos_id: str, nonce: str, payload: str, verify_checksum: bool
):
pos = await get_lnurlpos(pos_id)
if not pos:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="lnurlpos not found."
)
nonce1 = bytes.fromhex(nonce)
payload1 = bytes.fromhex(payload)
h = hashlib.sha256(nonce1)
return {
"status": "ERROR",
"reason": f"lnurlpos {pos_id} not found on this server.",
}
try:
nonceb = bytes.fromhex(nonce)
except ValueError:
try:
nonce += "=" * ((4 - len(nonce) % 4) % 4)
nonceb = base64.urlsafe_b64decode(nonce)
except:
return {
"status": "ERROR",
"reason": f"Invalid hex or base64 nonce: {nonce}",
}
try:
payloadb = bytes.fromhex(payload)
except ValueError:
try:
payload += "=" * ((4 - len(payload) % 4) % 4)
payloadb = base64.urlsafe_b64decode(payload)
except:
return {
"status": "ERROR",
"reason": f"Invalid hex or base64 payload: {payload}",
}
h = hashlib.sha256(nonceb)
h.update(pos.key.encode())
s = h.digest()
res = bytearray(payload1)
res = bytearray(payloadb)
for i in range(len(res)):
res[i] = res[i] ^ s[i]
decryptedAmount = float(int.from_bytes(res[2:6], "little") / 100)
decryptedPin = int.from_bytes(res[:2], "little")
if type(decryptedAmount) != float:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not an amount.")
if verify_checksum:
checksum = res[6:8]
if hashlib.sha256(res[0:6]).digest()[0:2] != checksum:
return {"status": "ERROR", "reason": "Invalid checksum!"}
pin = int.from_bytes(res[0:2], "little")
amount = int.from_bytes(res[2:6], "little")
price_msat = (
await fiat_amount_as_satoshis(decryptedAmount, pos.currency)
await fiat_amount_as_satoshis(float(amount) / 100, pos.currency)
if pos.currency != "sat"
else pos.currency
else amount
) * 1000
lnurlpospayment = await create_lnurlpospayment(
posid=pos.id,
payload=payload,
sats=price_msat,
pin=decryptedPin,
pin=pin,
payhash="payment_hash",
)
if not lnurlpospayment:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Could not create payment"
)
return {"status": "ERROR", "reason": "Could not create payment."}
resp = LnurlPayResponse(
callback=request.url_for(
return {
"tag": "payRequest",
"callback": request.url_for(
"lnurlpos.lnurl_callback", paymentid=lnurlpospayment.id
),
min_sendable=price_msat,
max_sendable=price_msat,
metadata=await pos.lnurlpay_metadata(),
)
return resp.dict()
"minSendable": price_msat,
"maxSendable": price_msat,
"metadata": await pos.lnurlpay_metadata(),
}
@lnurlpos_ext.get(
@ -102,10 +151,14 @@ async def lnurl_callback(request: Request, paymentid: str = Query(None)):
lnurlpospayment_id=paymentid, payhash=payment_hash
)
resp = LnurlPayActionResponse(
pr=payment_request,
success_action=pos.success_action(paymentid, request),
routes=[],
)
return {
"pr": payment_request,
"successAction": {
"tag": "url",
"description": "Check the attached link",
"url": req.url_for("lnurlpos.displaypin", paymentid=paymentid),
},
"routes": [],
}
return resp.dict()

View File

@ -35,16 +35,6 @@ class lnurlposs(BaseModel):
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
def success_action(
self, paymentid: str, req: Request
) -> Optional[LnurlPaySuccessAction]:
return UrlAction(
url=req.url_for("lnurlpos.displaypin", paymentid=paymentid),
description="Check the attached link",
)
class lnurlpospayment(BaseModel):
id: str
posid: str

View File

@ -1,7 +1,7 @@
<q-card>
<q-card-section>
<p>
Register LNURLPoS devices to recieve payments in your LNbits wallet.<br />
Register LNURLPoS devices to receive payments in your LNbits wallet.<br />
Build your own here
<a href="https://github.com/arcbtc/LNURLPoS"
>https://github.com/arcbtc/LNURLPoS</a

View File

@ -130,8 +130,7 @@
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-h6">Copy to LNURLPoS device</div>
<div class="text-subtitle2">
{% raw %} String server = "{{location}}";<br />
String posId = "{{settingsDialog.data.id}}";<br />
{% raw %} String server = "{{location}}/lnurlpos/api/v2/lnurl/{{settingsDialog.data.id}}";<br />
String key = "{{settingsDialog.data.key}}";<br />
String currency = "{{settingsDialog.data.currency}}";{% endraw %}
</div>

View File

@ -253,6 +253,7 @@ async def btc_price(currency: str) -> float:
await send_channel.put(rate)
except (
TypeError, # CoinMate returns HTTPStatus 200 but no data when a currency pair is not found
KeyError, # Kraken's response dictionary doesn't include keys we look up for
httpx.ConnectTimeout,
httpx.ConnectError,
httpx.ReadTimeout,