From 04cfdc2bf3b15bc9d9bdc5b0137d0d729c0ccadf Mon Sep 17 00:00:00 2001 From: Taylor Helsper Date: Thu, 29 Sep 2022 23:06:45 -0500 Subject: [PATCH 001/134] Update index.html --- lnbits/core/templates/core/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index f769b44f..1319fa1f 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -171,6 +171,19 @@ +
+
+ + + +
+
+   +
+
From fb58f1ed5e04895582ac79e889a74e2d9a07dd57 Mon Sep 17 00:00:00 2001 From: Taylor Helsper Date: Thu, 29 Sep 2022 23:42:38 -0500 Subject: [PATCH 002/134] Update myNode images --- lnbits/static/images/mynode.png | Bin 3757 -> 13994 bytes lnbits/static/images/mynodel.png | Bin 14352 -> 9272 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/lnbits/static/images/mynode.png b/lnbits/static/images/mynode.png index cf25bc586797b750a64ddba848b2499a0698a5b2..390446b8ee906da9e01142b477a59506269ce5e7 100644 GIT binary patch literal 13994 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 delta 3684 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 diff --git a/lnbits/static/images/mynodel.png b/lnbits/static/images/mynodel.png index b8afb9ffcd0dbe9c3c07cbd1e007b1067d92caa9..344b54b6db53406258f7f1207b78f72b9fb1cbf3 100644 GIT binary patch delta 5190 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 Date: Fri, 30 Sep 2022 10:19:17 -0500 Subject: [PATCH 003/134] Fix formatting --- lnbits/core/templates/core/index.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 1319fa1f..68a7b7ed 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -180,9 +180,7 @@ > -
-   -
+
 
From 41668d6f3288fa75ac47be685e338242e0ec85c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Sat, 1 Oct 2022 11:33:58 +0200 Subject: [PATCH 004/134] add warning for voidwallet for better coloring in logs --- lnbits/wallets/void.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 0de387aa..b74eb245 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -23,7 +23,7 @@ class VoidWallet(Wallet): raise Unsupported("") async def status(self) -> StatusResponse: - logger.info( + logger.warning( "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits." ) return StatusResponse(None, 0) From 3aa4d09103ac94b1e3703086f1528da1020f53e3 Mon Sep 17 00:00:00 2001 From: Indra <86242283+VajraOfIndra@users.noreply.github.com> Date: Sun, 2 Oct 2022 16:45:16 +0100 Subject: [PATCH 005/134] Adding missing theme options in conf file Missing bitcoin theme in comment. Missing flamingo theme in list of values for LNBITS_THEME_OPTIONS. --- .env.example | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 93b82325..7d6de35f 100644 --- a/.env.example +++ b/.env.example @@ -37,8 +37,8 @@ LNBITS_RESERVE_FEE_PERCENT=1.0 LNBITS_SITE_TITLE="LNbits" LNBITS_SITE_TAGLINE="free and open-source lightning wallet" LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'" -# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic -LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador" +# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic +LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador" # LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet @@ -91,4 +91,4 @@ LNBITS_DENOMINATION=sats # EclairWallet ECLAIR_URL=http://127.0.0.1:8283 -ECLAIR_PASS=eclairpw \ No newline at end of file +ECLAIR_PASS=eclairpw From b6755abc8e9911c34b96d3039140b2b64ff5634f Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Wed, 5 Oct 2022 09:16:54 +0200 Subject: [PATCH 006/134] updated poetry install notes - referenced option for python3.10 and newer - poetry install with --only main, instead of --no-dev (deprecated) --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 87679ed5..5e9fdb28 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -26,8 +26,8 @@ sudo apt install python3.9 python3.9-distutils curl -sSL https://install.python-poetry.org | python3 - export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal -poetry env use python3.9 -poetry install --no-dev +poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here +poetry install --only main poetry run python build.py mkdir data From bc2068cc87f5d7b867ce0e9f4ccc8fabf8af7732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 6 Oct 2022 09:42:38 +0200 Subject: [PATCH 007/134] fix poetry lock in main --- poetry.lock | 152 ++++++++++++++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/poetry.lock b/poetry.lock index 343fffbf..5b283d75 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.4" +version = "6.5.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -589,7 +589,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.21.6" +version = "4.21.7" description = "" category = "main" optional = false @@ -659,12 +659,12 @@ optional = false python-versions = ">=3.7,<4.0" [package.dependencies] -pyln-bolt7 = ">=1.0" -pyln-proto = ">=0.12" +pyln-bolt7 = ">=1.0,<2.0" +pyln-proto = ">=0.11,<0.12" [[package]] name = "pyln-proto" -version = "0.12.0" +version = "0.11.1" description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." category = "main" optional = false @@ -839,14 +839,14 @@ cffi = ">=1.3.0" [[package]] name = "setuptools" -version = "65.4.0" +version = "65.4.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] @@ -1051,7 +1051,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "72e4462285d0bc5e2cb83c88c613726beced959b268bd30b984d8baaeff178ea" +content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42" [metadata.files] aiofiles = [ @@ -1224,56 +1224,56 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ - {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, - {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, - {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, - {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, - {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, - {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, - {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, - {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, - {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, - {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, - {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, - {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, - {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, - {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, - {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, - {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] cryptography = [ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, @@ -1573,20 +1573,20 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] protobuf = [ - {file = "protobuf-4.21.6-cp310-abi3-win32.whl", hash = "sha256:49f88d56a9180dbb7f6199c920f5bb5c1dd0172f672983bb281298d57c2ac8eb"}, - {file = "protobuf-4.21.6-cp310-abi3-win_amd64.whl", hash = "sha256:7a6cc8842257265bdfd6b74d088b829e44bcac3cca234c5fdd6052730017b9ea"}, - {file = "protobuf-4.21.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ba596b9ffb85c909fcfe1b1a23136224ed678af3faf9912d3fa483d5f9813c4e"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4143513c766db85b9d7c18dbf8339673c8a290131b2a0fe73855ab20770f72b0"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6cea204865595a92a7b240e4b65bcaaca3ad5d2ce25d9db3756eba06041138e"}, - {file = "protobuf-4.21.6-cp37-cp37m-win32.whl", hash = "sha256:9666da97129138585b26afcb63ad4887f602e169cafe754a8258541c553b8b5d"}, - {file = "protobuf-4.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:308173d3e5a3528787bb8c93abea81d5a950bdce62840d9760effc84127fb39c"}, - {file = "protobuf-4.21.6-cp38-cp38-win32.whl", hash = "sha256:aa29113ec901281f29d9d27b01193407a98aa9658b8a777b0325e6d97149f5ce"}, - {file = "protobuf-4.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:8f9e60f7d44592c66e7b332b6a7b4b6e8d8b889393c79dbc3a91f815118f8eac"}, - {file = "protobuf-4.21.6-cp39-cp39-win32.whl", hash = "sha256:80e6540381080715fddac12690ee42d087d0d17395f8d0078dfd6f1181e7be4c"}, - {file = "protobuf-4.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:77b355c8604fe285536155286b28b0c4cbc57cf81b08d8357bf34829ea982860"}, - {file = "protobuf-4.21.6-py2.py3-none-any.whl", hash = "sha256:07a0bb9cc6114f16a39c866dc28b6e3d96fa4ffb9cc1033057412547e6e75cb9"}, - {file = "protobuf-4.21.6-py3-none-any.whl", hash = "sha256:c7c864148a237f058c739ae7a05a2b403c0dfa4ce7d1f3e5213f352ad52d57c6"}, - {file = "protobuf-4.21.6.tar.gz", hash = "sha256:6b1040a5661cd5f6e610cbca9cfaa2a17d60e2bb545309bc1b278bb05be44bdd"}, + {file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"}, + {file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"}, + {file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"}, + {file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"}, + {file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"}, + {file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"}, + {file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"}, + {file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"}, + {file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"}, + {file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"}, ] psycopg2-binary = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, @@ -1696,8 +1696,8 @@ pyln-client = [ {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] pyln-proto = [ - {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"}, - {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"}, + {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, + {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, ] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, @@ -1799,8 +1799,8 @@ secp256k1 = [ {file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"}, ] setuptools = [ - {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"}, - {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"}, + {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, + {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, ] shortuuid = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, From 79319faa2146da17cd22467d9dd4762c42540f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 6 Oct 2022 10:17:21 +0200 Subject: [PATCH 008/134] make workflows only run on pull_request --- .github/workflows/codeql.yml | 2 -- .github/workflows/formatting.yml | 2 -- .github/workflows/mypy.yml | 2 +- .github/workflows/regtest.yml | 2 +- .github/workflows/tests.yml | 2 +- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 876c8b8a..bbe95983 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,8 +1,6 @@ name: codeql on: - push: - branches: [main, ] pull_request: branches: [main] schedule: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e3d0fd35..21c7fb38 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,8 +1,6 @@ name: formatting on: - push: - branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index d80da678..96b574d2 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,6 +1,6 @@ name: mypy -on: [push, pull_request] +on: [pull_request] jobs: check: diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 2d7aae6b..f0adb3ac 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -1,6 +1,6 @@ name: regtest -on: [push, pull_request] +on: [pull_request] jobs: LndRestWallet: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d368fbb..c7b6e44b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ name: tests -on: [push, pull_request] +on: [pull_request] jobs: venv-sqlite: From 63e4f7d59c0505654d47b8793738211966b9b776 Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:25:14 +0200 Subject: [PATCH 009/134] changed comment places and small adjustments - #comments in own lines - cleaned up some ominous settings - added --debug option for running the server --- docs/guide/installation.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 5e9fdb28..37104fde 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -18,21 +18,25 @@ If you have problems installing LNbits using these instructions, please have a l git clone https://github.com/lnbits/lnbits-legend.git cd lnbits-legend/ -# for making sure python 3.9 is installed, skip if installed +# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt install python3.9 python3.9-distutils curl -sSL https://install.python-poetry.org | python3 - -export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal -poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here +# Once the above poetry install is completed, use the installation path printed to terminal and replace in the following command +export PATH="/home/user/.local/bin:$PATH" +# Next command, you can exchange with python3.10 or newer versions. +# Identify your version with python3 --version and specify in the next line +# command is only needed when your default python is not ^3.9 or ^3.10 +poetry env use python3.9 poetry install --only main -poetry run python build.py mkdir data cp .env.example .env -nano .env # set funding source +# set funding source amongst other options +nano .env ``` #### Running the server @@ -40,6 +44,7 @@ nano .env # set funding source ```sh poetry run lnbits # To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0' +# add --debug for slightly more verbose output ``` ## Option 2: Nix From 36b60ad318fffa87d8ec55753538525531d76dec Mon Sep 17 00:00:00 2001 From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:50:33 +0200 Subject: [PATCH 010/134] added debug option for .env --- docs/guide/installation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 37104fde..6b95f93b 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -44,7 +44,8 @@ nano .env ```sh poetry run lnbits # To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0' -# add --debug for slightly more verbose output +# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output +# Note that you have to add the line DEBUG=true in your .env file, too. ``` ## Option 2: Nix From 27bae6a0c6cce731256095465905c92c1547c1e2 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 14:16:21 +0100 Subject: [PATCH 011/134] adds atm check, so withdraw isnt cancelled on false call by wallets --- lnbits/extensions/lnurldevice/lnurl.py | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index df0cd4b8..ec7f3b48 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -184,22 +184,27 @@ async def lnurl_callback( raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." ) - if pr: - if lnurldevicepayment.id != k1: - return {"status": "ERROR", "reason": "Bad K1"} - if lnurldevicepayment.payhash != "payment_hash": - return {"status": "ERROR", "reason": f"Payment already claimed"} + if device.device == "atm": + if not pr: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="No payment request" + ) + else: + if lnurldevicepayment.id != k1: + return {"status": "ERROR", "reason": "Bad K1"} + if lnurldevicepayment.payhash != "payment_hash": + return {"status": "ERROR", "reason": f"Payment already claimed"} lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) - await pay_invoice( - wallet_id=device.wallet, - payment_request=pr, - max_sat=lnurldevicepayment.sats / 1000, - extra={"tag": "withdraw"}, - ) - return {"status": "OK"} + await pay_invoice( + wallet_id=device.wallet, + payment_request=pr, + max_sat=lnurldevicepayment.sats / 1000, + extra={"tag": "withdraw"}, + ) + return {"status": "OK"} payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, From 8e8bf08ea587aa9013a966a807600812fee3c780 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:21:09 +0100 Subject: [PATCH 012/134] fix issue with splitting to multiple wallets-queue --- lnbits/extensions/splitpayments/tasks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index b7cf1750..cfc6c226 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -28,6 +28,10 @@ async def on_invoice_paid(payment: Payment) -> None: # now we make some special internal transfers (from no one to the receiver) targets = await get_targets(payment.wallet_id) + + if not targets: + return + transfers = [ (target.wallet, int(target.percent * payment.amount / 100)) for target in targets @@ -41,9 +45,6 @@ async def on_invoice_paid(payment: Payment) -> None: ) return - if not targets: - return - # mark the original payment with one extra key, "splitted" # (this prevents us from doing this process again and it's informative) # and reduce it by the amount we're going to send to the producer @@ -76,5 +77,5 @@ async def on_invoice_paid(payment: Payment) -> None: ) # manually send this for now - await internal_invoice_queue.put(internal_checking_id) + await internal_invoice_queue.put(internal_checking_id) return From 11ec82e33962462880d08a9dc2ab6e828762edbd Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:21:53 +0100 Subject: [PATCH 013/134] floor amount when not whole sat --- lnbits/extensions/scrub/tasks.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 320d34da..62adc5e5 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -1,6 +1,7 @@ import asyncio import json from http import HTTPStatus +from math import floor from urllib.parse import urlparse import httpx @@ -26,7 +27,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) - if "scrubed" == payment.extra.get("tag"): + if payment.extra.get("tag") == "scrubed": # already scrubbed return @@ -42,12 +43,13 @@ async def on_invoice_paid(payment: Payment) -> None: # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267 domain = urlparse(data["callback"]).netloc - + rounded_amount = floor(payment.amount / 1000) * 1000 + async with httpx.AsyncClient() as client: try: r = await client.get( data["callback"], - params={"amount": payment.amount}, + params={"amount": rounded_amount}, timeout=40, ) if r.is_error: @@ -66,7 +68,8 @@ async def on_invoice_paid(payment: Payment) -> None: ) invoice = bolt11.decode(params["pr"]) - if invoice.amount_msat != payment.amount: + + if invoice.amount_msat != rounded_amount: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.", From 7316ef7dbb67a9327597e2e97aa7ef54f7a08ac0 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:33:20 +0100 Subject: [PATCH 014/134] add disclaimer about whole (integer) sats --- lnbits/extensions/scrub/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/extensions/scrub/README.md b/lnbits/extensions/scrub/README.md index 680c5e6d..3b8d0b2d 100644 --- a/lnbits/extensions/scrub/README.md +++ b/lnbits/extensions/scrub/README.md @@ -4,6 +4,8 @@ SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress! +Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet! + [**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) ## Usage From 92a756b9bb92078bb937f4a22c9b35b033f44fed Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 6 Oct 2022 15:34:37 +0100 Subject: [PATCH 015/134] make format --- lnbits/extensions/scrub/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 62adc5e5..852f3860 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -44,7 +44,7 @@ async def on_invoice_paid(payment: Payment) -> None: # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267 domain = urlparse(data["callback"]).netloc rounded_amount = floor(payment.amount / 1000) * 1000 - + async with httpx.AsyncClient() as client: try: r = await client.get( @@ -68,7 +68,7 @@ async def on_invoice_paid(payment: Payment) -> None: ) invoice = bolt11.decode(params["pr"]) - + if invoice.amount_msat != rounded_amount: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, From 3222ae198d4f4bda826f1a63f861d5e90d0df4ac Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 17:10:15 +0100 Subject: [PATCH 016/134] Adding bitcoinswitch to lnurldevices --- lnbits/extensions/lnurldevice/__init__.py | 7 +++- lnbits/extensions/lnurldevice/lnurl.py | 40 ++++++++++++++++-- lnbits/extensions/lnurldevice/migrations.py | 6 +++ lnbits/extensions/lnurldevice/models.py | 2 + lnbits/extensions/lnurldevice/tasks.py | 39 ++++++++++++++++++ lnbits/extensions/lnurldevice/views.py | 45 +++++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 lnbits/extensions/lnurldevice/tasks.py diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index 54849c95..dc4456b4 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -2,6 +2,7 @@ from fastapi import APIRouter from lnbits.db import Database from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart db = Database("ext_lnurldevice") @@ -11,7 +12,11 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) - +from .tasks import wait_for_paid_invoices from .lnurl import * # noqa from .views import * # noqa from .views_api import * # noqa + +def lnurldevice_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index ec7f3b48..a2bc0dd4 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -102,7 +102,22 @@ async def lnurl_v1_params( if device.device == "atm": if paymentcheck: return {"status": "ERROR", "reason": f"Payment already claimed"} - + if device.device == "switch": + lnurldevicepayment = await create_lnurldevicepayment( + deviceid=device.id, + sats=device.profit, + ) + if not lnurldevicepayment: + return {"status": "ERROR", "reason": "Could not create payment."} + return { + "tag": "payRequest", + "callback": request.url_for( + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + ), + "minSendable": device.profit * 1000, + "maxSendable": device.profit * 1000, + "metadata": await device.lnurlpay_metadata(), + } if len(p) % 4 > 0: p += "=" * (4 - (len(p) % 4)) @@ -205,6 +220,27 @@ async def lnurl_callback( extra={"tag": "withdraw"}, ) return {"status": "OK"} + if device.device == "switch": + payment_hash, payment_request = await create_invoice( + wallet_id=device.wallet, + amount=lnurldevicepayment.sats / 1000, + memo=device.title, + unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), + extra={"tag": "Switch", "id": device.paymentid, "time": device.amount}, + ) + lnurldevicepayment = await update_lnurldevicepayment( + lnurldevicepayment_id=paymentid, payhash=payment_hash + ) + + return { + "pr": payment_request, + "successAction": { + "tag": "url", + "description": "Check the attached link", + "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid), + }, + "routes": [], + } payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, @@ -226,5 +262,3 @@ async def lnurl_callback( }, "routes": [], } - - return resp.dict() diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index c7899282..65913b02 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -79,3 +79,9 @@ async def m002_redux(db): ) except: return + +async def m003_redux(db): + """ + Add 'meta' for storing various metadata about the wallet + """ + await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;") \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index fef0aec1..cf617141 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -17,6 +17,7 @@ class createLnurldevice(BaseModel): currency: str device: str profit: float + amount: int class lnurldevices(BaseModel): @@ -27,6 +28,7 @@ class lnurldevices(BaseModel): currency: str device: str profit: float + amount: int timestamp: str def from_row(cls, row: Row) -> "lnurldevices": diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py new file mode 100644 index 00000000..2337c0ad --- /dev/null +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -0,0 +1,39 @@ +import asyncio +import json +from http import HTTPStatus +from urllib.parse import urlparse + +import httpx +from fastapi import HTTPException + +from lnbits import bolt11 +from lnbits.core.models import Payment +from lnbits.core.services import pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener + +from .crud import get_lnurldevice +from .views import updater + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue, get_current_extension_name()) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + # (avoid loops) + if "switch" == payment.extra.get("tag"): + lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) + if not lnurldevicepayment: + return + if lnurldevicepayment.payhash == "used": + return + lnurldevicepayment = await update_lnurldevicepayment( + lnurldevicepayment_id=paymentid, payhash="used" + ) + return await updater(lnurldevicepayment.deviceid) + return \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 3389e17c..4a28a5c1 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -51,3 +51,48 @@ async def displaypin(request: Request, paymentid: str = Query(None)): "lnurldevice/error.html", {"request": request, "pin": "filler", "not_paid": True}, ) + + +##################WEBSOCKET ROUTES######################## + + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, lnurldevice_id: str): + await websocket.accept() + websocket.id = lnurldevice_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, lnurldevice_id: str): + for connection in self.active_connections: + if connection.id == lnurldevice_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@lnurldevice_ext.websocket("/ws/{lnurldevice_id}", name="lnurldevice.lnurldevice_by_id") +async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str): + await manager.connect(websocket, lnurldevice_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + + +async def updater(lnurldevice_id): + lnurldevice = await get_lnurldevice(lnurldevice_id) + if not lnurldevice: + return + await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) \ No newline at end of file From 05c8f02cb7cc714457e885c2c9002f6cc97c3074 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 17:41:06 +0100 Subject: [PATCH 017/134] Added code sample for websocket stuff --- docs/devs/websockets.md | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/devs/websockets.md diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md new file mode 100644 index 00000000..34cf940d --- /dev/null +++ b/docs/devs/websockets.md @@ -0,0 +1,87 @@ +--- +layout: default +parent: For developers +title: Websockets +nav_order: 2 +--- + + +Websockets +================= + +`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): + + +```sh +from fastapi import Request, WebSocket, WebSocketDisconnect + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, extension_id: str): + await websocket.accept() + websocket.id = extension_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, extension_id: str): + for connection in self.active_connections: + if connection.id == extension_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id") +async def websocket_endpoint(websocket: WebSocket, extension_id: str): + await manager.connect(websocket, extension_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + + +async def updater(extension_id, data): + extension = await get_extension(extension_id) + if not extension: + return + await manager.send_personal_message(f"{data}", extension_id) +``` + +Example vue-js function for listening to the websocket: + +``` +initWs: async function () { + if (location.protocol !== 'http:') { + localUrl = + 'wss://' + + document.domain + + ':' + + location.port + + '/extension/ws/' + + self.extension.id + } else { + localUrl = + 'ws://' + + document.domain + + ':' + + location.port + + '/extension/ws/' + + self.extension.id + } + this.ws = new WebSocket(localUrl) + this.ws.addEventListener('message', async ({data}) => { + const res = JSON.parse(data.toString()) + console.log(res) + }) +}, +``` \ No newline at end of file From 6d55445a1bb941ed2b13b290cb4d8994ef0a52ba Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:44:04 +0100 Subject: [PATCH 018/134] Update websockets.md --- docs/devs/websockets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md index 34cf940d..0638e4f2 100644 --- a/docs/devs/websockets.md +++ b/docs/devs/websockets.md @@ -9,7 +9,7 @@ nav_order: 2 Websockets ================= -`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): +`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): ```sh @@ -84,4 +84,4 @@ initWs: async function () { console.log(res) }) }, -``` \ No newline at end of file +``` From b46c06012d1e4e280eb9a8d3459276489a935b62 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:51:36 +0100 Subject: [PATCH 019/134] Revert "make gh workflows only run on pull_request" --- .github/workflows/codeql.yml | 2 ++ .github/workflows/formatting.yml | 2 ++ .github/workflows/mypy.yml | 2 +- .github/workflows/regtest.yml | 2 +- .github/workflows/tests.yml | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bbe95983..876c8b8a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,8 @@ name: codeql on: + push: + branches: [main, ] pull_request: branches: [main] schedule: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 21c7fb38..e3d0fd35 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -1,6 +1,8 @@ name: formatting on: + push: + branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 96b574d2..d80da678 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,6 +1,6 @@ name: mypy -on: [pull_request] +on: [push, pull_request] jobs: check: diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index f0adb3ac..2d7aae6b 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -1,6 +1,6 @@ name: regtest -on: [pull_request] +on: [push, pull_request] jobs: LndRestWallet: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c7b6e44b..5d368fbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ name: tests -on: [pull_request] +on: [push, pull_request] jobs: venv-sqlite: From ff98b87eca0ab2309ccc1b1be33969aaeb96b035 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 6 Oct 2022 22:19:30 +0100 Subject: [PATCH 020/134] Added a switch --- lnbits/extensions/lnurldevice/__init__.py | 1 + lnbits/extensions/lnurldevice/models.py | 2 +- .../templates/lnurldevice/_api_docs.html | 8 +-- .../templates/lnurldevice/index.html | 66 ++++++++++++++++++- lnbits/extensions/lnurldevice/views.py | 32 ++++++++- 5 files changed, 102 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index dc4456b4..5452e356 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -1,3 +1,4 @@ +import asyncio from fastapi import APIRouter from lnbits.db import Database diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index cf617141..0714c656 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -36,7 +36,7 @@ class lnurldevices(BaseModel): def lnurl(self, req: Request) -> Lnurl: url = req.url_for( - "lnurldevice.lnurl_response", device_id=self.id, _external=True + "lnurldevice.lnurl_v1_params", device_id=self.id ) return lnurl_encode(url) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index 7f9afa27..7497714e 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -1,10 +1,10 @@

- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here - https://github.com/arcbtc/bitcoinpos + Such as the LNPoS + https://lnbits.github.io/lnpos
Created by, Ben Arc + LNURLDevice Settings + + + bitcoinSwitch embeddable QRCode + +

LNURLDevice device string
+
{% raw + %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw + %} Click to copy URL + + Click to copy URL - +
@@ -191,6 +221,7 @@ label="Type of device" > +
+ + +
{ diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 4a28a5c1..a64de84b 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,4 +1,6 @@ from http import HTTPStatus +import pyqrcode +from io import BytesIO from fastapi import Request from fastapi.param_functions import Query @@ -14,6 +16,9 @@ from lnbits.decorators import check_user_exists from . import lnurldevice_ext, lnurldevice_renderer from .crud import get_lnurldevice, get_lnurldevicepayment +from fastapi import Request, WebSocket, WebSocketDisconnect + +from starlette.responses import HTMLResponse, StreamingResponse templates = Jinja2Templates(directory="templates") @@ -52,6 +57,30 @@ async def displaypin(request: Request, paymentid: str = Query(None)): {"request": request, "pin": "filler", "not_paid": True}, ) +@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse) +async def img(request: Request, lnurldevice_id): + lnurldevice = await get_lnurldevice(lnurldevice_id) + if not lnurldevice: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." + ) + qr = pyqrcode.create(lnurldevice.lnurl(request)) + stream = BytesIO() + qr.svg(stream, scale=3) + stream.seek(0) + + async def _generator(stream: BytesIO): + yield stream.getvalue() + + return StreamingResponse( + _generator(stream), + headers={ + "Content-Type": "image/svg+xml", + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + }, + ) ##################WEBSOCKET ROUTES######################## @@ -95,4 +124,5 @@ async def updater(lnurldevice_id): lnurldevice = await get_lnurldevice(lnurldevice_id) if not lnurldevice: return - await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) \ No newline at end of file + await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) + From 3bf38884ed8cb30ef9b5c428fdaea1acbedf1020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 7 Oct 2022 08:30:07 +0200 Subject: [PATCH 021/134] log successful connection to backend with logger.success() --- lnbits/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 51482538..8b9cf798 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -126,7 +126,7 @@ def check_funding_source(app: FastAPI) -> None: logger.info("Retrying connection to backend in 5 seconds...") await asyncio.sleep(5) signal.signal(signal.SIGINT, original_sigint_handler) - logger.info( + logger.success( f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat." ) From 52dc528a8a7557da685da624e22058e737e49a16 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 11:29:06 +0100 Subject: [PATCH 022/134] Added qrdialogue --- .../templates/lnurldevice/index.html | 61 +++++++++++++++---- lnbits/extensions/lnurldevice/views.py | 19 +----- lnbits/extensions/lnurldevice/views_api.py | 13 +++- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 528e5afe..b55c83f7 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -94,18 +94,17 @@ - bitcoinSwitch embeddable QRCode - + v-if="props.row.device == 'switch'" + :disable="protocol == 'http:'" + flat + unelevated + dense + size="xs" + icon="visibility" + :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" + @click="openQrCodeDialog(props.row.id)" + > LNURLs only work over HTTPS view LNURL + + + + + + {% raw %} + +

