From 97d1effe1c0a28ecd6f30f019d5650584a3dcfcf Mon Sep 17 00:00:00 2001 From: Dread <34528298+islandbitcoin@users.noreply.github.com> Date: Mon, 19 Dec 2022 18:55:05 -0500 Subject: [PATCH 01/20] Remove duplicate LNBITS_ALLOWED_USERS --- .env.example | 3 --- 1 file changed, 3 deletions(-) diff --git a/.env.example b/.env.example index 13765574..bb4e64a1 100644 --- a/.env.example +++ b/.env.example @@ -14,9 +14,6 @@ LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available LNBITS_ADMIN_UI=false -# Restricts access, User IDs seperated by comma -LNBITS_ALLOWED_USERS="" - LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" # Ad space description From 4bd0372819a600b22bad8424befc50dc9f14dedd Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 20 Dec 2022 10:30:32 +0100 Subject: [PATCH 02/20] deploy: build on minor changes --- .github/workflows/on-tag.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index f6fa53e9..a9f58985 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -7,6 +7,7 @@ on: push: tags: - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+.[0-9]+" - "[0-9]+.[0-9]+.[0-9]+-*" jobs: From 375f5d9f25c6aeacd5f2d4ee7d70150da8660e2d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 20 Dec 2022 11:07:26 +0100 Subject: [PATCH 03/20] docker: update to python 3.10 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a03e1eb2..cc3a14bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-slim +FROM python:3.10-slim RUN apt-get clean RUN apt-get update From 681883a8f9e74c571bb2d45265986ab23c46ca06 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 19 Dec 2022 19:41:10 +0200 Subject: [PATCH 04/20] fix: catch all exceptions for webhook --- lnbits/extensions/lnurlp/tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 23f312cb..19a39a32 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -2,6 +2,7 @@ import asyncio import json import httpx +from loguru import logger from lnbits.core import db as core_db from lnbits.core.models import Payment @@ -50,7 +51,8 @@ async def on_invoice_paid(payment: Payment) -> None: r = await client.post(pay_link.webhook_url, **kwargs) await mark_webhook_sent(payment, r.status_code) - except (httpx.ConnectError, httpx.RequestError): + except Exception as ex: + logger.error(ex) await mark_webhook_sent(payment, -1) From c5fdd35078d19293c24a895715a72c6d35659426 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 19 Dec 2022 19:42:11 +0200 Subject: [PATCH 05/20] feat: store mor info about the `webhook` call status --- lnbits/extensions/lnurlp/tasks.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 19a39a32..5de47f2e 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -49,15 +49,22 @@ async def on_invoice_paid(payment: Payment) -> None: if pay_link.webhook_headers: kwargs["headers"] = json.loads(pay_link.webhook_headers) - r = await client.post(pay_link.webhook_url, **kwargs) - await mark_webhook_sent(payment, r.status_code) + r: httpx.Response = await client.post(pay_link.webhook_url, **kwargs) + await mark_webhook_sent( + payment, r.status_code, r.is_success, r.reason_phrase, r.text + ) except Exception as ex: logger.error(ex) - await mark_webhook_sent(payment, -1) + await mark_webhook_sent(payment, -1, False, "Unexpected Error", str(ex)) -async def mark_webhook_sent(payment: Payment, status: int) -> None: - payment.extra["wh_status"] = status +async def mark_webhook_sent( + payment: Payment, status: int, is_success: bool, reason_phrase="", text="" +) -> None: + payment.extra["wh_status"] = status # keep for backwards compability + payment.extra["wh_success"] = is_success + payment.extra["wh_message"] = reason_phrase + payment.extra["wh_response"] = text await core_db.execute( """ From c90f7878d79dbea33a695eb7241a4fce9e5abc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 20 Dec 2022 13:14:49 +0100 Subject: [PATCH 06/20] fix usermanager mypy --- lnbits/extensions/usermanager/crud.py | 33 +++++------- lnbits/extensions/usermanager/models.py | 6 +-- lnbits/extensions/usermanager/views.py | 5 +- lnbits/extensions/usermanager/views_api.py | 60 +++++++++++----------- pyproject.toml | 1 - 5 files changed, 50 insertions(+), 55 deletions(-) diff --git a/lnbits/extensions/usermanager/crud.py b/lnbits/extensions/usermanager/crud.py index 649888a8..d7b64dbf 100644 --- a/lnbits/extensions/usermanager/crud.py +++ b/lnbits/extensions/usermanager/crud.py @@ -10,12 +10,10 @@ from lnbits.core.crud import ( from lnbits.core.models import Payment from . import db -from .models import CreateUserData, Users, Wallets - -### Users +from .models import CreateUserData, User, Wallet -async def create_usermanager_user(data: CreateUserData) -> Users: +async def create_usermanager_user(data: CreateUserData) -> User: account = await create_account() user = await get_user(account.id) assert user, "Newly created user couldn't be retrieved" @@ -50,17 +48,17 @@ async def create_usermanager_user(data: CreateUserData) -> Users: return user_created -async def get_usermanager_user(user_id: str) -> Optional[Users]: +async def get_usermanager_user(user_id: str) -> Optional[User]: row = await db.fetchone("SELECT * FROM usermanager.users WHERE id = ?", (user_id,)) - return Users(**row) if row else None + return User(**row) if row else None -async def get_usermanager_users(user_id: str) -> List[Users]: +async def get_usermanager_users(user_id: str) -> list[User]: rows = await db.fetchall( "SELECT * FROM usermanager.users WHERE admin = ?", (user_id,) ) - return [Users(**row) for row in rows] + return [User(**row) for row in rows] async def delete_usermanager_user(user_id: str, delete_core: bool = True) -> None: @@ -73,12 +71,9 @@ async def delete_usermanager_user(user_id: str, delete_core: bool = True) -> Non await db.execute("""DELETE FROM usermanager.wallets WHERE "user" = ?""", (user_id,)) -### Wallets - - async def create_usermanager_wallet( user_id: str, wallet_name: str, admin_id: str -) -> Wallets: +) -> Wallet: wallet = await create_wallet(user_id=user_id, wallet_name=wallet_name) await db.execute( """ @@ -92,28 +87,28 @@ async def create_usermanager_wallet( return wallet_created -async def get_usermanager_wallet(wallet_id: str) -> Optional[Wallets]: +async def get_usermanager_wallet(wallet_id: str) -> Optional[Wallet]: row = await db.fetchone( "SELECT * FROM usermanager.wallets WHERE id = ?", (wallet_id,) ) - return Wallets(**row) if row else None + return Wallet(**row) if row else None -async def get_usermanager_wallets(admin_id: str) -> Optional[Wallets]: +async def get_usermanager_wallets(admin_id: str) -> list[Wallet]: rows = await db.fetchall( "SELECT * FROM usermanager.wallets WHERE admin = ?", (admin_id,) ) - return [Wallets(**row) for row in rows] + return [Wallet(**row) for row in rows] -async def get_usermanager_users_wallets(user_id: str) -> Optional[Wallets]: +async def get_usermanager_users_wallets(user_id: str) -> list[Wallet]: rows = await db.fetchall( """SELECT * FROM usermanager.wallets WHERE "user" = ?""", (user_id,) ) - return [Wallets(**row) for row in rows] + return [Wallet(**row) for row in rows] -async def get_usermanager_wallet_transactions(wallet_id: str) -> Optional[Payment]: +async def get_usermanager_wallet_transactions(wallet_id: str) -> list[Payment]: return await get_payments( wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True ) diff --git a/lnbits/extensions/usermanager/models.py b/lnbits/extensions/usermanager/models.py index 15f50e28..05122cc8 100644 --- a/lnbits/extensions/usermanager/models.py +++ b/lnbits/extensions/usermanager/models.py @@ -19,7 +19,7 @@ class CreateUserWallet(BaseModel): admin_id: str = Query(...) -class Users(BaseModel): +class User(BaseModel): id: str name: str admin: str @@ -27,7 +27,7 @@ class Users(BaseModel): password: Optional[str] = None -class Wallets(BaseModel): +class Wallet(BaseModel): id: str admin: str name: str @@ -36,5 +36,5 @@ class Wallets(BaseModel): inkey: str @classmethod - def from_row(cls, row: Row) -> "Wallets": + def from_row(cls, row: Row) -> "Wallet": return cls(**dict(row)) diff --git a/lnbits/extensions/usermanager/views.py b/lnbits/extensions/usermanager/views.py index 420669b0..1891a447 100644 --- a/lnbits/extensions/usermanager/views.py +++ b/lnbits/extensions/usermanager/views.py @@ -9,7 +9,10 @@ from . import usermanager_ext, usermanager_renderer @usermanager_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +async def index( + request: Request, + user: User = Depends(check_user_exists) #type: ignore +): return usermanager_renderer().TemplateResponse( "usermanager/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index b1bf8ef8..06020cf7 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -21,40 +21,44 @@ from .crud import ( get_usermanager_wallet_transactions, get_usermanager_wallets, ) -from .models import CreateUserData, CreateUserWallet - -# Users +from .models import CreateUserData, CreateUserWallet, Wallet @usermanager_ext.get("/api/v1/users", status_code=HTTPStatus.OK) -async def api_usermanager_users(wallet: WalletTypeInfo = Depends(require_admin_key)): +async def api_usermanager_users( + wallet: WalletTypeInfo = Depends(require_admin_key) #type: ignore +): user_id = wallet.wallet.user return [user.dict() for user in await get_usermanager_users(user_id)] @usermanager_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) -async def api_usermanager_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_usermanager_user( + user_id, + wallet: WalletTypeInfo = Depends(get_key_type) #type: ignore +): user = await get_usermanager_user(user_id) + if not user: + return None return user.dict() -@usermanager_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED) -async def api_usermanager_users_create( - data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) -): +@usermanager_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED, dependencies=[Depends(get_key_type)]) +async def api_usermanager_users_create(data: CreateUserData): user = await create_usermanager_user(data) full = user.dict() - full["wallets"] = [ - wallet.dict() for wallet in await get_usermanager_users_wallets(user.id) - ] + wallets: list[Wallet] = await get_usermanager_users_wallets(user.id) + if wallets: + full["wallets"] = [ + wallet.dict() for wallet in wallets + ] return full -@usermanager_ext.delete("/api/v1/users/{user_id}") +@usermanager_ext.delete("/api/v1/users/{user_id}", dependencies=[Depends(require_admin_key)]) async def api_usermanager_users_delete( user_id, delete_core: bool = Query(True), - wallet: WalletTypeInfo = Depends(require_admin_key), ): user = await get_usermanager_user(user_id) if not user: @@ -84,10 +88,8 @@ async def api_usermanager_activate_extension( # Wallets -@usermanager_ext.post("/api/v1/wallets") -async def api_usermanager_wallets_create( - data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) -): +@usermanager_ext.post("/api/v1/wallets", dependencies=[Depends(get_key_type)]) +async def api_usermanager_wallets_create(data: CreateUserWallet): user = await create_usermanager_wallet( user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id ) @@ -95,31 +97,27 @@ async def api_usermanager_wallets_create( @usermanager_ext.get("/api/v1/wallets") -async def api_usermanager_wallets(wallet: WalletTypeInfo = Depends(require_admin_key)): +async def api_usermanager_wallets( + wallet: WalletTypeInfo = Depends(require_admin_key) #type: ignore +): admin_id = wallet.wallet.user return [wallet.dict() for wallet in await get_usermanager_wallets(admin_id)] -@usermanager_ext.get("/api/v1/transactions/{wallet_id}") -async def api_usermanager_wallet_transactions( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) -): +@usermanager_ext.get("/api/v1/transactions/{wallet_id}", dependencies=[Depends(get_key_type)]) +async def api_usermanager_wallet_transactions(wallet_id): return await get_usermanager_wallet_transactions(wallet_id) -@usermanager_ext.get("/api/v1/wallets/{user_id}") -async def api_usermanager_users_wallets( - user_id, wallet: WalletTypeInfo = Depends(require_admin_key) -): +@usermanager_ext.get("/api/v1/wallets/{user_id}", dependencies=[Depends(require_admin_key)]) +async def api_usermanager_users_wallets(user_id): return [ s_wallet.dict() for s_wallet in await get_usermanager_users_wallets(user_id) ] -@usermanager_ext.delete("/api/v1/wallets/{wallet_id}") -async def api_usermanager_wallets_delete( - wallet_id, wallet: WalletTypeInfo = Depends(require_admin_key) -): +@usermanager_ext.delete("/api/v1/wallets/{wallet_id}", dependencies=[Depends(require_admin_key)]) +async def api_usermanager_wallets_delete(wallet_id): get_wallet = await get_usermanager_wallet(wallet_id) if not get_wallet: raise HTTPException( diff --git a/pyproject.toml b/pyproject.toml index a08e5f71..e0a42fc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,7 +110,6 @@ exclude = """(?x)( | ^lnbits/extensions/streamalerts. | ^lnbits/extensions/tipjar. | ^lnbits/extensions/tpos. - | ^lnbits/extensions/usermanager. | ^lnbits/extensions/watchonly. | ^lnbits/extensions/withdraw. | ^lnbits/wallets/lnd_grpc_files. From 22cafe9ed0629f591c2ec606cbc4bf1358a19e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 20 Dec 2022 13:17:07 +0100 Subject: [PATCH 07/20] remove uneeded if --- lnbits/extensions/usermanager/views_api.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index 06020cf7..84545290 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -47,11 +47,9 @@ async def api_usermanager_user( async def api_usermanager_users_create(data: CreateUserData): user = await create_usermanager_user(data) full = user.dict() - wallets: list[Wallet] = await get_usermanager_users_wallets(user.id) - if wallets: - full["wallets"] = [ - wallet.dict() for wallet in wallets - ] + full["wallets"] = [ + wallet.dict() for wallet in await get_usermanager_users_wallets(user.id) + ] return full From 29869cfcda9575e41a6b91d1beb205ba6a2e44b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 20 Dec 2022 13:18:37 +0100 Subject: [PATCH 08/20] formatting --- lnbits/extensions/usermanager/views.py | 3 +-- lnbits/extensions/usermanager/views_api.py | 29 +++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lnbits/extensions/usermanager/views.py b/lnbits/extensions/usermanager/views.py index 1891a447..814659f7 100644 --- a/lnbits/extensions/usermanager/views.py +++ b/lnbits/extensions/usermanager/views.py @@ -10,8 +10,7 @@ from . import usermanager_ext, usermanager_renderer @usermanager_ext.get("/", response_class=HTMLResponse) async def index( - request: Request, - user: User = Depends(check_user_exists) #type: ignore + request: Request, user: User = Depends(check_user_exists) # type: ignore ): return usermanager_renderer().TemplateResponse( "usermanager/index.html", {"request": request, "user": user.dict()} diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index 84545290..56b5de9a 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -26,7 +26,7 @@ from .models import CreateUserData, CreateUserWallet, Wallet @usermanager_ext.get("/api/v1/users", status_code=HTTPStatus.OK) async def api_usermanager_users( - wallet: WalletTypeInfo = Depends(require_admin_key) #type: ignore + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): user_id = wallet.wallet.user return [user.dict() for user in await get_usermanager_users(user_id)] @@ -34,8 +34,7 @@ async def api_usermanager_users( @usermanager_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) async def api_usermanager_user( - user_id, - wallet: WalletTypeInfo = Depends(get_key_type) #type: ignore + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await get_usermanager_user(user_id) if not user: @@ -43,7 +42,11 @@ async def api_usermanager_user( return user.dict() -@usermanager_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED, dependencies=[Depends(get_key_type)]) +@usermanager_ext.post( + "/api/v1/users", + status_code=HTTPStatus.CREATED, + dependencies=[Depends(get_key_type)], +) async def api_usermanager_users_create(data: CreateUserData): user = await create_usermanager_user(data) full = user.dict() @@ -53,7 +56,9 @@ async def api_usermanager_users_create(data: CreateUserData): return full -@usermanager_ext.delete("/api/v1/users/{user_id}", dependencies=[Depends(require_admin_key)]) +@usermanager_ext.delete( + "/api/v1/users/{user_id}", dependencies=[Depends(require_admin_key)] +) async def api_usermanager_users_delete( user_id, delete_core: bool = Query(True), @@ -96,25 +101,31 @@ async def api_usermanager_wallets_create(data: CreateUserWallet): @usermanager_ext.get("/api/v1/wallets") async def api_usermanager_wallets( - wallet: WalletTypeInfo = Depends(require_admin_key) #type: ignore + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): admin_id = wallet.wallet.user return [wallet.dict() for wallet in await get_usermanager_wallets(admin_id)] -@usermanager_ext.get("/api/v1/transactions/{wallet_id}", dependencies=[Depends(get_key_type)]) +@usermanager_ext.get( + "/api/v1/transactions/{wallet_id}", dependencies=[Depends(get_key_type)] +) async def api_usermanager_wallet_transactions(wallet_id): return await get_usermanager_wallet_transactions(wallet_id) -@usermanager_ext.get("/api/v1/wallets/{user_id}", dependencies=[Depends(require_admin_key)]) +@usermanager_ext.get( + "/api/v1/wallets/{user_id}", dependencies=[Depends(require_admin_key)] +) async def api_usermanager_users_wallets(user_id): return [ s_wallet.dict() for s_wallet in await get_usermanager_users_wallets(user_id) ] -@usermanager_ext.delete("/api/v1/wallets/{wallet_id}", dependencies=[Depends(require_admin_key)]) +@usermanager_ext.delete( + "/api/v1/wallets/{wallet_id}", dependencies=[Depends(require_admin_key)] +) async def api_usermanager_wallets_delete(wallet_id): get_wallet = await get_usermanager_wallet(wallet_id) if not get_wallet: From 36ae851a8d4721e13fd8a97ab12ef75e6506e396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 20 Dec 2022 13:44:14 +0100 Subject: [PATCH 09/20] nitpicking vlad :) --- lnbits/extensions/usermanager/crud.py | 8 ++++---- lnbits/extensions/usermanager/views_api.py | 12 ++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/usermanager/crud.py b/lnbits/extensions/usermanager/crud.py index d7b64dbf..959914f2 100644 --- a/lnbits/extensions/usermanager/crud.py +++ b/lnbits/extensions/usermanager/crud.py @@ -53,7 +53,7 @@ async def get_usermanager_user(user_id: str) -> Optional[User]: return User(**row) if row else None -async def get_usermanager_users(user_id: str) -> list[User]: +async def get_usermanager_users(user_id: str) -> List[User]: rows = await db.fetchall( "SELECT * FROM usermanager.users WHERE admin = ?", (user_id,) ) @@ -94,21 +94,21 @@ async def get_usermanager_wallet(wallet_id: str) -> Optional[Wallet]: return Wallet(**row) if row else None -async def get_usermanager_wallets(admin_id: str) -> list[Wallet]: +async def get_usermanager_wallets(admin_id: str) -> List[Wallet]: rows = await db.fetchall( "SELECT * FROM usermanager.wallets WHERE admin = ?", (admin_id,) ) return [Wallet(**row) for row in rows] -async def get_usermanager_users_wallets(user_id: str) -> list[Wallet]: +async def get_usermanager_users_wallets(user_id: str) -> List[Wallet]: rows = await db.fetchall( """SELECT * FROM usermanager.wallets WHERE "user" = ?""", (user_id,) ) return [Wallet(**row) for row in rows] -async def get_usermanager_wallet_transactions(wallet_id: str) -> list[Payment]: +async def get_usermanager_wallet_transactions(wallet_id: str) -> List[Payment]: return await get_payments( wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True ) diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index 56b5de9a..bd111d49 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -21,7 +21,7 @@ from .crud import ( get_usermanager_wallet_transactions, get_usermanager_wallets, ) -from .models import CreateUserData, CreateUserWallet, Wallet +from .models import CreateUserData, CreateUserWallet @usermanager_ext.get("/api/v1/users", status_code=HTTPStatus.OK) @@ -32,14 +32,10 @@ async def api_usermanager_users( return [user.dict() for user in await get_usermanager_users(user_id)] -@usermanager_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) -async def api_usermanager_user( - user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore -): +@usermanager_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK, dependencies=[Depends(get_key_type)]) +async def api_usermanager_user(user_id): user = await get_usermanager_user(user_id) - if not user: - return None - return user.dict() + return user.dict() if user else None @usermanager_ext.post( From c2df13bf24dfafbd69db677ccb7329f1d4fccd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 20 Dec 2022 14:23:02 +0100 Subject: [PATCH 10/20] formatting --- lnbits/extensions/usermanager/views_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index bd111d49..493a71bc 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -32,7 +32,11 @@ async def api_usermanager_users( return [user.dict() for user in await get_usermanager_users(user_id)] -@usermanager_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK, dependencies=[Depends(get_key_type)]) +@usermanager_ext.get( + "/api/v1/users/{user_id}", + status_code=HTTPStatus.OK, + dependencies=[Depends(get_key_type)], +) async def api_usermanager_user(user_id): user = await get_usermanager_user(user_id) return user.dict() if user else None From b68b8a0292516604c5a14f082cf0d1fce05e0cca Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 14:56:27 +0200 Subject: [PATCH 11/20] fix: do not loose error, log it --- lnbits/core/views/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index ebce4b85..2bd19978 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -214,7 +214,8 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): lnurl_response = resp["reason"] else: lnurl_response = True - except (httpx.ConnectError, httpx.RequestError): + except (httpx.ConnectError, httpx.RequestError) as ex: + logger.error(ex) lnurl_response = False return { From dd4a9f10cf4c965f8efcf7285503bdbf57d04727 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 14:57:00 +0200 Subject: [PATCH 12/20] feat: add function to update the `extra` JSON values --- lnbits/core/crud.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 1c8c71ad..771ff397 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -451,6 +451,36 @@ async def update_payment_details( return +async def update_payment_extra( + payment_hash: str, + extra: dict, + 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. + """ + + row = await (conn or db).fetchone( + "SELECT hash, extra from apipayments WHERE hash = ?", + (payment_hash), + ) + if not row: + return + existing_extra = json.loads(row["extra"] if row["extra"] else "{}") + new_extra = { + **existing_extra, + **extra, + } + await (conn or db).execute( + """ + UPDATE apipayments SET extra = ? + WHERE hash = ? + """, + (json.dumps(new_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,) From 8fae90bb9dcd5976231fc7942f030f00adc8a75e Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:03:22 +0200 Subject: [PATCH 13/20] feat: add `webhook_headers` and `webhook_body` to withdraws --- lnbits/extensions/withdraw/crud.py | 6 ++- lnbits/extensions/withdraw/migrations.py | 10 +++++ lnbits/extensions/withdraw/models.py | 4 ++ lnbits/extensions/withdraw/static/js/index.js | 37 +++++++++++++------ .../withdraw/templates/withdraw/index.html | 24 ++++++++++++ 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py index 9868b057..83404c62 100644 --- a/lnbits/extensions/withdraw/crud.py +++ b/lnbits/extensions/withdraw/crud.py @@ -27,9 +27,11 @@ async def create_withdraw_link( open_time, usescsv, webhook_url, + webhook_headers, + webhook_body, custom_url ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( link_id, @@ -45,6 +47,8 @@ async def create_withdraw_link( int(datetime.now().timestamp()) + data.wait_time, usescsv, data.webhook_url, + data.webhook_headers, + data.webhook_body, data.custom_url, ), ) diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py index 0c6ed4fc..95805ae7 100644 --- a/lnbits/extensions/withdraw/migrations.py +++ b/lnbits/extensions/withdraw/migrations.py @@ -122,3 +122,13 @@ async def m005_add_custom_print_design(db): Adds custom print design """ await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;") + + +async def m006_webhook_headers_and_body(db): + """ + Add headers and body to webhooks + """ + await db.execute( + "ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_headers TEXT;" + ) + await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_body TEXT;") diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py index 2672537f..51c6a1cf 100644 --- a/lnbits/extensions/withdraw/models.py +++ b/lnbits/extensions/withdraw/models.py @@ -16,6 +16,8 @@ class CreateWithdrawData(BaseModel): wait_time: int = Query(..., ge=1) is_unique: bool webhook_url: str = Query(None) + webhook_headers: str = Query(None) + webhook_body: str = Query(None) custom_url: str = Query(None) @@ -35,6 +37,8 @@ class WithdrawLink(BaseModel): usescsv: str = Query(None) number: int = Query(0) webhook_url: str = Query(None) + webhook_headers: str = Query(None) + webhook_body: str = Query(None) custom_url: str = Query(None) @property diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index a3eaa593..ced78439 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -63,7 +63,8 @@ new Vue({ secondMultiplierOptions: ['seconds', 'minutes', 'hours'], data: { is_unique: false, - use_custom: false + use_custom: false, + has_webhook: false } }, simpleformDialog: { @@ -188,23 +189,35 @@ new Vue({ }, updateWithdrawLink: function (wallet, data) { var self = this + const body = _.pick( + data, + 'title', + 'min_withdrawable', + 'max_withdrawable', + 'uses', + 'wait_time', + 'is_unique', + 'webhook_url', + 'webhook_headers', + 'webhook_body', + 'custom_url' + ) + + if (data.has_webhook) { + body = { + ...body, + webhook_url: data.webhook_url, + webhook_headers: data.webhook_headers, + webhook_body: data.webhook_body + } + } LNbits.api .request( 'PUT', '/withdraw/api/v1/links/' + data.id, wallet.adminkey, - _.pick( - data, - 'title', - 'min_withdrawable', - 'max_withdrawable', - 'uses', - 'wait_time', - 'is_unique', - 'webhook_url', - 'custom_url' - ) + body ) .then(function (response) { self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index 27684f6b..3ae244e6 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -209,7 +209,13 @@ + + + From 0ab99bef414d47bce4413a4f3fb012b11a055a87 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:04:49 +0200 Subject: [PATCH 14/20] refactor: use `update_payment_extra` --- lnbits/extensions/lnurlp/tasks.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 5de47f2e..72805603 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -5,6 +5,7 @@ import httpx from loguru import logger from lnbits.core import db as core_db +from lnbits.core.crud import update_payment_extra from lnbits.core.models import Payment from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener @@ -66,10 +67,4 @@ async def mark_webhook_sent( payment.extra["wh_message"] = reason_phrase payment.extra["wh_response"] = text - await core_db.execute( - """ - UPDATE apipayments SET extra = ? - WHERE hash = ? - """, - (json.dumps(payment.extra), payment.payment_hash), - ) + await update_payment_extra(payment.payment_hash, payment.extra) From 60cd40d5e524d77945196d266056f7d24a0e8880 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:05:28 +0200 Subject: [PATCH 15/20] feat: `update_payment_extra` with webhook response --- lnbits/extensions/withdraw/lnurl.py | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 5737e54f..17ef6464 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -11,6 +11,7 @@ from loguru import logger from starlette.requests import Request from starlette.responses import HTMLResponse +from lnbits.core.crud import update_payment_extra from lnbits.core.services import pay_invoice from . import withdraw_ext @@ -44,7 +45,11 @@ async def api_lnurl_response(request: Request, unique_hash): "minWithdrawable": link.min_withdrawable * 1000, "maxWithdrawable": link.max_withdrawable * 1000, "defaultDescription": link.title, + "webhook_url": link.webhook_url, + "webhook_headers": link.webhook_headers, + "webhook_body": link.webhook_body, } + return json.dumps(withdrawResponse) @@ -56,7 +61,7 @@ async def api_lnurl_response(request: Request, unique_hash): name="withdraw.api_lnurl_callback", summary="lnurl withdraw callback", description=""" - This enpoints allows you to put unique_hash, k1 + This endpoints allows you to put unique_hash, k1 and a payment_request to get your payment_request paid. """, response_description="JSON with status", @@ -143,18 +148,39 @@ async def api_lnurl_callback( if link.webhook_url: async with httpx.AsyncClient() as client: try: - r = await client.post( - link.webhook_url, - json={ + kwargs = { + "json": { "payment_hash": payment_hash, "payment_request": payment_request, "lnurlw": link.id, }, - timeout=40, + "timeout": 40, + } + if link.webhook_body: + kwargs["json"]["body"] = json.loads(link.webhook_body) + if link.webhook_headers: + kwargs["headers"] = json.loads(link.webhook_headers) + + r: httpx.Response = await client.post(link.webhook_url, **kwargs) + await update_payment_extra( + payment_hash, + dict( + { + "wh_success": r.is_success, + "wh_message": r.reason_phrase, + "wh_response": r.text, + } + ), ) 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) + logger.error( + "Caught exception when dispatching webhook url: " + str(exc) + ) + await update_payment_extra( + payment_hash, + dict({"wh_success": False, "wh_message": str(exc)}), + ) return {"status": "OK"} From ec01c74da183a45b4218f89503a35ad4e1826556 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:10:12 +0200 Subject: [PATCH 16/20] fix: `mypy` --- lnbits/core/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 771ff397..8f514bea 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -463,7 +463,7 @@ async def update_payment_extra( row = await (conn or db).fetchone( "SELECT hash, extra from apipayments WHERE hash = ?", - (payment_hash), + (payment_hash,), ) if not row: return From f3c884111d6aed64dfdb8c0a83e6ca92f920ae1d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:47:22 +0200 Subject: [PATCH 17/20] refactor: use `dict.update()` --- lnbits/core/crud.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 8f514bea..7d9f2a5b 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -467,17 +467,15 @@ async def update_payment_extra( ) if not row: return - existing_extra = json.loads(row["extra"] if row["extra"] else "{}") - new_extra = { - **existing_extra, - **extra, - } + db_extra = json.loads(row["extra"] if row["extra"] else "{}") + db_extra.update(extra) + await (conn or db).execute( """ UPDATE apipayments SET extra = ? WHERE hash = ? """, - (json.dumps(new_extra), payment_hash), + (json.dumps(db_extra), payment_hash), ) From 7b0f9f61fc2092f6dcf16a354d42e058e7e65c0c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 21 Dec 2022 15:47:35 +0200 Subject: [PATCH 18/20] fix: remove double dict --- lnbits/extensions/withdraw/lnurl.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 17ef6464..7260df1e 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -164,13 +164,11 @@ async def api_lnurl_callback( r: httpx.Response = await client.post(link.webhook_url, **kwargs) await update_payment_extra( payment_hash, - dict( - { - "wh_success": r.is_success, - "wh_message": r.reason_phrase, - "wh_response": r.text, - } - ), + { + "wh_success": r.is_success, + "wh_message": r.reason_phrase, + "wh_response": r.text, + }, ) except Exception as exc: # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid @@ -179,7 +177,7 @@ async def api_lnurl_callback( ) await update_payment_extra( payment_hash, - dict({"wh_success": False, "wh_message": str(exc)}), + {"wh_success": False, "wh_message": str(exc)}, ) return {"status": "OK"} From 8d5f5e4afbf7230a1cc6f0793009b79f9222c11c Mon Sep 17 00:00:00 2001 From: Axel <84231978+AxelHamburch@users.noreply.github.com> Date: Wed, 21 Dec 2022 21:33:10 +0100 Subject: [PATCH 19/20] Add "How to activat the Admin UI" --- docs/guide/admin_ui.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/guide/admin_ui.md b/docs/guide/admin_ui.md index 1248d3f3..a870a2ba 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 activat +============= +``` +$ 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. From 49a68df5e4fd3a7c4169949ddf38845c808464d7 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:07:49 +0100 Subject: [PATCH 20/20] Apply suggestions from code review --- docs/guide/admin_ui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/admin_ui.md b/docs/guide/admin_ui.md index a870a2ba..9637a989 100644 --- a/docs/guide/admin_ui.md +++ b/docs/guide/admin_ui.md @@ -42,7 +42,7 @@ 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 activat +How to activate ============= ``` $ sudo systemctl stop lnbits.service