From 947dc50d2e6598e52e36d86741d6c12a9cd6b67d Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Mon, 12 Sep 2022 17:14:24 +0200 Subject: [PATCH 01/20] readme update --- lnbits/extensions/boltcards/README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md index f9c59409..a7302906 100644 --- a/lnbits/extensions/boltcards/README.md +++ b/lnbits/extensions/boltcards/README.md @@ -10,18 +10,17 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d ## About the keys -Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set: +Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set, but for the security reasons all five keys should be changed from default (empty) state. The keys directly needed by this extension are: -One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1. +- One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1. -One for calculating CMAC (c parameter), let's called it file key, key #02 or K2. +- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2. -The key #00, K0 (also know as auth key) is skipped to be use as authentification key. Is not needed by this extension, but can be filled in order to write the keys in cooperation with bolt-nfc-android-app. +The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually. ***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!*** ## Setting the card - bolt-nfc-android-app (easy way) -So far, regarding the keys, the app can only write a new key set on an empty card (with zero keys). **When you write non zero (and 'non debug') keys, they can't be rewrite with this app.** You have to do it on your computer. - Read the card with the app. Note UID so you can fill it in the extension later. - Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}` @@ -35,10 +34,10 @@ So far, regarding the keys, the app can only write a new key set on an empty car - If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field. - Advanced Options - Card Keys (k0, k1, k2) will be automatically generated if not explicitly set. - - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in debug mode. - - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead. + - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state. + - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead. - Click CREATE CARD button -- Click the QR code button next to a card to view its details. You can scan the QR code with the Android app to import the keys. +- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys. - Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys. - Tap the NFC card to write the keys to the card. @@ -48,7 +47,7 @@ Follow the guide. The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000` -Then fill up the card parameters in the extension. Card Auth key (K0) can be omitted. Initical counter can be 0. +Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0. ## Setting the card - android NXP app (hard way) - If you don't know the card ID, use NXP TagInfo app to find it out. From e1eb0895890c068a29ee32f4fe7910698522da0e Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Mon, 12 Sep 2022 22:44:22 +0200 Subject: [PATCH 02/20] readme minor changes --- lnbits/extensions/boltcards/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md index a7302906..8e093f3e 100644 --- a/lnbits/extensions/boltcards/README.md +++ b/lnbits/extensions/boltcards/README.md @@ -34,7 +34,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio - If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field. - Advanced Options - Card Keys (k0, k1, k2) will be automatically generated if not explicitly set. - - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state. + - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state. - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead. - Click CREATE CARD button - Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys. @@ -47,7 +47,7 @@ Follow the guide. The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000` -Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0. +Then fill up the card parameters in the extension. Card Auth key (K0) can be filled in the extension just for the record. Initical counter can be 0. ## Setting the card - android NXP app (hard way) - If you don't know the card ID, use NXP TagInfo app to find it out. From 3660d96295ea75be08cd1e55b7899da60cd66eb9 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 20 Sep 2022 15:53:57 +0200 Subject: [PATCH 03/20] rename setting app + delete msg --- lnbits/extensions/boltcards/README.md | 6 +++--- lnbits/extensions/boltcards/static/js/index.js | 2 +- lnbits/extensions/boltcards/templates/boltcards/index.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md index 8e093f3e..c6d12311 100644 --- a/lnbits/extensions/boltcards/README.md +++ b/lnbits/extensions/boltcards/README.md @@ -6,7 +6,7 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d **Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!*** -***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp). +***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [Boltcard NFC Card Creator](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp). ## About the keys @@ -16,11 +16,11 @@ Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In th - One for calculating CMAC (c parameter), let's called it file key, key #02 or K2. -The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually. +The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with Boltcard NFC Card Creator. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually. ***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!*** -## Setting the card - bolt-nfc-android-app (easy way) +## Setting the card - Boltcard NFC Card Creator (easy way) - Read the card with the app. Note UID so you can fill it in the extension later. - Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}` diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js index 11df222a..2ecde39d 100644 --- a/lnbits/extensions/boltcards/static/js/index.js +++ b/lnbits/extensions/boltcards/static/js/index.js @@ -398,7 +398,7 @@ new Vue({ let cards = _.findWhere(this.cards, {id: cardId}) LNbits.utils - .confirmDialog('Are you sure you want to delete this card') + .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!') .onOk(function () { LNbits.api .request( diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index 55cc1e5e..3e07024c 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -370,7 +370,7 @@ bolt-nfc-android-appBoltcard NFC Card Creator)