+ ID: {{ qrCodeDialog.data.id }}
+ +

+ {% endraw %} +
+ Copy LNURL + Close +
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -306,6 +333,7 @@ mixins: [windowMixin], data: function () { return { + protocol: window.location.protocol, location: window.location.hostname, wslocation: window.location.hostname, filter: '', @@ -404,6 +432,14 @@ } }, methods: { + openQrCodeDialog: function (lnurldevice_id) { + var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id}) + console.log(lnurldevice) + this.qrCodeDialog.data = _.clone(lnurldevice) + this.qrCodeDialog.data.url = + window.location.protocol + '//' + window.location.host + this.qrCodeDialog.show = true + }, cancellnurldevice: function (data) { var self = this self.formDialoglnurldevice.show = false @@ -460,6 +496,7 @@ .then(function (response) { if (response.data) { self.lnurldeviceLinks = response.data.map(maplnurldevice) + console.log(response.data) } }) .catch(function (error) { diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index a64de84b..26f581d3 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -64,23 +64,8 @@ async def img(request: Request, lnurldevice_id): raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) - qr = pyqrcode.create(lnurldevice.lnurl(request)) - stream = BytesIO() - qr.svg(stream, scale=3) - stream.seek(0) - - async def _generator(stream: BytesIO): - yield stream.getvalue() - - return StreamingResponse( - _generator(stream), - headers={ - "Content-Type": "image/svg+xml", - "Cache-Control": "no-cache, no-store, must-revalidate", - "Pragma": "no-cache", - "Expires": "0", - }, - ) + return lnurldevice.lnurl(request) + ##################WEBSOCKET ROUTES######################## diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index d152d210..4a4c2b90 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -45,14 +45,21 @@ async def api_lnurldevice_create_or_update( @lnurldevice_ext.get("/api/v1/lnurlpos") -async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)): wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: return [ - {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: - return "" + try: + return [ + {**lnurldevice.dict()} + for lnurldevice in await get_lnurldevices(wallet_ids) + ] + except: + return "" @lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}") From 7711387d6472171539493c2f830869edef1e49fd Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 15:23:57 +0300 Subject: [PATCH 023/134] fix: use bigint for amounts in postgres (#1030) --- lnbits/db.py | 6 ++++++ lnbits/extensions/boltcards/migrations.py | 8 ++++---- lnbits/extensions/boltz/migrations.py | 12 ++++++------ lnbits/extensions/invoices/migrations.py | 2 +- lnbits/extensions/lnurldevice/migrations.py | 2 +- lnbits/extensions/lnurlpayout/migrations.py | 4 ++-- lnbits/extensions/streamalerts/migrations.py | 2 +- lnbits/extensions/tipjar/migrations.py | 4 ++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lnbits/db.py b/lnbits/db.py index 66981784..f52b0391 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -52,6 +52,12 @@ class Compat: return "" return "" + @property + def big_int(self) -> str: + if self.type in {POSTGRES}: + return "BIGINT" + return "INT" + class Connection(Compat): def __init__(self, conn: AsyncConnection, txn, typ, name, schema): diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py index 08126013..9609e0c3 100644 --- a/lnbits/extensions/boltcards/migrations.py +++ b/lnbits/extensions/boltcards/migrations.py @@ -29,7 +29,7 @@ async def m001_initial(db): ) await db.execute( - """ + f""" CREATE TABLE boltcards.hits ( id TEXT PRIMARY KEY UNIQUE, card_id TEXT NOT NULL, @@ -38,7 +38,7 @@ async def m001_initial(db): useragent TEXT, old_ctr INT NOT NULL DEFAULT 0, new_ctr INT NOT NULL DEFAULT 0, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ @@ -47,11 +47,11 @@ async def m001_initial(db): ) await db.execute( - """ + f""" CREATE TABLE boltcards.refunds ( id TEXT PRIMARY KEY UNIQUE, hit_id TEXT NOT NULL, - refund_amount INT NOT NULL, + refund_amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ diff --git a/lnbits/extensions/boltz/migrations.py b/lnbits/extensions/boltz/migrations.py index e4026dd0..925322ec 100644 --- a/lnbits/extensions/boltz/migrations.py +++ b/lnbits/extensions/boltz/migrations.py @@ -1,16 +1,16 @@ async def m001_initial(db): await db.execute( - """ + f""" CREATE TABLE boltz.submarineswap ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, payment_hash TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, status TEXT NOT NULL, boltz_id TEXT NOT NULL, refund_address TEXT NOT NULL, refund_privkey TEXT NOT NULL, - expected_amount INT NOT NULL, + expected_amount {db.big_int} NOT NULL, timeout_block_height INT NOT NULL, address TEXT NOT NULL, bip21 TEXT NOT NULL, @@ -22,12 +22,12 @@ async def m001_initial(db): """ ) await db.execute( - """ + f""" CREATE TABLE boltz.reverse_submarineswap ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, onchain_address TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, instant_settlement BOOLEAN NOT NULL, status TEXT NOT NULL, boltz_id TEXT NOT NULL, @@ -37,7 +37,7 @@ async def m001_initial(db): claim_privkey TEXT NOT NULL, lockup_address TEXT NOT NULL, invoice TEXT NOT NULL, - onchain_amount INT NOT NULL, + onchain_amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT """ + db.timestamp_now + """ diff --git a/lnbits/extensions/invoices/migrations.py b/lnbits/extensions/invoices/migrations.py index c47a954a..74a0fdba 100644 --- a/lnbits/extensions/invoices/migrations.py +++ b/lnbits/extensions/invoices/migrations.py @@ -45,7 +45,7 @@ async def m001_initial_invoices(db): id TEXT PRIMARY KEY, invoice_id TEXT NOT NULL, - amount INT NOT NULL, + amount {db.big_int} NOT NULL, time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index c7899282..da62cb7e 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -29,7 +29,7 @@ async def m001_initial(db): payhash TEXT, payload TEXT NOT NULL, pin INT, - sats INT, + sats {db.big_int}, timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} ); """ diff --git a/lnbits/extensions/lnurlpayout/migrations.py b/lnbits/extensions/lnurlpayout/migrations.py index 6af04791..7a45e495 100644 --- a/lnbits/extensions/lnurlpayout/migrations.py +++ b/lnbits/extensions/lnurlpayout/migrations.py @@ -3,14 +3,14 @@ async def m001_initial(db): Initial lnurlpayouts table. """ await db.execute( - """ + f""" CREATE TABLE lnurlpayout.lnurlpayouts ( id TEXT PRIMARY KEY, title TEXT NOT NULL, wallet TEXT NOT NULL, admin_key TEXT NOT NULL, lnurlpay TEXT NOT NULL, - threshold INT NOT NULL + threshold {db.big_int} NOT NULL ); """ ) diff --git a/lnbits/extensions/streamalerts/migrations.py b/lnbits/extensions/streamalerts/migrations.py index 1b0cea37..7d50e8f1 100644 --- a/lnbits/extensions/streamalerts/migrations.py +++ b/lnbits/extensions/streamalerts/migrations.py @@ -25,7 +25,7 @@ async def m001_initial(db): name TEXT NOT NULL, message TEXT NOT NULL, cur_code TEXT NOT NULL, - sats INT NOT NULL, + sats {db.big_int} NOT NULL, amount FLOAT NOT NULL, service INTEGER NOT NULL, posted BOOLEAN NOT NULL, diff --git a/lnbits/extensions/tipjar/migrations.py b/lnbits/extensions/tipjar/migrations.py index 6b58fbca..d8f6da3f 100644 --- a/lnbits/extensions/tipjar/migrations.py +++ b/lnbits/extensions/tipjar/migrations.py @@ -19,8 +19,8 @@ async def m001_initial(db): wallet TEXT NOT NULL, name TEXT NOT NULL, message TEXT NOT NULL, - sats INT NOT NULL, - tipjar INT NOT NULL, + sats {db.big_int} NOT NULL, + tipjar {db.big_int} NOT NULL, FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id) ); """ From 8d4337679f93161966310ccf56d30eb4fd576317 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 13:46:40 +0100 Subject: [PATCH 024/134] Websocket/listener working --- lnbits/extensions/lnurldevice/crud.py | 6 +++-- lnbits/extensions/lnurldevice/lnurl.py | 26 +++++++++++-------- lnbits/extensions/lnurldevice/tasks.py | 8 +++--- .../templates/lnurldevice/index.html | 3 ++- lnbits/extensions/lnurldevice/views_api.py | 9 ++++--- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 45166521..4c25e4cb 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -22,9 +22,10 @@ async def create_lnurldevice( wallet, currency, device, - profit + profit, + amount ) - VALUES (?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( lnurldevice_id, @@ -34,6 +35,7 @@ async def create_lnurldevice( data.currency, data.device, data.profit, + data.amount, ), ) return await get_lnurldevice(lnurldevice_id) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index a2bc0dd4..3823005e 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -103,9 +103,19 @@ async def lnurl_v1_params( if paymentcheck: return {"status": "ERROR", "reason": f"Payment already claimed"} if device.device == "switch": + + price_msat = ( + await fiat_amount_as_satoshis(float(device.profit), device.currency) + if device.currency != "sat" + else amount_in_cent + ) * 1000 + lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, - sats=device.profit, + payload="bla", + sats=price_msat, + pin=1, + payhash="bla", ) if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create payment."} @@ -114,8 +124,8 @@ async def lnurl_v1_params( "callback": request.url_for( "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id ), - "minSendable": device.profit * 1000, - "maxSendable": device.profit * 1000, + "minSendable": price_msat, + "maxSendable": price_msat, "metadata": await device.lnurlpay_metadata(), } if len(p) % 4 > 0: @@ -224,21 +234,15 @@ async def lnurl_callback( payment_hash, payment_request = await create_invoice( wallet_id=device.wallet, amount=lnurldevicepayment.sats / 1000, - memo=device.title, + memo=device.title + "-" + lnurldevicepayment.id, unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"), - extra={"tag": "Switch", "id": device.paymentid, "time": device.amount}, + extra={"tag": "Switch", "id": paymentid, "time": device.amount}, ) lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=payment_hash ) - return { "pr": payment_request, - "successAction": { - "tag": "url", - "description": "Check the attached link", - "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid), - }, "routes": [], } diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2337c0ad..4bf0d3b9 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,8 +12,9 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice +from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment from .views import updater +from loguru import logger async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() @@ -23,17 +24,16 @@ async def wait_for_paid_invoices(): payment = await invoice_queue.get() await on_invoice_paid(payment) - async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) - if "switch" == payment.extra.get("tag"): + if "Switch" == payment.extra.get("tag"): lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) if not lnurldevicepayment: return if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=paymentid, payhash="used" + lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) return await updater(lnurldevicepayment.deviceid) return \ No newline at end of file diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index b55c83f7..ccb0a6e9 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -240,6 +240,7 @@ fill-mask="0" reverse-fill-mask :step="'0.01'" + value="0.00" > Date: Fri, 7 Oct 2022 23:18:57 +0100 Subject: [PATCH 025/134] Black --- lnbits/extensions/lnurldevice/__init__.py | 2 ++ lnbits/extensions/lnurldevice/lnurl.py | 2 +- lnbits/extensions/lnurldevice/migrations.py | 5 ++++- lnbits/extensions/lnurldevice/models.py | 4 +--- lnbits/extensions/lnurldevice/tasks.py | 10 ++++++---- lnbits/extensions/lnurldevice/views.py | 4 ++-- lnbits/extensions/lnurldevice/views_api.py | 12 +++++++----- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index 5452e356..d987e6cc 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -13,11 +13,13 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) + from .tasks import wait_for_paid_invoices from .lnurl import * # noqa from .views import * # noqa from .views_api import * # noqa + def lnurldevice_start(): loop = asyncio.get_event_loop() loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 3823005e..79892b78 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -122,7 +122,7 @@ async def lnurl_v1_params( return { "tag": "payRequest", "callback": request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id ), "minSendable": price_msat, "maxSendable": price_msat, diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 65913b02..fa56b3d1 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -80,8 +80,11 @@ async def m002_redux(db): except: return + async def m003_redux(db): """ Add 'meta' for storing various metadata about the wallet """ - await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;") \ No newline at end of file + await db.execute( + "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;" + ) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 0714c656..01bcc2ba 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -35,9 +35,7 @@ class lnurldevices(BaseModel): return cls(**dict(row)) def lnurl(self, req: Request) -> Lnurl: - url = req.url_for( - "lnurldevice.lnurl_v1_params", device_id=self.id - ) + url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) return lnurl_encode(url) async def lnurlpay_metadata(self) -> LnurlPayMetadata: diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 4bf0d3b9..2f048138 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -16,6 +16,7 @@ from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepay from .views import updater from loguru import logger + async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue, get_current_extension_name()) @@ -23,7 +24,8 @@ async def wait_for_paid_invoices(): while True: payment = await invoice_queue.get() await on_invoice_paid(payment) - + + async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) if "Switch" == payment.extra.get("tag"): @@ -33,7 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None: if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=payment.extra.get("id"), payhash="used" - ) + lnurldevicepayment_id=payment.extra.get("id"), payhash="used" + ) return await updater(lnurldevicepayment.deviceid) - return \ No newline at end of file + return diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 26f581d3..752d2c81 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -57,6 +57,7 @@ async def displaypin(request: Request, paymentid: str = Query(None)): {"request": request, "pin": "filler", "not_paid": True}, ) + @lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse) async def img(request: Request, lnurldevice_id): lnurldevice = await get_lnurldevice(lnurldevice_id) @@ -65,7 +66,7 @@ async def img(request: Request, lnurldevice_id): status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) return lnurldevice.lnurl(request) - + ##################WEBSOCKET ROUTES######################## @@ -110,4 +111,3 @@ async def updater(lnurldevice_id): if not lnurldevice: return await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id) - diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index 65625cbb..c034f66e 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -46,18 +46,20 @@ async def api_lnurldevice_create_or_update( @lnurldevice_ext.get("/api/v1/lnurlpos") -async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_lnurldevices_retrieve( + req: Request, wallet: WalletTypeInfo = Depends(get_key_type) +): wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: return [ - {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} - for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: try: return [ - {**lnurldevice.dict()} - for lnurldevice in await get_lnurldevices(wallet_ids) + {**lnurldevice.dict()} + for lnurldevice in await get_lnurldevices(wallet_ids) ] except: return "" From d4a5fe27768a8d05417e7ed8d6340b2420291a5f Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:48:42 +0100 Subject: [PATCH 026/134] isort --- lnbits/extensions/lnurldevice/tasks.py | 4 ++-- lnbits/extensions/lnurldevice/views.py | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 2f048138..db8d87f0 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,9 +12,9 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment +from .crud import (get_lnurldevice, get_lnurldevicepayment, + update_lnurldevicepayment) from .views import updater -from loguru import logger async def wait_for_paid_invoices(): diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 752d2c81..5c6eba24 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,13 +1,13 @@ from http import HTTPStatus -import pyqrcode from io import BytesIO -from fastapi import Request +import pyqrcode +from fastapi import Request, WebSocket, WebSocketDisconnect from fastapi.param_functions import Query from fastapi.params import Depends from fastapi.templating import Jinja2Templates from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse +from starlette.responses import HTMLResponse, StreamingResponse from lnbits.core.crud import update_payment_status from lnbits.core.models import User @@ -16,9 +16,6 @@ from lnbits.decorators import check_user_exists from . import lnurldevice_ext, lnurldevice_renderer from .crud import get_lnurldevice, get_lnurldevicepayment -from fastapi import Request, WebSocket, WebSocketDisconnect - -from starlette.responses import HTMLResponse, StreamingResponse templates = Jinja2Templates(directory="templates") From e3bd38fc6b11cbc9c1382fbac5fdfc005a0142db Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:51:28 +0100 Subject: [PATCH 027/134] back --- lnbits/extensions/lnurldevice/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index db8d87f0..c8f3db04 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -12,8 +12,7 @@ from lnbits.core.services import pay_invoice from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import (get_lnurldevice, get_lnurldevicepayment, - update_lnurldevicepayment) +from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment from .views import updater From 34effe3ae5690ad3b68d0a1af34ccc2d9d9b6e21 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 7 Oct 2022 23:57:28 +0100 Subject: [PATCH 028/134] isort --- lnbits/extensions/lnurldevice/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index d987e6cc..c27c9e17 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -2,8 +2,8 @@ import asyncio from fastapi import APIRouter from lnbits.db import Database -from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart +from lnbits.helpers import template_renderer db = Database("ext_lnurldevice") From fbc20b3579169b44227cc8ba8e20cc78be1b65f5 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 8 Oct 2022 01:28:46 +0100 Subject: [PATCH 029/134] added credits --- .../templates/lnurldevice/_api_docs.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index 7497714e..b239e981 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -2,12 +2,21 @@

For LNURL based Points of Sale, ATMs, and relay devices
- Such as the LNPoS + Use with:
+ LNPoS https://lnbits.github.io/lnpos
+ bitcoinSwitch + https://github.com/lnbits/bitcoinSwitch
+ FOSSA + https://github.com/lnbits/fossa
- Created by, Ben ArcBen Arc, BC, Vlad Stan

From 3bf3c91007ae4f589f06176a405061b92878e5ff Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:12:06 +0200 Subject: [PATCH 030/134] Add backend: ln.tips (LightningTipBot) wallet (#921) * add ln.tips * add wallet * revert lnbits * make format * .env.example * listener fix * reconnect fixed * make format * make format --- .env.example | 7 +- lnbits/extensions/lnurldevice/__init__.py | 5 +- .../templates/lnurldevice/_api_docs.html | 22 +-- .../templates/lnurldevice/index.html | 142 ++++++++------- lnbits/wallets/__init__.py | 2 + lnbits/wallets/lntips.py | 170 ++++++++++++++++++ 6 files changed, 263 insertions(+), 85 deletions(-) create mode 100644 lnbits/wallets/lntips.py diff --git a/.env.example b/.env.example index 7d6de35f..987c6ca6 100644 --- a/.env.example +++ b/.env.example @@ -41,7 +41,7 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador" # LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" -# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet +# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet # LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet # VoidWallet is just a fallback that works without any actual Lightning capabilities, @@ -92,3 +92,8 @@ LNBITS_DENOMINATION=sats # EclairWallet ECLAIR_URL=http://127.0.0.1:8283 ECLAIR_PASS=eclairpw + +# LnTipsWallet +# Enter /api in LightningTipBot to get your key +LNTIPS_API_KEY=LNTIPS_ADMIN_KEY +LNTIPS_API_ENDPOINT=https://ln.tips diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py index c27c9e17..d2010c44 100644 --- a/lnbits/extensions/lnurldevice/__init__.py +++ b/lnbits/extensions/lnurldevice/__init__.py @@ -1,9 +1,10 @@ import asyncio + from fastapi import APIRouter from lnbits.db import Database -from lnbits.tasks import catch_everything_and_restart from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart db = Database("ext_lnurldevice") @@ -14,8 +15,8 @@ def lnurldevice_renderer(): return template_renderer(["lnbits/extensions/lnurldevice/templates"]) -from .tasks import wait_for_paid_invoices from .lnurl import * # noqa +from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index b239e981..f93d44d8 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -3,20 +3,22 @@

For LNURL based Points of Sale, ATMs, and relay devices
Use with:
- LNPoS - https://lnbits.github.io/lnpos + https://lnbits.github.io/lnpos
- bitcoinSwitch - https://github.com/lnbits/bitcoinSwitch + https://github.com/lnbits/bitcoinSwitch
- FOSSA - https://github.com/lnbits/fossa + https://github.com/lnbits/fossa
- Created by, Ben Arc, BC, Vlad StanBen Arc, + BC, + Vlad Stan

diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index ccb0a6e9..028dd94b 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -94,17 +94,19 @@ LNURLs only work over HTTPS view LNURL + v-if="props.row.device == 'switch'" + :disable="protocol == 'http:'" + flat + unelevated + dense + size="xs" + icon="visibility" + :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" + @click="openQrCodeDialog(props.row.id)" + > + LNURLs only work over HTTPS view LNURL
LNURLDevice device string
- {% raw - %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw - %} Click to copy URL - - {% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% + endraw %} Click to copy URL + + {% raw - %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, - {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw - %} Click to copy URL - -
+ >{% raw + %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, + {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw + %} Click to copy URL + +
@@ -229,29 +230,28 @@ label="Profit margin (% added to invoices/deducted from faucets)" >
- - -
+ + +

ID: {{ qrCodeDialog.data.id }}
-

{% endraw %}
@@ -434,13 +433,15 @@ }, methods: { openQrCodeDialog: function (lnurldevice_id) { - var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id}) - console.log(lnurldevice) - this.qrCodeDialog.data = _.clone(lnurldevice) - this.qrCodeDialog.data.url = - window.location.protocol + '//' + window.location.host - this.qrCodeDialog.show = true - }, + var lnurldevice = _.findWhere(this.lnurldeviceLinks, { + id: lnurldevice_id + }) + console.log(lnurldevice) + this.qrCodeDialog.data = _.clone(lnurldevice) + this.qrCodeDialog.data.url = + window.location.protocol + '//' + window.location.host + this.qrCodeDialog.show = true + }, cancellnurldevice: function (data) { var self = this self.formDialoglnurldevice.show = false @@ -617,10 +618,7 @@ '//', window.location.host ].join('') - self.wslocation = [ - 'ws://', - window.location.host - ].join('') + self.wslocation = ['ws://', window.location.host].join('') LNbits.api .request('GET', '/api/v1/currencies') .then(response => { diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py index 41949652..fa533566 100644 --- a/lnbits/wallets/__init__.py +++ b/lnbits/wallets/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa + from .cliche import ClicheWallet from .cln import CoreLightningWallet # legacy .env support from .cln import CoreLightningWallet as CLightningWallet @@ -9,6 +10,7 @@ from .lnbits import LNbitsWallet from .lndgrpc import LndWallet from .lndrest import LndRestWallet from .lnpay import LNPayWallet +from .lntips import LnTipsWallet from .lntxbot import LntxbotWallet from .opennode import OpenNodeWallet from .spark import SparkWallet diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py new file mode 100644 index 00000000..54220c85 --- /dev/null +++ b/lnbits/wallets/lntips.py @@ -0,0 +1,170 @@ +import asyncio +import hashlib +import json +import time +from os import getenv +from typing import AsyncGenerator, Dict, Optional + +import httpx +from loguru import logger + +from .base import ( + InvoiceResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, + Wallet, +) + + +class LnTipsWallet(Wallet): + def __init__(self): + endpoint = getenv("LNTIPS_API_ENDPOINT") + self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint + + key = ( + getenv("LNTIPS_API_KEY") + or getenv("LNTIPS_ADMIN_KEY") + or getenv("LNTIPS_INVOICE_KEY") + ) + self.auth = {"Authorization": f"Basic {key}"} + + async def status(self) -> StatusResponse: + async with httpx.AsyncClient() as client: + r = await client.get( + f"{self.endpoint}/api/v1/balance", headers=self.auth, timeout=40 + ) + try: + data = r.json() + except: + return StatusResponse( + f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0 + ) + + if data.get("error"): + return StatusResponse(data["error"], 0) + + return StatusResponse(None, data["balance"] * 1000) + + async def create_invoice( + self, + amount: int, + memo: Optional[str] = None, + description_hash: Optional[bytes] = None, + unhashed_description: Optional[bytes] = None, + **kwargs, + ) -> InvoiceResponse: + data: Dict = {"amount": amount} + if description_hash: + data["description_hash"] = description_hash.hex() + elif unhashed_description: + data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest() + else: + data["memo"] = memo or "" + + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/createinvoice", + headers=self.auth, + json=data, + timeout=40, + ) + + if r.is_error: + try: + data = r.json() + error_message = data["message"] + except: + error_message = r.text + pass + + return InvoiceResponse(False, None, None, error_message) + + data = r.json() + return InvoiceResponse( + True, data["payment_hash"], data["payment_request"], None + ) + + async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/payinvoice", + headers=self.auth, + json={"pay_req": bolt11}, + timeout=None, + ) + if r.is_error: + return PaymentResponse(False, None, 0, None, r.text) + + if "error" in r.json(): + try: + data = r.json() + error_message = data["error"] + except: + error_message = r.text + pass + return PaymentResponse(False, None, 0, None, error_message) + + data = r.json()["details"] + checking_id = data["payment_hash"] + fee_msat = -data["fee"] + preimage = data["preimage"] + return PaymentResponse(True, checking_id, fee_msat, preimage, None) + + async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + async with httpx.AsyncClient() as client: + r = await client.post( + f"{self.endpoint}/api/v1/invoicestatus/{checking_id}", + headers=self.auth, + ) + + if r.is_error or len(r.text) == 0: + return PaymentStatus(None) + + data = r.json() + return PaymentStatus(data["paid"]) + + async def get_payment_status(self, checking_id: str) -> PaymentStatus: + async with httpx.AsyncClient() as client: + r = await client.post( + url=f"{self.endpoint}/api/v1/paymentstatus/{checking_id}", + headers=self.auth, + ) + + if r.is_error: + return PaymentStatus(None) + data = r.json() + + paid_to_status = {False: None, True: True} + return PaymentStatus(paid_to_status[data.get("paid")]) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + last_connected = None + while True: + url = f"{self.endpoint}/api/v1/invoicestream" + try: + async with httpx.AsyncClient(timeout=None, headers=self.auth) as client: + last_connected = time.time() + async with client.stream("GET", url) as r: + async for line in r.aiter_lines(): + try: + prefix = "data: " + if not line.startswith(prefix): + continue + data = line[len(prefix) :] # sse parsing + inv = json.loads(data) + if not inv.get("payment_hash"): + continue + except: + continue + yield inv["payment_hash"] + except Exception as e: + pass + + # do not sleep if the connection was active for more than 10s + # since the backend is expected to drop the connection after 90s + if last_connected is None or time.time() - last_connected < 10: + logger.error( + f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds" + ) + await asyncio.sleep(5) From d1302e4868f0fe12cf6bdf7d25e82045981077c2 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:52:39 +0200 Subject: [PATCH 031/134] show progress (#987) --- lnbits/core/crud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index cbed6292..bb1ca0c1 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -333,7 +333,7 @@ async def delete_expired_invoices( """ ) logger.debug(f"Checking expiry of {len(rows)} invoices") - for (payment_request,) in rows: + for i, (payment_request,) in enumerate(rows): try: invoice = bolt11.decode(payment_request) except: @@ -343,7 +343,7 @@ async def delete_expired_invoices( if expiration_date > datetime.datetime.utcnow(): continue logger.debug( - f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})" + f"Deleting expired invoice {i}/{len(rows)}: {invoice.payment_hash} (expired: {expiration_date})" ) await (conn or db).execute( """ From 42704eaf1bcf0a0ec6fc4309ea63f692f0721286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 18 Oct 2022 08:49:27 +0200 Subject: [PATCH 032/134] change boltz logs from warn to debug (#1057) --- lnbits/extensions/boltz/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/boltz/tasks.py b/lnbits/extensions/boltz/tasks.py index ace94557..d1ace04b 100644 --- a/lnbits/extensions/boltz/tasks.py +++ b/lnbits/extensions/boltz/tasks.py @@ -57,7 +57,7 @@ async def check_for_pending_swaps(): swap_status = get_swap_status(swap) # should only happen while development when regtest is reset if swap_status.exists is False: - logger.warning(f"Boltz - swap: {swap.boltz_id} does not exist.") + logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.") await update_swap_status(swap.id, "failed") continue @@ -73,7 +73,7 @@ async def check_for_pending_swaps(): else: if swap_status.hit_timeout: if not swap_status.has_lockup: - logger.warning( + logger.debug( f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..." ) await update_swap_status(swap.id, "timeout") From 9de38f908bfa603c1cfb98cfd078e0136e815320 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 20 Oct 2022 17:40:23 +0300 Subject: [PATCH 033/134] fix: wallet not found error (#1063) --- lnbits/extensions/satspay/crud.py | 9 ++++++++- lnbits/extensions/satspay/helpers.py | 17 +++++++++++++++++ lnbits/extensions/satspay/models.py | 1 - .../satspay/templates/satspay/display.html | 15 +++------------ lnbits/extensions/satspay/views.py | 5 ++--- lnbits/extensions/satspay/views_api.py | 17 +++-------------- 6 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 lnbits/extensions/satspay/helpers.py diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 47d7a4a8..23d391b7 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -102,7 +102,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]: charge = await get_charge(charge_id) if not charge.paid: if charge.onchainaddress: - config = await get_config(charge.user) + config = await get_charge_config(charge_id) try: async with httpx.AsyncClient() as client: r = await client.get( @@ -122,3 +122,10 @@ async def check_address_balance(charge_id: str) -> List[Charges]: return await update_charge(charge_id=charge_id, balance=charge.amount) row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) return Charges.from_row(row) if row else None + + +async def get_charge_config(charge_id: str): + row = await db.fetchone( + """SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,) + ) + return await get_config(row.user) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py new file mode 100644 index 00000000..2d15b557 --- /dev/null +++ b/lnbits/extensions/satspay/helpers.py @@ -0,0 +1,17 @@ +from .models import Charges + + +def compact_charge(charge: Charges): + return { + "id": charge.id, + "description": charge.description, + "onchainaddress": charge.onchainaddress, + "payment_request": charge.payment_request, + "payment_hash": charge.payment_hash, + "time": charge.time, + "amount": charge.amount, + "balance": charge.balance, + "paid": charge.paid, + "timestamp": charge.timestamp, + "completelink": charge.completelink, # should be secret? + } diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index e8638d5e..daf63f42 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -19,7 +19,6 @@ class CreateCharge(BaseModel): class Charges(BaseModel): id: str - user: str description: Optional[str] onchainwallet: Optional[str] onchainaddress: Optional[str] diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index f34ac509..12288c80 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -328,7 +328,7 @@ ) }, checkBalances: async function () { - if (!this.charge.hasStaleBalance) await this.refreshCharge() + if (this.charge.hasStaleBalance) return try { const {data} = await LNbits.api.request( 'GET', @@ -339,18 +339,9 @@ LNbits.utils.notifyApiError(error) } }, - refreshCharge: async function () { - try { - const {data} = await LNbits.api.request( - 'GET', - `/satspay/api/v1/charge/${this.charge.id}` - ) - this.charge = mapCharge(data, this.charge) - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, checkPendingOnchain: async function () { + if (!this.charge.onchainaddress) return + const { bitcoin: {addresses: addressesAPI} } = mempoolJS({ diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 69d81dad..b789bf8f 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -9,10 +9,9 @@ from starlette.responses import HTMLResponse from lnbits.core.crud import get_wallet from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.extensions.watchonly.crud import get_config from . import satspay_ext, satspay_renderer -from .crud import get_charge +from .crud import get_charge, get_charge_config templates = Jinja2Templates(directory="templates") @@ -32,7 +31,7 @@ async def display(request: Request, charge_id: str): status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist." ) wallet = await get_wallet(charge.lnbitswallet) - onchainwallet_config = await get_config(charge.user) + onchainwallet_config = await get_charge_config(charge_id) inkey = wallet.inkey if wallet else None mempool_endpoint = ( onchainwallet_config.mempool_endpoint if onchainwallet_config else None diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index f94b970a..73c87e7c 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -20,6 +20,7 @@ from .crud import ( get_charges, update_charge, ) +from .helpers import compact_charge from .models import CreateCharge #############################CHARGES########################## @@ -123,25 +124,13 @@ async def api_charge_balance(charge_id): try: r = await client.post( charge.webhook, - json={ - "id": charge.id, - "description": charge.description, - "onchainaddress": charge.onchainaddress, - "payment_request": charge.payment_request, - "payment_hash": charge.payment_hash, - "time": charge.time, - "amount": charge.amount, - "balance": charge.balance, - "paid": charge.paid, - "timestamp": charge.timestamp, - "completelink": charge.completelink, - }, + json=compact_charge(charge), timeout=40, ) except AssertionError: charge.webhook = None return { - **charge.dict(), + **compact_charge(charge), **{"time_elapsed": charge.time_elapsed}, **{"time_left": charge.time_left}, **{"paid": charge.paid}, From daf5768d52c39cbcf24279d2c8604a2fefa1a197 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 21 Oct 2022 15:49:17 +0300 Subject: [PATCH 034/134] fix: remove user from model (#1067) --- lnbits/extensions/watchonly/crud.py | 4 ++-- lnbits/extensions/watchonly/models.py | 1 - lnbits/extensions/watchonly/views_api.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index de338b91..c4a1df72 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -10,7 +10,7 @@ from .models import Address, Config, WalletAccount ##########################WALLETS#################### -async def create_watch_wallet(w: WalletAccount) -> WalletAccount: +async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount: wallet_id = urlsafe_short_hash() await db.execute( """ @@ -30,7 +30,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount: """, ( wallet_id, - w.user, + user, w.masterpub, w.fingerprint, w.title, diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index 622f5ec8..d8c278ff 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -14,7 +14,6 @@ class CreateWallet(BaseModel): class WalletAccount(BaseModel): id: str - user: str masterpub: str fingerprint: str title: str diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 750d46c9..9030b9c3 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -86,7 +86,6 @@ async def api_wallet_create_or_update( new_wallet = WalletAccount( id="none", - user=w.wallet.user, masterpub=data.masterpub, fingerprint=descriptor.keys[0].fingerprint.hex(), type=descriptor.scriptpubkey_type(), @@ -115,7 +114,7 @@ async def api_wallet_create_or_update( ) ) - wallet = await create_watch_wallet(new_wallet) + wallet = await create_watch_wallet(w.wallet.user, new_wallet) await api_get_addresses(wallet.id, w) except Exception as e: From 2d61a8854f38a61507289af3520e52ff54ccb1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 17:35:58 +0200 Subject: [PATCH 035/134] improving on split payment handling --- lnbits/extensions/splitpayments/models.py | 2 +- lnbits/extensions/splitpayments/tasks.py | 72 ++++++++--------------- 2 files changed, 24 insertions(+), 50 deletions(-) diff --git a/lnbits/extensions/splitpayments/models.py b/lnbits/extensions/splitpayments/models.py index 4b95ed18..6338d97f 100644 --- a/lnbits/extensions/splitpayments/models.py +++ b/lnbits/extensions/splitpayments/models.py @@ -14,7 +14,7 @@ class Target(BaseModel): class TargetPutList(BaseModel): wallet: str = Query(...) alias: str = Query("") - percent: float = Query(..., ge=0.01) + percent: float = Query(..., ge=0.01, lt=100) class TargetPut(BaseModel): diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index cfc6c226..53378b20 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -1,13 +1,11 @@ import asyncio -import json from loguru import logger -from lnbits.core import db as core_db -from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_queue, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_targets @@ -22,60 +20,36 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"): - # already splitted, ignore + if payment.extra.get("tag") == "splitpayments": + # already a splitted payment, ignore return - # now we make some special internal transfers (from no one to the receiver) targets = await get_targets(payment.wallet_id) if not targets: return - transfers = [ - (target.wallet, int(target.percent * payment.amount / 100)) - for target in targets - ] - transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0] - amount_left = payment.amount - sum([amount for _, amount in transfers]) + total_percent = sum([target.percent for target in targets]) - if amount_left < 0: - logger.error( - "splitpayments failure: amount_left is negative.", payment.payment_hash - ) + if total_percent > 100: + logger.error("splitpayment failure: total percent adds up to more than 100%") return - # mark the original payment with one extra key, "splitted" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps(dict(**payment.extra, splitted=True)), - amount_left, - payment.payment_hash, - ), - ) - - # perform the internal transfer using the same payment_hash - for wallet, amount in transfers: - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( - wallet_id=wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=amount, - memo=payment.memo, - pending=False, + logger.debug(f"performing split payments to {len(targets)} targets") + for target in targets: + amount = int(payment.amount * target.percent / 100) # msats + payment_hash, payment_request = await create_invoice( + wallet_id=target.wallet, + amount=int(amount / 1000), # sats + internal=True, + memo=f"split payment: {target.percent}% for {target.alias or target.wallet}", extra={"tag": "splitpayments"}, ) + logger.debug(f"created split invoice: {payment_hash}") - # manually send this for now - await internal_invoice_queue.put(internal_checking_id) - return + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "splitpayments"}, + ) + logger.debug(f"paid split invoice: {checking_id}") From fe879ad9b964766c25f8b59a0323d009d332166d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 18:15:27 +0200 Subject: [PATCH 036/134] add tpos to pr --- lnbits/extensions/tpos/tasks.py | 58 ++++++++++----------------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index f18d1689..6369bbc7 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -1,11 +1,11 @@ import asyncio -import json -from lnbits.core import db as core_db -from lnbits.core.crud import create_payment +from loguru import logger + from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_queue, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_tpos @@ -20,11 +20,9 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "tpos" and payment.extra.get("tipSplitted"): - # already splitted, ignore + if payment.extra.get("tag") != "tpos": return - # now we make some special internal transfers (from no one to the receiver) tpos = await get_tpos(payment.extra.get("tposId")) tipAmount = payment.extra.get("tipAmount") @@ -32,39 +30,17 @@ async def on_invoice_paid(payment: Payment) -> None: # no tip amount return - tipAmount = tipAmount * 1000 - amount = payment.amount - tipAmount - - # mark the original payment with one extra key, "splitted" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps(dict(**payment.extra, tipSplitted=True)), - amount, - payment.payment_hash, - ), - ) - - # perform the internal transfer using the same payment_hash - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( + payment_hash, payment_request = await create_invoice( wallet_id=tpos.tip_wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=tipAmount, - memo=f"Tip for {payment.memo}", - pending=False, - extra={"tipSplitted": True}, + amount=int(tipAmount), # sats + internal=True, + memo=f"tpos tip", ) + logger.debug(f"tpos: tip invoice created: {payment_hash}") - # manually send this for now - await internal_invoice_queue.put(internal_checking_id) - return + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "tpos"}, + ) + logger.debug(f"tpos: tip invoice paid: {checking_id}") From e1fbb8c3cd112d56bbd147565cc649f007d42287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 18:28:39 +0200 Subject: [PATCH 037/134] fix livestream extension --- lnbits/extensions/livestream/tasks.py | 52 ++++++++------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index 626c698c..d081332f 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -4,10 +4,10 @@ import json from loguru import logger from lnbits.core import db as core_db -from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name, urlsafe_short_hash -from lnbits.tasks import internal_invoice_listener, register_invoice_listener +from lnbits.core.services import create_invoice, pay_invoice +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener from .crud import get_livestream_by_track, get_producer, get_track @@ -44,44 +44,20 @@ async def on_invoice_paid(payment: Payment) -> None: # now we make a special kind of internal transfer amount = int(payment.amount * (100 - ls.fee_pct) / 100) - # mark the original payment with two extra keys, "shared_with" and "received" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps( - dict( - **payment.extra, - shared_with=[producer.name, producer.id], - received=payment.amount, - ) - ), - payment.amount - amount, - payment.payment_hash, - ), - ) - - # perform an internal transfer using the same payment_hash to the producer wallet - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( - wallet_id=producer.wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=amount, + payment_hash, payment_request = await create_invoice( + wallet_id=tpos.tip_wallet, + amount=amount, # sats + internal=True, memo=f"Revenue from '{track.name}'.", - pending=False, ) + logger.debug(f"livestream: producer invoice created: {payment_hash}") - # manually send this for now - # await internal_invoice_paid.send(internal_checking_id) - await internal_invoice_listener.put(internal_checking_id) + checking_id = await pay_invoice( + payment_request=payment_request, + wallet_id=payment.wallet_id, + extra={"tag": "livestream"}, + ) + logger.debug(f"livestream: producer invoice paid: {checking_id}") # so the flow is the following: # - we receive, say, 1000 satoshis From 3814989b3a142baaea1d010f7022882b72280f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Pha=CC=A3m-Bachelart?= Date: Mon, 24 Oct 2022 11:46:32 +0200 Subject: [PATCH 038/134] Add cliche wallet (#1071) --- docs/guide/wallets.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guide/wallets.md b/docs/guide/wallets.md index 80fb54c0..10724f34 100644 --- a/docs/guide/wallets.md +++ b/docs/guide/wallets.md @@ -79,3 +79,8 @@ For the invoice to work you must have a publicly accessible URL in your LNbits. - `LNBITS_BACKEND_WALLET_CLASS`: **OpenNodeWallet** - `OPENNODE_API_ENDPOINT`: https://api.opennode.com/ - `OPENNODE_KEY`: opennodeAdminApiKey + + +### Cliche Wallet + +- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000 From fc1a87f617a60de29552c8196a7f08050bb60ce5 Mon Sep 17 00:00:00 2001 From: Aaron Dewes Date: Mon, 24 Oct 2022 11:50:52 +0200 Subject: [PATCH 039/134] Remove unused proxy_fix (#1007) --- lnbits/app.py | 1 - lnbits/proxy_fix.py | 95 --------------------------------------------- 2 files changed, 96 deletions(-) delete mode 100644 lnbits/proxy_fix.py diff --git a/lnbits/app.py b/lnbits/app.py index 8b9cf798..075828ef 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -91,7 +91,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: ) app.add_middleware(GZipMiddleware, minimum_size=1000) - # app.add_middleware(ASGIProxyFix) check_funding_source(app) register_assets(app) diff --git a/lnbits/proxy_fix.py b/lnbits/proxy_fix.py deleted file mode 100644 index 897835e0..00000000 --- a/lnbits/proxy_fix.py +++ /dev/null @@ -1,95 +0,0 @@ -from functools import partial -from typing import Callable, List, Optional -from urllib.parse import urlparse -from urllib.request import parse_http_list as _parse_list_header - -from quart import Request -from quart_trio.asgi import TrioASGIHTTPConnection -from werkzeug.datastructures import Headers - - -class ASGIProxyFix(TrioASGIHTTPConnection): - def _create_request_from_scope(self, send: Callable) -> Request: - headers = Headers() - headers["Remote-Addr"] = (self.scope.get("client") or [""])[0] - for name, value in self.scope["headers"]: - headers.add(name.decode("latin1").title(), value.decode("latin1")) - if self.scope["http_version"] < "1.1": - headers.setdefault("Host", self.app.config["SERVER_NAME"] or "") - - path = self.scope["path"] - path = path if path[0] == "/" else urlparse(path).path - - x_proto = self._get_real_value(1, headers.get("X-Forwarded-Proto")) - if x_proto: - self.scope["scheme"] = x_proto - - x_host = self._get_real_value(1, headers.get("X-Forwarded-Host")) - if x_host: - headers["host"] = x_host.lower() - - return self.app.request_class( - self.scope["method"], - self.scope["scheme"], - path, - self.scope["query_string"], - headers, - self.scope.get("root_path", ""), - self.scope["http_version"], - max_content_length=self.app.config["MAX_CONTENT_LENGTH"], - body_timeout=self.app.config["BODY_TIMEOUT"], - send_push_promise=partial(self._send_push_promise, send), - scope=self.scope, - ) - - def _get_real_value(self, trusted: int, value: Optional[str]) -> Optional[str]: - """Get the real value from a list header based on the configured - number of trusted proxies. - :param trusted: Number of values to trust in the header. - :param value: Comma separated list header value to parse. - :return: The real value, or ``None`` if there are fewer values - than the number of trusted proxies. - .. versionchanged:: 1.0 - Renamed from ``_get_trusted_comma``. - .. versionadded:: 0.15 - """ - if not (trusted and value): - return None - - values = self.parse_list_header(value) - if len(values) >= trusted: - return values[-trusted] - - return None - - def parse_list_header(self, value: str) -> List[str]: - result = [] - for item in _parse_list_header(value): - if item[:1] == item[-1:] == '"': - item = self.unquote_header_value(item[1:-1]) - result.append(item) - return result - - def unquote_header_value(self, value: str, is_filename: bool = False) -> str: - r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). - This does not use the real unquoting but what browsers are actually - using for quoting. - .. versionadded:: 0.5 - :param value: the header value to unquote. - :param is_filename: The value represents a filename or path. - """ - if value and value[0] == value[-1] == '"': - # this is not the real unquoting, but fixing this so that the - # RFC is met will result in bugs with internet explorer and - # probably some other browsers as well. IE for example is - # uploading files with "C:\foo\bar.txt" as filename - value = value[1:-1] - - # if this is a filename and the starting characters look like - # a UNC path, then just return the value without quotes. Using the - # replace sequence below on a UNC path has the effect of turning - # the leading double slash into a single slash and then - # _fix_ie_filename() doesn't work correctly. See #458. - if not is_filename or value[:2] != "\\\\": - return value.replace("\\\\", "\\").replace('\\"', '"') - return value From 193d037ad6aa102152a824611c65a96a6ff876df Mon Sep 17 00:00:00 2001 From: Anton Kovalenko Date: Mon, 24 Oct 2022 13:23:11 +0300 Subject: [PATCH 040/134] Allow more than 2-nd level domain in incoming ln-addresses (#914) --- lnbits/core/views/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c07df568..983d5a26 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -476,7 +476,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type except: # parse internet identifier (user@domain.com) name_domain = code.split("@") - if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2: + if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2: name, domain = name_domain url = ( ("http://" if domain.endswith(".onion") else "https://") From 09cf654427d8f42baee484a5146a180b1261b1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 24 Oct 2022 12:32:20 +0200 Subject: [PATCH 041/134] Proper Error Handling for qrcode-stream components (#1076) * proper errorhandling for camera * fix javascript for calle --- lnbits/core/static/js/wallet.js | 29 ++++++++++++++++++++++++++ lnbits/core/templates/core/wallet.html | 2 ++ 2 files changed, 31 insertions(+) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 76d82ad4..66801313 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -361,6 +361,35 @@ new Vue({ this.receive.status = 'pending' }) }, + onInitQR: async function (promise) { + try { + await promise + } catch (error) { + let mapping = { + NotAllowedError: 'ERROR: you need to grant camera access permission', + NotFoundError: 'ERROR: no camera on this device', + NotSupportedError: + 'ERROR: secure context required (HTTPS, localhost)', + NotReadableError: 'ERROR: is the camera already in use?', + OverconstrainedError: 'ERROR: installed cameras are not suitable', + StreamApiNotSupportedError: + 'ERROR: Stream API is not supported in this browser', + InsecureContextError: + 'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.' + } + let valid_error = Object.keys(mapping).filter(key => { + return error.name === key + }) + let camera_error = valid_error + ? mapping[valid_error] + : `ERROR: Camera error (${error.name})` + this.parse.camera.show = false + this.$q.notify({ + message: camera_error, + type: 'negative' + }) + } + }, decodeQR: function (res) { this.parse.data.request = res this.decodeRequest() diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index bccdc2b4..4bf6067c 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -653,6 +653,7 @@ @@ -671,6 +672,7 @@
From 09871bbabc27b69886d56770178b19f445b7c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 24 Oct 2022 16:29:30 +0200 Subject: [PATCH 042/134] fix mypy for extensions (#873) * explicitly exclude all extensions from mypy * fix example extension mypy * fix subdomains extension mypy + 1 type error fixed * fix mypy discordbot * mypy check copilot extensnion * copilot black * add invoices ext to ignore * add boltz and boltcard * copilit id is necessary * was discordbot is ok Co-authored-by: dni --- lnbits/extensions/copilot/crud.py | 8 ++--- lnbits/extensions/copilot/tasks.py | 20 ++++++------ lnbits/extensions/copilot/views.py | 8 +++-- lnbits/extensions/copilot/views_api.py | 9 +++--- lnbits/extensions/discordbot/crud.py | 6 ++-- lnbits/extensions/discordbot/views.py | 4 ++- lnbits/extensions/discordbot/views_api.py | 39 +++++++++++++---------- lnbits/extensions/example/views.py | 5 ++- lnbits/extensions/subdomains/crud.py | 4 +-- lnbits/extensions/subdomains/models.py | 30 ++++++++--------- lnbits/extensions/subdomains/tasks.py | 4 +-- lnbits/extensions/subdomains/views.py | 4 ++- lnbits/extensions/subdomains/views_api.py | 25 +++++++++++---- pyproject.toml | 30 +++++++++++++++-- 14 files changed, 125 insertions(+), 71 deletions(-) diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index d0da044e..5ecb5cd4 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -10,7 +10,7 @@ from .models import Copilots, CreateCopilotData async def create_copilot( data: CreateCopilotData, inkey: Optional[str] = "" -) -> Copilots: +) -> Optional[Copilots]: copilot_id = urlsafe_short_hash() await db.execute( """ @@ -67,19 +67,19 @@ async def create_copilot( async def update_copilot( - data: CreateCopilotData, copilot_id: Optional[str] = "" + data: CreateCopilotData, copilot_id: str ) -> Optional[Copilots]: q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] items.append(copilot_id) - await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items)) + await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items,)) row = await db.fetchone( "SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,) ) return Copilots(**row) if row else None -async def get_copilot(copilot_id: str) -> Copilots: +async def get_copilot(copilot_id: str) -> Optional[Copilots]: row = await db.fetchone( "SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,) ) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index c59ef4cc..48ad7813 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -26,7 +26,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: webhook = None data = None - if payment.extra.get("tag") != "copilot": + if not payment.extra or payment.extra.get("tag") != "copilot": # not an copilot invoice return @@ -71,12 +71,12 @@ async def on_invoice_paid(payment: Payment) -> None: async def mark_webhook_sent(payment: Payment, status: int) -> None: - payment.extra["wh_status"] = status - - await core_db.execute( - """ - UPDATE apipayments SET extra = ? - WHERE hash = ? - """, - (json.dumps(payment.extra), payment.payment_hash), - ) + if payment.extra: + payment.extra["wh_status"] = status + await core_db.execute( + """ + UPDATE apipayments SET extra = ? + WHERE hash = ? + """, + (json.dumps(payment.extra), payment.payment_hash), + ) diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index 7ee7f590..b4a2354a 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -15,7 +15,9 @@ templates = Jinja2Templates(directory="templates") @copilot_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 copilot_renderer().TemplateResponse( "copilot/index.html", {"request": request, "user": user.dict()} ) @@ -44,7 +46,7 @@ class ConnectionManager: async def connect(self, websocket: WebSocket, copilot_id: str): await websocket.accept() - websocket.id = copilot_id + websocket.id = copilot_id # type: ignore self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): @@ -52,7 +54,7 @@ class ConnectionManager: async def send_personal_message(self, message: str, copilot_id: str): for connection in self.active_connections: - if connection.id == copilot_id: + if connection.id == copilot_id: # type: ignore await connection.send_text(message) async def broadcast(self, message: str): diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index 91b0572a..46611a2e 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -23,7 +23,7 @@ from .views import updater @copilot_ext.get("/api/v1/copilot") async def api_copilots_retrieve( - req: Request, wallet: WalletTypeInfo = Depends(get_key_type) + req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): wallet_user = wallet.wallet.user copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)] @@ -37,7 +37,7 @@ async def api_copilots_retrieve( async def api_copilot_retrieve( req: Request, copilot_id: str = Query(None), - wallet: WalletTypeInfo = Depends(get_key_type), + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore ): copilot = await get_copilot(copilot_id) if not copilot: @@ -54,7 +54,7 @@ async def api_copilot_retrieve( async def api_copilot_create_or_update( data: CreateCopilotData, copilot_id: str = Query(None), - wallet: WalletTypeInfo = Depends(require_admin_key), + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): data.user = wallet.wallet.user data.wallet = wallet.wallet.id @@ -67,7 +67,8 @@ async def api_copilot_create_or_update( @copilot_ext.delete("/api/v1/copilot/{copilot_id}") async def api_copilot_delete( - copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key) + copilot_id: str = Query(None), + wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore ): copilot = await get_copilot(copilot_id) diff --git a/lnbits/extensions/discordbot/crud.py b/lnbits/extensions/discordbot/crud.py index 5661fcb4..629a5c00 100644 --- a/lnbits/extensions/discordbot/crud.py +++ b/lnbits/extensions/discordbot/crud.py @@ -98,21 +98,21 @@ async def get_discordbot_wallet(wallet_id: str) -> Optional[Wallets]: return Wallets(**row) if row else None -async def get_discordbot_wallets(admin_id: str) -> Optional[Wallets]: +async def get_discordbot_wallets(admin_id: str) -> List[Wallets]: rows = await db.fetchall( "SELECT * FROM discordbot.wallets WHERE admin = ?", (admin_id,) ) return [Wallets(**row) for row in rows] -async def get_discordbot_users_wallets(user_id: str) -> Optional[Wallets]: +async def get_discordbot_users_wallets(user_id: str) -> List[Wallets]: rows = await db.fetchall( """SELECT * FROM discordbot.wallets WHERE "user" = ?""", (user_id,) ) return [Wallets(**row) for row in rows] -async def get_discordbot_wallet_transactions(wallet_id: str) -> Optional[Payment]: +async def get_discordbot_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/discordbot/views.py b/lnbits/extensions/discordbot/views.py index a5395e21..ec7d18cc 100644 --- a/lnbits/extensions/discordbot/views.py +++ b/lnbits/extensions/discordbot/views.py @@ -9,7 +9,9 @@ from . import discordbot_ext, discordbot_renderer @discordbot_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 discordbot_renderer().TemplateResponse( "discordbot/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/discordbot/views_api.py b/lnbits/extensions/discordbot/views_api.py index 6f213a89..e6d004db 100644 --- a/lnbits/extensions/discordbot/views_api.py +++ b/lnbits/extensions/discordbot/views_api.py @@ -27,32 +27,37 @@ from .models import CreateUserData, CreateUserWallet @discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK) -async def api_discordbot_users(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_users( + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore +): user_id = wallet.wallet.user return [user.dict() for user in await get_discordbot_users(user_id)] @discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) -async def api_discordbot_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_user( + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore +): user = await get_discordbot_user(user_id) - return user.dict() + if user: + return user.dict() @discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED) async def api_discordbot_users_create( - data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) + data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await create_discordbot_user(data) full = user.dict() - full["wallets"] = [ - wallet.dict() for wallet in await get_discordbot_users_wallets(user.id) - ] + wallets = await get_discordbot_users_wallets(user.id) + if wallets: + full["wallets"] = [wallet for wallet in wallets] return full @discordbot_ext.delete("/api/v1/users/{user_id}") async def api_discordbot_users_delete( - user_id, wallet: WalletTypeInfo = Depends(get_key_type) + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await get_discordbot_user(user_id) if not user: @@ -75,7 +80,7 @@ async def api_discordbot_activate_extension( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - update_user_extension(user_id=userid, extension=extension, active=active) + await update_user_extension(user_id=userid, extension=extension, active=active) return {"extension": "updated"} @@ -84,7 +89,7 @@ async def api_discordbot_activate_extension( @discordbot_ext.post("/api/v1/wallets") async def api_discordbot_wallets_create( - data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) + data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): user = await create_discordbot_wallet( user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id @@ -93,28 +98,30 @@ async def api_discordbot_wallets_create( @discordbot_ext.get("/api/v1/wallets") -async def api_discordbot_wallets(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_discordbot_wallets( + wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore +): admin_id = wallet.wallet.user - return [wallet.dict() for wallet in await get_discordbot_wallets(admin_id)] + return await get_discordbot_wallets(admin_id) @discordbot_ext.get("/api/v1/transactions/{wallet_id}") async def api_discordbot_wallet_transactions( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) + wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): return await get_discordbot_wallet_transactions(wallet_id) @discordbot_ext.get("/api/v1/wallets/{user_id}") async def api_discordbot_users_wallets( - user_id, wallet: WalletTypeInfo = Depends(get_key_type) + user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): - return [s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id)] + return await get_discordbot_users_wallets(user_id) @discordbot_ext.delete("/api/v1/wallets/{wallet_id}") async def api_discordbot_wallets_delete( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) + wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore ): get_wallet = await get_discordbot_wallet(wallet_id) if not get_wallet: diff --git a/lnbits/extensions/example/views.py b/lnbits/extensions/example/views.py index 252b4726..29b257f4 100644 --- a/lnbits/extensions/example/views.py +++ b/lnbits/extensions/example/views.py @@ -12,7 +12,10 @@ templates = Jinja2Templates(directory="templates") @example_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 example_renderer().TemplateResponse( "example/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/subdomains/crud.py b/lnbits/extensions/subdomains/crud.py index 207e2d1d..aa358d11 100644 --- a/lnbits/extensions/subdomains/crud.py +++ b/lnbits/extensions/subdomains/crud.py @@ -3,10 +3,10 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash from . import db -from .models import CreateDomain, Domains, Subdomains +from .models import CreateDomain, CreateSubdomain, Domains, Subdomains -async def create_subdomain(payment_hash, wallet, data: CreateDomain) -> Subdomains: +async def create_subdomain(payment_hash, wallet, data: CreateSubdomain) -> Subdomains: await db.execute( """ INSERT INTO subdomains.subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type) diff --git a/lnbits/extensions/subdomains/models.py b/lnbits/extensions/subdomains/models.py index 17004504..39e17615 100644 --- a/lnbits/extensions/subdomains/models.py +++ b/lnbits/extensions/subdomains/models.py @@ -3,24 +3,24 @@ from pydantic.main import BaseModel class CreateDomain(BaseModel): - wallet: str = Query(...) - domain: str = Query(...) - cf_token: str = Query(...) - cf_zone_id: str = Query(...) - webhook: str = Query("") - description: str = Query(..., min_length=0) - cost: int = Query(..., ge=0) - allowed_record_types: str = Query(...) + wallet: str = Query(...) # type: ignore + domain: str = Query(...) # type: ignore + cf_token: str = Query(...) # type: ignore + cf_zone_id: str = Query(...) # type: ignore + webhook: str = Query("") # type: ignore + description: str = Query(..., min_length=0) # type: ignore + cost: int = Query(..., ge=0) # type: ignore + allowed_record_types: str = Query(...) # type: ignore class CreateSubdomain(BaseModel): - domain: str = Query(...) - subdomain: str = Query(...) - email: str = Query(...) - ip: str = Query(...) - sats: int = Query(..., ge=0) - duration: int = Query(...) - record_type: str = Query(...) + domain: str = Query(...) # type: ignore + subdomain: str = Query(...) # type: ignore + email: str = Query(...) # type: ignore + ip: str = Query(...) # type: ignore + sats: int = Query(..., ge=0) # type: ignore + duration: int = Query(...) # type: ignore + record_type: str = Query(...) # type: ignore class Domains(BaseModel): diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index 04ee2dd4..c5a7f47b 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -20,7 +20,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "lnsubdomain": + if not payment.extra or payment.extra.get("tag") != "lnsubdomain": # not an lnurlp invoice return @@ -37,7 +37,7 @@ async def on_invoice_paid(payment: Payment) -> None: ) ### Use webhook to notify about cloudflare registration - if domain.webhook: + if domain and domain.webhook: async with httpx.AsyncClient() as client: try: r = await client.post( diff --git a/lnbits/extensions/subdomains/views.py b/lnbits/extensions/subdomains/views.py index df387ba8..962f850d 100644 --- a/lnbits/extensions/subdomains/views.py +++ b/lnbits/extensions/subdomains/views.py @@ -16,7 +16,9 @@ templates = Jinja2Templates(directory="templates") @subdomains_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 subdomains_renderer().TemplateResponse( "subdomains/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py index b01e6ffb..34d8e75b 100644 --- a/lnbits/extensions/subdomains/views_api.py +++ b/lnbits/extensions/subdomains/views_api.py @@ -29,12 +29,15 @@ from .crud import ( @subdomains_ext.get("/api/v1/domains") async def api_domains( - g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) + g: WalletTypeInfo = Depends(get_key_type), # type: ignore + all_wallets: bool = Query(False), ): wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + if user is not None: + wallet_ids = user.wallet_ids return [domain.dict() for domain in await get_domains(wallet_ids)] @@ -42,7 +45,9 @@ async def api_domains( @subdomains_ext.post("/api/v1/domains") @subdomains_ext.put("/api/v1/domains/{domain_id}") async def api_domain_create( - data: CreateDomain, domain_id=None, g: WalletTypeInfo = Depends(get_key_type) + data: CreateDomain, + domain_id=None, + g: WalletTypeInfo = Depends(get_key_type), # type: ignore ): if domain_id: domain = await get_domain(domain_id) @@ -63,7 +68,9 @@ async def api_domain_create( @subdomains_ext.delete("/api/v1/domains/{domain_id}") -async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)): +async def api_domain_delete( + domain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): domain = await get_domain(domain_id) if not domain: @@ -82,12 +89,14 @@ async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type) @subdomains_ext.get("/api/v1/subdomains") async def api_subdomains( - all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) + all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) # type: ignore ): wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + if user is not None: + wallet_ids = user.wallet_ids return [domain.dict() for domain in await get_subdomains(wallet_ids)] @@ -173,7 +182,9 @@ async def api_subdomain_send_subdomain(payment_hash): @subdomains_ext.delete("/api/v1/subdomains/{subdomain_id}") -async def api_subdomain_delete(subdomain_id, g: WalletTypeInfo = Depends(get_key_type)): +async def api_subdomain_delete( + subdomain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): subdomain = await get_subdomain(subdomain_id) if not subdomain: diff --git a/pyproject.toml b/pyproject.toml index 19dac860..7418de27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,8 +89,34 @@ profile = "black" ignore_missing_imports = "True" files = "lnbits" exclude = """(?x)( - ^lnbits/extensions. - | ^lnbits/wallets/lnd_grpc_files. + ^lnbits/extensions/bleskomat. + | ^lnbits/extensions/boltz. + | ^lnbits/extensions/boltcards. + | ^lnbits/extensions/events. + | ^lnbits/extensions/hivemind. + | ^lnbits/extensions/invoices. + | ^lnbits/extensions/jukebox. + | ^lnbits/extensions/livestream. + | ^lnbits/extensions/lnaddress. + | ^lnbits/extensions/lndhub. + | ^lnbits/extensions/lnticket. + | ^lnbits/extensions/lnurldevice. + | ^lnbits/extensions/lnurlp. + | ^lnbits/extensions/lnurlpayout. + | ^lnbits/extensions/ngrok. + | ^lnbits/extensions/offlineshop. + | ^lnbits/extensions/paywall. + | ^lnbits/extensions/satsdice. + | ^lnbits/extensions/satspay. + | ^lnbits/extensions/scrub. + | ^lnbits/extensions/splitpayments. + | ^lnbits/extensions/streamalerts. + | ^lnbits/extensions/tipjar. + | ^lnbits/extensions/tpos. + | ^lnbits/extensions/usermanager. + | ^lnbits/extensions/watchonly. + | ^lnbits/extensions/withdraw. + | ^lnbits/wallets/lnd_grpc_files. )""" [tool.pytest.ini_options] From 708f855c1d485639dfa559c2f2a26a5a9f41c1d7 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:03:32 +0000 Subject: [PATCH 043/134] Added old admin extension --- .env.example | 14 +- lnbits/extensions/admin/README.md | 11 + lnbits/extensions/admin/__init__.py | 10 + lnbits/extensions/admin/config.json | 6 + lnbits/extensions/admin/crud.py | 59 ++ lnbits/extensions/admin/migrations.py | 256 ++++++++ lnbits/extensions/admin/models.py | 38 ++ .../admin/templates/admin/index.html | 565 ++++++++++++++++++ lnbits/extensions/admin/views.py | 20 + lnbits/extensions/admin/views_api.py | 41 ++ 10 files changed, 1014 insertions(+), 6 deletions(-) create mode 100644 lnbits/extensions/admin/README.md create mode 100644 lnbits/extensions/admin/__init__.py create mode 100644 lnbits/extensions/admin/config.json create mode 100644 lnbits/extensions/admin/crud.py create mode 100644 lnbits/extensions/admin/migrations.py create mode 100644 lnbits/extensions/admin/models.py create mode 100644 lnbits/extensions/admin/templates/admin/index.html create mode 100644 lnbits/extensions/admin/views.py create mode 100644 lnbits/extensions/admin/views_api.py diff --git a/.env.example b/.env.example index 987c6ca6..bfaeb515 100644 --- a/.env.example +++ b/.env.example @@ -3,17 +3,19 @@ PORT=5000 DEBUG=false -LNBITS_ALLOWED_USERS="" -LNBITS_ADMIN_USERS="" -# Extensions only admin can access -LNBITS_ADMIN_EXTENSIONS="ngrok" +LNBITS_ADMIN_USERS="" # User IDs seperated by comma +LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Extensions only admin can access + +LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma + LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" # csv ad image filepaths or urls, extensions can choose to honor LNBITS_AD_SPACE="" # Hides wallet api, extensions can choose to honor -LNBITS_HIDE_API=false +LNBITS_HIDE_API=false # Disable extensions for all users, use "all" to disable all extensions LNBITS_DISABLED_EXTENSIONS="amilk" @@ -67,7 +69,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING" -# To use an AES-encrypted macaroon, set +# To use an AES-encrypted macaroon, set # LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" # LNPayWallet diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md new file mode 100644 index 00000000..27729459 --- /dev/null +++ b/lnbits/extensions/admin/README.md @@ -0,0 +1,11 @@ +

Example Extension

+

*tagline*

+This is an example extension to help you organise and build you own. + +Try to include an image + + + +

If your extension has API endpoints, include useful ones here

+ +curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py new file mode 100644 index 00000000..d5f26c90 --- /dev/null +++ b/lnbits/extensions/admin/__init__.py @@ -0,0 +1,10 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_admin") + +admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json new file mode 100644 index 00000000..69661733 --- /dev/null +++ b/lnbits/extensions/admin/config.json @@ -0,0 +1,6 @@ +{ + "name": "Admin", + "short_description": "Manage your LNbits install", + "icon": "build", + "contributors": ["benarc"] +} diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py new file mode 100644 index 00000000..cb8f9b5b --- /dev/null +++ b/lnbits/extensions/admin/crud.py @@ -0,0 +1,59 @@ +from typing import List, Optional + +from . import db +from .models import Admin, Funding +from lnbits.settings import * +from lnbits.helpers import urlsafe_short_hash +from lnbits.core.crud import create_payment +from lnbits.db import Connection + + +def update_wallet_balance(wallet_id: str, amount: int) -> str: + temp_id = f"temp_{urlsafe_short_hash()}" + internal_id = f"internal_{urlsafe_short_hash()}" + create_payment( + wallet_id=wallet_id, + checking_id=internal_id, + payment_request="admin_internal", + payment_hash="admin_internal", + amount=amount * 1000, + memo="Admin top up", + pending=False, + ) + return "success" + + +async def update_admin( +) -> Optional[Admin]: + if not CLightningWallet: + print("poo") + await db.execute( + """ + UPDATE admin + SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? + WHERE 1 + """, + ( + + ), + ) + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + +async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + ) + row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) + return Jukebox(**row) if row else None + +async def get_admin() -> List[Admin]: + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + + +async def get_funding() -> List[Funding]: + rows = await db.fetchall("SELECT * FROM funding") + + return [Funding.from_row(row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py new file mode 100644 index 00000000..82d934cb --- /dev/null +++ b/lnbits/extensions/admin/migrations.py @@ -0,0 +1,256 @@ +from sqlalchemy.exc import OperationalError # type: ignore +from os import getenv +from lnbits.helpers import urlsafe_short_hash + + +async def m001_create_admin_table(db): + user = None + site_title = None + site_tagline = None + site_description = None + allowed_users = None + admin_user = None + default_wallet_name = None + data_folder = None + disabled_ext = None + force_https = True + service_fee = 0 + funding_source = "" + + if getenv("LNBITS_SITE_TITLE"): + site_title = getenv("LNBITS_SITE_TITLE") + + if getenv("LNBITS_SITE_TAGLINE"): + site_tagline = getenv("LNBITS_SITE_TAGLINE") + + if getenv("LNBITS_SITE_DESCRIPTION"): + site_description = getenv("LNBITS_SITE_DESCRIPTION") + + if getenv("LNBITS_ALLOWED_USERS"): + allowed_users = getenv("LNBITS_ALLOWED_USERS") + + if getenv("LNBITS_ADMIN_USER"): + admin_user = getenv("LNBITS_ADMIN_USER") + + if getenv("LNBITS_DEFAULT_WALLET_NAME"): + default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + + if getenv("LNBITS_DATA_FOLDER"): + data_folder = getenv("LNBITS_DATA_FOLDER") + + if getenv("LNBITS_DISABLED_EXTENSIONS"): + disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + if getenv("LNBITS_FORCE_HTTPS"): + force_https = getenv("LNBITS_FORCE_HTTPS") + + if getenv("LNBITS_SERVICE_FEE"): + service_fee = getenv("LNBITS_SERVICE_FEE") + + if getenv("LNBITS_BACKEND_WALLET_CLASS"): + funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS admin ( + user TEXT, + site_title TEXT, + site_tagline TEXT, + site_description TEXT, + admin_user TEXT, + allowed_users TEXT, + default_wallet_name TEXT, + data_folder TEXT, + disabled_ext TEXT, + force_https BOOLEAN, + service_fee INT, + funding_source TEXT + ); + """ + ) + await db.execute( + """ + INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + site_title, + site_tagline, + site_description, + admin_user, + allowed_users, + default_wallet_name, + data_folder, + disabled_ext, + force_https, + service_fee, + funding_source, + ), + ) + + +async def m001_create_funding_table(db): + + funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") + + # Make the funding table, if it does not already exist + await db.execute( + """ + CREATE TABLE IF NOT EXISTS funding ( + id TEXT PRIMARY KEY, + backend_wallet TEXT, + endpoint TEXT, + port INT, + read_key TEXT, + invoice_key TEXT, + admin_key TEXT, + cert TEXT, + balance INT, + selected INT + ); + """ + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, selected) + VALUES (?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "CLightningWallet", + getenv("CLIGHTNING_RPC"), + 1 if funding_wallet == "CLightningWallet" else 0, + ), + ) + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LnbitsWallet", + getenv("LNBITS_ENDPOINT"), + getenv("LNBITS_KEY"), + 1 if funding_wallet == "LnbitsWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndWallet", + getenv("LND_GRPC_ENDPOINT"), + getenv("LND_GRPC_PORT"), + getenv("LND_GRPC_MACAROON"), + getenv("LND_GRPC_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndRestWallet", + getenv("LND_REST_ENDPOINT"), + getenv("LND_REST_MACAROON"), + getenv("LND_REST_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LNPayWallet", + getenv("LNPAY_API_ENDPOINT"), + getenv("LNPAY_WALLET_KEY"), + getenv("LNPAY_API_KEY"), # this is going in as the cert + 1 if funding_wallet == "LNPayWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LntxbotWallet", + getenv("LNTXBOT_API_ENDPOINT"), + getenv("LNTXBOT_KEY"), + 1 if funding_wallet == "LntxbotWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "OpenNodeWallet", + getenv("OPENNODE_API_ENDPOINT"), + getenv("OPENNODE_KEY"), + 1 if funding_wallet == "OpenNodeWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + ## PLACEHOLDER FOR ECLAIR WALLET + # await db.execute( + # """ + # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # VALUES (?, ?, ?, ?, ?) + # """, + # ( + # urlsafe_short_hash(), + # "EclairWallet", + # getenv("ECLAIR_URL"), + # getenv("ECLAIR_PASS"), + # 1 if funding_wallet == "EclairWallet" else 0, + # ), + # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py new file mode 100644 index 00000000..c38f17f4 --- /dev/null +++ b/lnbits/extensions/admin/models.py @@ -0,0 +1,38 @@ +from typing import NamedTuple +from sqlite3 import Row + +class Admin(NamedTuple): + user: str + site_title: str + site_tagline: str + site_description:str + allowed_users: str + admin_user: str + default_wallet_name: str + data_folder: str + disabled_ext: str + force_https: str + service_fee: str + funding_source: str + + @classmethod + def from_row(cls, row: Row) -> "Admin": + data = dict(row) + return cls(**data) + +class Funding(NamedTuple): + id: str + backend_wallet: str + endpoint: str + port: str + read_key: str + invoice_key: str + admin_key: str + cert: str + balance: int + selected: int + + @classmethod + def from_row(cls, row: Row) -> "Funding": + data = dict(row) + return cls(**data) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html new file mode 100644 index 00000000..87cf09ef --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -0,0 +1,565 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +

Admin

+

+ +
+
+ + +
Settings
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+

+
+
+ +
+
+
+
+ +
+ + +
Wallet topup
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py new file mode 100644 index 00000000..5e17919c --- /dev/null +++ b/lnbits/extensions/admin/views.py @@ -0,0 +1,20 @@ +from quart import g, render_template, request, jsonify +import json + +from lnbits.decorators import check_user_exists, validate_uuids +from lnbits.extensions.admin import admin_ext +from lnbits.core.crud import get_user, create_account +from .crud import get_admin, get_funding +from lnbits.settings import WALLET + + +@admin_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + user_id = g.user + admin = await get_admin() + + funding = [{**funding._asdict()} for funding in await get_funding()] + + return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py new file mode 100644 index 00000000..2a61b6f5 --- /dev/null +++ b/lnbits/extensions/admin/views_api.py @@ -0,0 +1,41 @@ +from quart import jsonify, g, request +from http import HTTPStatus +from .crud import update_wallet_balance +from lnbits.extensions.admin import admin_ext +from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from lnbits.core.crud import get_wallet +from .crud import get_admin,update_admin +import json + +@admin_ext.route("/api/v1/admin//", methods=["GET"]) +@api_check_wallet_key("admin") +async def api_update_balance(wallet_id, topup_amount): + print(g.data.wallet) + try: + wallet = await get_wallet(wallet_id) + except: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + print(wallet) + print(topup_amount) + return jsonify({"status": "Success"}), HTTPStatus.OK + + +@admin_ext.route("/api/v1/admin/", methods=["POST"]) +@api_check_wallet_key("admin") +@api_validate_post_request(schema={}) +async def api_update_admin(): + body = await request.get_json() + admin = await get_admin() + print(g.wallet[2]) + print(body["admin_user"]) + if not admin.admin_user == g.wallet[2] and admin.admin_user != None: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + updated = await update_admin(body) + print(updated) + return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file From a3b1d9528c92008b42116904f5ebbdf8d9360173 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:11:55 +0000 Subject: [PATCH 044/134] old admin setup UI --- lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html new file mode 100644 index 00000000..e8176555 --- /dev/null +++ b/lnbits/core/templates/core/admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From b325566302f079575e478d9ca9eeff47a8f10a1a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:09 +0000 Subject: [PATCH 045/134] convert to FastAPI --- lnbits/extensions/admin/__init__.py | 11 +++- lnbits/extensions/admin/crud.py | 50 +++++++-------- lnbits/extensions/admin/migrations.py | 23 ++++--- lnbits/extensions/admin/models.py | 51 ++++++++++------ .../admin/templates/admin/index.html | 23 +++++-- lnbits/extensions/admin/views.py | 39 ++++++++---- lnbits/extensions/admin/views_api.py | 61 ++++++++++--------- 7 files changed, 151 insertions(+), 107 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index d5f26c90..6a56b2bb 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -1,10 +1,15 @@ -from quart import Blueprint +from fastapi import APIRouter + from lnbits.db import Database +from lnbits.helpers import template_renderer db = Database("ext_admin") -admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") +admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + +def admin_renderer(): + return template_renderer(["lnbits/extensions/admin/templates"]) -from .views_api import * # noqa from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cb8f9b5b..872d6c97 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,11 +1,11 @@ from typing import List, Optional +from lnbits.core.crud import create_payment +from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import * + from . import db from .models import Admin, Funding -from lnbits.settings import * -from lnbits.helpers import urlsafe_short_hash -from lnbits.core.crud import create_payment -from lnbits.db import Connection def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str: ) return "success" - -async def update_admin( -) -> Optional[Admin]: - if not CLightningWallet: - print("poo") - await db.execute( - """ - UPDATE admin - SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? - WHERE 1 - """, - ( - - ), - ) - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None - -async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: +async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + print("UPDATE", q) await db.execute( - f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) - return Jukebox(**row) if row else None + row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + assert row, "Newly updated settings couldn't be retrieved" + return Admin(**row) if row else None + +# async def update_admin(user: str, **kwargs) -> Optional[Admin]: +# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) +# await db.execute( +# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) +# ) +# new_settings = await get_admin() +# return new_settings async def get_admin() -> List[Admin]: - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None + row = await db.fetchone("SELECT * FROM admin") + return Admin(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") - return [Funding.from_row(row) for row in rows] + return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 82d934cb..13b76923 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,5 +1,7 @@ -from sqlalchemy.exc import OperationalError # type: ignore from os import getenv + +from sqlalchemy.exc import OperationalError # type: ignore + from lnbits.helpers import urlsafe_short_hash @@ -9,7 +11,7 @@ async def m001_create_admin_table(db): site_tagline = None site_description = None allowed_users = None - admin_user = None + admin_users = None default_wallet_name = None data_folder = None disabled_ext = None @@ -29,8 +31,9 @@ async def m001_create_admin_table(db): if getenv("LNBITS_ALLOWED_USERS"): allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_ADMIN_USER"): - admin_user = getenv("LNBITS_ADMIN_USER") + if getenv("LNBITS_ADMIN_USERS"): + admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + user = admin_users.split(',')[0] if getenv("LNBITS_DEFAULT_WALLET_NAME"): default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") @@ -53,32 +56,32 @@ async def m001_create_admin_table(db): await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - user TEXT, + "user" TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_user TEXT, + admin_users TEXT, allowed_users TEXT, default_wallet_name TEXT, data_folder TEXT, disabled_ext TEXT, force_https BOOLEAN, - service_fee INT, + service_fee REAL, funding_source TEXT ); """ ) await db.execute( """ - INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - user, + user.strip(), site_title, site_tagline, site_description, - admin_user, + admin_users[1:], allowed_users, default_wallet_name, data_folder, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index c38f17f4..4080ff01 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,18 +1,35 @@ -from typing import NamedTuple from sqlite3 import Row +from typing import List, Optional -class Admin(NamedTuple): +from fastapi import Query +from pydantic import BaseModel + + +class UpdateAdminSettings(BaseModel): + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: Optional[str] + default_wallet_name: Optional[str] + data_folder: Optional[str] + disabled_ext: Optional[str] + force_https: Optional[bool] + service_fee: Optional[float] + funding_source: Optional[str] + +class Admin(BaseModel): user: str - site_title: str - site_tagline: str - site_description:str - allowed_users: str - admin_user: str + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: str default_wallet_name: str data_folder: str disabled_ext: str - force_https: str - service_fee: str + force_https: Optional[bool] = Query(True) + service_fee: float funding_source: str @classmethod @@ -20,16 +37,16 @@ class Admin(NamedTuple): data = dict(row) return cls(**data) -class Funding(NamedTuple): +class Funding(BaseModel): id: str backend_wallet: str - endpoint: str - port: str - read_key: str - invoice_key: str - admin_key: str - cert: str - balance: int + endpoint: str = Query(None) + port: str = Query(None) + read_key: str = Query(None) + invoice_key: str = Query(None) + admin_key: str = Query(None) + cert: str = Query(None) + balance: int = Query(None) selected: int @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 87cf09ef..a6b45625 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -87,7 +87,7 @@ @@ -442,13 +442,14 @@ site_title: '{{admin.site_title}}', tagline: '{{admin.site_tagline}}', description: '{{admin.site_description}}', - admin_user: '{{admin.admin_user}}', - service_fee: parseInt('{{admin.service_fee}}'), + admin_users: '{{admin.admin_users}}', + service_fee: parseFloat('{{admin.service_fee}}'), default_wallet_name: '{{admin.default_wallet_name}}', data_folder: '{{admin.data_folder}}', funding_source_primary: '{{admin.funding_source}}', disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], + funding: {}, senddata: {} } }, @@ -528,15 +529,27 @@ }, UpdateLNbits: function () { var self = this - console.log(self.data.admin) + let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + let data = { + site_title, + site_tagline: this.data.admin.tagline, + site_description: this.data.admin.description, + admin_users: admin_users.toString(), + default_wallet_name, + data_folder, + disabled_ext: disabled_ext.toString(), + service_fee, + funding_source: funding_source_primary} + console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', self.g.user.wallets[0].adminkey, - self.data.admin + data ) .then(function (response) { + console.log(response.data) self.$q.notify({ type: 'positive', message: diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 5e17919c..00a0c99f 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -1,20 +1,33 @@ -from quart import g, render_template, request, jsonify -import json +from email.policy import default +from os import getenv -from lnbits.decorators import check_user_exists, validate_uuids +from fastapi import Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists from lnbits.extensions.admin import admin_ext -from lnbits.core.crud import get_user, create_account +from lnbits.requestvars import g + +from . import admin_ext, admin_renderer from .crud import get_admin, get_funding -from lnbits.settings import WALLET +templates = Jinja2Templates(directory="templates") -@admin_ext.route("/") -@validate_uuids(["usr"], required=True) -@check_user_exists() -async def index(): - user_id = g.user +@admin_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() + print(g()) + funding = [f.dict() for f in await get_funding()] - funding = [{**funding._asdict()} for funding in await get_funding()] - - return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) + print("ADMIN", admin.dict()) + return admin_renderer().TemplateResponse( + "admin/index.html", { + "request": request, + "user": user.dict(), + "admin": admin.dict(), + "funding": funding + } + ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 2a61b6f5..b2c65be2 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,41 +1,42 @@ -from quart import jsonify, g, request from http import HTTPStatus -from .crud import update_wallet_balance -from lnbits.extensions.admin import admin_ext -from lnbits.decorators import api_check_wallet_key, api_validate_post_request -from lnbits.core.crud import get_wallet -from .crud import get_admin,update_admin -import json -@admin_ext.route("/api/v1/admin//", methods=["GET"]) -@api_check_wallet_key("admin") -async def api_update_balance(wallet_id, topup_amount): - print(g.data.wallet) +from fastapi import Body, Depends, Request +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_wallet +from lnbits.decorators import WalletTypeInfo, require_admin_key +from lnbits.extensions.admin import admin_ext +from lnbits.extensions.admin.models import Admin, UpdateAdminSettings + +from .crud import get_admin, update_admin, update_wallet_balance + + +@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) +async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): + print(g.wallet) try: wallet = await get_wallet(wallet_id) except: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) print(wallet) print(topup_amount) - return jsonify({"status": "Success"}), HTTPStatus.OK + return {"status": "Success"} -@admin_ext.route("/api/v1/admin/", methods=["POST"]) -@api_check_wallet_key("admin") -@api_validate_post_request(schema={}) -async def api_update_admin(): - body = await request.get_json() +@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK) +async def api_update_admin( + request: Request, + data: UpdateAdminSettings = Body(...), + g: WalletTypeInfo = Depends(require_admin_key) + ): admin = await get_admin() - print(g.wallet[2]) - print(body["admin_user"]) - if not admin.admin_user == g.wallet[2] and admin.admin_user != None: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) - updated = await update_admin(body) + print(data) + if not admin.user == g.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + updated = await update_admin(user=g.wallet.user, **data.dict()) print(updated) - return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file + return {"status": "Success"} From b4885de9e2fcf598994dd5ca2360cc29629aaa23 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:58 +0000 Subject: [PATCH 046/134] remove core admin html (renamed for now) --- lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/core_admin.html diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html new file mode 100644 index 00000000..835fc00a --- /dev/null +++ b/lnbits/core/templates/core/core_admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From c41b4e714c2e20c36d6a262c77966598b1fd85c4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:21:38 +0000 Subject: [PATCH 047/134] typo --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index bfaeb515..4192f82e 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma From 922206b365cff523a12bf5a8014b544aa88398f3 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:22:23 +0000 Subject: [PATCH 048/134] add admin_ui env --- lnbits/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/settings.py b/lnbits/settings.py index 3f4e31cc..43cb87cb 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,5 +1,6 @@ import importlib import subprocess +from email.policy import default from os import path from typing import List @@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) LNBITS_ALLOWED_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) ] +LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False) LNBITS_ADMIN_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) ] From b47ed3068105f2526365f6708efe5134a5fe1c0e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:16 +0000 Subject: [PATCH 049/134] add db config at startup --- lnbits/commands.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lnbits/commands.py b/lnbits/commands.py index 0f7454f2..8c39c338 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -52,6 +52,25 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) +async def get_admin_settings(): + from lnbits.extensions.admin.models import Admin + + async with core_db.connect() as conn: + + if conn.type == SQLITE: + exists = await conn.fetchone( + "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" + ) + elif conn.type in {POSTGRES, COCKROACH}: + exists = await conn.fetchone( + "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" + ) + if not exists: + return False + + row = await conn.fetchone("SELECT * from admin") + + return Admin(**row) if row else None async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" From 4bf17c5df2d07443c972be9356be710e4578c79b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:53 +0000 Subject: [PATCH 050/134] get admin settings at startup --- lnbits/app.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 075828ef..7ff9e4eb 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -18,6 +18,7 @@ from loguru import logger import lnbits.settings from lnbits.core.tasks import register_task_listeners +from .commands import get_admin_settings from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ +<<<<<<< HEAD configure_logger() app = FastAPI( @@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") +======= + app = FastAPI() + + if lnbits.settings.LNBITS_ADMIN_UI: + check_settings(app) + + app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") +>>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), @@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: app.add_middleware( CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"] ) - g().config = lnbits.settings g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}" @@ -101,6 +110,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app +def check_settings(app: FastAPI): + @app.on_event("startup") + async def check_settings_admin(): + while True: + admin_set = await get_admin_settings() + if admin_set : + break + print("ERROR:", admin_set) + await asyncio.sleep(5) + # admin_set = await get_admin_settings() + g().admin_conf = admin_set + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") async def check_wallet_status(): From 48d6a89e5ba9e1e082a2ff189b3307d685f521e4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:24:11 +0000 Subject: [PATCH 051/134] remove core admin.html --- lnbits/core/templates/core/admin.html | 717 -------------------------- 1 file changed, 717 deletions(-) delete mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html deleted file mode 100644 index e8176555..00000000 --- a/lnbits/core/templates/core/admin.html +++ /dev/null @@ -1,717 +0,0 @@ -{% extends "public.html" %} {% from "macros.jinja" import window_vars with -context %} {% block page %} -
-
- - -

-
Welcome to LNbits
-

-
- Fill in the information below to setup your LNbits instance. Details - can be changed later. -
-

- - -
- -
Branding
-
-
- -
-
- -
-
-
-
- - - -
-
- - - -
-
- -
Service settings
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
- Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details - should be filled in for you
-
- - - - - - - - - - - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
-
- -
- - -
-
-
-
- View project in GitHub - Donate -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }} - -{% endblock %} From 87bee88de403723b9d98f64716c573e21289a50a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:55:31 +0000 Subject: [PATCH 052/134] refactor ui --- .../admin/templates/admin/index.html | 727 +++++++++++++++++- 1 file changed, 712 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index a6b45625..65ac9f33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,6 +1,670 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %} +
+
+ +
+
+ + + + + + +
+
+ + + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • Funding Source: {{data.admin.funding_source}}
  • +
  • Balance: {{data.admin.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+

Active Funding

+ +
+
+
+ +

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+

Funding Sources

+ + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+
+ +
+ Save +
+
+
+ + +
User Management
+
+

+ Super Admin: {% raw + %}{{this.data.admin.user}}{% endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
+ + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • SQlite: {{data.admin.data_folder}}
  • +
  • Postgres: {{data.admin.database_url}}
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
+ + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+ +
+ Save +
+
+
+
+
+
+
+
+

Admin

-
+
@@ -426,6 +1090,7 @@ return { wallet: {data: {}}, cancel: {}, + tab: 'funding', data: { funding_source: [ 'CLightningWallet', @@ -436,24 +1101,14 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { - user: '{{ user.id }}', - site_title: '{{admin.site_title}}', - tagline: '{{admin.site_tagline}}', - description: '{{admin.site_description}}', - admin_users: '{{admin.admin_users}}', - service_fee: parseFloat('{{admin.service_fee}}'), - default_wallet_name: '{{admin.default_wallet_name}}', - data_folder: '{{admin.data_folder}}', - funding_source_primary: '{{admin.funding_source}}', - disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], funding: {}, senddata: {} } }, - + themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], options: [ 'bleskomat', 'captcha', @@ -489,9 +1144,51 @@ for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] } - console.log(self.data.admin) + let settings = JSON.parse('{{ settings | tojson|safe }}') + settings.balance = '{{ balance }}' + this.data.admin = {...this.data.admin, ...settings} + console.log(this.g.user) }, methods: { + addAdminUser(){ + let addUser = this.data.admin_users_add + let admin_users = this.data.admin.admin_users + if(addUser.length && !admin_users.includes(addUser)){ + admin_users.push(addUser) + this.data.admin.admin_users = admin_users + this.data.admin_users_add = "" + } + }, + removeAdminUser(user){ + let admin_users = this.data.admin.admin_users + this.data.admin.admin_users = admin_users.filter(u => u !== user) + }, + addAllowedUser(){ + let addUser = this.data.allowed_users_add + let allowed_users = this.data.admin.allowed_users + if(addUser.length && !allowed_users.includes(addUser)){ + allowed_users.push(addUser) + this.data.admin.allowed_users = allowed_users + this.data.allowed_users_add = "" + } + }, + removeAllowedUser(user){ + let allowed_users = this.data.admin.allowed_users + this.data.admin.allowed_users = allowed_users.filter(u => u !== user) + }, + addAdSpace(){ + let adSpace = this.data.ad_space_add + let spaces = this.data.admin.ad_space + if(adSpace.length && !spaces.includes(adSpace)){ + spaces.push(adSpace) + this.data.admin.ad_space = spaces + this.data.ad_space_add = "" + } + }, + removeAdSpace(ad){ + let spaces = this.data.admin.ad_space + this.data.admin.ad_space = spaces.filter(s => s !== ad) + }, topupWallet: function () { var self = this LNbits.api From 8c1c7d13b87ba7b7408dccd719a9a9c66e6e4325 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:59:06 +0000 Subject: [PATCH 053/134] make it work from g() --- lnbits/app.py | 34 +++--- lnbits/config.py | 62 ++++++++++ lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++--------- lnbits/extensions/admin/models.py | 27 +++-- lnbits/extensions/admin/views.py | 8 +- lnbits/settings.py | 2 +- 7 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 lnbits/config.py diff --git a/lnbits/app.py b/lnbits/app.py index 7ff9e4eb..1ffedb54 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -19,6 +19,7 @@ import lnbits.settings from lnbits.core.tasks import register_task_listeners from .commands import get_admin_settings +from .config import WALLET, conf from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -29,7 +30,8 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g -from .settings import WALLET + +# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ -<<<<<<< HEAD configure_logger() app = FastAPI( @@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") -======= - app = FastAPI() - - if lnbits.settings.LNBITS_ADMIN_UI: - check_settings(app) - - app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") ->>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), name="core_static", ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET + origins = ["*"] app.add_middleware( @@ -109,18 +108,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app - def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): + + def removeEmptyString(arr): + return list(filter(None, arr)) + while True: admin_set = await get_admin_settings() if admin_set : break print("ERROR:", admin_set) await asyncio.sleep(5) - # admin_set = await get_admin_settings() - g().admin_conf = admin_set + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) + admin_set.theme = removeEmptyString(admin_set.theme.split(',')) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + g().admin_conf = conf.copy(update=admin_set.dict()) def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/config.py b/lnbits/config.py new file mode 100644 index 00000000..02e8cf53 --- /dev/null +++ b/lnbits/config.py @@ -0,0 +1,62 @@ +import importlib +import json +from os import getenv, path +from typing import List, Optional + +from pydantic import BaseSettings, Field, validator + +wallets_module = importlib.import_module("lnbits.wallets") +wallet_class = getattr( + wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") +) + +WALLET = wallet_class() + +def list_parse_fallback(v): + try: + return json.loads(v) + except Exception as e: + return v.replace(' ','').split(',') + +class Settings(BaseSettings): + # users + admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") + allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") + admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") + disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") + # ops + data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") + database_url: str = Field(default=None, env="LNBITS_DATABASE_URL") + force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS") + service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE") + hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") + denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") + # Change theme + site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") + default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") + # .env + env: Optional[str] + debug: Optional[str] + host: Optional[str] + port: Optional[str] + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + + # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) + # def validate(cls, val): + # print(val) + # return val.split(',') + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + case_sensitive = False + json_loads = list_parse_fallback + + +conf = Settings() +WALLET = wallet_class() diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 872d6c97..6fccb8ee 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin: # new_settings = await get_admin() # return new_settings -async def get_admin() -> List[Admin]: +async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 13b76923..574f772d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -2,93 +2,151 @@ from os import getenv from sqlalchemy.exc import OperationalError # type: ignore +from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash async def m001_create_admin_table(db): - user = None - site_title = None - site_tagline = None - site_description = None - allowed_users = None - admin_users = None - default_wallet_name = None - data_folder = None - disabled_ext = None - force_https = True - service_fee = 0 - funding_source = "" + # users/server + user = conf.admin_users[0] + admin_users = ",".join(conf.admin_users) + allowed_users = ",".join(conf.allowed_users) + admin_ext = ",".join(conf.admin_ext) + disabled_ext = ",".join(conf.disabled_ext) + funding_source = conf.funding_source + #operational + data_folder = conf.data_folder + database_url = conf.database_url + force_https = conf.force_https + service_fee = conf.service_fee + hide_api = conf.hide_api + denomination = conf.denomination + # Theme'ing + site_title = conf.site_title + site_tagline = conf.site_tagline + site_description = conf.site_description + default_wallet_name = conf.default_wallet_name + theme = ",".join(conf.theme) + ad_space = ",".join(conf.ad_space) - if getenv("LNBITS_SITE_TITLE"): - site_title = getenv("LNBITS_SITE_TITLE") + # if getenv("LNBITS_ADMIN_EXTENSIONS"): + # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - if getenv("LNBITS_SITE_TAGLINE"): - site_tagline = getenv("LNBITS_SITE_TAGLINE") + # if getenv("LNBITS_DATABASE_URL"): + # database_url = getenv("LNBITS_DATABASE_URL") - if getenv("LNBITS_SITE_DESCRIPTION"): - site_description = getenv("LNBITS_SITE_DESCRIPTION") + # if getenv("LNBITS_HIDE_API"): + # hide_api = getenv("LNBITS_HIDE_API") - if getenv("LNBITS_ALLOWED_USERS"): - allowed_users = getenv("LNBITS_ALLOWED_USERS") + # if getenv("LNBITS_THEME_OPTIONS"): + # theme = getenv("LNBITS_THEME_OPTIONS") - if getenv("LNBITS_ADMIN_USERS"): - admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - user = admin_users.split(',')[0] + # if getenv("LNBITS_AD_SPACE"): + # ad_space = getenv("LNBITS_AD_SPACE") - if getenv("LNBITS_DEFAULT_WALLET_NAME"): - default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + # if getenv("LNBITS_SITE_TITLE"): + # site_title = getenv("LNBITS_SITE_TITLE") - if getenv("LNBITS_DATA_FOLDER"): - data_folder = getenv("LNBITS_DATA_FOLDER") + # if getenv("LNBITS_SITE_TAGLINE"): + # site_tagline = getenv("LNBITS_SITE_TAGLINE") - if getenv("LNBITS_DISABLED_EXTENSIONS"): - disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + # if getenv("LNBITS_SITE_DESCRIPTION"): + # site_description = getenv("LNBITS_SITE_DESCRIPTION") - if getenv("LNBITS_FORCE_HTTPS"): - force_https = getenv("LNBITS_FORCE_HTTPS") + # if getenv("LNBITS_ALLOWED_USERS"): + # allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_SERVICE_FEE"): - service_fee = getenv("LNBITS_SERVICE_FEE") + # if getenv("LNBITS_ADMIN_USERS"): + # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + # user = admin_users.split(',')[0] + + # if getenv("LNBITS_DEFAULT_WALLET_NAME"): + # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - if getenv("LNBITS_BACKEND_WALLET_CLASS"): - funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + # if getenv("LNBITS_DATA_FOLDER"): + # data_folder = getenv("LNBITS_DATA_FOLDER") + + # if getenv("LNBITS_DISABLED_EXTENSIONS"): + # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + # if getenv("LNBITS_FORCE_HTTPS"): + # force_https = getenv("LNBITS_FORCE_HTTPS") + + # if getenv("LNBITS_SERVICE_FEE"): + # service_fee = getenv("LNBITS_SERVICE_FEE") + + # if getenv("LNBITS_DENOMINATION"): + # denomination = getenv("LNBITS_DENOMINATION", "sats") + + # if getenv("LNBITS_BACKEND_WALLET_CLASS"): + # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - "user" TEXT, + "user" TEXT PRIMARY KEY, + admin_users TEXT, + allowed_users TEXT, + admin_ext TEXT, + disabled_ext TEXT, + funding_source TEXT, + data_folder TEXT, + database_url TEXT, + force_https BOOLEAN, + service_fee REAL, + hide_api BOOLEAN, + denomination TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_users TEXT, - allowed_users TEXT, default_wallet_name TEXT, - data_folder TEXT, - disabled_ext TEXT, - force_https BOOLEAN, - service_fee REAL, - funding_source TEXT + theme TEXT, + ad_space TEXT ); """ ) await db.execute( """ - INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user.strip(), + INSERT INTO admin ( + "user", + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + data_folder, + database_url, + force_https, + service_fee, + hide_api, + denomination, site_title, site_tagline, site_description, - admin_users[1:], - allowed_users, default_wallet_name, - data_folder, + theme, + ad_space) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + admin_users, + allowed_users, + admin_ext, disabled_ext, + funding_source, + data_folder, + database_url, force_https, service_fee, - funding_source, + hide_api, + denomination, + site_title, + site_tagline, + site_description, + default_wallet_name, + theme, + ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 4080ff01..f7c64de5 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -2,7 +2,7 @@ from sqlite3 import Row from typing import List, Optional from fastapi import Query -from pydantic import BaseModel +from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): @@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel): funding_source: Optional[str] class Admin(BaseModel): + # users user: str + admin_users: Optional[str] + allowed_users: Optional[str] + admin_ext: Optional[str] + disabled_ext: Optional[str] + funding_source: Optional[str] + # ops + data_folder: Optional[str] + database_url: Optional[str] + force_https: bool = Field(default=True) + service_fee: float = Field(default=0) + hide_api: bool = Field(default=False) + # Change theme site_title: Optional[str] site_tagline: Optional[str] site_description: Optional[str] - allowed_users: Optional[str] - admin_users: str - default_wallet_name: str - data_folder: str - disabled_ext: str - force_https: Optional[bool] = Query(True) - service_fee: float - funding_source: str + default_wallet_name: Optional[str] + denomination: str = Field(default="sats") + theme: Optional[str] + ad_space: Optional[str] @classmethod def from_row(cls, row: Row) -> "Admin": diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 00a0c99f..105f05a1 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates") @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() - print(g()) funding = [f.dict() for f in await get_funding()] - + error, balance = await g().WALLET.status() print("ADMIN", admin.dict()) + print(g().admin_conf) return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, "user": user.dict(), "admin": admin.dict(), - "funding": funding + "funding": funding, + "settings": g().admin_conf.dict(), + "balance": balance } ) diff --git a/lnbits/settings.py b/lnbits/settings.py index 43cb87cb..ed5c77f7 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -4,7 +4,7 @@ from email.policy import default from os import path from typing import List -from environs import Env # type: ignore +from environs import Env env = Env() env.read_env() From a72ed98997656a709b00d171c27c387c26dfe0bd Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:07 +0000 Subject: [PATCH 054/134] topup wallet endpoint --- lnbits/extensions/admin/crud.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 6fccb8ee..683558f9 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,26 +1,31 @@ +import json from typing import List, Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash from lnbits.settings import * +from lnbits.tasks import internal_invoice_queue from . import db from .models import Admin, Funding -def update_wallet_balance(wallet_id: str, amount: int) -> str: +async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - create_payment( + + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount * 1000, + amount=amount*1000, memo="Admin top up", pending=False, ) - return "success" + # manually send this for now + await internal_invoice_queue.put(internal_id) + return payment async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) From 42a9af986ab3620bdb61270961c6adbda4a66c95 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:44 +0000 Subject: [PATCH 055/134] update admin settings in db --- lnbits/extensions/admin/models.py | 29 +++++++++++++++++----------- lnbits/extensions/admin/views_api.py | 8 ++++---- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index f7c64de5..36d9b815 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,17 +6,24 @@ from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - allowed_users: Optional[str] - admin_users: Optional[str] - default_wallet_name: Optional[str] - data_folder: Optional[str] - disabled_ext: Optional[str] - force_https: Optional[bool] - service_fee: Optional[float] - funding_source: Optional[str] + # users + admin_users: str = Query(None) + allowed_users: str = Query(None) + admin_ext: str = Query(None) + disabled_ext: str = Query(None) + funding_source: str = Query(None) + # ops + force_https: bool = Query(None) + service_fee: float = Query(None, ge=0) + hide_api: bool = Query(None) + # Change theme + site_title: str = Query(None) + site_tagline: str = Query(None) + site_description: str = Query(None) + default_wallet_name: str = Query(None) + denomination: str = Query(None) + theme: str = Query(None) + ad_space: str = Query(None) class Admin(BaseModel): # users diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b2c65be2..cb526aa5 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): - print(g.wallet) +async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - print(wallet) - print(topup_amount) + + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + return {"status": "Success"} From 2ebb4448d5ff8daa3b1c4729ce33dd4c5c744c7b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:29:18 +0000 Subject: [PATCH 056/134] update settings and topup logic --- .../admin/templates/admin/index.html | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 65ac9f33..e9ddc7c4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -30,7 +30,7 @@
- + @@ -61,7 +61,7 @@
- +

TopUp a wallet

@@ -87,13 +87,13 @@
- +
@@ -577,7 +577,6 @@

Site Description

s !== ad) }, - topupWallet: function () { - var self = this + topupWallet() { LNbits.api .request( 'GET', '/admin/api/v1/admin/' + - self.wallet.id + + this.wallet.data.id + '/' + - self.wallet.data.amount, - self.g.user.wallets[0].adminkey + this.wallet.data.amount, + this.g.user.wallets[0].adminkey ) - .then(function (response) { - self.$q.notify({ + .then((response) => { + this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - self.wallet.amount + - ' to ' + - self.wallet.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) + this.wallet.data = {} }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -1224,36 +1224,59 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, - UpdateLNbits: function () { - var self = this - let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + UpdateLNbits() { + let { + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme, + ad_space + } = this.data.admin + //console.log("this", this.data.admin) let data = { - site_title, - site_tagline: this.data.admin.tagline, - site_description: this.data.admin.description, - admin_users: admin_users.toString(), - default_wallet_name, - data_folder, + admin_users: admin_users.toString(), + allowed_users: allowed_users.toString(), + admin_ext: admin_ext.toString(), disabled_ext: disabled_ext.toString(), - service_fee, - funding_source: funding_source_primary} + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme: theme.toString(), + ad_space: ad_space.toString() + } console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', - self.g.user.wallets[0].adminkey, + this.g.user.wallets[0].adminkey, data ) - .then(function (response) { + .then(response => { console.log(response.data) - self.$q.notify({ + this.$q.notify({ type: 'positive', message: 'Success! Added ' + - self.wallet.amount + + this.wallet.amount + ' to ' + - self.wallet.id, + this.wallet.id, icon: null }) }) From 3082a393436b88a24d860f87983560e758a02f66 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:34:47 +0000 Subject: [PATCH 057/134] make removeEmptyString fn as helper fn --- lnbits/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 1ffedb54..c8f5c60a 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -26,6 +26,7 @@ from .helpers import ( get_css_vendored, get_js_vendored, get_valid_extensions, + removeEmptyString, template_renderer, url_for_vendored, ) @@ -112,9 +113,6 @@ def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - def removeEmptyString(arr): - return list(filter(None, arr)) - while True: admin_set = await get_admin_settings() if admin_set : From c419bd27ebcd212da37f27994e630df08bdba7d4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:35:15 +0000 Subject: [PATCH 058/134] add some defaults --- lnbits/config.py | 6 +++--- lnbits/extensions/admin/models.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 02e8cf53..b2fbfff1 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -33,10 +33,10 @@ class Settings(BaseSettings): hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme - site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 36d9b815..0f25679d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel): service_fee: float = Query(None, ge=0) hide_api: bool = Query(None) # Change theme - site_title: str = Query(None) - site_tagline: str = Query(None) + site_title: str = Query("LNbits") + site_tagline: str = Query("free and open-source lightning wallet") site_description: str = Query(None) - default_wallet_name: str = Query(None) - denomination: str = Query(None) + default_wallet_name: str = Query("LNbits wallet") + denomination: str = Query("sats") theme: str = Query(None) ad_space: str = Query(None) From 0897d0476356d1956711fc0f1c6448fbb88275fb Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:36:04 +0000 Subject: [PATCH 059/134] removeEmtpy sting as helper fn --- lnbits/helpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e213240c..e456f715 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url +def removeEmptyString(arr): + return list(filter(None, arr)) def template_renderer(additional_folders: List = []) -> Jinja2Templates: + if(settings.LNBITS_ADMIN_UI): + _ = g().admin_conf + settings.LNBITS_AD_SPACE = _.ad_space + settings.LNBITS_HIDE_API = _.hide_api + settings.LNBITS_SITE_TITLE = _.site_title + settings.LNBITS_DENOMINATION = _.denomination + settings.LNBITS_SITE_TAGLINE = _.site_tagline + settings.LNBITS_SITE_DESCRIPTION = _.site_description + settings.LNBITS_THEME_OPTIONS = _.theme + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 8c93aa304f0d484e908cd40c0a851b95fbbfd992 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:41:11 +0000 Subject: [PATCH 060/134] cleanup --- lnbits/extensions/admin/crud.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 683558f9..e14ad194 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,9 +1,7 @@ -import json -from typing import List, Optional +from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import * from lnbits.tasks import internal_invoice_queue from . import db @@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None -# async def update_admin(user: str, **kwargs) -> Optional[Admin]: -# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) -# await db.execute( -# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) -# ) -# new_settings = await get_admin() -# return new_settings - async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None From 5ce73697d47aa6eb96bb6d69c7da1e4521bc4943 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:28 +0000 Subject: [PATCH 061/134] make string to list --- lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index cb526aa5..1d4e6a9c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,5 +1,6 @@ from http import HTTPStatus +# from config import conf from fastapi import Body, Depends, Request from starlette.exceptions import HTTPException @@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.helpers import removeEmptyString +from lnbits.requestvars import g from .crud import get_admin, update_admin, update_wallet_balance @@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) return {"status": "Success"} @@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - g: WalletTypeInfo = Depends(require_admin_key) + w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() print(data) - if not admin.user == g.wallet.user: + if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - updated = await update_admin(user=g.wallet.user, **data.dict()) - print(updated) + updated = await update_admin(user=w.wallet.user, **data.dict()) + + updated.admin_users = removeEmptyString(updated.admin_users.split(',')) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) + updated.theme = removeEmptyString(updated.theme.split(',')) + updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + + g().admin_conf = g().admin_conf.copy(update=updated.dict()) + + print(g().admin_conf) return {"status": "Success"} From c299927e7ca1e16c8bc29a98b5af38ed402496de Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:47 +0000 Subject: [PATCH 062/134] success message --- lnbits/extensions/admin/templates/admin/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index e9ddc7c4..9aa4f12a 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1273,10 +1273,7 @@ this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.amount + - ' to ' + - this.wallet.id, + 'Success! Settings changed!', icon: null }) }) From 2a63fb191423b2f23de7f70e8197109a290f3689 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 10:42:26 +0100 Subject: [PATCH 063/134] allow html to be passed to description --- lnbits/core/templates/core/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 68a7b7ed..146fc6ad 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -82,7 +82,7 @@ >
-

{{SITE_DESCRIPTION}}

+

{{SITE_DESCRIPTION | safe}}

From 3fbdac127adf9459d8af4453a802248f7c3d94fc Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 16:37:13 +0100 Subject: [PATCH 064/134] update funding wallets --- lnbits/extensions/admin/crud.py | 12 + .../admin/templates/admin/index.html | 523 ++++++++++-------- lnbits/extensions/admin/views.py | 3 +- lnbits/extensions/admin/views_api.py | 19 +- 4 files changed, 315 insertions(+), 242 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index e14ad194..dd39e8e4 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -39,6 +39,18 @@ async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None +async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE funding + SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? + WHERE id = ? + """, + (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + ) + row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + assert row, "Newly updated settings couldn't be retrieved" + return Funding(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 9aa4f12a..d56b3d79 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -31,11 +31,11 @@ - - - -
Wallets Management
-
+ + + +
Wallets Management
+
@@ -62,43 +62,96 @@
-

TopUp a wallet

-
-
- -
-
-
- -
+

TopUp a wallet

+
+
+ +
-
- +
+
+
+
+ +

Funding Sources

- + {% raw %} + + + + + + + + + + + + + + {% endraw %} + +
+ + + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} +

+

Admin Users

+ hint="Users with admin privileges" + >
{% raw %} -
-
-
-

Allowed Users

- - - +
- {% raw %} - Allowed Users

+ - {{ user }} -
- {% endraw %} + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+ + + + +
Server Management

-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-

Server Info

    {%raw%} -
  • SQlite: {{data.admin.data_folder}}
  • -
  • Postgres: {{data.admin.database_url}}
  • +
  • + SQlite: {{data.admin.data_folder}} +
  • +
  • + Postgres: {{data.admin.database_url}} +
  • {%endraw%}

@@ -520,7 +576,10 @@ Hide API - Hides wallet api, extensions can choose to honor + Hides wallet api, extensions can choose to + honor
-
- -
- Save -
-
-
- - -
UI Management
-
+
+ +
+ Save +
+ + + + +
UI Management
+
@@ -575,15 +629,15 @@
-

Site Description

- -
-
+

Site Description

+ +
+

Default Wallet Name

@@ -628,12 +682,15 @@ @keydown.enter="addAdSpace" type="text" label="Ad image URL" - hint="Ad image filepaths or urls, extensions can choose to honor"> + hint="Ad image filepaths or urls, extensions can choose to honor" + >
{% raw %} -
-
- -
- Save -
-
-
- - +
+ +
+ Save +
+
+
+
+
- - -

Admin

-

+ + - -
- - -
Wallet topup
-
-
- -
-
- -
-
-
- -
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -1100,14 +1114,22 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { edited: [], - funding: {}, + funding: [], senddata: {} } }, - themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], + themes: [ + 'classic', + 'bitcoin', + 'flamingo', + 'mint', + 'autumn', + 'monochrome', + 'salvador' + ], options: [ 'bleskomat', 'captcha', @@ -1139,10 +1161,13 @@ self.cancel.on = true } funding = JSON.parse(String('{{ funding | tojson|safe }}')) - var i + funding.map(f => { + this.data.admin.funding.push(f) + }) + /*var i for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] - } + }*/ let settings = JSON.parse('{{ settings | tojson|safe }}') settings.balance = '{{ balance }}' this.data.admin = {...this.data.admin, ...settings} @@ -1150,42 +1175,42 @@ console.log(settings) }, methods: { - addAdminUser(){ + addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if(addUser.length && !admin_users.includes(addUser)){ + if (addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users - this.data.admin_users_add = "" + this.data.admin_users_add = '' } }, - removeAdminUser(user){ + removeAdminUser(user) { let admin_users = this.data.admin.admin_users this.data.admin.admin_users = admin_users.filter(u => u !== user) }, - addAllowedUser(){ + addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if(addUser.length && !allowed_users.includes(addUser)){ + if (addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users - this.data.allowed_users_add = "" + this.data.allowed_users_add = '' } }, - removeAllowedUser(user){ + removeAllowedUser(user) { let allowed_users = this.data.admin.allowed_users this.data.admin.allowed_users = allowed_users.filter(u => u !== user) }, - addAdSpace(){ + addAdSpace() { let adSpace = this.data.ad_space_add let spaces = this.data.admin.ad_space - if(adSpace.length && !spaces.includes(adSpace)){ + if (adSpace.length && !spaces.includes(adSpace)) { spaces.push(adSpace) this.data.admin.ad_space = spaces - this.data.ad_space_add = "" + this.data.ad_space_add = '' } }, - removeAdSpace(ad){ + removeAdSpace(ad) { let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, @@ -1199,14 +1224,14 @@ this.wallet.data.amount, this.g.user.wallets[0].adminkey ) - .then((response) => { + .then(response => { this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.data.amount + - ' to ' + - this.wallet.data.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) this.wallet.data = {} @@ -1224,6 +1249,29 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, + updateFunding(fund) { + let data = this.data.admin.funding.find(v => v.backend_wallet == fund) + + LNbits.api + .request( + 'POST', + '/admin/api/v1/admin/funding', + this.g.user.wallets[0].adminkey, + data + ) + .then(response => { + //let wallet = response.data.backend_wallet + //this.data.admin.funding[wallet] = response.data + //this.data.admin.funding[wallet].endpoint = response.data.endpoint + //console.log(this.data.admin.funding) + //console.log(this.data.admin) + this.$q.notify({ + type: 'positive', + message: `Success! ${response.data.backend_wallet} changed!`, + icon: null + }) + }) + }, UpdateLNbits() { let { admin_users, @@ -1272,8 +1320,7 @@ console.log(response.data) this.$q.notify({ type: 'positive', - message: - 'Success! Settings changed!', + message: 'Success! Settings changed!', icon: null }) }) diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 105f05a1..24b8ca85 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - print("ADMIN", admin.dict()) - print(g().admin_conf) + return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 1d4e6a9c..b797dc2d 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext -from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings from lnbits.helpers import removeEmptyString from lnbits.requestvars import g -from .crud import get_admin, update_admin, update_wallet_balance +from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) @@ -53,3 +53,18 @@ async def api_update_admin( print(g().admin_conf) return {"status": "Success"} + +@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) +async def api_update_funding( + request: Request, + data: Funding = Body(...), + w: WalletTypeInfo = Depends(require_admin_key) + ): + admin = await get_admin() + + if not admin.user == w.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + funding = await update_funding(data=data) + return funding From a07fbf0187c72b45e0e102951faf3ed6cbfb75fc Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:06 +0100 Subject: [PATCH 065/134] allow user settings without restart --- lnbits/core/views/generic.py | 7 ++++++- lnbits/decorators.py | 8 ++++++++ lnbits/helpers.py | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 31a7b030..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,7 +15,9 @@ from lnbits.core import db from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for +from lnbits.requestvars import g from lnbits.settings import ( + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, LNBITS_CUSTOM_LOGO, @@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) - @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") @@ -119,6 +120,10 @@ async def wallet( wallet_name = nme service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + if not user_id: user = await get_user((await create_account()).id) logger.info(f"Create user {user.id}") # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index d4aa63ae..f951163f 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet from lnbits.requestvars import g from lnbits.settings import ( LNBITS_ADMIN_EXTENSIONS, + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, ) @@ -138,6 +139,9 @@ async def get_key_type( detail="Invoice (or Admin) key required.", ) + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] ): @@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) + + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e456f715..1167143f 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -24,6 +24,9 @@ class Extension(NamedTuple): class ExtensionManager: def __init__(self): + if settings.LNBITS_ADMIN_UI: + settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext + settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS self._admin_only: List[str] = [ x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS From 931286b476bed767e3900a3b60cf10ab962c29e1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:26 +0100 Subject: [PATCH 066/134] advert for server restart option --- lnbits/extensions/admin/templates/admin/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d56b3d79..089c5f1c 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -51,7 +51,9 @@
-

Active Funding

+

+ Active Funding (Requires server restart) +

Date: Thu, 21 Apr 2022 11:08:26 +0100 Subject: [PATCH 067/134] cleanup prints and console logs --- lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- lnbits/extensions/admin/views_api.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index dd39e8e4..f866bc1a 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - print("UPDATE", q) + # print("UPDATE", q) await db.execute( f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 089c5f1c..584d3a33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1310,7 +1310,7 @@ theme: theme.toString(), ad_space: ad_space.toString() } - console.log(data) + //console.log(data) LNbits.api .request( 'POST', @@ -1319,7 +1319,7 @@ data ) .then(response => { - console.log(response.data) + //console.log(response.data) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b797dc2d..c0650c8a 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -35,7 +35,7 @@ async def api_update_admin( w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() - print(data) + # print(data) if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" @@ -51,7 +51,7 @@ async def api_update_admin( g().admin_conf = g().admin_conf.copy(update=updated.dict()) - print(g().admin_conf) + # print(g().admin_conf) return {"status": "Success"} @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) From 3f38a9094b9c9e5e3c6b29df67b3508efbc41be6 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 10:49:21 +0100 Subject: [PATCH 068/134] create first user on fresh install --- .env.example | 4 ++-- lnbits/config.py | 3 ++- lnbits/extensions/admin/README.md | 15 ++++++++------- lnbits/extensions/admin/migrations.py | 15 ++++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 4192f82e..f0e21aa8 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ PORT=5000 DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma diff --git a/lnbits/config.py b/lnbits/config.py index b2fbfff1..3ce51c3c 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -19,6 +19,7 @@ def list_parse_fallback(v): return v.replace(' ','').split(',') class Settings(BaseSettings): + admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") # users admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") @@ -37,7 +38,7 @@ class Settings(BaseSettings): site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md index 27729459..6cf073a1 100644 --- a/lnbits/extensions/admin/README.md +++ b/lnbits/extensions/admin/README.md @@ -1,11 +1,12 @@ -

Example Extension

-

*tagline*

-This is an example extension to help you organise and build you own. +# Admin Extension -Try to include an image - +## Dashboard to manage LNbits from the UI +With AdminUI you can manage your LNbits from the UI -

If your extension has API endpoints, include useful ones here

+![AdminUI](https://i.imgur.com/BIyLkyG.png) -curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" +## Before you start + +**This extension doesn't discard the need for the `.env` file!** +In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id. diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 574f772d..0e22e667 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,9 +6,22 @@ from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash +async def get_admin_user(): + if(conf.admin_users[0]): + return conf.admin_users[0] + from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") + account = await create_account() + user = account.id + assert user, "Newly created user couldn't be retrieved" + print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + return user + + + async def m001_create_admin_table(db): # users/server - user = conf.admin_users[0] + user = await get_admin_user() admin_users = ",".join(conf.admin_users) allowed_users = ",".join(conf.allowed_users) admin_ext = ",".join(conf.admin_ext) From 0a29fb736093aeca3987122a7a78381b6f760499 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:29:58 +0100 Subject: [PATCH 069/134] fix schemas for admin --- lnbits/commands.py | 11 ++++++++--- lnbits/extensions/admin/crud.py | 12 ++++++------ lnbits/extensions/admin/migrations.py | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lnbits/commands.py b/lnbits/commands.py index 8c39c338..7d9b49e2 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -55,8 +55,12 @@ def bundle_vendored(): async def get_admin_settings(): from lnbits.extensions.admin.models import Admin - async with core_db.connect() as conn: + try: + ext_db = importlib.import_module(f"lnbits.extensions.admin").db + except: + return False + async with ext_db.connect() as conn: if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -65,11 +69,12 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) + print("EXISTS", exists) if not exists: return False - row = await conn.fetchone("SELECT * from admin") - + row = await conn.fetchone("SELECT * from admin.admin") + return Admin(**row) if row else None async def migrate_databases(): diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index f866bc1a..67fbc614 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin") + row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None async def update_funding(data: Funding) -> Funding: await db.execute( """ - UPDATE funding + UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? """, (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), ) - row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None async def get_funding() -> List[Funding]: - rows = await db.fetchall("SELECT * FROM funding") + rows = await db.fetchall("SELECT * FROM admin.funding") return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 0e22e667..c94d140b 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -96,7 +96,7 @@ async def m001_create_admin_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin ( + CREATE TABLE IF NOT EXISTS admin.admin ( "user" TEXT PRIMARY KEY, admin_users TEXT, allowed_users TEXT, @@ -120,7 +120,7 @@ async def m001_create_admin_table(db): ) await db.execute( """ - INSERT INTO admin ( + INSERT INTO admin.admin ( "user", admin_users, allowed_users, @@ -171,7 +171,7 @@ async def m001_create_funding_table(db): # Make the funding table, if it does not already exist await db.execute( """ - CREATE TABLE IF NOT EXISTS funding ( + CREATE TABLE IF NOT EXISTS admin.funding ( id TEXT PRIMARY KEY, backend_wallet TEXT, endpoint TEXT, @@ -188,7 +188,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) VALUES (?, ?, ?, ?) """, ( @@ -200,7 +200,7 @@ async def m001_create_funding_table(db): ) await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -214,7 +214,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -228,7 +228,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( @@ -244,7 +244,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -259,7 +259,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -274,7 +274,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -288,7 +288,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -302,7 +302,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -317,7 +317,7 @@ async def m001_create_funding_table(db): ## PLACEHOLDER FOR ECLAIR WALLET # await db.execute( # """ - # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) # VALUES (?, ?, ?, ?, ?) # """, # ( From ac74cfaab7e70d8df8229241e040403d473f04e1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:53:47 +0100 Subject: [PATCH 070/134] fix sqlite and show user account --- lnbits/app.py | 1 + lnbits/commands.py | 2 +- lnbits/extensions/admin/migrations.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index c8f5c60a..2b483758 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -116,6 +116,7 @@ def check_settings(app: FastAPI): while True: admin_set = await get_admin_settings() if admin_set : + print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break print("ERROR:", admin_set) await asyncio.sleep(5) diff --git a/lnbits/commands.py b/lnbits/commands.py index 7d9b49e2..763a5b90 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -69,7 +69,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - print("EXISTS", exists) + if not exists: return False diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c94d140b..6c5b507d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -15,6 +15,7 @@ async def get_admin_user(): user = account.id assert user, "Newly created user couldn't be retrieved" print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + conf.admin_users.insert(0, user) return user From 1ebd557b1d544f6baab770111a4bf89f31abea66 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 15:35:04 +0100 Subject: [PATCH 071/134] cleanup and info to user on startup --- lnbits/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 2b483758..eaa33136 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -112,13 +112,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - while True: admin_set = await get_admin_settings() if admin_set : - print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break - print("ERROR:", admin_set) + print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) @@ -128,6 +126,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(',')) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) g().admin_conf = conf.copy(update=admin_set.dict()) + print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") From e04e24faec18e01c306d86a6bd45f238a74e1d38 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 11:00:43 +0100 Subject: [PATCH 072/134] add custom logo --- lnbits/config.py | 1 + lnbits/extensions/admin/migrations.py | 58 ++----------------- lnbits/extensions/admin/models.py | 2 + .../admin/templates/admin/index.html | 20 +++++-- lnbits/helpers.py | 3 +- 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 3ce51c3c..d07ca044 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -39,6 +39,7 @@ class Settings(BaseSettings): site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 6c5b507d..aad66f02 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -41,60 +41,9 @@ async def m001_create_admin_table(db): site_description = conf.site_description default_wallet_name = conf.default_wallet_name theme = ",".join(conf.theme) + custom_logo = conf.custom_logo ad_space = ",".join(conf.ad_space) - # if getenv("LNBITS_ADMIN_EXTENSIONS"): - # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - - # if getenv("LNBITS_DATABASE_URL"): - # database_url = getenv("LNBITS_DATABASE_URL") - - # if getenv("LNBITS_HIDE_API"): - # hide_api = getenv("LNBITS_HIDE_API") - - # if getenv("LNBITS_THEME_OPTIONS"): - # theme = getenv("LNBITS_THEME_OPTIONS") - - # if getenv("LNBITS_AD_SPACE"): - # ad_space = getenv("LNBITS_AD_SPACE") - - # if getenv("LNBITS_SITE_TITLE"): - # site_title = getenv("LNBITS_SITE_TITLE") - - # if getenv("LNBITS_SITE_TAGLINE"): - # site_tagline = getenv("LNBITS_SITE_TAGLINE") - - # if getenv("LNBITS_SITE_DESCRIPTION"): - # site_description = getenv("LNBITS_SITE_DESCRIPTION") - - # if getenv("LNBITS_ALLOWED_USERS"): - # allowed_users = getenv("LNBITS_ALLOWED_USERS") - - # if getenv("LNBITS_ADMIN_USERS"): - # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - # user = admin_users.split(',')[0] - - # if getenv("LNBITS_DEFAULT_WALLET_NAME"): - # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - - # if getenv("LNBITS_DATA_FOLDER"): - # data_folder = getenv("LNBITS_DATA_FOLDER") - - # if getenv("LNBITS_DISABLED_EXTENSIONS"): - # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") - - # if getenv("LNBITS_FORCE_HTTPS"): - # force_https = getenv("LNBITS_FORCE_HTTPS") - - # if getenv("LNBITS_SERVICE_FEE"): - # service_fee = getenv("LNBITS_SERVICE_FEE") - - # if getenv("LNBITS_DENOMINATION"): - # denomination = getenv("LNBITS_DENOMINATION", "sats") - - # if getenv("LNBITS_BACKEND_WALLET_CLASS"): - # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") - await db.execute( """ CREATE TABLE IF NOT EXISTS admin.admin ( @@ -115,6 +64,7 @@ async def m001_create_admin_table(db): site_description TEXT, default_wallet_name TEXT, theme TEXT, + custom_logo TEXT, ad_space TEXT ); """ @@ -139,8 +89,9 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( user, @@ -160,6 +111,7 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 0f25679d..3b17e720 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel): default_wallet_name: str = Query("LNbits wallet") denomination: str = Query("sats") theme: str = Query(None) + custom_logo: str = Query(None) ad_space: str = Query(None) class Admin(BaseModel): @@ -46,6 +47,7 @@ class Admin(BaseModel): default_wallet_name: Optional[str] denomination: str = Field(default="sats") theme: Optional[str] + custom_logo: Optional[str] ad_space: Optional[str] @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 584d3a33..d9790051 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -705,6 +705,19 @@
+
+
+

Custom Logo

+ +
+
+
@@ -718,10 +731,7 @@
- +
@@ -1292,6 +1323,8 @@ disabled_ext, funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, @@ -1311,6 +1344,8 @@ disabled_ext: disabled_ext.toString(), funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, diff --git a/lnbits/settings.py b/lnbits/settings.py index ed5c77f7..8e5c321a 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,5 @@ import importlib import subprocess -from email.policy import default from os import path from typing import List From 929d174ba4bc20c074ff7bfd2741521c846d005b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 15:38:28 +0100 Subject: [PATCH 074/134] calle's semantics --- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 832629bc..d34b9068 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -67,7 +67,7 @@
-

Minimum wallet reserve

+

Fee reserve

From 8e0baf7b2dcc2eb018ed972aba856ff3aca41b18 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 5 Jul 2022 16:25:02 +0100 Subject: [PATCH 075/134] blacked --- lnbits/extensions/admin/__init__.py | 1 + lnbits/extensions/admin/crud.py | 23 ++++++++++++--- lnbits/extensions/admin/migrations.py | 10 ++++--- lnbits/extensions/admin/models.py | 2 ++ lnbits/extensions/admin/views.py | 10 ++++--- lnbits/extensions/admin/views_api.py | 41 ++++++++++++++------------- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index 6a56b2bb..24b91fe2 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -7,6 +7,7 @@ db = Database("ext_admin") admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + def admin_renderer(): return template_renderer(["lnbits/extensions/admin/templates"]) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 67fbc614..0d7019cc 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -11,13 +11,13 @@ from .models import Admin, Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount*1000, + amount=amount * 1000, memo="Admin top up", pending=False, ) @@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) return payment + async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) @@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None + async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None + async def update_funding(data: Funding) -> Funding: await db.execute( """ UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? - """, - (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + """, + ( + data.backend_wallet, + data.endpoint, + data.port, + data.read_key, + data.invoice_key, + data.admin_key, + data.cert, + data.balance, + data.selected, + data.id, + ), ) row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None + async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index f3663435..388f5ec6 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if(conf.admin_users[0]): + if conf.admin_users[0]: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") account = await create_account() user = account.id assert user, "Newly created user couldn't be retrieved" - print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + print( + f"Your newly created account/user id is: {user}. This will be the Super Admin user." + ) conf.admin_users.insert(0, user) return user - async def m001_create_admin_table(db): # users/server user = await get_admin_user() @@ -28,7 +30,7 @@ async def m001_create_admin_table(db): admin_ext = ",".join(conf.admin_ext) disabled_ext = ",".join(conf.disabled_ext) funding_source = conf.funding_source - #operational + # operational data_folder = conf.data_folder database_url = conf.database_url force_https = conf.force_https diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 3d8efdcd..6e95d68f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel): custom_logo: str = Query(None) ad_space: str = Query(None) + class Admin(BaseModel): # users user: str @@ -59,6 +60,7 @@ class Admin(BaseModel): data = dict(row) return cls(**data) + class Funding(BaseModel): id: str backend_wallet: str diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 24b8ca85..ceda5192 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -16,19 +16,21 @@ from .crud import get_admin, get_funding templates = Jinja2Templates(directory="templates") + @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - + return admin_renderer().TemplateResponse( - "admin/index.html", { + "admin/index.html", + { "request": request, "user": user.dict(), "admin": admin.dict(), "funding": funding, "settings": g().admin_conf.dict(), - "balance": balance - } + "balance": balance, + }, ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c0650c8a..784ad97f 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): +async def api_update_balance( + wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key) +): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) - + return {"status": "Success"} @@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() # print(data) if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) updated = await update_admin(user=w.wallet.user, **data.dict()) - updated.admin_users = removeEmptyString(updated.admin_users.split(',')) - updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) - updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) - updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) - updated.theme = removeEmptyString(updated.theme.split(',')) - updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + updated.admin_users = removeEmptyString(updated.admin_users.split(",")) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(",")) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(",")) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(",")) + updated.theme = removeEmptyString(updated.theme.split(",")) + updated.ad_space = removeEmptyString(updated.ad_space.split(",")) g().admin_conf = g().admin_conf.copy(update=updated.dict()) - + # print(g().admin_conf) return {"status": "Success"} + @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) async def api_update_funding( request: Request, data: Funding = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) funding = await update_funding(data=data) return funding From 429217f5a4876920277ad0719458daca52045b90 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:28:13 +0100 Subject: [PATCH 076/134] Had to add a couple of tries --- lnbits/core/views/generic.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..63f7af68 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,12 +133,19 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True + try: + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + except: + pass + + try: + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True + except: + pass if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore From 1aa2f01d29a25dd5fffac385afd3b5814e10b45c Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:31:31 +0100 Subject: [PATCH 077/134] Added couple more tries --- lnbits/decorators.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index f951163f..a810892d 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True - + try: + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + except: + pass + try: + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True + except: + pass return g().user From 10a065a7ca4f98c725e690d66f9c4f5c60d0c4e3 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:35:06 +0100 Subject: [PATCH 078/134] Reverted try --- lnbits/decorators.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index a810892d..904ca1c2 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - try: - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - except: - pass - try: - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True except: pass return g().user From 3129692ab16fbd66727faba6fb04c87e56b6e0f7 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:37:07 +0100 Subject: [PATCH 079/134] reverted other try --- lnbits/core/views/generic.py | 19 ++++++------------- lnbits/decorators.py | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 63f7af68..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,19 +133,12 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - try: - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - except: - pass - - try: - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True - except: - pass + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 904ca1c2..dd26d8fe 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User: ) if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: g().user.admin = True - except: - pass return g().user From 42f6acd9f4f2f076cf278a843d48db11cbfb2b63 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 21 Sep 2022 18:40:46 +0100 Subject: [PATCH 080/134] fix main merge missing settings --- lnbits/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lnbits/app.py b/lnbits/app.py index eaa33136..176c6bb9 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( "/core/static", From a6bdd8c575f74685c9f71dfd142c908543b1d210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 10:46:11 +0200 Subject: [PATCH 081/134] format --- lnbits/app.py | 22 +++++++++++++--------- lnbits/commands.py | 6 +++++- lnbits/config.py | 25 ++++++++++++++++++------- lnbits/core/views/generic.py | 1 + lnbits/decorators.py | 2 +- lnbits/helpers.py | 8 +++++--- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 176c6bb9..f4e44a0f 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -114,24 +114,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app + def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): while True: admin_set = await get_admin_settings() - if admin_set : + if admin_set: break print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) - - admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) - admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) - admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) - admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) - admin_set.theme = removeEmptyString(admin_set.theme.split(',')) - admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(",")) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(",")) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(",")) + admin_set.theme = removeEmptyString(admin_set.theme.split(",")) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") + print( + f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" + ) + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/commands.py b/lnbits/commands.py index 763a5b90..86868f1f 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -5,6 +5,7 @@ import re import warnings import click +from genericpath import exists from loguru import logger from .core import db as core_db @@ -52,6 +53,7 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) + async def get_admin_settings(): from lnbits.extensions.admin.models import Admin @@ -61,6 +63,7 @@ async def get_admin_settings(): return False async with ext_db.connect() as conn: + if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -69,7 +72,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - + if not exists: return False @@ -77,6 +80,7 @@ async def get_admin_settings(): return Admin(**row) if row else None + async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" diff --git a/lnbits/config.py b/lnbits/config.py index 37b700fd..cf26ad21 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -6,17 +6,19 @@ from typing import List, Optional from pydantic import BaseSettings, Field wallets_module = importlib.import_module("lnbits.wallets") -wallet_class = getattr( +wallet_class = getattr( wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") ) WALLET = wallet_class() + def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(' ','').split(',') + return v.replace(" ", "").split(",") + class Settings(BaseSettings): admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") @@ -24,7 +26,9 @@ class Settings(BaseSettings): admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") - disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + disabled_ext: List[str] = Field( + default_factory=list, env="LNBITS_DISABLED_EXTENSIONS" + ) funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") # ops data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") @@ -37,10 +41,17 @@ class Settings(BaseSettings): denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") + site_tagline: str = Field( + default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE" + ) site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + default_wallet_name: str = Field( + default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" + ) + theme: List[str] = Field( + default=["classic, flamingo, mint, salvador, monochrome, autumn"], + env="LNBITS_THEME_OPTIONS", + ) custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -48,7 +59,7 @@ class Settings(BaseSettings): debug: Optional[str] host: Optional[str] port: Optional[str] - lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) # def validate(cls, val): diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..3a1fbdfc 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) + @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") diff --git a/lnbits/decorators.py b/lnbits/decorators.py index dd26d8fe..58b025aa 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - + if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 7bd1b54a..f4255c86 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url + def removeEmptyString(arr): return list(filter(None, arr)) + def template_renderer(additional_folders: List = []) -> Jinja2Templates: - if(settings.LNBITS_ADMIN_UI): + if settings.LNBITS_ADMIN_UI: _ = g().admin_conf settings.LNBITS_AD_SPACE = _.ad_space settings.LNBITS_HIDE_API = _.hide_api @@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: settings.LNBITS_SITE_TAGLINE = _.site_tagline settings.LNBITS_SITE_DESCRIPTION = _.site_description settings.LNBITS_THEME_OPTIONS = _.theme - settings.LNBITS_CUSTOM_LOGO = _.custom_logo - + settings.LNBITS_CUSTOM_LOGO = _.custom_logo + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 635bcf66d6f8cf7a65322a41abed92153039cd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 11:47:24 +0200 Subject: [PATCH 082/134] format black --- lnbits/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/app.py b/lnbits/app.py index f4e44a0f..118bea98 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: g().admin_conf = conf check_settings(app) From 11393ef7e9ed808d9034baa8515eabcd37918a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:29:12 +0200 Subject: [PATCH 083/134] fix AD_SPACE --- lnbits/core/templates/core/wallet.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 4bf6067c..cc45eb68 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -385,12 +385,9 @@ - {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = - ADS.split(';') %} + {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - {% endfor %} {% endif %}
From 0beb112d5b4f938201adfc6fc2814717ddac20a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:55:37 +0200 Subject: [PATCH 084/134] fix some javascript errors when adding users --- lnbits/extensions/admin/templates/admin/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d34b9068..1e881cb6 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1221,7 +1221,7 @@ addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if (addUser.length && !admin_users.includes(addUser)) { + if (addUser && addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users this.data.admin_users_add = '' @@ -1234,7 +1234,7 @@ addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if (addUser.length && !allowed_users.includes(addUser)) { + if (addUser && addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users this.data.allowed_users_add = '' @@ -1336,7 +1336,6 @@ custom_logo, ad_space } = this.data.admin - //console.log("this", this.data.admin) let data = { admin_users: admin_users.toString(), allowed_users: allowed_users.toString(), From bfff5f3775cd090b454869c434042b4acdb37434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:04:55 +0200 Subject: [PATCH 085/134] fix ADMIN_UI=false errors --- lnbits/core/views/generic.py | 3 +++ lnbits/decorators.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 3a1fbdfc..db4fac43 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -124,6 +124,9 @@ async def wallet( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] if not user_id: user = await get_user((await create_account()).id) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 58b025aa..5a3c0a5c 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -141,6 +141,8 @@ async def get_key_type( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users + else: + LNBITS_ADMIN_USERS = [] for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] @@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." From cede3317f3655c09b196c5293174a1dbaaaa18ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:14:17 +0200 Subject: [PATCH 086/134] prettier --- lnbits/core/templates/core/wallet.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index cc45eb68..5393007d 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -386,8 +386,7 @@ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - - {% endfor %} {% endif %} From b442acc24f02c91971d29fbfe03dda9d5b5fc34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 18:35:47 +0200 Subject: [PATCH 087/134] fix migration tests --- lnbits/extensions/admin/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 388f5ec6..196c9fc0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if conf.admin_users[0]: + if len(conf.admin_users) > 0: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user From 6db5fb16c85d3fd654afd55ee8d6969d243572bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:54:19 +0200 Subject: [PATCH 088/134] change comments to use multiple lines --- .env.example | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index f0e21aa8..673470b7 100644 --- a/.env.example +++ b/.env.example @@ -3,11 +3,15 @@ PORT=5000 DEBUG=false -LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +# User IDs seperated by comma +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 +LNBITS_ADMIN_UI=false -LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma +# Restricts access, User IDs seperated by comma +LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" From affec50a3da3ac812b5046c49483676e93962b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:59:30 +0200 Subject: [PATCH 089/134] fix merge error --- lnbits/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 118bea98..82a8f20e 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) - if lnbits.settings.LNBITS_ADMIN_UI: - g().admin_conf = conf - check_settings(app) - g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( From 7aba2f989cdd78539ca22c69dc1a2e7d5ceb3d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:36 +0200 Subject: [PATCH 090/134] use logger in app.py --- lnbits/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 82a8f20e..e07d2ada 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -31,8 +31,6 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g - -# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -119,7 +117,7 @@ def check_settings(app: FastAPI): admin_set = await get_admin_settings() if admin_set: break - print("Waiting for admin settings... retrying in 5 seconds!") + logger.info("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) @@ -129,7 +127,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(",")) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print( + logger.info( f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" ) From 212b8f9fdd67c73b16f2c0d97cd549bc7bb8f29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:57 +0200 Subject: [PATCH 091/134] fix config --- lnbits/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index cf26ad21..874effae 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -17,7 +17,11 @@ def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(" ", "").split(",") + replaced = v.replace(" ", "") + if replaced: + return replaced.split(",") + else: + return [] class Settings(BaseSettings): @@ -48,10 +52,7 @@ class Settings(BaseSettings): default_wallet_name: str = Field( default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" ) - theme: List[str] = Field( - default=["classic, flamingo, mint, salvador, monochrome, autumn"], - env="LNBITS_THEME_OPTIONS", - ) + theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS") custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -74,4 +75,5 @@ class Settings(BaseSettings): conf = Settings() +print(conf) WALLET = wallet_class() From b6a0a321844ff8a185f728b42f4ede3142334637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:17:20 +0200 Subject: [PATCH 092/134] concatenate first migrations script fixup --- lnbits/config.py | 1 - lnbits/extensions/admin/migrations.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 874effae..fe8dabf9 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -75,5 +75,4 @@ class Settings(BaseSettings): conf = Settings() -print(conf) WALLET = wallet_class() diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 196c9fc0..2d48a8e4 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -23,6 +23,8 @@ async def get_admin_user(): async def m001_create_admin_table(db): + + # users/server user = await get_admin_user() admin_users = ",".join(conf.admin_users) @@ -78,7 +80,7 @@ async def m001_create_admin_table(db): await db.execute( """ INSERT INTO admin.admin ( - "user", + "user", admin_users, allowed_users, admin_ext, @@ -126,9 +128,6 @@ async def m001_create_admin_table(db): ), ) - -async def m001_create_funding_table(db): - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") # Make the funding table, if it does not already exist From 1eeb9de7de8c2409f9a1d6b828e051ea640870d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 16:37:08 +0200 Subject: [PATCH 093/134] add restart button to frontend --- .../admin/templates/admin/index.html | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 1e881cb6..319ca3f0 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -65,6 +65,14 @@ :options="data.funding_source" >
+
+ +

Fee reserve

@@ -89,7 +97,7 @@ >
-
+ @@ -1257,6 +1265,24 @@ let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, + restartServer() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/admin/restart/', + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: 'Success! Restarted Server', + icon: null + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, topupWallet() { LNbits.api .request( From 82e322ae43658bef7735974324fd3bec7919b756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:34:52 +0200 Subject: [PATCH 094/134] make extension use new settings --- lnbits/extensions/boltz/boltz.py | 14 ++++++-------- lnbits/extensions/boltz/mempool.py | 15 ++++++--------- lnbits/extensions/boltz/views_api.py | 4 ++-- lnbits/extensions/lndhub/views_api.py | 4 ++-- lnbits/extensions/tpos/views.py | 14 +++++++------- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py index ac99d4f4..424d0bf7 100644 --- a/lnbits/extensions/boltz/boltz.py +++ b/lnbits/extensions/boltz/boltz.py @@ -12,7 +12,7 @@ from loguru import logger from lnbits.core.services import create_invoice, pay_invoice from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL +from lnbits.settings import settings from .crud import update_swap_status from .mempool import ( @@ -33,9 +33,7 @@ from .models import ( ) from .utils import check_balance, get_timestamp, req_wrap -net = NETWORKS[BOLTZ_NETWORK] -logger.trace(f"BOLTZ_URL: {BOLTZ_URL}") -logger.trace(f"Bitcoin Network: {net['name']}") +net = NETWORKS[settings.boltz_network] async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: @@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "submarine", "pairId": "BTC/BTC", @@ -129,7 +127,7 @@ async def create_reverse_swap( res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "reversesubmarine", "pairId": "BTC/BTC", @@ -409,7 +407,7 @@ def check_boltz_limits(amount): def get_boltz_pairs(): res = req_wrap( "get", - f"{BOLTZ_URL}/getpairs", + f"{settings.boltz_url}/getpairs", headers={"Content-Type": "application/json"}, ) return res.json() @@ -418,7 +416,7 @@ def get_boltz_pairs(): def get_boltz_status(boltzid): res = req_wrap( "post", - f"{BOLTZ_URL}/swapstatus", + f"{settings.boltz_url}/swapstatus", json={"id": boltzid}, ) return res.json() diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py index a44c0f02..a64cadad 100644 --- a/lnbits/extensions/boltz/mempool.py +++ b/lnbits/extensions/boltz/mempool.py @@ -7,14 +7,11 @@ import websockets from embit.transaction import Transaction from loguru import logger -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS +from lnbits.settings import settings from .utils import req_wrap -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") - -websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws" +websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws" async def wait_for_websocket_message(send, message_string): @@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string): def get_mempool_tx(address): res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs", + f"{settings.boltz_mempool_space_url}/api/address/{address}/txs", headers={"Content-Type": "text/plain"}, ) txs = res.json() @@ -70,7 +67,7 @@ def get_fee_estimation() -> int: def get_mempool_fees() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended", + f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended", headers={"Content-Type": "text/plain"}, ) fees = res.json() @@ -80,7 +77,7 @@ def get_mempool_fees() -> int: def get_mempool_blockheight() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height", + f"{settings.boltz_mempool_space_url}/api/blocks/tip/height", headers={"Content-Type": "text/plain"}, ) return int(res.text) @@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction): logger.debug(f"Boltz - mempool sending onchain tx...") req_wrap( "post", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx", + f"{settings.boltz_mempool_space_url}/api/tx", headers={"Content-Type": "text/plain"}, content=raw, ) diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py index a4b7d318..18ca14cb 100644 --- a/lnbits/extensions/boltz/views_api.py +++ b/lnbits/extensions/boltz/views_api.py @@ -14,7 +14,7 @@ from starlette.requests import Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL +from lnbits.settings import settings from . import boltz_ext from .boltz import ( @@ -55,7 +55,7 @@ from .utils import check_balance response_model=str, ) async def api_mempool_url(): - return BOLTZ_MEMPOOL_SPACE_URL + return settings.boltz_mempool_space_url # NORMAL SWAP diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py index 8cbe5a6b..b2328c39 100644 --- a/lnbits/extensions/lndhub/views_api.py +++ b/lnbits/extensions/lndhub/views_api.py @@ -12,7 +12,7 @@ from lnbits import bolt11 from lnbits.core.crud import delete_expired_invoices, get_payments from lnbits.core.services import create_invoice, pay_invoice from lnbits.decorators import WalletTypeInfo -from lnbits.settings import LNBITS_SITE_TITLE, WALLET +from lnbits.settings import WALLET, settings from . import lndhub_ext from .decorators import check_wallet, require_admin_key @@ -56,7 +56,7 @@ async def lndhub_addinvoice( _, pr = await create_invoice( wallet_id=wallet.wallet.id, amount=int(data.amt), - memo=data.memo or LNBITS_SITE_TITLE, + memo=data.memo or settings.lnbits_site_title, extra={"tag": "lndhub"}, ) except: diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py index e1f1d21e..dac129a9 100644 --- a/lnbits/extensions/tpos/views.py +++ b/lnbits/extensions/tpos/views.py @@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE +from lnbits.settings import settings from . import tpos_ext, tpos_renderer from .crud import get_tpos @@ -50,12 +50,12 @@ async def manifest(tpos_id: str): ) return { - "short_name": LNBITS_SITE_TITLE, - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "short_name": settings.lnbits_site_title, + "name": tpos.name + " - " + settings.lnbits_site_title, "icons": [ { - "src": LNBITS_CUSTOM_LOGO - if LNBITS_CUSTOM_LOGO + "src": settings.lnbits_custom_logo + if settings.lnbits_custom_logo else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", "type": "image/png", "sizes": "900x900", @@ -69,9 +69,9 @@ async def manifest(tpos_id: str): "theme_color": "#1F2234", "shortcuts": [ { - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "name": tpos.name + " - " + settings.lnbits_site_title, "short_name": tpos.name, - "description": tpos.name + " - " + LNBITS_SITE_TITLE, + "description": tpos.name + " - " + settings.lnbits_site_title, "url": "/tpos/" + tpos_id, } ], From 5aa9cdd45631379341f31d17d9484c5e3ad05a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:35:26 +0200 Subject: [PATCH 095/134] remove enviroms --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7418de27..92c43dce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ charset-normalizer = "2.0.6" click = "8.0.1" ecdsa = "0.17.0" embit = "0.4.9" -environs = "9.3.3" fastapi = "0.78.0" h11 = "0.12.0" httpcore = "0.15.0" From 6a2c7414783a5edb4b7932355f3fe145053332a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:36:14 +0200 Subject: [PATCH 096/134] fix admin --- lnbits/extensions/admin/crud.py | 24 +- lnbits/extensions/admin/migrations.py | 337 +---- lnbits/extensions/admin/models.py | 58 +- .../admin/templates/admin/_tab_funding.html | 158 +++ .../admin/templates/admin/_tab_server.html | 78 ++ .../admin/templates/admin/_tab_theme.html | 122 ++ .../admin/templates/admin/_tab_users.html | 96 ++ .../admin/templates/admin/index.html | 1133 +---------------- lnbits/extensions/admin/views.py | 16 +- lnbits/extensions/admin/views_api.py | 28 +- 10 files changed, 576 insertions(+), 1474 deletions(-) create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 0d7019cc..e4cb5d77 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -2,10 +2,11 @@ from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import Settings from lnbits.tasks import internal_invoice_queue from . import db -from .models import Admin, Funding +from .models import Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: ) # manually send this for now await internal_invoice_queue.put(internal_id) - return payment -async def update_admin(user: str, **kwargs) -> Admin: +async def update_settings(user: str, **kwargs) -> Settings: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" - return Admin(**row) if row else None - - -async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin.admin") - return Admin(**row) if row else None + return Settings(**row) if row else None async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE admin.settings SET funding_source = ? WHERE user = ? + """, + (data.backend_wallet, data.user), + ) await db.execute( """ UPDATE admin.funding @@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding: async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") - return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 2d48a8e4..8f6c76a0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,292 +1,57 @@ -from os import getenv - -from sqlalchemy.exc import OperationalError # type: ignore - -from lnbits.config import conf -from lnbits.helpers import urlsafe_short_hash - - -async def get_admin_user(): - if len(conf.admin_users) > 0: - return conf.admin_users[0] - from lnbits.core.crud import create_account, get_user - - print("Seems like there's no admin users yet. Let's create an account for you!") - account = await create_account() - user = account.id - assert user, "Newly created user couldn't be retrieved" - print( - f"Your newly created account/user id is: {user}. This will be the Super Admin user." - ) - conf.admin_users.insert(0, user) - return user - - -async def m001_create_admin_table(db): - - - # users/server - user = await get_admin_user() - admin_users = ",".join(conf.admin_users) - allowed_users = ",".join(conf.allowed_users) - admin_ext = ",".join(conf.admin_ext) - disabled_ext = ",".join(conf.disabled_ext) - funding_source = conf.funding_source - # operational - data_folder = conf.data_folder - database_url = conf.database_url - force_https = conf.force_https - reserve_fee_min = conf.reserve_fee_min - reserve_fee_pct = conf.reserve_fee_pct - service_fee = conf.service_fee - hide_api = conf.hide_api - denomination = conf.denomination - # Theme'ing - site_title = conf.site_title - site_tagline = conf.site_tagline - site_description = conf.site_description - default_wallet_name = conf.default_wallet_name - theme = ",".join(conf.theme) - custom_logo = conf.custom_logo - ad_space = ",".join(conf.ad_space) - +async def m001_create_admin_settings_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin.admin ( - "user" TEXT PRIMARY KEY, - admin_users TEXT, - allowed_users TEXT, - admin_ext TEXT, - disabled_ext TEXT, - funding_source TEXT, - data_folder TEXT, - database_url TEXT, - force_https BOOLEAN, - reserve_fee_min INT, - reserve_fee_pct REAL, - service_fee REAL, - hide_api BOOLEAN, - denomination TEXT, - site_title TEXT, - site_tagline TEXT, - site_description TEXT, - default_wallet_name TEXT, - theme TEXT, - custom_logo TEXT, - ad_space TEXT - ); - """ - ) - await db.execute( - """ - INSERT INTO admin.admin ( - "user", - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user, - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space, - ), - ) - - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") - - # Make the funding table, if it does not already exist - await db.execute( - """ - CREATE TABLE IF NOT EXISTS admin.funding ( - id TEXT PRIMARY KEY, - backend_wallet TEXT, - endpoint TEXT, + CREATE TABLE IF NOT EXISTS admin.settings ( + lnbits_admin_ui TEXT, + debug TEXT, + host TEXT, port INT, - read_key TEXT, - invoice_key TEXT, - admin_key TEXT, - cert TEXT, - balance INT, - selected INT + 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, + lnbits_site_description TEXT, + lnbits_default_wallet_name TEXT, + lnbits_theme_options TEXT, + lnbits_custom_logo TEXT, + lnbits_ad_space TEXT, + lnbits_data_folder TEXT, + lnbits_database_url TEXT, + lnbits_force_https TEXT, + lnbits_reserve_fee_min TEXT, + lnbits_reserve_fee_percent TEXT, + lnbits_service_fee TEXT, + lnbits_hide_api TEXT, + lnbits_denomination TEXT, + lnbits_backend_wallet_class TEXT, + fake_wallet_secret TEXT, + lnbits_endpoint TEXT, + lnbits_key TEXT, + cliche_endpoint TEXT, + corelightning_rpc TEXT, + eclair_url TEXT, + eclair_pass TEXT, + lnd_rest_endpoint TEXT, + lnd_rest_cert TEXT, + lnd_rest_macaroon TEXT, + lnpay_api_endpoint TEXT, + lnpay_api_key TEXT, + lnpay_wallet_key TEXT, + lntxbot_api_endpoint TEXT, + lntxbot_key TEXT, + opennode_api_endpoint TEXT, + opennode_key TEXT, + spark_url TEXT, + spark_token TEXT, + boltz_network TEXT, + boltz_url TEXT, + boltz_mempool_space_url TEXT, + boltz_mempool_space_url_ws TEXT ); """ ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) - VALUES (?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "CLightningWallet", - getenv("CLIGHTNING_RPC"), - 1 if funding_wallet == "CLightningWallet" else 0, - ), - ) - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LnbitsWallet", - getenv("LNBITS_ENDPOINT"), - getenv("LNBITS_KEY"), - 1 if funding_wallet == "LnbitsWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndWallet", - getenv("LND_GRPC_ENDPOINT"), - getenv("LND_GRPC_PORT"), - getenv("LND_GRPC_MACAROON"), - getenv("LND_GRPC_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndRestWallet", - getenv("LND_REST_ENDPOINT"), - getenv("LND_REST_MACAROON"), - getenv("LND_REST_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LNPayWallet", - getenv("LNPAY_API_ENDPOINT"), - getenv("LNPAY_WALLET_KEY"), - getenv("LNPAY_API_KEY"), # this is going in as the cert - 1 if funding_wallet == "LNPayWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LntxbotWallet", - getenv("LNTXBOT_API_ENDPOINT"), - getenv("LNTXBOT_KEY"), - 1 if funding_wallet == "LntxbotWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "OpenNodeWallet", - getenv("OPENNODE_API_ENDPOINT"), - getenv("OPENNODE_KEY"), - 1 if funding_wallet == "OpenNodeWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - ## PLACEHOLDER FOR ECLAIR WALLET - # await db.execute( - # """ - # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - # VALUES (?, ?, ?, ?, ?) - # """, - # ( - # urlsafe_short_hash(), - # "EclairWallet", - # getenv("ECLAIR_URL"), - # getenv("ECLAIR_PASS"), - # 1 if funding_wallet == "EclairWallet" else 0, - # ), - # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 6e95d68f..ef57cadd 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel): ad_space: str = Query(None) -class Admin(BaseModel): - # users - user: str - admin_users: Optional[str] - allowed_users: Optional[str] - admin_ext: Optional[str] - disabled_ext: Optional[str] - funding_source: Optional[str] - # ops - data_folder: Optional[str] - database_url: Optional[str] - force_https: bool = Field(default=True) - reserve_fee_min: Optional[int] - reserve_fee_pct: Optional[float] - service_fee: float = Optional[float] - hide_api: bool = Field(default=False) - # Change theme - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - default_wallet_name: Optional[str] - denomination: str = Field(default="sats") - theme: Optional[str] - custom_logo: Optional[str] - ad_space: Optional[str] +# class Admin(BaseModel): +# # users +# user: str +# admin_users: Optional[str] +# allowed_users: Optional[str] +# admin_ext: Optional[str] +# disabled_ext: Optional[str] +# funding_source: Optional[str] +# # ops +# data_folder: Optional[str] +# database_url: Optional[str] +# force_https: bool = Field(default=True) +# reserve_fee_min: Optional[int] +# reserve_fee_pct: Optional[float] +# service_fee: float = Optional[float] +# hide_api: bool = Field(default=False) +# # Change theme +# site_title: Optional[str] +# site_tagline: Optional[str] +# site_description: Optional[str] +# default_wallet_name: Optional[str] +# denomination: str = Field(default="sats") +# theme: Optional[str] +# custom_logo: Optional[str] +# ad_space: Optional[str] - @classmethod - def from_row(cls, row: Row) -> "Admin": - data = dict(row) - return cls(**data) +# @classmethod +# def from_row(cls, row: Row) -> "Admin": +# data = dict(row) +# return cls(**data) class Funding(BaseModel): diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html new file mode 100644 index 00000000..2ed0aae2 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -0,0 +1,158 @@ + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • + Funding Source: {{data.settings.lnbits_backend_wallet_class}} +
  • +
  • Balance: {{data.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+
+
+

Active Funding (Requires server restart)

+ +
+
+ +
+
+
+

Fee reserve

+
+
+ +
+
+ +
+
+
+
+
+
+

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+

Funding Sources

+ {% raw %} + + + + + + + + + + + + + + {% endraw %} +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html new file mode 100644 index 00000000..2924e6a4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_server.html @@ -0,0 +1,78 @@ + + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • + SQlite: {{data.settings.lnbits_data_folder}} +
  • +
  • + Postgres: {{data.settings.lnbits_database_url}} +
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html new file mode 100644 index 00000000..41dc0447 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -0,0 +1,122 @@ + + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+
+

Custom Logo

+ +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html new file mode 100644 index 00000000..3eb53ac4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -0,0 +1,96 @@ + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{% + endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 319ca3f0..87e89321 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -29,1118 +29,16 @@ - - - -
Wallets Management
-
-
-
-
-

Funding Source Info

-
    - {%raw%} -
  • Funding Source: {{data.admin.funding_source}}
  • -
  • Balance: {{data.admin.balance / 1000}} sats
  • - {%endraw%} -
-
-
-
-
-
-
-
-

- Active Funding - (Requires server restart) -

- -
-
- -
-
-
-

Fee reserve

-
-
- -
-
- -
-
- -
-
-
-
- -

TopUp a wallet

-
-
- -
-
-
- -
-
-
- -
- -
-
-
-

Funding Sources

- {% raw %} - - - - - - - - - - - - - - {% endraw %} - -
-
-
- - -
User Management
-
-

- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} -

-
-
-

Admin Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-

Allowed Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-
-
-
-
-

Server Info

-
    - {%raw%} -
  • - SQlite: {{data.admin.data_folder}} -
  • -
  • - Postgres: {{data.admin.database_url}} -
  • - {%endraw%} -
-
-
-
-
-
-

Service Fee

- -
-
-
-

Miscelaneous

- - - Force HTTPS - Prefer secure URLs - - - - - - - - Hide API - Hides wallet api, extensions can choose to - honor - - - - - -
-
-
-
- -
- Save -
-
-
- - -
UI Management
-
-
-
-
-

Site Title

- -
-
-
-

Site Tagline

- -
-
-
-
-

Site Description

- -
-
-
-
-

Default Wallet Name

- -
-
-
-

Denomination

- -
-
-
-
-
-

Themes

- -
-
-
-

Advertisement Slots

- - - -
- {% raw %} - - {{ space.slice(0, 8) + " ... " + space.slice(-8) }} - - {% endraw %} -
-
-
-
-
-
-

Custom Logo

- -
-
-
-
- -
- Save -
-
-
+ {% include "admin/_tab_funding.html" %} {% include + "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {% + include "admin/_tab_theme.html" %}
- - -
- -
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} From 04b37458983094ff9deec4ba010ec2a93c002cd9 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:24:07 +0100 Subject: [PATCH 122/134] make saving possible (possible will change in future) --- .../admin/templates/admin/index.html | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 72352651..18df16a9 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -121,8 +121,11 @@ created: function () { this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data this.balance = +'{{ balance|safe }}' - this.formData = this.settings //model + this.formData = _.clone(this.settings) //model + //this.formData.lnbits_ad_space = "hdh" console.log(this.formData) + console.log(_.isEqual(this.settings, this.formData)) + }, methods: { addAdminUser() { @@ -206,18 +209,27 @@ }, updateSettings() { let data = { - ...this.settings, - ...this.formData + lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, + lnbits_admin_users: this.formData.lnbits_admin_users.toString(), + lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), + lnbits_admin_ext: this.formData.lnbits_admin_ext, + lnbits_disabled_ext: this.formData.lnbits_disabled_ext, + lnbits_funding_source: this.formData.lnbits_funding_source, + lnbits_force_https: this.formData.lnbits_force_https, + lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, + lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, + lnbits_service_fee: this.formData.lnbits_service_fee, + lnbits_hide_api: this.formData.lnbits_hide_api, + lnbits_site_title: this.formData.lnbits_site_title, + lnbits_site_tagline: this.formData.lnbits_site_tagline, + lnbits_site_description: this.formData.lnbits_site_description, + lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, + lnbits_denomination: this.formData.lnbits_denomination, + lnbits_theme: this.formData.lnbits_theme, + lnbits_custom_logo: this.formData.lnbits_custom_logo, + lnbits_ad_space: this.formData.lnbits_ad_space.toString() } - /* - const formElement = document.getElementById('settings_form') - const formData = new FormData(formElement) - const data = {} - formData.forEach((value, key) => (data[key] = value)) - // only for debugging - for (const [key, value] of formData) { - console.log(`${key}: ${value}\n`) - }*/ + console.log(data) LNbits.api .request( 'PUT', From cc42df12f4d3927c854675f114aff0b75bef6d19 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:44:03 +0100 Subject: [PATCH 123/134] some more refining --- lnbits/extensions/admin/models.py | 10 +++++----- .../extensions/admin/templates/admin/_tab_users.html | 2 ++ lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 13a6cd23..45cd990d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -4,10 +4,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) - lnbits_allowed_users: str = Query(None) - lnbits_admin_ext: str = Query(None) - lnbits_disabled_ext: str = Query(None) + lnbits_admin_users: str = Query(None) #this should be List[str] ?? + lnbits_allowed_users: str = Query(None) #this should be List[str] ?? + lnbits_admin_ext: str = Query(None) #this should be List[str] ?? + lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +21,4 @@ class UpdateSettings(BaseModel): 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: str = Query(None) #this should be List[str] ?? diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html index c396ba7d..08b08a62 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_users.html +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -71,6 +71,7 @@ multiple hint="Extensions only user with admin privileges can use" label="Admin extensions" + :options="g.extensions.map(e => e.name)" >
@@ -79,6 +80,7 @@
- + Date: Mon, 10 Oct 2022 12:17:35 +0100 Subject: [PATCH 124/134] get saved data and alert when data changed --- .../admin/templates/admin/index.html | 18 +++++++----------- lnbits/extensions/admin/views_api.py | 5 +++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4754656d..4e401cb4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,7 @@
- + { + this.settings = response.data.settings + this.formData = _.clone(this.settings) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c8120564..c2079e37 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -43,8 +43,9 @@ async def api_update_settings( user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): - await update_settings(data) - return {"status": "Success"} + settings = await update_settings(data) + logger.debug(settings) + return {"status": "Success", "settings": settings.dict()} @admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) From 001f6589bb3c173c9ada0f86ec0a2eca08f3b051 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 10 Oct 2022 12:23:19 +0100 Subject: [PATCH 125/134] cleanup and typing fix for data --- lnbits/extensions/admin/models.py | 12 +++++----- .../admin/templates/admin/index.html | 22 ++----------------- lnbits/extensions/admin/views_api.py | 1 - 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 45cd990d..94fa56bb 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,13 +1,15 @@ +from typing import List + from fastapi import Query from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) #this should be List[str] ?? - lnbits_allowed_users: str = Query(None) #this should be List[str] ?? - lnbits_admin_ext: str = Query(None) #this should be List[str] ?? - lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? + 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_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +23,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: str = Query(None) #this should be List[str] ?? + lnbits_ad_space: List[str] = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4e401cb4..d8111595 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -209,26 +209,8 @@ }) }, updateSettings() { - let data = { - lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, - lnbits_admin_users: this.formData.lnbits_admin_users.toString(), - lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), - lnbits_admin_ext: this.formData.lnbits_admin_ext, - lnbits_disabled_ext: this.formData.lnbits_disabled_ext, - lnbits_funding_source: this.formData.lnbits_funding_source, - lnbits_force_https: this.formData.lnbits_force_https, - lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, - lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, - lnbits_service_fee: this.formData.lnbits_service_fee, - lnbits_hide_api: this.formData.lnbits_hide_api, - lnbits_site_title: this.formData.lnbits_site_title, - lnbits_site_tagline: this.formData.lnbits_site_tagline, - lnbits_site_description: this.formData.lnbits_site_description, - lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, - lnbits_denomination: this.formData.lnbits_denomination, - lnbits_theme: this.formData.lnbits_theme, - lnbits_custom_logo: this.formData.lnbits_custom_logo, - lnbits_ad_space: this.formData.lnbits_ad_space.toString() + let data = { + ...this.formData } LNbits.api .request( diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c2079e37..19b52e35 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -44,7 +44,6 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) - logger.debug(settings) return {"status": "Success", "settings": settings.dict()} From 1cc54ff4b7e19366499743fc9cb881bb58ccf2c6 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 12 Oct 2022 19:04:46 +0100 Subject: [PATCH 126/134] add funding sources options --- lnbits/app.py | 1 + lnbits/extensions/admin/models.py | 41 +++- .../admin/templates/admin/_tab_funding.html | 25 +- .../admin/templates/admin/index.html | 217 +++++++++++++++++- 4 files changed, 259 insertions(+), 25 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index a8371950..50f218b7 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,6 +84,7 @@ async def check_funding_source() -> None: def signal_handler(signal, frame): logger.debug(f"SIGINT received, terminating LNbits.") sys.exit(1) + signal.signal(signal.SIGINT, signal_handler) WALLET = get_wallet_class() diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 94fa56bb..d9d2b22f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,10 +6,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): 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_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_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -23,4 +23,35 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: List[str] = Query(None) + lnbits_ad_space: List[str] = Query(None) + + # funding sources + fake_wallet_secret: str = Query(None) + lnbits_endpoint: str = Query(None) + lnbits_key: str = Query(None) + cliche_endpoint: str = Query(None) + corelightning_rpc: str = Query(None) + eclair_url: str = Query(None) + eclair_pass: str = Query(None) + lnd_rest_endpoint: str = Query(None) + lnd_rest_cert: str = Query(None) + lnd_rest_macaroon: str = Query(None) + lnd_rest_macaroon_encrypted: str = Query(None) + lnd_cert: str = Query(None) + lnd_admin_macaroon: str = Query(None) + lnd_invoice_macaroon: str = Query(None) + lnd_grpc_endpoint: str = Query(None) + lnd_grpc_cert: str = Query(None) + lnd_grpc_port: int = Query(None, ge=0) + lnd_grpc_admin_macaroon: str = Query(None) + lnd_grpc_invoice_macaroon: str = Query(None) + lnd_grpc_macaroon_encrypted: str = Query(None) + lnpay_api_endpoint: str = Query(None) + lnpay_api_key: str = Query(None) + lnpay_wallet_key: str = Query(None) + lntxbot_api_endpoint: str = Query(None) + lntxbot_key: str = Query(None) + opennode_api_endpoint: str = Query(None) + opennode_key: str = Query(None) + spark_url: str = Query(None) + spark_token: str = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index 8b5456f1..a523d4e5 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -8,9 +8,7 @@

Funding Source Info

    {%raw%} -
  • - Funding Source: {{settings.lnbits_backend_wallet_class}} -
  • +
  • Funding Source: {{settings.lnbits_backend_wallet_class}}
  • Balance: {{balance / 1000}} sats
  • {%endraw%}
@@ -60,21 +58,30 @@
-

Funding Sources

+

Funding Sources (Requires server restart)

- + - - + diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d8111595..ccaddda4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,13 @@
- + u !== user - ) + this.settings.lnbits_admin_users = admin_users.filter(u => u !== user) }, addAllowedUser() { let addUser = this.formData.allowed_users_add @@ -155,7 +335,9 @@ }, removeAllowedUser(user) { let allowed_users = this.settings.lnbits_allowed_users - this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user) + this.settings.lnbits_allowed_users = allowed_users.filter( + u => u !== user + ) }, addAdSpace() { let adSpace = this.formData.ad_space_add @@ -208,8 +390,19 @@ LNbits.utils.notifyApiError(error) }) }, + updateFundingData(){ + this.settings.lnbits_allowed_funding_sources.map(f => { + let opts = this.funding_sources.get(f) + if (!opts) return + + Object.keys(opts).forEach(e => { + opts[e].value = this.settings[e] + }) + }) + console.log("funding", this.funding_sources) + }, updateSettings() { - let data = { + let data = { ...this.formData } LNbits.api @@ -222,11 +415,13 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) + console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -262,7 +457,7 @@ LNbits.utils.notifyApiError(error) }) } - }, + } }) {% endblock %} From 761fc427defc590913124f1ad2f1b518633fbad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 10 Oct 2022 23:27:46 +0200 Subject: [PATCH 127/134] add callback for saas app --- lnbits/app.py | 2 +- lnbits/extensions/admin/migrations.py | 3 +++ lnbits/extensions/admin/views_api.py | 5 ----- lnbits/settings.py | 32 +++++++++++++++++++++++---- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 50f218b7..49ad8d77 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -64,7 +64,7 @@ def create_app() -> FastAPI: # TODO: why those 2? g().config = settings - # g().base_url = f"http://{settings.host}:{settings.port}" + g().base_url = f"http://{settings.host}:{settings.port}" app.add_middleware(GZipMiddleware, minimum_size=1000) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c4bc98d8..ea698c27 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db): debug TEXT, host TEXT, port INTEGER, + lnbits_saas_instance_id TEXT, + lnbits_saas_callback TEXT, + lnbits_saas_secret TEXT, lnbits_path TEXT, lnbits_commit TEXT, lnbits_admin_users TEXT, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 19b52e35..ae2959bc 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -53,8 +53,3 @@ async def api_delete_settings( ): await delete_settings() return {"status": "Success"} - - -@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK) -async def api_backup(user: User = Depends(check_admin)): - return {"status": "not implemented"} diff --git a/lnbits/settings.py b/lnbits/settings.py index ffcdcc0a..f183211c 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,7 @@ import importlib import json import subprocess +import httpx from os import path from sqlite3 import Row from typing import List, Optional @@ -9,6 +10,7 @@ from loguru import logger from pydantic import BaseSettings, Field, validator + def list_parse_fallback(v): try: return json.loads(v) @@ -34,6 +36,11 @@ class Settings(BaseSettings): lnbits_path: str = Field(default=".") lnbits_commit: str = Field(default="unknown") + # saas + lnbits_saas_callback: Optional[str] = Field(default=None) + lnbits_saas_secret: Optional[str] = Field(default=None) + lnbits_saas_instance_id: Optional[str] = Field(default=None) + # users lnbits_admin_users: List[str] = Field(default=[]) lnbits_allowed_users: List[str] = Field(default=[]) @@ -230,11 +237,28 @@ async def check_admin_settings(): http = "https" if settings.lnbits_force_https else "http" user = settings.lnbits_admin_users[0] - logger.warning( - f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}" - ) + + admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + logger.warning(f"✔️ Access admin user account at: {admin_url}") + + 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: + r = 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}") + except: - logger.warning("admin.settings tables does not exist.") + logger.error("admin.settings tables does not exist.") raise From 620fd2569655aa0f45b486201bcac75654f28326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 12 Oct 2022 13:08:59 +0200 Subject: [PATCH 128/134] bugfixes and fix topup wallet --- .../admin/templates/admin/index.html | 6 ++-- lnbits/extensions/admin/views_api.py | 36 ++++++++++--------- lnbits/server.py | 1 + lnbits/settings.py | 29 ++++++++++----- lnbits/wallets/fake.py | 2 +- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index ccaddda4..575b377f 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -369,10 +369,10 @@ topupWallet() { LNbits.api .request( - 'POST', + 'PUT', '/admin/api/v1/topup/?usr=' + this.g.user.id, - this.wallet.id, - this.wallet.amount + this.g.user.wallets[0].adminkey, + this.wallet ) .then(response => { this.$q.notify({ diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index ae2959bc..63ed5b3c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,7 +1,6 @@ from http import HTTPStatus -from fastapi import Body, Depends, Request -from loguru import logger +from fastapi import Body, Depends, Query from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet @@ -9,47 +8,50 @@ from lnbits.core.models import User from lnbits.decorators import check_admin from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings -from lnbits.requestvars import g from lnbits.server import server_restart -from lnbits.settings import settings from .crud import delete_settings, update_settings, update_wallet_balance -@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK) -async def api_restart_server(user: User = Depends(check_admin)): +@admin_ext.get( + "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_restart_server() -> dict[str, str]: server_restart.set() return {"status": "Success"} -@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_balance( - wallet_id, topup_amount: int, user: User = Depends(check_admin) -): + id: str = Body(...), amount: int = Body(...) +) -> dict[str, str]: try: - wallet = await get_wallet(wallet_id) + await get_wallet(id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist." ) - await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + await update_wallet_balance(wallet_id=id, amount=int(amount)) return {"status": "Success"} -@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_settings( - user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): settings = await update_settings(data) return {"status": "Success", "settings": settings.dict()} -@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) -async def api_delete_settings( - user: User = Depends(check_admin), -): +@admin_ext.delete( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_delete_settings() -> dict[str, str]: await delete_settings() return {"status": "Success"} diff --git a/lnbits/server.py b/lnbits/server.py index 79af8112..6d4cd2e7 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: port=port, host=host, reload=reload, + forwarded_allow_ips="*", ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, **d diff --git a/lnbits/settings.py b/lnbits/settings.py index f183211c..61dbd6f2 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,20 +1,19 @@ import importlib import json import subprocess -import httpx from os import path from sqlite3 import Row from typing import List, Optional +import httpx from loguru import logger from pydantic import BaseSettings, Field, validator - def list_parse_fallback(v): try: return json.loads(v) - except Exception as e: + except Exception: replaced = v.replace(" ", "") if replaced: return replaced.split(",") @@ -238,24 +237,36 @@ async def check_admin_settings(): 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}" + admin_url = ( + f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + ) logger.warning(f"✔️ Access admin user account at: {admin_url}") - if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id: + 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 + "X-API-KEY": settings.lnbits_saas_secret, } payload = { "instance_id": settings.lnbits_saas_instance_id, - "adminuser": user + "adminuser": user, } try: - r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload) + 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}") + logger.error( + f"error sending admin user to saas: {settings.lnbits_saas_callback}" + ) except: logger.error("admin.settings tables does not exist.") diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 73458e8c..94ff5f48 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -19,7 +19,6 @@ from .base import ( class FakeWallet(Wallet): - queue: asyncio.Queue = asyncio.Queue(0) secret: str = settings.fake_wallet_secret privkey: str = hashlib.pbkdf2_hmac( "sha256", @@ -98,6 +97,7 @@ class FakeWallet(Wallet): return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + self.queue: asyncio.Queue = asyncio.Queue(0) while True: value: Invoice = await self.queue.get() yield value.payment_hash From 9dbcd89c6ebdba4db6c0bc7d70d570a8ee5fffaa Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:02 +0100 Subject: [PATCH 129/134] added tooltips, moved reset, and warnings --- .../admin/templates/admin/index.html | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 575b377f..7d268301 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,8 +1,14 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
-
- +
+ + Save your changes - - - + + + Restart the server for changes to take effect + + + + + Add funds to a wallet. + + > --> + + Delete all settings and reset to defaults. +
@@ -121,6 +136,7 @@ show: false }, tab: 'funding', + needsRestart: false, funding_sources: new Map([ ['VoidWallet', null], [ @@ -302,13 +318,12 @@ this.balance = +'{{ balance|safe }}' this.formData = _.clone(this.settings) //model this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { return !_.isEqual(this.settings, this.formData) - }, + } }, methods: { addAdminUser() { @@ -361,6 +376,7 @@ message: 'Success! Restarted Server', icon: null }) + this.needsRestart = false }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -390,16 +406,15 @@ LNbits.utils.notifyApiError(error) }) }, - updateFundingData(){ + updateFundingData() { this.settings.lnbits_allowed_funding_sources.map(f => { let opts = this.funding_sources.get(f) if (!opts) return - + Object.keys(opts).forEach(e => { opts[e].value = this.settings[e] }) }) - console.log("funding", this.funding_sources) }, updateSettings() { let data = { @@ -415,31 +430,41 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.needsRestart = true this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) - console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, deleteSettings() { - LNbits.api - .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id) - .then(response => { - this.$q.notify({ - type: 'positive', - message: - 'Success! Restored settings to defaults, restart required!', - icon: null - }) - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) + LNbits.utils + .confirmDialog( + 'Are you sure you want to restore settings to default?' + ) + .onOk(() => { + LNbits.api + .request( + 'DELETE', + '/admin/api/v1/settings/?usr=' + this.g.user.id + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: + 'Success! Restored settings to defaults, restart required!', + icon: null + }) + this.needsRestart = true + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }) }, downloadBackup() { From 31a7a87774e5fcd87962440bc8c20510f607a328 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:26 +0100 Subject: [PATCH 130/134] moved to columns --- .../admin/templates/admin/_tab_funding.html | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index a523d4e5..a69ecb47 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -27,38 +27,38 @@ :options="settings.lnbits_allowed_funding_sources" >
-
-

Fee reserve

-
-
- -
-
- -
+
+
+
+
+

Fee reserve

+
+
+ + +
+
+
-
-
-
-

Funding Sources (Requires server restart)

+

+ Funding Sources (Requires server restart) +

Date: Thu, 13 Oct 2022 15:52:39 +0100 Subject: [PATCH 131/134] themes not displaying fixed --- lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html index 46bf83e9..c327733f 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_theme.html +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -63,7 +63,7 @@

Themes

Date: Fri, 21 Oct 2022 10:00:47 +0200 Subject: [PATCH 132/134] add loop to uvicorn --- lnbits/server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lnbits/server.py b/lnbits/server.py index 6d4cd2e7..eb7c12b1 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,12 +1,7 @@ -import asyncio - import uvloop - uvloop.install() -import contextlib import multiprocessing as mp -import sys import time import click @@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: while True: config = uvicorn.Config( "lnbits.__main__:app", + loop="uvloop", port=port, host=host, reload=reload, @@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: server_restart.clear() server.should_exit = True server.force_exit = True + time.sleep(3) process.terminate() process.join() - time.sleep(3) + time.sleep(1) server_restart = mp.Event() From b14b9f3b3a48f5a2544864783de7129e8ae5bfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 11:13:40 +0200 Subject: [PATCH 133/134] add get settings endpoint with only values you can also save --- lnbits/app.py | 6 +---- lnbits/extensions/admin/crud.py | 12 +++++++++- lnbits/extensions/admin/models.py | 6 ++++- .../admin/templates/admin/index.html | 22 +++++++++++++++---- lnbits/extensions/admin/views_api.py | 7 +++++- lnbits/server.py | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 49ad8d77..959a8168 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -62,10 +62,6 @@ def create_app() -> FastAPI: CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) - # TODO: why those 2? - g().config = settings - g().base_url = f"http://{settings.host}:{settings.port}" - app.add_middleware(GZipMiddleware, minimum_size=1000) register_startup(app) @@ -174,7 +170,7 @@ def register_assets(app: FastAPI): @app.on_event("startup") async def vendored_assets_variable(): - if g().config.debug: + if settings.debug: g().VENDORED_JS = map(url_for_vendored, get_js_vendored()) g().VENDORED_CSS = map(url_for_vendored, get_css_vendored()) else: diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cc937b5e..2ce91612 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables from lnbits.tasks import internal_invoice_queue from . import db -from .models import UpdateSettings +from .models import AdminSettings, UpdateSettings async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) +async def get_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 + + async def update_settings(data: UpdateSettings) -> Settings: fields = [] for key, value in data.dict(exclude_none=True).items(): diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index d9d2b22f..31811659 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from fastapi import Query from pydantic import BaseModel @@ -55,3 +55,7 @@ class UpdateSettings(BaseModel): opennode_key: str = Query(None) spark_url: str = Query(None) spark_token: str = Query(None) + + +class AdminSettings(UpdateSettings): + lnbits_allowed_funding_sources: Optional[List[str]] diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 7d268301..10391261 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -314,11 +314,8 @@ } }, created: function () { - this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data + this.getSettings() this.balance = +'{{ balance|safe }}' - this.formData = _.clone(this.settings) //model - this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { @@ -416,6 +413,23 @@ }) }) }, + getSettings() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/settings/?usr=' + this.g.user.id, + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.settings = response.data + this.formData = _.clone(this.settings) + this.updateFundingData() + console.log(this.settings) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, updateSettings() { let data = { ...this.formData diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 63ed5b3c..57d62ed4 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings from lnbits.server import server_restart -from .crud import delete_settings, update_settings, update_wallet_balance +from .crud import delete_settings, get_settings, update_settings, update_wallet_balance @admin_ext.get( @@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]: return {"status": "Success"} +@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)]) +async def api_get_settings() -> UpdateSettings: + return await get_settings() + + @admin_ext.put( "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) diff --git a/lnbits/server.py b/lnbits/server.py index eb7c12b1..ecf7ff62 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,4 +1,5 @@ import uvloop + uvloop.install() import multiprocessing as mp From 8fbf10909961c13b6c39008ae4ce3aaa750b9a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 25 Oct 2022 09:25:37 +0200 Subject: [PATCH 134/134] fix poetry lockCCC --- poetry.lock | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 45968390..bbce3c5c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -258,24 +258,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "environs" -version = "9.3.3" -description = "simplified environment variable parsing" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - [[package]] name = "fastapi" version = "0.78.0" @@ -1051,7 +1033,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42" +content-hash = "e798b36b5941b43ee249bc196fcfb28d8ee712947336d21467651c672ba0106b" [metadata.files] aiofiles = [ @@ -1307,10 +1289,6 @@ enum34 = [ {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] -environs = [ - {file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"}, - {file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"}, -] fastapi = [ {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},