get base lws api calls working

master
lza_menace 1 year ago
parent eaa4ec395a
commit adaae35cc0

@ -7,4 +7,7 @@ run:
.venv/bin/poetry run start
clean:
rm -rf .venv poetry.lock
rm -rf .venv poetry.lock
shell:
.venv/bin/python3

@ -1,30 +1,25 @@
from os import environ as env
from secrets import token_urlsafe
import asyncio
import requests
import monero.address
from dotenv import load_dotenv
from quart import Quart, render_template, redirect, request, flash
from quart import Quart, render_template, redirect, request, flash, jsonify
from quart_auth import (
AuthUser, AuthManager, current_user, login_required, login_user, logout_user, Unauthorized
AuthUser, AuthManager, login_required, login_user, current_user, Unauthorized
)
from quart_bcrypt import Bcrypt
from quart_session import Session
from lws.models import Admin, Wallet
load_dotenv()
from lws.models import User, Wallet
from lws import config
app = Quart(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.config["DEBUG"] = 1 == env.get("DEBUG", 1)
app.config["QUART_ENV"] = env.get("QUART_ENV", "development")
app.config["SECRET_KEY"] = env.get("SECRET_KEY", token_urlsafe(12))
app.config["SESSION_URI"] = env.get("SESSION_URI", "redis://127.0.0.1:6379")
app.config["SESSION_TYPE"] = "redis"
app.config["SESSION_PROTECTION"] = True
app.config["QUART_AUTH_DURATION"] = 60 * 60 # 1 hour
app.config["TEMPLATES_AUTO_RELOAD"] = config.TEMPLATES_AUTO_RELOAD
app.config["DEBUG"] = config.DEBUG
app.config["QUART_ENV"] = config.QUART_ENV
app.config["SECRET_KEY"] = config.SECRET_KEY
app.config["SESSION_URI"] = config.SESSION_URI
app.config["SESSION_TYPE"] = config.SESSION_TYPE
app.config["SESSION_PROTECTION"] = config.SESSION_PROTECTION
app.config["QUART_AUTH_DURATION"] = config.QUART_AUTH_DURATION
Session(app)
AuthManager(app)
bcrypt = Bcrypt(app)
@ -33,15 +28,28 @@ bcrypt = Bcrypt(app)
@app.route("/")
@login_required
async def index():
admin_exists = Admin.select().first()
admin_exists = User.select().first()
if not admin_exists:
return redirect("/setup")
return await render_template("index.html")
@app.route("/debug")
async def debug():
admin = User.get(1)
data = {
"auth": admin.view_key,
"params": {
"height": 2836540,
"addresses": ["46pSfwbyukuduh13pqUo7R6S5W8Uk2EnqcKuPg4T9KaoHVSFQ5Qb33nBEN6xVxpeKG1TgYoxo4GxhJm2JFYN1sHJBEH1MwY"]
}
}
r = requests.post("http://127.0.0.1:8081/rescan", json=data)
return jsonify(r.json())
@app.route("/login", methods=["GET", "POST"])
async def login():
if not Admin.select().first():
if not User.select().first():
await flash("must setup first")
return redirect("/setup")
form = await request.form
@ -54,7 +62,7 @@ async def login():
if not password:
await flash("must provide a password")
return redirect("/login")
user = Admin.select().where(Admin.username == username).first()
user = User.select().where(User.username == username).first()
if not user:
await flash("this user does not exist")
return redirect("/login")
@ -69,7 +77,7 @@ async def login():
@app.route("/setup", methods=["GET", "POST"])
async def setup():
if Admin.select().first():
if User.select().first():
await flash("Setup already completed")
return redirect("/")
form = await request.form
@ -101,7 +109,7 @@ async def setup():
await flash("Invalid view key provided for address")
return redirect("/setup")
pw_hash = bcrypt.generate_password_hash(password).decode("utf-8")
admin = Admin.create(
admin = User.create(
username=username,
password=pw_hash,
address=address,
@ -111,7 +119,69 @@ async def setup():
return redirect("/")
return await render_template("setup.html")
# bcrypt.check_password_hash(pw_hash, "hunter2") # returns True
@app.route("/wallet/add", methods=["GET", "POST"])
async def wallet_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)
valid_view_key = False
if not address:
await flash("must provide an LWS admin address")
return redirect("/wallet/add")
if not view_key:
await flash("must provide an LWS admin view_key")
return redirect("/wallet/add")
try:
_a = monero.address.Address(address)
valid_view_key = _a.check_private_view_key(view_key)
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")
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()
await flash("wallet added")
return redirect(f"/wallet/{wallet.id}")
return await render_template("wallet/add.html")
@app.route("/wallet/<id>")
@login_required
async def wallet_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
)
@app.route("/wallet/<id>/rescan")
@login_required
async def wallet_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}")
# / - redirect to /setup if user not setup, to /login if not authenticated
# /setup - first time setup user account, encrypted session
@ -121,45 +191,17 @@ async def setup():
# /wallet/:id/remove - remove a wallet from LWS
# /wallet/:id/resync - resync wallet
# get_address_info
# get_address_txs
# get_random_outs
# get_unspent_outs
# import_request
# submit_raw_tx
@app.errorhandler(Unauthorized)
async def redirect_to_login(*_):
return redirect("/login")
# @app.get("/replay")
# async def replay():
# data = list()
# messages = Message.select().order_by(Message.datestamp.asc()).limit(100)
# for m in messages:
# data.append({
# "message": m.message,
# "datestamp": m.datestamp
# })
# return jsonify(data)
# @app.websocket("/ws")
# async def ws() -> None:
# try:
# task = asyncio.ensure_future(_receive())
# async for message in broker.subscribe():
# await websocket.send(message)
# finally:
# task.cancel()
# await task
# async def _receive() -> None:
# while True:
# message = await websocket.receive()
# if len(message) > 120:
# print("too long, skipping")
# break
# await broker.publish(message)
def run() -> None:
app.run()

