diff --git a/.env.example b/.env.example index bb4e64a1..6a3710c2 100644 --- a/.env.example +++ b/.env.example @@ -11,7 +11,11 @@ LNBITS_ALLOWED_USERS="" LNBITS_ADMIN_USERS="" # Extensions only admin can access LNBITS_ADMIN_EXTENSIONS="ngrok, admin" + # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +# Warning: Enabling this will make LNbits ignore this configuration file. Your settings will +# be stored in your database and you will be able to change them only through the Admin UI. +# Disable this to make LNbits use this config file again. LNBITS_ADMIN_UI=false LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" diff --git a/Makefile b/Makefile index 4f99f1da..ebf2a872 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ format: prettier isort black check: mypy checkprettier checkisort checkblack -prettier: $(shell find lnbits -name "*.js" -name ".html") +prettier: $(shell find lnbits -name "*.js" -o -name ".html") ./node_modules/.bin/prettier --write lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html black: @@ -18,7 +18,7 @@ mypy: isort: poetry run isort . -checkprettier: $(shell find lnbits -name "*.js" -name ".html") +checkprettier: $(shell find lnbits -name "*.js" -o -name ".html") ./node_modules/.bin/prettier --check lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html checkblack: diff --git a/README.md b/README.md index a22c857c..3bc169dd 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ LNbits is a very simple Python server that sits on top of any funding source, an LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly. -See [legend.lnbits.org](https://legend.lnbits.org) for more detailed documentation. +See [docs.lnbits.org](https://docs.lnbits.org) for more detailed documentation. Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series. @@ -70,7 +70,7 @@ Wallets can be easily generated and given out to people at events (one click mul If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! -[docs]: https://legend.lnbits.org/ +[docs]: https://docs.lnbits.org/ [docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg [github-mypy]: https://github.com/lnbits/lnbits/actions?query=workflow%3Amypy [github-mypy-badge]: https://github.com/lnbits/lnbits/workflows/mypy/badge.svg diff --git a/docs/CNAME b/docs/CNAME index 9981b110..e7e04e60 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -legend.lnbits.org \ No newline at end of file +docs.lnbits.org \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml index 6c3d6512..d937f5dc 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -3,7 +3,7 @@ remote_theme: pmarsceill/just-the-docs color_scheme: dark logo: "/logos/lnbits-full--inverse.png" search_enabled: true -url: https://legend.lnbits.org +url: https://docs.lnbits.org aux_links: "LNbits on GitHub": - "//github.com/lnbits/lnbits" diff --git a/docs/devs/api.md b/docs/devs/api.md index a8217b9c..8e150889 100644 --- a/docs/devs/api.md +++ b/docs/devs/api.md @@ -9,4 +9,4 @@ nav_order: 3 API reference ============= -[Swagger Docs](https://legend.lnbits.org/devs/swagger.html) +[Swagger Docs](https://docs.lnbits.org/devs/swagger.html) diff --git a/docs/guide/admin_ui.md b/docs/guide/admin_ui.md index 1248d3f3..9637a989 100644 --- a/docs/guide/admin_ui.md +++ b/docs/guide/admin_ui.md @@ -40,3 +40,33 @@ Allowed Users ============= enviroment variable: LNBITS_ALLOWED_USERS, comma-seperated list of user ids By defining this users, LNbits will no longer be useable by the public, only defined users and admins can then access the LNbits frontend. + + +How to activate +============= +``` +$ sudo systemctl stop lnbits.service +$ cd ~/lnbits-legend +$ sudo nano .env +``` +-> set: `LNBITS_ADMIN_UI=true` + +Now start LNbits once in the terminal window +``` +$ poetry run lnbits +``` +It will now show you the Super User Account: + +`SUCCESS | ✔️ Access super user account at: https://127.0.0.1:5000/wallet?usr=5711d7..` + +The `/wallet?usr=..` is your super user account. You just have to append it to your normal LNbits web domain. + +After that you will find the __`Admin` / `Manage Server`__ between `Wallets` and `Extensions` + +Here you can design the interface, it has TOPUP to fill wallets and you can restrict access rights to extensions only for admins or generally deactivated for everyone. You can make users admins or set up Allowed Users if you want to restrict access. And of course the classic settings of the .env file, e.g. to change the funding source wallet or set a charge fee. + +Do not forget +``` +sudo systemctl start lnbits.service +``` +A little hint, if you set `RESET TO DEFAULTS`, then a new Super User Account will also be created. The old one is then no longer valid. diff --git a/flake.nix b/flake.nix index af25ba5c..d9f0f1f0 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ }; outputs = { self, nixpkgs, poetry2nix }@inputs: let - supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; + supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forSystems = systems: f: nixpkgs.lib.genAttrs systems (system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; })); diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 32b43feb..0bc40158 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -1,7 +1,6 @@ import hashlib import re import time -from binascii import unhexlify from decimal import Decimal from typing import List, NamedTuple, Optional @@ -75,7 +74,7 @@ def decode(pr: str) -> Invoice: data_length = len(tagdata) / 5 if tag == "d": - invoice.description = _trim_to_bytes(tagdata).decode("utf-8") + invoice.description = _trim_to_bytes(tagdata).decode() elif tag == "h" and data_length == 52: invoice.description_hash = _trim_to_bytes(tagdata).hex() elif tag == "p" and data_length == 52: @@ -108,7 +107,7 @@ def decode(pr: str) -> Invoice: message = bytearray([ord(c) for c in hrp]) + data.tobytes() sig = signature[0:64] if invoice.payee: - key = VerifyingKey.from_string(unhexlify(invoice.payee), curve=SECP256k1) + key = VerifyingKey.from_string(bytes.fromhex(invoice.payee), curve=SECP256k1) key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string) else: keys = VerifyingKey.from_public_key_recovery( @@ -131,7 +130,7 @@ def encode(options): if options["timestamp"]: addr.date = int(options["timestamp"]) - addr.paymenthash = unhexlify(options["paymenthash"]) + addr.paymenthash = bytes.fromhex(options["paymenthash"]) if options["description"]: addr.tags.append(("d", options["description"])) @@ -149,8 +148,8 @@ def encode(options): while len(splits) >= 5: route.append( ( - unhexlify(splits[0]), - unhexlify(splits[1]), + bytes.fromhex(splits[0]), + bytes.fromhex(splits[1]), int(splits[2]), int(splits[3]), int(splits[4]), @@ -235,7 +234,7 @@ def lnencode(addr, privkey): raise ValueError("Must include either 'd' or 'h'") # We actually sign the hrp, then data (padded to 8 bits with zeroes). - privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey))) + privkey = secp256k1.PrivateKey(bytes.fromhex(privkey)) sig = privkey.ecdsa_sign_recoverable( bytearray([ord(c) for c in hrp]) + data.tobytes() ) @@ -261,7 +260,7 @@ class LnAddr(object): def __str__(self): return "LnAddr[{}, amount={}{} tags=[{}]]".format( - hexlify(self.pubkey.serialize()).decode("utf-8"), + bytes.hex(self.pubkey.serialize()).decode(), self.amount, self.currency, ", ".join([k + "=" + str(v) for k, v in self.tags]), diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 1c8c71ad..a80fadf2 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -451,6 +451,34 @@ async def update_payment_details( return +async def update_payment_extra( + payment_hash: str, + extra: dict, + outgoing: bool = False, + conn: Optional[Connection] = None, +) -> None: + """ + Only update the `extra` field for the payment. + Old values in the `extra` JSON object will be kept unless the new `extra` overwrites them. + """ + + amount_clause = "AND amount < 0" if outgoing else "AND amount > 0" + + row = await (conn or db).fetchone( + f"SELECT hash, extra from apipayments WHERE hash = ? {amount_clause}", + (payment_hash,), + ) + if not row: + return + db_extra = json.loads(row["extra"] if row["extra"] else "{}") + db_extra.update(extra) + + await (conn or db).execute( + f"UPDATE apipayments SET extra = ? WHERE hash = ? {amount_clause} ", + (json.dumps(db_extra), payment_hash), + ) + + async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None: await (conn or db).execute( "DELETE FROM apipayments WHERE checking_id = ?", (checking_id,) diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index 81413246..41ba5644 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -224,7 +224,7 @@ async def m007_set_invoice_expiries(db): ) ).fetchall() if len(rows): - logger.info(f"Mirgraion: Checking expiry of {len(rows)} invoices") + logger.info(f"Migration: Checking expiry of {len(rows)} invoices") for i, ( payment_request, checking_id, @@ -238,7 +238,7 @@ async def m007_set_invoice_expiries(db): invoice.date + invoice.expiry ) logger.info( - f"Mirgraion: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}" + f"Migration: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}" ) await db.execute( """ diff --git a/lnbits/core/models.py b/lnbits/core/models.py index 65c72b41..31383667 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -4,13 +4,13 @@ import hmac import json import time from sqlite3 import Row -from typing import Dict, List, NamedTuple, Optional +from typing import Dict, List, Optional from ecdsa import SECP256k1, SigningKey # type: ignore from fastapi import Query from lnurl import encode as lnurl_encode # type: ignore from loguru import logger -from pydantic import BaseModel, Extra, validator +from pydantic import BaseModel from lnbits.db import Connection from lnbits.helpers import url_for @@ -46,8 +46,8 @@ class Wallet(BaseModel): return "" 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") + hashing_key = hashlib.sha256(self.id.encode()).digest() + linking_key = hmac.digest(hashing_key, domain.encode(), "sha256") return SigningKey.from_string( linking_key, curve=SECP256k1, hashfunc=hashlib.sha256 @@ -88,7 +88,7 @@ class Payment(BaseModel): preimage: str payment_hash: str expiry: Optional[float] - extra: Optional[Dict] = {} + extra: Dict = {} wallet_id: str webhook: Optional[str] webhook_status: Optional[int] diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 336d2665..8dc973e7 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -1,6 +1,5 @@ import asyncio import json -from binascii import unhexlify from io import BytesIO from typing import Dict, List, Optional, Tuple from urllib.parse import parse_qs, urlparse @@ -13,12 +12,7 @@ from loguru import logger from lnbits import bolt11 from lnbits.db import Connection -from lnbits.decorators import ( - WalletTypeInfo, - get_key_type, - require_admin_key, - require_invoice_key, -) +from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.requestvars import g from lnbits.settings import ( @@ -308,7 +302,7 @@ async def perform_lnurlauth( ) -> Optional[LnurlErrorResponse]: cb = urlparse(callback) - k1 = unhexlify(parse_qs(cb.query)["k1"][0]) + k1 = bytes.fromhex(parse_qs(cb.query)["k1"][0]) key = wallet.wallet.lnurlauth_key(cb.netloc) diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index b57e2625..e11f764b 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -4,7 +4,6 @@ from typing import Dict import httpx from loguru import logger -from lnbits.helpers import get_current_extension_name from lnbits.tasks import SseListenersDict, register_invoice_listener from . import db diff --git a/lnbits/core/templates/core/extensions.html b/lnbits/core/templates/core/extensions.html index 1b527903..88e50269 100644 --- a/lnbits/core/templates/core/extensions.html +++ b/lnbits/core/templates/core/extensions.html @@ -23,14 +23,55 @@ > - - {% raw %} -
{{ extension.name }}
- {{ extension.shortDescription }} {% endraw %} +
+
+ +
+
+ {% raw %} +
+ {{ extension.name }} +
+
+ {{ extension.shortDescription }} +
+
+ {{ extension.name }} +
+
+ {{ extension.shortDescription }} +
+ {% endraw %} +
+
+
+ +
+ Ratings coming soon +
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index e2edfdca..46cfc690 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -23,7 +23,7 @@ {% raw %}{{ formattedBalance }} {% endraw %} {{LNBITS_DENOMINATION}} - - -