From 632d35682d84c981b926ef47f8e6fc9d274b948e Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Fri, 23 Sep 2022 15:17:21 +0200 Subject: [PATCH 04/20] payment notification webhook --- lnbits/extensions/boltcards/crud.py | 6 +++-- lnbits/extensions/boltcards/lnurl.py | 23 ++++++++++++++++++- lnbits/extensions/boltcards/migrations.py | 8 +++++++ lnbits/extensions/boltcards/models.py | 2 ++ .../extensions/boltcards/static/js/index.js | 8 +++++-- .../boltcards/templates/boltcards/index.html | 12 +++++++++- 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py index c541346e..39ee3f40 100644 --- a/lnbits/extensions/boltcards/crud.py +++ b/lnbits/extensions/boltcards/crud.py @@ -27,9 +27,10 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card: k0, k1, k2, - otp + otp, + webhook_url ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( card_id, @@ -45,6 +46,7 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card: data.k1, data.k2, secrets.token_hex(16), + data.webhook_url, ), ) card = await get_card(card_id) diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py index 6fb9ad8d..be3e09d8 100644 --- a/lnbits/extensions/boltcards/lnurl.py +++ b/lnbits/extensions/boltcards/lnurl.py @@ -8,6 +8,7 @@ from io import BytesIO from typing import Optional from urllib.parse import urlparse +import httpx from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query @@ -119,12 +120,32 @@ async def lnurl_callback( invoice = bolt11.decode(pr) hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) try: - await pay_invoice( + payment_hash = await pay_invoice( wallet_id=card.wallet, payment_request=pr, max_sat=card.tx_limit, extra={"tag": "boltcard", "tag": hit.id}, ) + + if card.webhook_url: + async with httpx.AsyncClient() as client: + try: + r = await client.post( + card.webhook_url, + json={ + "notification": "card_payment", + "payment_hash": payment_hash, + "payment_request": pr, + "card_external_id": card.external_id, + "card_name": card.card_name, + "amount": int(invoice.amount_msat / 1000), + }, + timeout=40, + ) + except Exception as exc: + # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid + logger.error("Caught exception when dispatching webhook url:", exc) + return {"status": "OK"} except: return {"status": "ERROR", "reason": f"Payment failed"} diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py index 08126013..25a59fdb 100644 --- a/lnbits/extensions/boltcards/migrations.py +++ b/lnbits/extensions/boltcards/migrations.py @@ -58,3 +58,11 @@ async def m001_initial(db): ); """ ) + + +async def m002_add_webhook(db): + await db.execute( + """ + ALTER TABLE boltcards.cards ADD COLUMN webhook_url TEXT NOT NULL DEFAULT ''; + """ + ) diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py index 47ca1df0..8e6f77c9 100644 --- a/lnbits/extensions/boltcards/models.py +++ b/lnbits/extensions/boltcards/models.py @@ -30,6 +30,7 @@ class Card(BaseModel): prev_k1: str prev_k2: str otp: str + webhook_url: str time: int def from_row(cls, row: Row) -> "Card": @@ -56,6 +57,7 @@ class CreateCardData(BaseModel): prev_k0: str = Query(ZERO_KEY) prev_k1: str = Query(ZERO_KEY) prev_k2: str = Query(ZERO_KEY) + webhook_url: str = Query(...) class Hit(BaseModel): diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js index 2ecde39d..e13c14fb 100644 --- a/lnbits/extensions/boltcards/static/js/index.js +++ b/lnbits/extensions/boltcards/static/js/index.js @@ -23,6 +23,7 @@ new Vue({ cardDialog: { show: false, data: { + webhook_url: '', counter: 1, k0: '', k1: '', @@ -270,7 +271,8 @@ new Vue({ k1: card.k1, k2: card.k2, k3: card.k1, - k4: card.k2 + k4: card.k2, + webhook_url: card.webhook_url } this.qrCodeDialog.show = true }, @@ -398,7 +400,9 @@ new Vue({ let cards = _.findWhere(this.cards, {id: cardId}) LNbits.utils - .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!') + .confirmDialog( + "Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!" + ) .onOk(function () { LNbits.api .request( diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index 3e07024c..f795e454 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -283,7 +283,7 @@ v-model="toggleAdvanced" label="Show advanced options" > -

+
Zero if you don't know. + + Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
+ Notification webhook: {{ qrCodeDialog.data.webhook_url + }}


Date: Fri, 23 Sep 2022 16:19:58 +0200 Subject: [PATCH 05/20] Update README.md Updated for Boltcard NFC Card Creator v0.1.1 --- lnbits/extensions/boltcards/README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md index c6d12311..5140ecc2 100644 --- a/lnbits/extensions/boltcards/README.md +++ b/lnbits/extensions/boltcards/README.md @@ -21,10 +21,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio ***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!*** ## Setting the card - Boltcard NFC Card Creator (easy way) - -- Read the card with the app. Note UID so you can fill it in the extension later. -- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}` - - `{external_id}` should be replaced with the External ID found in the LNBits dialog. +Updated for v0.1.1 - Add new card in the extension. - Set a max sats per transaction. Any transaction greater than this amount will be rejected. @@ -32,14 +29,16 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio - Set a card name. This is just for your reference inside LNBits. - Set the card UID. This is the unique identifier on your NFC card and is 7 bytes. - If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field. + - Otherwise read it with the Android app (Advanced -> Read NFC) and paste it to the field. - Advanced Options - Card Keys (k0, k1, k2) will be automatically generated if not explicitly set. - - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state. - - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead. + - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure). + - GENERATE KEY button fill the keys randomly. - Click CREATE CARD button -- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys. -- Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys. -- Tap the NFC card to write the keys to the card. +- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager. + - Now you can scan the QR code with the Android app (Create Bolt Card -> SCAN QR CODE). + - Or you can Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL). +- Click WRITE CARD NOW and tap the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY! ## Setting the card - computer (hard way) @@ -69,4 +68,4 @@ Then fill up the card parameters in the extension. Card Auth key (K0) can be fil - Save & Write - Scan with compatible Wallet -This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secure. Card Auth key (K0) can be omitted anyway. Initical counter can be 0. +This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secured. Card Auth key (K0) can be omitted anyway. Initical counter can be 0. From ea2d66fbd628681618dbd411f544f63ee8692a5f Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Fri, 23 Sep 2022 17:11:19 +0200 Subject: [PATCH 06/20] refund notification --- lnbits/extensions/boltcards/tasks.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py index 1b51c98b..27929efc 100644 --- a/lnbits/extensions/boltcards/tasks.py +++ b/lnbits/extensions/boltcards/tasks.py @@ -2,12 +2,13 @@ import asyncio import json import httpx +from loguru import logger from lnbits.core import db as core_db from lnbits.core.models import Payment from lnbits.tasks import register_invoice_listener -from .crud import create_refund, get_hit +from .crud import create_refund, get_card, get_hit async def wait_for_paid_invoices(): @@ -34,6 +35,25 @@ async def on_invoice_paid(payment: Payment) -> None: ) await mark_webhook_sent(payment, 1) + card = await get_card(hit.card_id) + if card.webhook_url: + async with httpx.AsyncClient() as client: + try: + r = await client.post( + card.webhook_url, + json={ + "notification": "card_refund", + "payment_hash": payment.payment_hash, + "payment_request": payment.bolt11, + "card_external_id": card.external_id, + "card_name": card.card_name, + "amount": int(payment.amount / 1000), + }, + timeout=40, + ) + except Exception as exc: + logger.error("Caught exception when dispatching webhook url:", exc) + async def mark_webhook_sent(payment: Payment, status: int) -> None: payment.extra["wh_status"] = status From 8b3af03519c06437325e32e78a71febf9f91d3a1 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Sun, 25 Sep 2022 17:01:22 +0200 Subject: [PATCH 07/20] webhook to pay_invoice --- lnbits/core/services.py | 31 ++++++++++++++++- lnbits/extensions/boltcards/lnurl.py | 42 ++++++++---------------- lnbits/extensions/boltcards/views_api.py | 5 --- lnbits/extensions/withdraw/lnurl.py | 30 +++++++---------- 4 files changed, 56 insertions(+), 52 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 10693f4b..aeb4f938 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -2,7 +2,7 @@ import asyncio import json from binascii import unhexlify from io import BytesIO -from typing import Dict, Optional, Tuple +from typing import Dict, Optional, Tuple, Union from urllib.parse import parse_qs, urlparse import httpx @@ -102,6 +102,7 @@ async def pay_invoice( extra: Optional[Dict] = None, description: str = "", conn: Optional[Connection] = None, + webhook: Optional[Union[str, tuple]] = None, ) -> str: """ Pay a Lightning invoice. @@ -231,6 +232,34 @@ async def pay_invoice( f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}" ) + if type(webhook) is str: + webhook_url = webhook + elif type(webhook) is tuple: + webhook_url = webhook[0] + additionals = webhook[1] + else: + webhook_url = None + + if webhook_url: + async with httpx.AsyncClient() as client: + try: + json = { + "payment_hash": invoice.payment_hash, + "payment_request": payment_request, + "amount": int(invoice.amount_msat / 1000), + } + if type(additionals) is dict: + json.update(additionals) + + r = await client.post( + webhook_url, + json=json, + timeout=40, + ) + except Exception as exc: + # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid + logger.error("Caught exception when dispatching webhook url:", exc) + return invoice.payment_hash diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py index be3e09d8..320b1666 100644 --- a/lnbits/extensions/boltcards/lnurl.py +++ b/lnbits/extensions/boltcards/lnurl.py @@ -1,19 +1,12 @@ -import base64 -import hashlib -import hmac import json import secrets from http import HTTPStatus -from io import BytesIO -from typing import Optional from urllib.parse import urlparse -import httpx from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query from fastapi.params import Depends, Query -from lnurl import Lnurl, LnurlWithdrawResponse from lnurl import encode as lnurl_encode # type: ignore from lnurl.types import LnurlPayMetadata # type: ignore from loguru import logger @@ -34,7 +27,6 @@ from .crud import ( get_hit, get_hits_today, spend_hit, - update_card, update_card_counter, update_card_otp, ) @@ -120,32 +112,26 @@ async def lnurl_callback( invoice = bolt11.decode(pr) hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) try: - payment_hash = await pay_invoice( + webhook = ( + ( + card.webhook_url, + { + "notification": "card_payment", + "card_external_id": card.external_id, + "card_name": card.card_name, + }, + ) + if card.webhook_url + else None + ) + await pay_invoice( wallet_id=card.wallet, payment_request=pr, max_sat=card.tx_limit, extra={"tag": "boltcard", "tag": hit.id}, + webhook=webhook, ) - if card.webhook_url: - async with httpx.AsyncClient() as client: - try: - r = await client.post( - card.webhook_url, - json={ - "notification": "card_payment", - "payment_hash": payment_hash, - "payment_request": pr, - "card_external_id": card.external_id, - "card_name": card.card_name, - "amount": int(invoice.amount_msat / 1000), - }, - timeout=40, - ) - except Exception as exc: - # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid - logger.error("Caught exception when dispatching webhook url:", exc) - return {"status": "OK"} except: return {"status": "ERROR", "reason": f"Payment failed"} diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py index 7b8357cf..2fc11dbc 100644 --- a/lnbits/extensions/boltcards/views_api.py +++ b/lnbits/extensions/boltcards/views_api.py @@ -12,21 +12,16 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import boltcards_ext from .crud import ( create_card, - create_hit, delete_card, enable_disable_card, get_card, - get_card_by_otp, get_card_by_uid, get_cards, get_hits, get_refunds, update_card, - update_card_counter, - update_card_otp, ) from .models import CreateCardData -from .nxp424 import decryptSUN, getSunMAC @boltcards_ext.get("/api/v1/cards") diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 18a99599..3379fd17 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -3,7 +3,6 @@ import traceback from datetime import datetime from http import HTTPStatus -import httpx import shortuuid # type: ignore from fastapi import HTTPException from fastapi.param_functions import Query @@ -115,29 +114,24 @@ async def api_lnurl_callback( payment_request = pr - payment_hash = await pay_invoice( + webhook = ( + ( + link.webhook_url, + { + "lnurlw": link.id, + }, + ) + if link.webhook_url + else None + ) + await pay_invoice( wallet_id=link.wallet, payment_request=payment_request, max_sat=link.max_withdrawable, extra={"tag": "withdraw"}, + webhook=webhook, ) - if link.webhook_url: - async with httpx.AsyncClient() as client: - try: - r = await client.post( - link.webhook_url, - json={ - "payment_hash": payment_hash, - "payment_request": payment_request, - "lnurlw": link.id, - }, - timeout=40, - ) - except Exception as exc: - # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid - logger.error("Caught exception when dispatching webhook url:", exc) - return {"status": "OK"} except Exception as e: From 0bede387f5ed5620781c50b689fc7016ad5d15af Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:11:25 +0200 Subject: [PATCH 08/20] webhook to pay_invoice/fix --- lnbits/core/services.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index aeb4f938..4f937ec3 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -232,6 +232,7 @@ async def pay_invoice( f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}" ) + additionals = None if type(webhook) is str: webhook_url = webhook elif type(webhook) is tuple: From 5c6cd70d3bf08720a094c4aac263f53c583664e9 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Mon, 26 Sep 2022 11:47:00 +0200 Subject: [PATCH 09/20] disabling card with just otp which is also part of notify --- lnbits/extensions/boltcards/crud.py | 10 ++++++++-- lnbits/extensions/boltcards/lnurl.py | 3 +++ lnbits/extensions/boltcards/views_api.py | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py index 39ee3f40..0724ea04 100644 --- a/lnbits/extensions/boltcards/crud.py +++ b/lnbits/extensions/boltcards/crud.py @@ -114,8 +114,14 @@ async def get_card_by_external_id(external_id: str) -> Optional[Card]: return Card.parse_obj(card) -async def get_card_by_otp(otp: str) -> Optional[Card]: - row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,)) +async def get_card_by_otp(otp: str, half: bool = False) -> Optional[Card]: + if half and len(otp) == 16: + otp = "%" + otp + row = await db.fetchone( + "SELECT * FROM boltcards.cards WHERE otp LIKE ?", (otp,) + ) + else: + row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,)) if not row: return None diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py index 320b1666..064bde2c 100644 --- a/lnbits/extensions/boltcards/lnurl.py +++ b/lnbits/extensions/boltcards/lnurl.py @@ -119,6 +119,9 @@ async def lnurl_callback( "notification": "card_payment", "card_external_id": card.external_id, "card_name": card.card_name, + "card_otp": card.otp[ + -16: + ], # actually only half of the OTP is sent (full otp reveals the keys) }, ) if card.webhook_url diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py index 2fc11dbc..d7f5cf7c 100644 --- a/lnbits/extensions/boltcards/views_api.py +++ b/lnbits/extensions/boltcards/views_api.py @@ -15,11 +15,13 @@ from .crud import ( delete_card, enable_disable_card, get_card, + get_card_by_otp, get_card_by_uid, get_cards, get_hits, get_refunds, update_card, + update_card_otp, ) from .models import CreateCardData @@ -111,6 +113,22 @@ async def enable_card( return card.dict() +@boltcards_ext.post("/api/v1/disablecard") +async def disble_card_with_otp(a): + if len(a) < 16: + raise HTTPException(detail="Invalid OTP.", status_code=HTTPStatus.BAD_REQUEST) + card = await get_card_by_otp(a, half=True) + if not card: + raise HTTPException(detail="No card found.", status_code=HTTPStatus.NOT_FOUND) + + new_otp = secrets.token_hex(16) + await update_card_otp(new_otp, card.id) + + card = await enable_disable_card(enable=False, id=card.id) + + return {"status": "OK"} + + @boltcards_ext.delete("/api/v1/cards/{card_id}") async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)): card = await get_card(card_id) From 49c58cc8d0cb48fd874ba40d28c4331a065424df Mon Sep 17 00:00:00 2001 From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:48:34 +0200 Subject: [PATCH 10/20] return a reason of failed payment to the pos; disable a change of wallet in the form --- lnbits/extensions/boltcards/lnurl.py | 4 ++-- lnbits/extensions/boltcards/templates/boltcards/index.html | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py index 064bde2c..cd4c6ba4 100644 --- a/lnbits/extensions/boltcards/lnurl.py +++ b/lnbits/extensions/boltcards/lnurl.py @@ -136,8 +136,8 @@ async def lnurl_callback( ) return {"status": "OK"} - except: - return {"status": "ERROR", "reason": f"Payment failed"} + except Exception as exc: + return {"status": "ERROR", "reason": f"Payment failed - {exc}"} # /boltcards/api/v1/auth?a=00000000000000000000000000000000 diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index f795e454..7b9713e2 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -215,6 +215,7 @@ emit-value v-model="cardDialog.data.wallet" :options="g.user.walletOptions" + :disable="cardDialog.data.id != null" label="Wallet *" > From 678c269a9124a967300a34d75542ed0c0c35150a Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Fri, 18 Nov 2022 16:57:39 +0100 Subject: [PATCH 11/20] wipe card --- .../extensions/boltcards/static/js/index.js | 16 ++- .../boltcards/templates/boltcards/index.html | 102 ++++++++++++++---- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js index e13c14fb..1949050b 100644 --- a/lnbits/extensions/boltcards/static/js/index.js +++ b/lnbits/extensions/boltcards/static/js/index.js @@ -150,6 +150,7 @@ new Vue({ }, qrCodeDialog: { show: false, + wipe: false, data: null } } @@ -260,9 +261,10 @@ new Vue({ }) }) }, - openQrCodeDialog(cardId) { + openQrCodeDialog(cardId, wipe) { var card = _.findWhere(this.cards, {id: cardId}) this.qrCodeDialog.data = { + id: card.id, link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp, name: card.card_name, uid: card.uid, @@ -274,6 +276,18 @@ new Vue({ k4: card.k2, webhook_url: card.webhook_url } + this.qrCodeDialog.data_wipe = JSON.stringify({ + action: 'wipe', + id: 1, + k0: card.k0, + k1: card.k1, + k2: card.k2, + k3: card.k1, + k4: card.k2, + uid: card.uid, + version: 1 + }) + this.qrCodeDialog.wipe = wipe this.qrCodeDialog.show = true }, addCardOpen: function () { diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html index 7b9713e2..b80e9685 100644 --- a/lnbits/extensions/boltcards/templates/boltcards/index.html +++ b/lnbits/extensions/boltcards/templates/boltcards/index.html @@ -48,6 +48,7 @@ +