From 35920bae4880e035a396abbe5f668e24d612c03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 5 Dec 2022 15:43:26 +0100 Subject: [PATCH] code improvements, bugfixes --- lnbits/extensions/admin/crud.py | 26 ++-- lnbits/extensions/admin/migrations.py | 14 +- lnbits/extensions/admin/models.py | 21 ++- lnbits/extensions/admin/views.py | 6 +- lnbits/extensions/admin/views_api.py | 12 +- lnbits/settings.py | 189 ++++++++++++++------------ 6 files changed, 140 insertions(+), 128 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index e75854e01..51dc4eae1 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -2,7 +2,7 @@ from typing import Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import Settings, read_only_variables, settings +from lnbits.settings import read_only_variables, settings from lnbits.tasks import internal_invoice_queue from . import db @@ -27,21 +27,20 @@ async def update_wallet_balance(wallet_id: str, amount: int): return payment -async def get_settings() -> AdminSettings: +async def get_admin_settings() -> AdminSettings: row = await db.fetchone("SELECT * FROM admin.settings") - all_settings = Settings(**row) - settings = AdminSettings() - for key, value in row.items(): - if hasattr(settings, key): - setattr(settings, key, getattr(all_settings, key)) - return settings + admin_settings = AdminSettings(**row, lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources) + for key, _ in row.items(): + if hasattr(admin_settings, key): + setattr(admin_settings, key, getattr(settings, key)) + return admin_settings -async def update_settings(data: UpdateSettings) -> Optional[Settings]: +async def update_admin_settings(data: UpdateSettings) -> Optional[AdminSettings]: fields = [] - for key, value in data.dict(exclude_none=True).items(): - setattr(settings, key, value) + for key, value in data.items(): if not key in read_only_variables: + setattr(settings, key, value) if type(value) == list: joined = ",".join(value) fields.append(f"{key} = '{joined}'") @@ -52,13 +51,12 @@ async def update_settings(data: UpdateSettings) -> Optional[Settings]: if type(value) == str: value = value.replace("'", "") fields.append(f"{key} = '{value}'") - q = ", ".join(fields) await db.execute(f"UPDATE admin.settings SET {q}") row = await db.fetchone("SELECT * FROM admin.settings") assert row, "Newly updated settings couldn't be retrieved" - return Settings(**row) if row else None + return AdminSettings(**row) if row else None -async def delete_settings(): +async def delete_admin_settings(): await db.execute("DELETE FROM admin.settings") diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 185712bc0..b4e217c10 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -2,20 +2,8 @@ async def m001_create_admin_settings_table(db): await db.execute( """ CREATE TABLE IF NOT EXISTS admin.settings ( - lnbits_admin_ui TEXT, - debug TEXT, - host TEXT, - port INTEGER, - forwarded_allow_ips TEXT, - lnbits_saas_instance_id TEXT, - lnbits_saas_callback TEXT, - lnbits_saas_secret TEXT, - lnbits_path TEXT, - lnbits_commit TEXT, lnbits_admin_users TEXT, lnbits_allowed_users TEXT, - lnbits_allowed_funding_sources TEXT, - lnbits_admin_extensions TEXT, lnbits_disabled_extensions TEXT, lnbits_site_title TEXT, lnbits_site_tagline TEXT, @@ -35,9 +23,9 @@ async def m001_create_admin_settings_table(db): lnbits_hide_api TEXT, lnbits_denomination TEXT, lnbits_backend_wallet_class TEXT, - fake_wallet_secret TEXT, lnbits_endpoint TEXT, lnbits_key TEXT, + fake_wallet_secret TEXT, cliche_endpoint TEXT, corelightning_rpc TEXT, eclair_url TEXT, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index aebf30dee..04c1d5823 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,16 +1,28 @@ from typing import List, Optional from fastapi import Query -from pydantic import BaseModel +from pydantic import BaseModel, validator class UpdateSettings(BaseModel): + + @validator( + "lnbits_admin_users", + "lnbits_allowed_users", + "lnbits_theme_options", + "lnbits_disabled_extensions", + pre=True, + ) + def validate(cls, val): + if type(val) == str: + val = val.split(",") if val else [] + return val + lnbits_backend_wallet_class: str = Query(None) lnbits_admin_users: List[str] = Query(None) lnbits_allowed_users: List[str] = Query(None) - lnbits_admin_ext: List[str] = Query(None) - lnbits_disabled_ext: List[str] = Query(None) - lnbits_funding_source: str = Query(None) + lnbits_disabled_extensions: List[str] = Query(None) + lnbits_theme_options: List[str] = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) lnbits_reserve_fee_percent: float = Query(None, ge=0) @@ -21,7 +33,6 @@ class UpdateSettings(BaseModel): lnbits_site_description: str = Query(None) lnbits_default_wallet_name: str = Query(None) lnbits_denomination: str = Query(None) - lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) lnbits_ad_space: str = Query(None) lnbits_ad_space_title: str = Query(None) diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index b1d680262..72f47b63c 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -1,6 +1,3 @@ -from email.policy import default -from os import getenv - from fastapi import Request from fastapi.params import Depends from fastapi.templating import Jinja2Templates @@ -8,7 +5,6 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_admin -from lnbits.requestvars import g from lnbits.settings import get_wallet_class, settings from . import admin_ext, admin_renderer @@ -19,7 +15,7 @@ templates = Jinja2Templates(directory="templates") @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_admin)): # type: ignore WALLET = get_wallet_class() - error, balance = await WALLET.status() + _, balance = await WALLET.status() return admin_renderer().TemplateResponse( "admin/index.html", diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 2d64be49b..a39d67524 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -7,10 +7,10 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet from lnbits.decorators import check_admin from lnbits.extensions.admin import admin_ext -from lnbits.extensions.admin.models import UpdateSettings +from lnbits.extensions.admin.models import AdminSettings, UpdateSettings from lnbits.server import server_restart -from .crud import delete_settings, get_settings, update_settings, update_wallet_balance +from .crud import delete_admin_settings, get_admin_settings, update_admin_settings, update_wallet_balance @admin_ext.get( @@ -22,8 +22,8 @@ async def api_restart_server() -> dict[str, str]: @admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)]) -async def api_get_settings() -> UpdateSettings: - return await get_settings() +async def api_get_settings() -> AdminSettings: + return await get_admin_settings() @admin_ext.put( @@ -50,7 +50,7 @@ async def api_update_balance( async def api_update_settings( data: UpdateSettings = Body(...), ): - settings = await update_settings(data) + settings = await update_admin_settings(data) if settings: return {"status": "Success", "settings": settings.dict()} @@ -59,5 +59,5 @@ async def api_update_settings( "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) async def api_delete_settings() -> dict[str, str]: - await delete_settings() + await delete_admin_settings() return {"status": "Success"} diff --git a/lnbits/settings.py b/lnbits/settings.py index 7994cf8e3..1263e5d28 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -10,6 +10,7 @@ from loguru import logger from pydantic import BaseSettings, Field, validator + def list_parse_fallback(v): try: return json.loads(v) @@ -24,6 +25,13 @@ def list_parse_fallback(v): read_only_variables = [ "host", "port", + "debug", + "lnbits_allowed_funding_sources", + "lnbits_admin_extensions", + "lnbits_saas_secret", + "lnbits_saas_callback", + "lnbits_saas_instance_id", + "lnbits_admin_ui", "lnbits_commit", "lnbits_path", "forwarded_allow_ips", @@ -178,6 +186,7 @@ except: settings.lnbits_commit = "docker" +# printing enviroment variable for debugging if not settings.lnbits_admin_ui: logger.debug(f"Enviroment Settings:") for key, value in settings.dict(exclude_none=True).items(): @@ -198,95 +207,47 @@ async def check_admin_settings(): raise async with ext_db.connect() as db: - try: - row = await db.fetchone("SELECT * FROM admin.settings") - if not row or len(row) == 0: - logger.warning( - "admin.settings empty. inserting new settings and creating admin account" - ) + row = await db.fetchone("SELECT * FROM admin.settings") - from lnbits.core.crud import create_account - - account = await create_account() - settings.lnbits_admin_users.insert(0, account.id) - keys = [] - values = "" - for key, value in settings.dict(exclude_none=True).items(): - keys.append(key) - if type(value) == list: - joined = ",".join(value) - values += f"'{joined}'" - if type(value) == int or type(value) == float: - values += str(value) - if type(value) == bool: - values += "true" if value else "false" - if type(value) == str: - value = value.replace("'", "") - values += f"'{value}'" - values += "," - q = ", ".join(keys) - v = values.rstrip(",") - sql = f"INSERT INTO admin.settings ({q}) VALUES ({v})" - await db.execute(sql) - logger.warning( - "initialized admin.settings from enviroment variables." - ) - - row = await db.fetchone("SELECT * FROM admin.settings") - assert row, "Newly updated settings couldn't be retrieved" - - admin = Settings(**row) - - for key, value in admin.dict(exclude_none=True).items(): - if not key in read_only_variables: - try: - setattr(settings, key, value) - except: - logger.error( - f"error overriding setting: {key}, value: {value}" - ) - - logger.debug(f"Admin settings:") - for key, value in settings.dict(exclude_none=True).items(): - logger.debug(f"{key}: {value}") - - http = "https" if settings.lnbits_force_https else "http" - user = settings.lnbits_admin_users[0] - - admin_url = ( - f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + # create new settings if table is empty + if not row or len(row) == 0: + logger.warning( + "admin.settings empty. inserting new settings and creating admin account" ) - logger.warning(f"✔️ Access admin user account at: {admin_url}") + row = await create_admin_settings(db) - if ( - settings.lnbits_saas_callback - and settings.lnbits_saas_secret - and settings.lnbits_saas_instance_id - ): - with httpx.Client() as client: - headers = { - "Content-Type": "application/json; charset=utf-8", - "X-API-KEY": settings.lnbits_saas_secret, - } - payload = { - "instance_id": settings.lnbits_saas_instance_id, - "adminuser": user, - } - try: - client.post( - settings.lnbits_saas_callback, - headers=headers, - json=payload, - ) - logger.warning("sent admin user to saas application") - except: - logger.error( - f"error sending admin user to saas: {settings.lnbits_saas_callback}" - ) + # setting settings from database into memory + from lnbits.extensions.admin.models import AdminSettings + sets = AdminSettings(**row, lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources) + for key, value in sets.dict().items(): + if not key in read_only_variables: + try: + setattr(settings, key, value) + except: + logger.error( + f"error overriding setting: {key}, value: {value}" + ) - except: - logger.error("admin.settings tables does not exist.") - raise + # printing settings for debugging + logger.debug(f"Admin settings:") + for key, value in settings.dict(exclude_none=True).items(): + logger.debug(f"{key}: {value}") + + http = "https" if settings.lnbits_force_https else "http" + user = settings.lnbits_admin_users[0] + admin_url = ( + f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + ) + logger.warning(f"✔️ Access admin user account at: {admin_url}") + + + # callback for saas + if ( + settings.lnbits_saas_callback + and settings.lnbits_saas_secret + and settings.lnbits_saas_instance_id + ): + send_admin_user_to_saas(user) wallets_module = importlib.import_module("lnbits.wallets") @@ -296,3 +257,61 @@ FAKE_WALLET = getattr(wallets_module, "FakeWallet")() def get_wallet_class(): wallet_class = getattr(wallets_module, settings.lnbits_backend_wallet_class) return wallet_class() + + +async def create_admin_settings(db): + + # if not imported here, circular import error + from lnbits.core.crud import create_account + + account = await create_account() + settings.lnbits_admin_users.insert(0, account.id) + keys = [] + values = "" + for key, value in settings.dict(exclude_none=True).items(): + if not key in read_only_variables: + keys.append(key) + if type(value) == list: + joined = ",".join(value) + values += f"'{joined}'" + if type(value) == int or type(value) == float: + values += str(value) + if type(value) == bool: + values += "true" if value else "false" + if type(value) == str: + value = value.replace("'", "") + values += f"'{value}'" + values += "," + q = ", ".join(keys) + v = values.rstrip(",") + sql = f"INSERT INTO admin.settings ({q}) VALUES ({v})" + await db.execute(sql) + logger.warning( + "initialized admin.settings from enviroment variables." + ) + row = await db.fetchone("SELECT * FROM admin.settings") + assert row, "Newly updated settings couldn't be retrieved" + return row + + +def send_admin_user_to_saas(user): + with httpx.Client() as client: + headers = { + "Content-Type": "application/json; charset=utf-8", + "X-API-KEY": settings.lnbits_saas_secret, + } + payload = { + "instance_id": settings.lnbits_saas_instance_id, + "adminuser": user, + } + try: + client.post( + settings.lnbits_saas_callback, + headers=headers, + json=payload, + ) + logger.warning("sent admin user to saas application") + except: + logger.error( + f"error sending admin user to saas: {settings.lnbits_saas_callback}" + )