mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-19 18:11:30 +01:00
4e6c30a909
Run via `make test`
129 lines
5.1 KiB
Python
129 lines
5.1 KiB
Python
import json
|
|
import math
|
|
import traceback
|
|
from http import HTTPStatus
|
|
|
|
from starlette.requests import Request
|
|
|
|
from . import bleskomat_ext
|
|
from .crud import (
|
|
create_bleskomat_lnurl,
|
|
get_bleskomat_by_api_key_id,
|
|
get_bleskomat_lnurl,
|
|
)
|
|
from .exchange_rates import fetch_fiat_exchange_rate
|
|
from .helpers import (
|
|
LnurlHttpError,
|
|
LnurlValidationError,
|
|
generate_bleskomat_lnurl_secret,
|
|
generate_bleskomat_lnurl_signature,
|
|
prepare_lnurl_params,
|
|
query_to_signing_payload,
|
|
unshorten_lnurl_query,
|
|
)
|
|
|
|
|
|
# Handles signed URL from Bleskomat ATMs and "action" callback of auto-generated LNURLs.
|
|
@bleskomat_ext.get("/u", name="bleskomat.api_bleskomat_lnurl")
|
|
async def api_bleskomat_lnurl(req: Request):
|
|
try:
|
|
query = req.query_params
|
|
|
|
# Unshorten query if "s" is used instead of "signature".
|
|
if "s" in query:
|
|
query = unshorten_lnurl_query(query)
|
|
|
|
if "signature" in query:
|
|
|
|
# Signature provided.
|
|
# Use signature to verify that the URL was generated by an authorized device.
|
|
# Later validate parameters, auto-generate LNURL, reply with LNURL response object.
|
|
signature = query["signature"]
|
|
|
|
# The API key ID, nonce, and tag should be present in the query string.
|
|
for field in ["id", "nonce", "tag"]:
|
|
if not field in query:
|
|
raise LnurlHttpError(
|
|
f'Failed API key signature check: Missing "{field}"',
|
|
HTTPStatus.BAD_REQUEST,
|
|
)
|
|
|
|
# URL signing scheme is described here:
|
|
# https://github.com/chill117/lnurl-node#how-to-implement-url-signing-scheme
|
|
payload = query_to_signing_payload(query)
|
|
api_key_id = query["id"]
|
|
bleskomat = await get_bleskomat_by_api_key_id(api_key_id)
|
|
if not bleskomat:
|
|
raise LnurlHttpError("Unknown API key", HTTPStatus.BAD_REQUEST)
|
|
api_key_secret = bleskomat.api_key_secret
|
|
api_key_encoding = bleskomat.api_key_encoding
|
|
expected_signature = generate_bleskomat_lnurl_signature(
|
|
payload, api_key_secret, api_key_encoding
|
|
)
|
|
if signature != expected_signature:
|
|
raise LnurlHttpError("Invalid API key signature", HTTPStatus.FORBIDDEN)
|
|
|
|
# Signature is valid.
|
|
# In the case of signed URLs, the secret is deterministic based on the API key ID and signature.
|
|
secret = generate_bleskomat_lnurl_secret(api_key_id, signature)
|
|
lnurl = await get_bleskomat_lnurl(secret)
|
|
if not lnurl:
|
|
try:
|
|
tag = query["tag"]
|
|
params = prepare_lnurl_params(tag, query)
|
|
if "f" in query:
|
|
rate = await fetch_fiat_exchange_rate(
|
|
currency=query["f"],
|
|
provider=bleskomat.exchange_rate_provider,
|
|
)
|
|
# Convert fee (%) to decimal:
|
|
fee = float(bleskomat.fee) / 100
|
|
if tag == "withdrawRequest":
|
|
for key in ["minWithdrawable", "maxWithdrawable"]:
|
|
amount_sats = int(
|
|
math.floor((params[key] / rate) * 1e8)
|
|
)
|
|
fee_sats = int(math.floor(amount_sats * fee))
|
|
amount_sats_less_fee = amount_sats - fee_sats
|
|
# Convert to msats:
|
|
params[key] = int(amount_sats_less_fee * 1e3)
|
|
except LnurlValidationError as e:
|
|
raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST)
|
|
# Create a new LNURL using the query parameters provided in the signed URL.
|
|
params = json.JSONEncoder().encode(params)
|
|
lnurl = await create_bleskomat_lnurl(
|
|
bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1
|
|
)
|
|
|
|
# Reply with LNURL response object.
|
|
return lnurl.get_info_response_object(secret, req)
|
|
|
|
# No signature provided.
|
|
# Treat as "action" callback.
|
|
|
|
if not "k1" in query:
|
|
raise LnurlHttpError("Missing secret", HTTPStatus.BAD_REQUEST)
|
|
|
|
secret = query["k1"]
|
|
lnurl = await get_bleskomat_lnurl(secret)
|
|
if not lnurl:
|
|
raise LnurlHttpError("Invalid secret", HTTPStatus.BAD_REQUEST)
|
|
|
|
if not lnurl.has_uses_remaining():
|
|
raise LnurlHttpError(
|
|
"Maximum number of uses already reached", HTTPStatus.BAD_REQUEST
|
|
)
|
|
|
|
try:
|
|
await lnurl.execute_action(query)
|
|
except LnurlValidationError as e:
|
|
raise LnurlHttpError(str(e), HTTPStatus.BAD_REQUEST)
|
|
|
|
except LnurlHttpError as e:
|
|
return {"status": "ERROR", "reason": str(e)}
|
|
except Exception as e:
|
|
print(str(e))
|
|
return {"status": "ERROR", "reason": "Unexpected error"}
|
|
|
|
return {"status": "OK"}
|