@ -0,0 +1,18 @@
from os import environ as env
from secrets import token_urlsafe
from dotenv import load_dotenv
load_dotenv()
TEMPLATES_AUTO_RELOAD = True
DEBUG = 1 == env.get("DEBUG", 1)
QUART_ENV = env.get("QUART_ENV", "development")
SECRET_KEY = env.get("SECRET_KEY", token_urlsafe(12))
SESSION_URI = env.get("SESSION_URI", "redis://127.0.0.1:6379")
SESSION_TYPE = "redis"
SESSION_PROTECTION = True
QUART_AUTH_DURATION = 60 * 60 # 1 hour
# LWS
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")

@ -0,0 +1,3 @@
import requests

@ -1,13 +1,16 @@
from datetime import datetime
import requests
from peewee import *
from playhouse.sqliteq import SqliteQueueDatabase
from lws import config
db = SqliteQueueDatabase('data/lws-web.db')
class Admin(Model):
class User(Model):
username = CharField()
password = CharField()
address = CharField()
@ -19,15 +22,99 @@ class Admin(Model):
class Wallet(Model):
name = CharField()
description = TextField()
address = CharField()
view_key = CharField()
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 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):
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 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 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(r.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([Admin, Wallet])
db.create_tables([User, Wallet])

@ -2,7 +2,7 @@
<li>{{ message }}</li>
{% endfor %}
<br />
<div id="setup">
<div id="login">
<form method="post">
<label for="username">Username</label>
<input type="text" name="username" />

@ -0,0 +1,28 @@
{% for message in get_flashed_messages() %}
<li>{{ message }}</li>
{% endfor %}
<br />
<div id="wallet_add">
<form method="post">
<label for="name">Name</label>
<input type="text" name="name" />
<label for="description">Description</label>
<input type="text" name="description" />
<label for="address">Address</label>
<input type="text" name="address" />
<label for="view_key">View Key</label>
<input type="text" name="view_key" />
<label for="restore_height">Restore Height</label>
<input type="number" name="restore_height" />
<button type="submit">Send</button>
</form>
</div>
<style>
input {
display: block;
padding-bottom: .5em;
margin-bottom: 1em;
}
</style>

@ -0,0 +1,10 @@
<p>{{ wallet.name }}</p>
<p>{{ wallet.description }}</p>
<p>{{ wallet.date }}</p>
<p>{{ wallet.restore_height }}</p>
<p>{{ wallet.address }}</p>
<pre>
{{ wallet.get_wallet_info() }}
</pre>
<a href="{{ url_for('wallet_rescan', id=wallet.id) }}"><button>rescan</button></a>
Loading…
Cancel
Save