From eaec3480e6a2fc96d362bb5862cbca39136943cd Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 10 Nov 2020 00:25:46 -0300 Subject: [PATCH 1/7] lnurl-auth from lnbits wallets to services. --- lnbits/core/models.py | 10 +++ lnbits/core/services.py | 30 ++++++++ lnbits/core/static/js/wallet.js | 26 +++++++ lnbits/core/templates/core/wallet.html | 28 +++++++- lnbits/core/views/api.py | 95 ++++++++++++++++---------- lnbits/static/js/base.js | 5 ++ 6 files changed, 156 insertions(+), 38 deletions(-) diff --git a/lnbits/core/models.py b/lnbits/core/models.py index c65bdf93..23c111f3 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -1,4 +1,6 @@ import json +import hashlib +from ecdsa import SECP256k1, SigningKey # type: ignore from typing import List, NamedTuple, Optional, Dict from sqlite3 import Row @@ -33,6 +35,14 @@ class Wallet(NamedTuple): def balance(self) -> int: return self.balance_msat // 1000 + @property + def lnurlauth_key(self) -> SigningKey: + return SigningKey.from_string( + hashlib.sha256(self.id.encode("utf-8")).digest(), + curve=SECP256k1, + hashfunc=hashlib.sha256, + ) + def get_payment(self, payment_hash: str) -> Optional["Payment"]: from .crud import get_wallet_payment diff --git a/lnbits/core/services.py b/lnbits/core/services.py index cda16d22..00bfc84b 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -1,8 +1,12 @@ import trio # type: ignore +import json import httpx +from binascii import unhexlify from typing import Optional, Tuple, Dict +from urllib.parse import urlparse, parse_qs from quart import g from lnurl import LnurlWithdrawResponse # type: ignore +from ecdsa.util import sigencode_der # type: ignore try: from typing import TypedDict # type: ignore @@ -155,6 +159,32 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo ) +async def perform_lnurlauth(callback: str): + k1 = unhexlify(parse_qs(urlparse(callback).query)["k1"][0]) + key = g.wallet.lnurlauth_key + sig = key.sign_digest_deterministic(k1, sigencode=sigencode_der) + + async with httpx.AsyncClient() as client: + r = await client.get( + callback, + params={ + "k1": k1.hex(), + "key": key.verifying_key.to_string("compressed").hex(), + "sig": sig.hex(), + }, + ) + try: + resp = json.loads(r.text) + if resp["status"] == "OK": + return None + + return resp["reason"] + except (KeyError, json.decoder.JSONDecodeError): + return r.text[:200] + "..." if len(r.text) > 200 else r.text + + return None + + def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus: payment = get_wallet_payment(wallet_id, payment_hash) if not payment: diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 2cf64dfa..9bba45ad 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -128,6 +128,7 @@ new Vue({ show: false, invoice: null, lnurlpay: null, + lnurlauth: null, data: { request: '', amount: 0, @@ -237,6 +238,7 @@ new Vue({ this.parse.show = true this.parse.invoice = null this.parse.lnurlpay = null + this.parse.lnurlauth = null this.parse.data.request = '' this.parse.data.comment = '' this.parse.data.paymentChecker = null @@ -363,6 +365,8 @@ new Vue({ if (data.kind === 'pay') { this.parse.lnurlpay = Object.freeze(data) this.parse.data.amount = data.minSendable / 1000 + } else if (data.kind === 'auth') { + this.parse.lnurlauth = Object.freeze(data) } else if (data.kind === 'withdraw') { this.parse.show = false this.receive.show = true @@ -542,6 +546,28 @@ new Vue({ LNbits.utils.notifyApiError(err) }) }, + authLnurl: function () { + let dismissAuthMsg = this.$q.notify({ + timeout: 10, + message: 'Performing authentication...' + }) + + LNbits.api + .authLnurl(this.g.wallet, this.parse.lnurlauth.callback) + .then(response => { + dismissAuthMsg() + this.$q.notify({ + message: `Authentication successful.`, + type: 'positive', + timeout: 3500 + }) + this.parse.show = false + }) + .catch(err => { + dismissAuthMsg() + LNbits.utils.notifyApiError(err) + }) + }, deleteWallet: function (walletId, user) { LNbits.utils .confirmDialog('Are you sure you want to delete this wallet?') diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 8662d365..88f28b68 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -329,7 +329,7 @@ {% raw %}
{{ parse.invoice.fsat }} sat
-

+

