From 166530eb0c985575a140124fb4c9e5a23ee9e5a7 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:03:32 +0000 Subject: [PATCH] Added old admin extension --- .env.example | 12 +- 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, 1013 insertions(+), 5 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 93b823250..68e25ad15 100644 --- a/.env.example +++ b/.env.example @@ -3,10 +3,12 @@ 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 @@ -91,4 +93,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 diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md new file mode 100644 index 000000000..277294592 --- /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 000000000..d5f26c90d --- /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 000000000..696617335 --- /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 000000000..cb8f9b5be --- /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 000000000..82d934cb6 --- /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 000000000..c38f17f48 --- /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 000000000..87cf09efa --- /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 000000000..5e17919c5 --- /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 000000000..2a61b6f55 --- /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