diff --git a/dev.compose.yaml b/dev.compose.yaml index dd523fc..7515943 100644 --- a/dev.compose.yaml +++ b/dev.compose.yaml @@ -7,13 +7,13 @@ x-log-config: &log-config max-file: "5" volumes: lws: - lws-web: + lwsadmin: mymonero: services: - # lws-web: - # container_name: lws-web + # lwsadmin: + # container_name: lwsadmin # build: - # context: lws-web + # context: lwsadmin # dockerfile: Dockerfile # restart: unless-stopped # depends_on: @@ -27,30 +27,30 @@ services: # ports: # - 127.0.0.1:5000:5000 # volumes: - # - lws-web:/srv/lws-web + # - lwsadmin:/srv/lwsadmin # user: "1000:1000" # command: # ./.venv/bin/poetry run start # <<: *log-config - mymonero-web: - container_name: mymonero-web - build: - context: mymonero-web-js - dockerfile: Dockerfile - restart: unless-stopped - environment: - MYMONERO_WEB_NETTYPE: 0 - MYMONERO_WEB_SERVER_URL: localhost:8080 - MYMONERO_WEB_APP_NAME: MyMonero-SelfHosted - depends_on: - - monero-lws - expose: - - 80/tcp - ports: - - 127.0.0.1:8000:80 - volumes: - - mymonero:/app - <<: *log-config + # mymonero-web: + # container_name: mymonero-web + # build: + # context: mymonero-web-js + # dockerfile: Dockerfile + # restart: unless-stopped + # environment: + # MYMONERO_WEB_NETTYPE: 0 + # MYMONERO_WEB_SERVER_URL: localhost:8080 + # MYMONERO_WEB_APP_NAME: MyMonero-SelfHosted + # depends_on: + # - monero-lws + # expose: + # - 80/tcp + # ports: + # - 127.0.0.1:8000:80 + # volumes: + # - mymonero:/app + # <<: *log-config monero-lws: container_name: monero-lws build: diff --git a/lwsadmin/Dockerfile b/lwsadmin/Dockerfile index 02990eb..4ed87b0 100644 --- a/lwsadmin/Dockerfile +++ b/lwsadmin/Dockerfile @@ -2,19 +2,19 @@ FROM ubuntu:22.04 RUN apt-get update RUN apt-get install -y python3 python3-venv -WORKDIR /srv/lws-web +WORKDIR /srv/lwsadmin RUN adduser \ --system \ --shell /bin/bash \ - --gecos 'lws-web' \ + --gecos 'lwsadmin' \ --group \ --disabled-password \ - --home /srv/lws-web \ + --home /srv/lwsadmin \ --uid 1000 \ - lws-web + lwsadmin COPY . . -RUN chown -R lws-web:lws-web . -USER lws-web +RUN chown -R lwsadmin:lwsadmin . +USER lwsadmin RUN python3 -m venv .venv RUN .venv/bin/pip install poetry RUN .venv/bin/poetry install \ No newline at end of file diff --git a/lwsadmin/lws/helpers.py b/lwsadmin/lws/helpers.py index 2e12001..7ed8002 100644 --- a/lwsadmin/lws/helpers.py +++ b/lwsadmin/lws/helpers.py @@ -1,13 +1,53 @@ import requests +from lws.models import User from lws import config +# accept_requests: {"type": "import"|"create", "addresses":[...]} +# add_account: {"address": ..., "key": ...} +# list_accounts: {} +# list_requests: {} +# modify_account_status: {"status": "active"|"hidden"|"inactive", "addresses":[...]} +# reject_requests: {"type": "import"|"create", "addresses":[...]} +# rescan: {"height":..., "addresses":[...]} +# webhook_add: {"type":"tx-confirmation", "address":"...", "url":"...", ...} with optional fields: +# token: A string to be returned when the webhook is triggered +# payment_id: 16 hex characters representing a unique identifier for a transaction +# webhook_delete: {"addresses":[...]} +# webhook_delete_uuid: {"event_ids": [...]} +# webhook_list: {} + + class LWS: - def __init__(self, admin_key): + def __init__(self): + pass + + def init(self, admin_key): self.admin_key = admin_key - def list_accounts(self): + def get_wallet(self, address: str) -> dict: + try: + res = self.list_accounts() + for _status in res: + for _wallet in res[_status]: + if _wallet["address"] == address: + _wallet["status"] = _status + return _wallet + return {} + except Exception as e: + print(f"Failed to check wallet active: {e}") + return {} + + def exists(self, address: str) -> bool: + try: + res = self.get_wallet(address) + return False if res == {} else True + except Exception as e: + print(f"Failed to check wallet active: {e}") + return False + + def list_accounts(self) -> dict: endpoint = f"{config.LWS_ADMIN_URL}/list_accounts" data = {"auth": self.admin_key} try: @@ -20,7 +60,20 @@ class LWS: print(f"Failed to list accounts: {e}") return {} - def get_address_txs(self, address, view_key): + def list_requests(self) -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/list_requests" + data = {"auth": self.admin_key} + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to list accounts: {e}") + return {} + + def get_address_txs(self, address: str, view_key: str) -> dict: endpoint = f"{config.LWS_URL}/get_address_txs" data = { "address": address, @@ -34,4 +87,103 @@ class LWS: return {} except Exception as e: print(f"Failed to get wallet info {address}: {e}") - return False + return {} + + def add_wallet(self, address: str, view_key: str) -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/add_account" + data = { + "auth": self.admin_key, + "params": { + "address": address, + "key": view_key + } + } + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to add wallet {address}: {e}") + return {} + + def modify_wallet(self, address: str, active: bool) -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/modify_account_status" + status = "active" if active else "inactive" + data = { + "auth": self.admin_key, + "params": { + "addresses": [address], + "status": status + } + } + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to modify wallet {address}: {e}") + return {} + + def accept_request(self, address: str, req_type: str="create") -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/accept_requests" + data = { + "auth": self.admin_key, + "params": { + "addresses": [address], + "type": req_type + } + } + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to accept request wallet {address}: {e}") + return {} + + def reject_request(self, address: str, req_type: str="create") -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/reject_requests" + data = { + "auth": self.admin_key, + "params": { + "addresses": [address], + "type": req_type + } + } + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to reject request wallet {address}: {e}") + return {} + + def rescan(self, address: str, height: int) -> dict: + endpoint = f"{config.LWS_ADMIN_URL}/rescan" + data = { + "auth": self.admin_key, + "params": { + "addresses": [address], + "height": height + } + } + try: + req = requests.post(endpoint, json=data, timeout=5) + req.raise_for_status() + if req.ok: + return req.json() + return {} + except Exception as e: + print(f"Failed to rescan wallet {address}: {e}") + return {} + + +lws = LWS() diff --git a/lwsadmin/lws/models.py b/lwsadmin/lws/models.py index cfb7dbe..37e5808 100644 --- a/lwsadmin/lws/models.py +++ b/lwsadmin/lws/models.py @@ -1,10 +1,7 @@ from datetime import datetime -import requests from peewee import * -from lws import config - db = SqliteDatabase('lws.db') @@ -20,186 +17,4 @@ class User(Model): database = db -class Wallet(Model): - name = CharField(unique=True) - description = TextField(default="") - address = CharField(unique=True) - view_key = CharField(unique=True) - restore_height = IntegerField() - added = BooleanField(default=False) - date = DateTimeField(default=datetime.utcnow) - date_added = DateTimeField(null=True) - user = ForeignKeyField(User, backref="wallets") - - def is_active(self): - endpoint = f"{config.LWS_ADMIN_URL}/list_accounts" - data = { - "auth": self.user.view_key, - "params": {} - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - res = req.json() - for _status in res: - for _wallet in res[_status]: - if _wallet["address"] == self.address: - if _status == "active": - return True - return False - return False - except Exception as e: - print(f"Failed to list wallets: {e}") - return False - - def check_wallet_lws(self): - endpoint = f"{config.LWS_ADMIN_URL}/list_accounts" - data = { - "auth": self.user.view_key, - "params": {} - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - res = req.json() - for _status in res: - for _wallet in res[_status]: - if _wallet["address"] == self.address: - self.added = True - self.save() - return True - return False - return False - except Exception as e: - print(f"Failed to list wallets: {e}") - return False - - def add_wallet_lws(self): - if self.check_wallet_lws() and self.added is False: - self.added = True - self.date_added = datetime.utcnow() - self.save() - return True - endpoint = f"{config.LWS_ADMIN_URL}/add_account" - data = { - "auth": self.user.view_key, - "params": { - "address": self.address, - "key": self.view_key - } - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - self.added = True - self.date_added = datetime.utcnow() - self.save() - return True - return False - except Exception as e: - print(f"Failed to add wallet {self.address}: {e}") - return False - - def set_active(self, status): - endpoint = f"{config.LWS_ADMIN_URL}/modify_account_status" - _status = "" - if status: - _status = "active" - else: - _status = "inactive" - data = { - "auth": self.user.view_key, - "params": { - "addresses": [self.address], - "status": _status - } - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - return True - return False - except Exception as e: - print(f"Failed to add wallet {self.address}: {e}") - return False - - def enable_wallet_lws(self): - endpoint = f"{config.LWS_ADMIN_URL}/modify_account_status" - data = { - "auth": self.user.view_key, - "params": { - "addresses": [self.address], - "status": "inactive" - } - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - return True - return False - except Exception as e: - print(f"Failed to add wallet {self.address}: {e}") - return False - - def get_wallet_info(self): - endpoint = f"{config.LWS_URL}/get_address_info" - data = { - "address": self.address, - "view_key": self.view_key - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - return req.json() - return {} - except Exception as e: - print(f"Failed to get wallet info {self.address}: {e}") - return False - - def get_wallet_txes(self): - endpoint = f"{config.LWS_URL}/get_address_txs" - data = { - "address": self.address, - "view_key": self.view_key - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - return req.json() - return {} - except Exception as e: - print(f"Failed to get wallet info {self.address}: {e}") - return False - - def rescan(self): - endpoint = f"{config.LWS_ADMIN_URL}/rescan" - data = { - "auth": self.user.view_key, - "params": { - "height": self.restore_height, - "addresses": [self.address] - } - } - try: - req = requests.post(endpoint, json=data, timeout=5) - req.raise_for_status() - if req.ok: - print(req.content) - return True - return False - except Exception as e: - print(f"Failed to add wallet {self.address}: {e}") - return False - - class Meta: - database = db - - -db.create_tables([User, Wallet]) \ No newline at end of file +db.create_tables([User]) diff --git a/lwsadmin/lws/routes/auth.py b/lwsadmin/lws/routes/auth.py index b200df7..b3c39f5 100644 --- a/lwsadmin/lws/routes/auth.py +++ b/lwsadmin/lws/routes/auth.py @@ -87,6 +87,7 @@ async def setup(): address=address, view_key=view_key ) + admin.save() login_user(AuthUser(admin.id)) return redirect("/") return await render_template("setup.html") diff --git a/lwsadmin/lws/routes/meta.py b/lwsadmin/lws/routes/meta.py index 07fb901..ad663e9 100644 --- a/lwsadmin/lws/routes/meta.py +++ b/lwsadmin/lws/routes/meta.py @@ -2,34 +2,28 @@ import monero.seed from quart import Blueprint, redirect, request, flash, render_template from quart_auth import login_required -from lws.models import Wallet, User -from lws.helpers import LWS +from lws.models import User +from lws.helpers import lws from lws import config bp = Blueprint("meta", "meta") @bp.route("/") -@login_required async def index(): admin = User.select().first() if not admin: await flash("must setup admin first") return redirect("/setup") - lws = LWS(admin.view_key) + lws.init(admin.view_key) accounts = lws.list_accounts() - data = {} - for status in accounts: - if status == "hidden": - continue - for account in accounts[status]: - account["wallet"] = Wallet.select().where(Wallet.address ** account["address"]).order_by(Wallet.date.asc()).first() - account["status"] = status - data[account["address"]] = account + del accounts["hidden"] + requests = lws.list_requests() return await render_template( "index.html", config=config, - data=data + accounts=accounts, + requests=requests ) diff --git a/lwsadmin/lws/routes/wallet.py b/lwsadmin/lws/routes/wallet.py index 6a02819..1273182 100644 --- a/lwsadmin/lws/routes/wallet.py +++ b/lwsadmin/lws/routes/wallet.py @@ -2,29 +2,17 @@ import monero.address from quart import Blueprint, render_template, request, flash, redirect from quart_auth import login_required, current_user -from lws.models import Wallet, User +from lws.helpers import lws bp = Blueprint('wallet', 'wallet') -# @bp.route("/") - -# accept_requests: {"type": "import"|"create", "addresses":[...]} -# add_account: {"address": ..., "key": ...} -# list_accounts: {} -# list_requests: {} -# modify_account_status: {"status": "active"|"hidden"|"inactive", "addresses":[...]} -# reject_requests: {"type": "import"|"create", "addresses":[...]} -# rescan: {"height":..., "addresses":[...]} - @bp.route("/wallet/add", methods=["GET", "POST"]) @login_required async def add(): form = await request.form if form: - name = form.get("name", "") - description = form.get("description", "") address = form.get("address", "") view_key = form.get("view_key", "") restore_height = form.get("restore_height", 0) @@ -44,63 +32,63 @@ async def add(): if not valid_view_key: await flash("Invalid view key provided for address") return redirect("/wallet/add") - wallet = Wallet.create( - name=name, - description=description, - address=address, - view_key=view_key, - restore_height=restore_height, - user=User.get(current_user.auth_id) - ) - if not name: - wallet.name = f"wallet-{id}" - wallet.add_wallet_lws() + lws.add_wallet(address, view_key) + lws.rescan(address, int(restore_height)) await flash("wallet added") - return redirect(f"/wallet/{wallet.id}/rescan") + return redirect(f"/") return await render_template("wallet/add.html") -@bp.route("/wallet/") -@login_required -async def show(id): - wallet = Wallet.select().where(Wallet.id == id).first() - if not wallet: - await flash("wallet does not exist") - return redirect("/") - return await render_template( - "wallet/show.html", - wallet=wallet - ) - -@bp.route("/wallet//rescan") +# @bp.route("/wallet/") +# @login_required +# async def show(id): +# wallet = Wallet.select().where(Wallet.id == id).first() +# if not wallet: +# await flash("wallet does not exist") +# return redirect("/") +# return await render_template( +# "wallet/show.html", +# wallet=wallet +# ) + +# @bp.route("/wallet//rescan") +# @login_required +# async def rescan(id): +# wallet = Wallet.select().where(Wallet.id == id).first() +# if not wallet: +# await flash("wallet does not exist") +# return redirect("/") +# wallet.rescan() +# return redirect(f"/wallet/{id}") + + +@bp.route("/wallet/
/disable") @login_required -async def rescan(id): - wallet = Wallet.select().where(Wallet.id == id).first() - if not wallet: - await flash("wallet does not exist") - return redirect("/") - wallet.rescan() - return redirect(f"/wallet/{id}") +async def disable(address): + lws.modify_wallet(address, False) + await flash(f"{address} disabled in LWS") + return redirect(f"/") -@bp.route("/wallet//disable") +@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 disable(id): - wallet = Wallet.select().where(Wallet.id == id).first() - if not wallet: - await flash("wallet does not exist") - return redirect("/") - wallet.set_active(False) - return redirect(f"/wallet/{id}") +async def accept(address): + lws.accept_request(address) + await flash(f"{address} accepted") + return redirect(f"/") -@bp.route("/wallet//enable") +@bp.route("/wallet/
/reject") @login_required -async def enable(id): - wallet = Wallet.select().where(Wallet.id == id).first() - if not wallet: - await flash("wallet does not exist") - return redirect("/") - wallet.set_active(True) - return redirect(f"/wallet/{id}") +async def reject(address): + lws.reject_request(address) + await flash(f"{address} rejected") + return redirect(f"/") diff --git a/lwsadmin/lws/templates/index.html b/lwsadmin/lws/templates/index.html index c0763c7..e4b9a61 100644 --- a/lwsadmin/lws/templates/index.html +++ b/lwsadmin/lws/templates/index.html @@ -10,38 +10,50 @@ Status - Wallet + Action Address - Description Height - {% for address in data %} - {% set _data = data[address] %} + + {% for status in accounts %} + {% for account in accounts[status] %} - - {{ _data['status'] | upper }} + + {{ status | upper }} - {% if _data['wallet'] %} - {{ data[address]['wallet'].name }} + {% if status == 'active' %} + disable {% else %} - ADD - {% endif %} + enable + {% endif %} - {{ address | shorten }} + {{ account['address'] | shorten }} + {{ account['scan_height'] }} + + {% endfor %} + {% endfor %} + + {% for status in requests %} + {% for request in requests[status] %} + - {% if _data['wallet'] %} - {{ _data['wallet'].description or "-" }} - {% else %} - - - {% endif %} + + PENDING + + + + approve | + reject - {{ data[address]['scan_height'] }} + {{ request['address'] | shorten }} + {{ request['start_height'] }} + {% endfor %} {% endfor %} diff --git a/lwsadmin/lws/templates/wallet/add.html b/lwsadmin/lws/templates/wallet/add.html index c981aa4..32be7c7 100644 --- a/lwsadmin/lws/templates/wallet/add.html +++ b/lwsadmin/lws/templates/wallet/add.html @@ -4,10 +4,6 @@ Go Back

Add a Wallet

- - - -