Description: {{ parse.invoice.description }}
Expire date: {{ parse.invoice.expireDate }}
Hash: {{ parse.invoice.hash }} @@ -346,6 +346,32 @@ Cancel +

+ {% raw %} + +

+ Authenticate with {{ parse.lnurlauth.domain }}? +

+ +

+ For every website and for every LNbits wallet, a new keypair will be + deterministically generated so your identity can't be tied to your + LNbits wallet or linked across websites. No other data will be shared + with {{ parse.lnurlauth.domain }}. +

+

Your public key for {{ parse.lnurlauth.domain }} is:

+

+ {{ parse.lnurlauth.pubkey }} +

+
+ Login + Cancel +
+
+ {% endraw %} +
{% raw %} diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 0742220a..ecef466b 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -13,7 +13,7 @@ from lnbits import bolt11 from lnbits.decorators import api_check_wallet_key, api_validate_post_request from .. import core_app -from ..services import create_invoice, pay_invoice +from ..services import create_invoice, pay_invoice, perform_lnurlauth from ..crud import delete_expired_invoices from ..tasks import sse_listeners @@ -303,48 +303,69 @@ async def api_lnurlscan(code: str): return jsonify({"error": "invalid lnurl"}), HTTPStatus.BAD_REQUEST domain = urlparse(url.url).netloc + + # params is what will be returned to the client + params: Dict = {"domain": domain} + if url.is_login: - return jsonify({"domain": domain, "kind": "auth", "error": "unsupported"}), HTTPStatus.BAD_REQUEST + params.update(kind="auth") + params.update(callback=url.url) # with k1 already in it + params.update(pubkey=g.wallet.lnurlauth_key.verifying_key.to_string("compressed").hex()) + else: + async with httpx.AsyncClient() as client: + r = await client.get(url.url, timeout=40) + if r.is_error: + return jsonify({"domain": domain, "error": "failed to get parameters"}), HTTPStatus.SERVICE_UNAVAILABLE - async with httpx.AsyncClient() as client: - r = await client.get(url.url, timeout=40) - if r.is_error: - return jsonify({"domain": domain, "error": "failed to get parameters"}), HTTPStatus.SERVICE_UNAVAILABLE + try: + jdata = json.loads(r.text) + data: lnurl.LnurlResponseModel = lnurl.LnurlResponse.from_dict(jdata) + except (json.decoder.JSONDecodeError, lnurl.exceptions.LnurlResponseException): + return ( + jsonify({"domain": domain, "error": f"got invalid response '{r.text[:200]}'"}), + HTTPStatus.SERVICE_UNAVAILABLE, + ) - try: - jdata = json.loads(r.text) - data: lnurl.LnurlResponseModel = lnurl.LnurlResponse.from_dict(jdata) - except (json.decoder.JSONDecodeError, lnurl.exceptions.LnurlResponseException): - return ( - jsonify({"domain": domain, "error": f"got invalid response '{r.text[:200]}'"}), - HTTPStatus.SERVICE_UNAVAILABLE, - ) + if type(data) is lnurl.LnurlChannelResponse: + return jsonify({"domain": domain, "kind": "channel", "error": "unsupported"}), HTTPStatus.BAD_REQUEST - if type(data) is lnurl.LnurlChannelResponse: - return jsonify({"domain": domain, "kind": "channel", "error": "unsupported"}), HTTPStatus.BAD_REQUEST + params.update(**data.dict()) - params: Dict = data.dict() - if type(data) is lnurl.LnurlWithdrawResponse: - params.update(kind="withdraw") - params.update(fixed=data.min_withdrawable == data.max_withdrawable) + if type(data) is lnurl.LnurlWithdrawResponse: + params.update(kind="withdraw") + params.update(fixed=data.min_withdrawable == data.max_withdrawable) - # callback with k1 already in it - parsed_callback: ParseResult = urlparse(data.callback) - qs: Dict = parse_qs(parsed_callback.query) - qs["k1"] = data.k1 - parsed_callback = parsed_callback._replace(query=urlencode(qs, doseq=True)) - params.update(callback=urlunparse(parsed_callback)) + # callback with k1 already in it + parsed_callback: ParseResult = urlparse(data.callback) + qs: Dict = parse_qs(parsed_callback.query) + qs["k1"] = data.k1 + parsed_callback = parsed_callback._replace(query=urlencode(qs, doseq=True)) + params.update(callback=urlunparse(parsed_callback)) - if type(data) is lnurl.LnurlPayResponse: - params.update(kind="pay") - params.update(fixed=data.min_sendable == data.max_sendable) - params.update(description_hash=data.metadata.h) - params.update(description=data.metadata.text) - if data.metadata.images: - image = min(data.metadata.images, key=lambda image: len(image[1])) - data_uri = "data:" + image[0] + "," + image[1] - params.update(image=data_uri) - params.update(commentAllowed=jdata.get("commentAllowed", 0)) + if type(data) is lnurl.LnurlPayResponse: + params.update(kind="pay") + params.update(fixed=data.min_sendable == data.max_sendable) + params.update(description_hash=data.metadata.h) + params.update(description=data.metadata.text) + if data.metadata.images: + image = min(data.metadata.images, key=lambda image: len(image[1])) + data_uri = "data:" + image[0] + "," + image[1] + params.update(image=data_uri) + params.update(commentAllowed=jdata.get("commentAllowed", 0)) - params.update(domain=domain) return jsonify(params) + + +@core_app.route("/api/v1/lnurlauth", methods=["POST"]) +@api_check_wallet_key("admin") +@api_validate_post_request( + schema={ + "callback": {"type": "string", "required": True}, + } +) +async def api_perform_lnurlauth(): + try: + await perform_lnurlauth(g.data["callback"]) + return "", HTTPStatus.OK + except Exception as exc: + return jsonify({"error": str(exc)}), HTTPStatus.SERVICE_UNAVAILABLE diff --git a/lnbits/static/js/base.js b/lnbits/static/js/base.js index f765c38b..94041e38 100644 --- a/lnbits/static/js/base.js +++ b/lnbits/static/js/base.js @@ -44,6 +44,11 @@ window.LNbits = { description }) }, + authLnurl: function (wallet, callback) { + return this.request('post', '/api/v1/lnurlauth', wallet.adminkey, { + callback + }) + }, getWallet: function (wallet) { return this.request('get', '/api/v1/wallet', wallet.inkey) }, From d4e30356c742a1d7aae25faedb04113d3cee511a Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 10 Nov 2020 22:59:50 -0300 Subject: [PATCH 2/7] fix: return "message" instead of "error" so it is handled better at the client. --- lnbits/core/static/js/wallet.js | 7 ++++++- lnbits/core/views/api.py | 13 ++++++++----- lnbits/extensions/usermanager/views_api.py | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 9bba45ad..5e35f379 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -565,7 +565,12 @@ new Vue({ }) .catch(err => { dismissAuthMsg() - LNbits.utils.notifyApiError(err) + this.$q.notify({ + message: `Authentication failed. ${this.parse.lnurlauth.domain} says:`, + caption: err.response.data.message, + type: 'warning', + timeout: 5000 + }) }) }, deleteWallet: function (walletId, user) { diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index ecef466b..569467f3 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -300,7 +300,7 @@ async def api_lnurlscan(code: str): try: url = lnurl.Lnurl(code) except ValueError: - return jsonify({"error": "invalid lnurl"}), HTTPStatus.BAD_REQUEST + return jsonify({"message": "invalid lnurl"}), HTTPStatus.BAD_REQUEST domain = urlparse(url.url).netloc @@ -315,19 +315,22 @@ async def api_lnurlscan(code: str): async with httpx.AsyncClient() as client: r = await client.get(url.url, timeout=40) if r.is_error: - return jsonify({"domain": domain, "error": "failed to get parameters"}), HTTPStatus.SERVICE_UNAVAILABLE + return ( + jsonify({"domain": domain, "message": "failed to get parameters"}), + HTTPStatus.SERVICE_UNAVAILABLE, + ) try: jdata = json.loads(r.text) data: lnurl.LnurlResponseModel = lnurl.LnurlResponse.from_dict(jdata) except (json.decoder.JSONDecodeError, lnurl.exceptions.LnurlResponseException): return ( - jsonify({"domain": domain, "error": f"got invalid response '{r.text[:200]}'"}), + jsonify({"domain": domain, "message": f"got invalid response '{r.text[:200]}'"}), HTTPStatus.SERVICE_UNAVAILABLE, ) if type(data) is lnurl.LnurlChannelResponse: - return jsonify({"domain": domain, "kind": "channel", "error": "unsupported"}), HTTPStatus.BAD_REQUEST + return jsonify({"domain": domain, "kind": "channel", "message": "unsupported"}), HTTPStatus.BAD_REQUEST params.update(**data.dict()) @@ -368,4 +371,4 @@ async def api_perform_lnurlauth(): await perform_lnurlauth(g.data["callback"]) return "", HTTPStatus.OK except Exception as exc: - return jsonify({"error": str(exc)}), HTTPStatus.SERVICE_UNAVAILABLE + return jsonify({"message": str(exc)}), HTTPStatus.SERVICE_UNAVAILABLE diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index 821d0e0c..288fc04d 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -69,7 +69,7 @@ async def api_usermanager_users_delete(user_id): async def api_usermanager_activate_extension(): user = get_user(g.data["userid"]) if not user: - return jsonify({"error": "no such user"}), HTTPStatus.NO_CONTENT + return jsonify({"message": "no such user"}), HTTPStatus.NOT_FOUND update_user_extension(user_id=g.data["userid"], extension=g.data["extension"], active=g.data["active"]) return jsonify({"extension": "updated"}), HTTPStatus.CREATED From bb94dc65263ed6afed096b0dcf6e34c6745dc28f Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 10 Nov 2020 23:01:55 -0300 Subject: [PATCH 3/7] fix perform_lnurl error handling. --- lnbits/core/services.py | 12 ++++++------ lnbits/core/static/js/wallet.js | 16 ++++++++++------ lnbits/core/views/api.py | 9 ++++----- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 00bfc84b..ee44cac9 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -5,8 +5,8 @@ from binascii import unhexlify from typing import Optional, Tuple, Dict from urllib.parse import urlparse, parse_qs from quart import g -from lnurl import LnurlWithdrawResponse # type: ignore from ecdsa.util import sigencode_der # type: ignore +from lnurl import LnurlErrorResponse, LnurlWithdrawResponse # type: ignore try: from typing import TypedDict # type: ignore @@ -159,7 +159,7 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo ) -async def perform_lnurlauth(callback: str): +async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]: k1 = unhexlify(parse_qs(urlparse(callback).query)["k1"][0]) key = g.wallet.lnurlauth_key sig = key.sign_digest_deterministic(k1, sigencode=sigencode_der) @@ -178,11 +178,11 @@ async def perform_lnurlauth(callback: str): if resp["status"] == "OK": return None - return resp["reason"] + return LnurlErrorResponse(reason=resp["reason"]) except (KeyError, json.decoder.JSONDecodeError): - return r.text[:200] + "..." if len(r.text) > 200 else r.text - - return None + return LnurlErrorResponse( + reason=r.text[:200] + "..." if len(r.text) > 200 else r.text, + ) def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus: diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 5e35f379..01464955 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -565,12 +565,16 @@ new Vue({ }) .catch(err => { dismissAuthMsg() - this.$q.notify({ - message: `Authentication failed. ${this.parse.lnurlauth.domain} says:`, - caption: err.response.data.message, - type: 'warning', - timeout: 5000 - }) + if (err.response.data.reason) { + this.$q.notify({ + message: `Authentication failed. ${this.parse.lnurlauth.domain} says:`, + caption: err.response.data.reason, + type: 'warning', + timeout: 5000 + }) + } else { + LNbits.utils.notifyApiError(err) + } }) }, deleteWallet: function (walletId, user) { diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 569467f3..61b3854a 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -367,8 +367,7 @@ async def api_lnurlscan(code: str): } ) async def api_perform_lnurlauth(): - try: - await perform_lnurlauth(g.data["callback"]) - return "", HTTPStatus.OK - except Exception as exc: - return jsonify({"message": str(exc)}), HTTPStatus.SERVICE_UNAVAILABLE + err = await perform_lnurlauth(g.data["callback"]) + if err: + return jsonify({"reason": err.reason}), HTTPStatus.SERVICE_UNAVAILABLE + return "", HTTPStatus.OK From 805000cd066d636f7299bfe8d3d285c10c73689e Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 11 Nov 2020 02:41:30 -0300 Subject: [PATCH 4/7] =?UTF-8?q?fix=20ecdsa=20signing=20because=20these=20l?= =?UTF-8?q?ibraries=20are=20too=20na=C3=AFve.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lnbits/core/services.py | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index ee44cac9..511bf1e9 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -1,11 +1,11 @@ import trio # type: ignore import json import httpx +from io import BytesIO from binascii import unhexlify from typing import Optional, Tuple, Dict from urllib.parse import urlparse, parse_qs from quart import g -from ecdsa.util import sigencode_der # type: ignore from lnurl import LnurlErrorResponse, LnurlWithdrawResponse # type: ignore try: @@ -162,7 +162,50 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]: k1 = unhexlify(parse_qs(urlparse(callback).query)["k1"][0]) key = g.wallet.lnurlauth_key - sig = key.sign_digest_deterministic(k1, sigencode=sigencode_der) + + def int_to_bytes_suitable_der(x: int) -> bytes: + """for strict DER we need to encode the integer with some quirks""" + b = x.to_bytes((x.bit_length() + 7) // 8, "big") + + if len(b) == 0: + # ensure there's at least one byte when the int is zero + return bytes([0]) + + if b[0] & 0x80 != 0: + # ensure it doesn't start with a 0x80 and so it isn't + # interpreted as a negative number + return bytes([0]) + b + + return b + + def encode_strict_der(r_int, s_int, order): + # if s > order/2 verification will fail sometimes + # so we must fix it here (see https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147) + if s_int > order // 2: + s_int = order - s_int + + # now we do the strict DER encoding copied from + # https://github.com/KiriKiri/bip66 (without any checks) + r = int_to_bytes_suitable_der(r_int) + s = int_to_bytes_suitable_der(s_int) + + r_len = len(r) + s_len = len(s) + sign_len = 6 + r_len + s_len + + signature = BytesIO() + signature.write(0x30 .to_bytes(1, "big", signed=False)) + signature.write((sign_len - 2).to_bytes(1, "big", signed=False)) + signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(r_len.to_bytes(1, "big", signed=False)) + signature.write(r) + signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(s_len.to_bytes(1, "big", signed=False)) + signature.write(s) + + return signature.getvalue() + + sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der) async with httpx.AsyncClient() as client: r = await client.get( From b794f8302d685e4768eec7a8c85cd0c6d42c070f Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 11 Nov 2020 22:37:55 -0300 Subject: [PATCH 5/7] lnurl-auth: hashing_key -> linking_key. --- lnbits/core/models.py | 9 ++++++--- lnbits/core/services.py | 6 ++++-- lnbits/core/views/api.py | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lnbits/core/models.py b/lnbits/core/models.py index 23c111f3..4655c256 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -1,4 +1,5 @@ import json +import hmac import hashlib from ecdsa import SECP256k1, SigningKey # type: ignore from typing import List, NamedTuple, Optional, Dict @@ -35,10 +36,12 @@ class Wallet(NamedTuple): def balance(self) -> int: return self.balance_msat // 1000 - @property - def lnurlauth_key(self) -> SigningKey: + def lnurlauth_key(self, domain: str) -> SigningKey: + hashing_key = hashlib.sha256(self.id.encode("utf-8")).digest() + linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256") + return SigningKey.from_string( - hashlib.sha256(self.id.encode("utf-8")).digest(), + linking_key, curve=SECP256k1, hashfunc=hashlib.sha256, ) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 511bf1e9..991e278b 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -160,8 +160,10 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]: - k1 = unhexlify(parse_qs(urlparse(callback).query)["k1"][0]) - key = g.wallet.lnurlauth_key + cb = urlparse(callback) + + k1 = unhexlify(parse_qs(cb.query)["k1"][0]) + key = g.wallet.lnurlauth_key(cb.netloc) def int_to_bytes_suitable_der(x: int) -> bytes: """for strict DER we need to encode the integer with some quirks""" diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 61b3854a..fd4a1159 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -310,7 +310,9 @@ async def api_lnurlscan(code: str): if url.is_login: params.update(kind="auth") params.update(callback=url.url) # with k1 already in it - params.update(pubkey=g.wallet.lnurlauth_key.verifying_key.to_string("compressed").hex()) + + lnurlauth_key = g.wallet.lnurlauth_key(domain) + params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) else: async with httpx.AsyncClient() as client: r = await client.get(url.url, timeout=40) From b7e337b0eecac7e5301e91a333c75a2715c6449a Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 11 Nov 2020 22:42:40 -0300 Subject: [PATCH 6/7] lnurl-auth: show the correct wallet linking_key on modal. --- lnbits/core/static/js/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 01464955..097a7819 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -344,7 +344,7 @@ new Vue({ .request( 'GET', '/api/v1/lnurlscan/' + this.parse.data.request, - this.g.user.wallets[0].adminkey + this.g.wallet.adminkey ) .catch(err => { LNbits.utils.notifyApiError(err) From 54881d777e01a3bb1474b953e058e191eba1b5f8 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 11 Nov 2020 22:43:01 -0300 Subject: [PATCH 7/7] lnurl-auth: show wallet name on modal. --- lnbits/core/templates/core/wallet.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 88f28b68..8ca21c11 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -350,7 +350,7 @@ {% raw %}

- Authenticate with {{ parse.lnurlauth.domain }}? + Authenticate with {{ parse.lnurlauth.domain }} from wallet {{ g.wallet.name }}?