diff --git a/docker-compose.yaml b/docker-compose.yaml index e436615..756cf62 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,9 +7,44 @@ x-log-config: &log-config max-file: "5" volumes: lws: - lwsadmin: mymonero: + lwsadmin: services: + lwsadmin: + container_name: lwsadmin + image: lalanza808/lwsadmin:latest + restart: unless-stopped + depends_on: + - monero-lws + environment: + LWS_URL: http://monero-lws:8080 + LWS_ADMIN_URL: http://monero-lws:8081 + QUART_ENV: production + HOST: 0.0.0.0 + DEBUG: 0 + SERVER_NAME: ${SERVER_NAME:-127.0.0.1:5000} + SECRET_KEY: ${SECRET_KEY:-thisisasecret} + volumes: + - lwsadmin:/srv/lwsadmin/data + user: "1000:1000" + command: + ./.venv/bin/poetry run start + ports: + - 127.0.0.1:5000:5000 + <<: *log-config + mymonero-web: + container_name: mymonero-web + image: lalanza808/mymonero-web-js:latest + restart: unless-stopped + environment: + MYMONERO_WEB_NETTYPE: 0 + MYMONERO_WEB_SERVER_URL: http://localhost:8080/ + MYMONERO_WEB_APP_NAME: LZAXMR + ports: + - 127.0.0.1:9110:80 + volumes: + - mymonero:/app + <<: *log-config monero-lws: container_name: monero-lws image: lalanza808/monero-lws:develop diff --git a/lwsadmin/Dockerfile b/lwsadmin/Dockerfile index 4ed87b0..885e8a2 100644 --- a/lwsadmin/Dockerfile +++ b/lwsadmin/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:22.04 -RUN apt-get update +RUN apt-get update -y RUN apt-get install -y python3 python3-venv WORKDIR /srv/lwsadmin RUN adduser \ diff --git a/lwsadmin/lws/__init__.py b/lwsadmin/lws/__init__.py index e69de29..44b7576 100644 --- a/lwsadmin/lws/__init__.py +++ b/lwsadmin/lws/__init__.py @@ -0,0 +1 @@ +from lws.factory import create_app \ No newline at end of file diff --git a/lwsadmin/lws/config.py b/lwsadmin/lws/config.py index c6c837c..ec99c9e 100644 --- a/lwsadmin/lws/config.py +++ b/lwsadmin/lws/config.py @@ -18,4 +18,8 @@ QUART_AUTH_DURATION = int(env.get('QUART_AUTH_DURATION', 60 * 60)) # 1 hour LWS_URL = env.get("LWS_URL", "http://127.0.0.1:8080") LWS_ADMIN_URL = env.get("LWS_ADMIN_URL", "http://127.0.0.1:8081") -# \ No newline at end of file +# Monerod +MONEROD_URL = env.get("MONEROD_URL", "http://singapore.node.xmr.pm:18089") + +# MyMonero +MYMONERO_URL = env.get("MYMONERO_URL", "http://localhost:9110") \ No newline at end of file diff --git a/lwsadmin/lws/factory.py b/lwsadmin/lws/factory.py index ee76031..b93790d 100644 --- a/lwsadmin/lws/factory.py +++ b/lwsadmin/lws/factory.py @@ -20,16 +20,21 @@ def create_app(): @app.before_serving async def startup(): - from lws.routes import auth, wallet, meta + from lws.routes import auth, wallet, meta, htmx from lws import filters app.register_blueprint(filters.bp) app.register_blueprint(auth.bp) app.register_blueprint(meta.bp) app.register_blueprint(wallet.bp) + app.register_blueprint(htmx.bp) @app.errorhandler(Unauthorized) async def redirect_to_login(*_): - return redirect(f"/login?next={request.path}") + if request.path == "/": + return redirect(f"/login?next={request.path}") + else: + return f"

you need to authenticate first

login" + return app bcrypt = Bcrypt(create_app()) \ No newline at end of file diff --git a/lwsadmin/lws/filters.py b/lwsadmin/lws/filters.py index bd70a3a..a840e36 100644 --- a/lwsadmin/lws/filters.py +++ b/lwsadmin/lws/filters.py @@ -1,6 +1,8 @@ from monero.numbers import from_atomic from quart import Blueprint +from lws.models import Wallet, get_random_words + bp = Blueprint('filters', 'filters') @@ -12,4 +14,12 @@ def atomic(amt): @bp.app_template_filter('shorten') def shorten(s): - return f"{s[:6]}...{s[-6:]}" \ No newline at end of file + return f"{s[:6]}...{s[-6:]}" + +@bp.app_template_filter('find_label') +def find_label(s): + w = Wallet.select().where(Wallet.address == s).first() + if w: + return w.label + else: + return get_random_words() \ No newline at end of file diff --git a/lwsadmin/lws/helpers.py b/lwsadmin/lws/helpers.py index 7ed8002..a5495a7 100644 --- a/lwsadmin/lws/helpers.py +++ b/lwsadmin/lws/helpers.py @@ -18,7 +18,6 @@ from lws import config # webhook_delete_uuid: {"event_ids": [...]} # webhook_list: {} - class LWS: def __init__(self): pass @@ -26,6 +25,19 @@ class LWS: def init(self, admin_key): self.admin_key = admin_key + def _init(self): + self.admin_key = User.select().first().view_key + + def get_address_info(self, address, view_key): + endpoint = f"{config.LWS_URL}/get_address_info" + data = { + "address": address, + "view_key": view_key + } + r = requests.post(endpoint, json=data, timeout=5) + r.raise_for_status() + return r.json() + def get_wallet(self, address: str) -> dict: try: res = self.list_accounts() @@ -108,9 +120,8 @@ class LWS: print(f"Failed to add wallet {address}: {e}") return {} - def modify_wallet(self, address: str, active: bool) -> dict: + def modify_wallet(self, address: str, status: str) -> dict: endpoint = f"{config.LWS_ADMIN_URL}/modify_account_status" - status = "active" if active else "inactive" data = { "auth": self.admin_key, "params": { diff --git a/lwsadmin/lws/models.py b/lwsadmin/lws/models.py index bb1c0cf..2f75dfe 100644 --- a/lwsadmin/lws/models.py +++ b/lwsadmin/lws/models.py @@ -1,9 +1,16 @@ +from random import choice from datetime import datetime from peewee import * +from monero.wordlists import English -db = SqliteDatabase('data/lws.db') +db = SqliteDatabase("data/lws.db") + + +def get_random_words(): + e = English().word_list + return f"{choice(e)}-{choice(e)}-{choice(e)}" class User(Model): @@ -17,4 +24,13 @@ class User(Model): database = db -db.create_tables([User]) +class Wallet(Model): + date = DateTimeField(default=datetime.utcnow) + address = CharField() + label = CharField(default=get_random_words, null=False) + + class Meta: + database = db + + +db.create_tables([User, Wallet]) diff --git a/lwsadmin/lws/routes/htmx.py b/lwsadmin/lws/routes/htmx.py new file mode 100644 index 0000000..fb4f15b --- /dev/null +++ b/lwsadmin/lws/routes/htmx.py @@ -0,0 +1,80 @@ +from quart import Blueprint, render_template, request +from monero.seed import Seed +from quart_auth import login_required + +from lws.models import User, Wallet +from lws.helpers import lws +from lws import config + +bp = Blueprint('htmx', 'htmx', url_prefix="/htmx") + + +@bp.route("/create_wallet") +async def create_wallet(): + """Creating a new wallet with newly generated seeds""" + seed = Seed() + return await render_template( + "htmx/create_wallet.html", + seed=seed.phrase, + address=seed.public_address(), + psk=seed.public_spend_key(), + pvk=seed.public_view_key(), + ssk=seed.secret_spend_key(), + svk=seed.secret_view_key() + ) + + +@bp.route("/import_wallet") +async def import_wallet(): + """Importing an existing wallet""" + return await render_template("htmx/import_wallet.html") + + +@bp.route("/label_wallet") +async def label_wallet(): + """Changing the label on a stored wallet""" + address = request.args.get("address") + label = request.args.get("label") + return await render_template( + "htmx/label_wallet.html", + address=address, + label=label + ) + + +@bp.route("/set_height") +async def set_height(): + """Setting a new height to scan from""" + address = request.args.get("address") + height = request.args.get("height") + return await render_template( + "htmx/set_height.html", + address=address, + height=height + ) + + +@bp.route("/show_wallets") +@login_required +async def show_wallets(): + """Showing all wallets in the database in a table""" + admin = User.select().first() + lws.init(admin.view_key) + accounts = lws.list_accounts() + if 'hidden' in accounts: + del accounts["hidden"] + # save wallets if they don't exist in the db + for status in accounts: + for account in accounts[status]: + w = Wallet.select().where(Wallet.address == account["address"]).first() + if not w: + w = Wallet( + address=account["address"] + ) + w.save() + requests = lws.list_requests() + return await render_template( + "htmx/show_wallets.html", + accounts=accounts, + requests=requests + ) diff --git a/lwsadmin/lws/routes/meta.py b/lwsadmin/lws/routes/meta.py index 54c6f81..b3d92ee 100644 --- a/lwsadmin/lws/routes/meta.py +++ b/lwsadmin/lws/routes/meta.py @@ -9,20 +9,15 @@ from lws import config bp = Blueprint("meta", "meta") + @bp.route("/") @login_required async def index(): admin = User.select().first() lws.init(admin.view_key) - accounts = lws.list_accounts() - if 'hidden' in accounts: - del accounts["hidden"] - requests = lws.list_requests() return await render_template( "index.html", - config=config, - accounts=accounts, - requests=requests + config=config ) diff --git a/lwsadmin/lws/routes/wallet.py b/lwsadmin/lws/routes/wallet.py index 25e535a..aec9293 100644 --- a/lwsadmin/lws/routes/wallet.py +++ b/lwsadmin/lws/routes/wallet.py @@ -1,11 +1,13 @@ import monero.address -from quart import Blueprint, render_template, request, flash, redirect -from quart_auth import login_required, current_user +from monero.seed import Seed +from quart import Blueprint, request, flash, redirect, url_for +from quart_auth import login_required from lws.helpers import lws +from lws.models import Wallet, get_random_words -bp = Blueprint('wallet', 'wallet') +bp = Blueprint("wallet", "wallet") @bp.route("/wallet/add", methods=["GET", "POST"]) @@ -13,74 +15,61 @@ bp = Blueprint('wallet', 'wallet') async def add(): form = await request.form if form: - address = form.get("address", "") - view_key = form.get("view_key", "") - restore_height = form.get("restore_height", 0) - valid_view_key = False - if not address: - await flash("must provide an address") - return redirect("/wallet/add") - if not view_key: - await flash("must provide a view_key") - return redirect("/wallet/add") + label = form.get("label") + seed = form.get("seed") + restore_height = form.get("restore_height", None) try: - _a = monero.address.Address(address) - valid_view_key = _a.check_private_view_key(view_key) + seed = Seed(seed) except ValueError: - await flash("Invalid Monero address") - return redirect("/wallet/add") - if not valid_view_key: - await flash("Invalid view key provided for address") - return redirect("/wallet/add") - lws.add_wallet(address, view_key) - lws.rescan(address, int(restore_height)) - await flash("wallet added") - return redirect(f"/") - return await render_template("wallet/add.html") + await flash("Invalid mnemonic seed") + return "" + lws._init() + address = str(seed.public_address()) + svk = str(seed.secret_view_key()) + lws.add_wallet(address, svk) + if restore_height != "-1": + lws.rescan(address, int(restore_height)) + w = Wallet( + address=seed.public_address(), + label=label if label else get_random_words() + ) + w.save() + return redirect(url_for("htmx.show_wallets")) -@bp.route("/wallet/rescan") +@bp.route("/wallet/
/rescan/") @login_required -async def rescan(): - address = request.args.get('address') - height = request.args.get('height') - if not address or not height: - await flash("you need to provide both address and height") - return redirect("/") - if lws.rescan(address, int(height)): - await flash(f"rescanning {address} from block {height}") - else: - await flash("probz") - return redirect(f"/") +async def rescan(address, height): + lws.rescan(address, int(height)) + return redirect(url_for("htmx.show_wallets")) -@bp.route("/wallet/
/disable") +@bp.route("/wallet/
/modify/") @login_required -async def disable(address): - lws.modify_wallet(address, False) - await flash(f"{address} disabled in LWS") - return redirect(f"/") +async def modify(address, status): + lws.modify_wallet(address, status) + return redirect(url_for("htmx.show_wallets")) -@bp.route("/wallet/
/enable") -@login_required -async def enable(address): - lws.modify_wallet(address, True) - await flash(f"{address} enabled in LWS") - return redirect(f"/") - @bp.route("/wallet/
/accept") @login_required async def accept(address): lws.accept_request(address) - await flash(f"{address} accepted") - return redirect(f"/") + return redirect(url_for("htmx.show_wallets")) @bp.route("/wallet/
/reject") @login_required async def reject(address): lws.reject_request(address) - await flash(f"{address} rejected") - return redirect(f"/") + return redirect(url_for("htmx.show_wallets")) + +@bp.route("/wallet/
/label/