split out routes

master
lza_menace 1 year ago
parent 9d7827b28a
commit 04c74bb243

@ -1,238 +1,7 @@
import requests
import monero.address
import monero.seed
from quart import Quart, render_template, redirect, request, flash, jsonify
from quart_auth import (
AuthUser, AuthManager, login_required, login_user, logout_user, current_user, Unauthorized
)
from quart_bcrypt import Bcrypt
from quart_session import Session
from lws.factory import create_app
from lws.models import User, Wallet
from lws import config
app = create_app()
app = Quart(__name__)
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)
@app.route("/")
@login_required
async def index():
admin_exists = User.select().first()
if not admin_exists:
return redirect("/setup")
wallets = Wallet.select().order_by(Wallet.date.desc())
return await render_template(
"index.html",
config=config,
wallets=wallets
)
@app.route("/utils")
async def utils():
return await render_template("utils/index.html")
@app.route("/utils/mnemonic", methods=["GET", "POST"])
async def utils_mnemonic():
form = await request.form
if form:
seed = form.get("seed", "")
if not seed:
await flash("must provide mnemonic seed")
return redirect("/utils/mnemonic")
try:
s = monero.seed.Seed(seed)
return await render_template(
"utils/mnemonic.html",
results=s
)
except Exception as e:
print(f"failed to read mnemonic seed: {e}")
await flash("failed to parse mnemonic seed")
return await render_template("utils/mnemonic.html")
@app.route("/login", methods=["GET", "POST"])
async def login():
if not User.select().first():
await flash("must setup first")
return redirect("/setup")
form = await request.form
if form:
username = form.get("username", "")
password = form.get("password", "")
if not username:
await flash("must provide a username")
return redirect("/login")
if not password:
await flash("must provide a password")
return redirect("/login")
user = User.select().where(User.username == username).first()
if not user:
await flash("this user does not exist")
return redirect("/login")
pw_matches = bcrypt.check_password_hash(user.password, password)
if not pw_matches:
await flash("invalid password")
return redirect("/login")
login_user(AuthUser(user.id))
return redirect("/")
return await render_template("login.html")
@app.route("/logout")
async def logout():
if await current_user.is_authenticated:
logout_user()
return redirect("/")
@app.route("/setup", methods=["GET", "POST"])
async def setup():
if User.select().first():
await flash("Setup already completed")
return redirect("/")
form = await request.form
if form:
username = form.get("username", "")
password = form.get("password", "")
address = form.get("address", "")
view_key = form.get("view_key", "")
valid_view_key = False
if not username:
await flash("must provide a username")
return redirect("/setup")
if not password:
await flash("must provide a password")
return redirect("/setup")
if not address:
await flash("must provide an LWS admin address")
return redirect("/setup")
if not view_key:
await flash("must provide an LWS admin view_key")
return redirect("/setup")
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("/setup")
if not valid_view_key:
await flash("Invalid view key provided for address")
return redirect("/setup")
pw_hash = bcrypt.generate_password_hash(password).decode("utf-8")
admin = User.create(
username=username,
password=pw_hash,
address=address,
view_key=view_key
)
login_user(AuthUser(admin.id))
return redirect("/")
return await render_template("setup.html")
@app.route("/wallets")
@login_required
async def wallets():
wallets = Wallet.select().order_by(Wallet.id.asc())
return await render_template("wallet/list.html", wallets=wallets)
@app.route("/wallet/add", methods=["GET", "POST"])
@login_required
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
# /login - log into encrypted session
# /wallet/add - add a wallet to LWS
# /wallet/:id - show wallet details (balances, txes, etc)
# /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")
def run() -> None:
app.run()
if __name__ == '__main__':
app.run()

@ -0,0 +1,39 @@
from quart import Quart, redirect
from quart_auth import (
AuthManager, Unauthorized
)
from quart_bcrypt import Bcrypt
from quart_session import Session
from lws import config
def create_app():
app = Quart(__name__)
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)
@app.before_serving
async def startup():
from lws.routes import auth, wallet, meta
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.errorhandler(Unauthorized)
async def redirect_to_login(*_):
return redirect("/login")
return app
bcrypt = Bcrypt(create_app())

@ -0,0 +1,10 @@
from monero.numbers import from_atomic
from quart import Blueprint
bp = Blueprint('filters', 'filters')
@bp.app_template_filter('atomic')
def atomic(amt):
return float(from_atomic(amt))

@ -0,0 +1,89 @@
import monero.address
from quart import Blueprint, redirect, request, flash, render_template
from quart_auth import login_user, AuthUser, current_user, logout_user
from lws.factory import bcrypt
from lws.models import User
bp = Blueprint('auth', 'auth')
@bp.route("/login", methods=["GET", "POST"])
async def login():
if not User.select().first():
await flash("must setup first")
return redirect("/setup")
form = await request.form
if form:
username = form.get("username", "")
password = form.get("password", "")
if not username:
await flash("must provide a username")
return redirect("/login")
if not password:
await flash("must provide a password")
return redirect("/login")
user = User.select().where(User.username == username).first()
if not user:
await flash("this user does not exist")
return redirect("/login")
pw_matches = bcrypt.check_password_hash(user.password, password)
if not pw_matches:
await flash("invalid password")
return redirect("/login")
login_user(AuthUser(user.id))
return redirect("/")
return await render_template("login.html")
@bp.route("/logout")
async def logout():
if await current_user.is_authenticated:
logout_user()
return redirect("/")
@bp.route("/setup", methods=["GET", "POST"])
async def setup():
if User.select().first():
await flash("Setup already completed")
return redirect("/")
form = await request.form
if form:
username = form.get("username", "")
password = form.get("password", "")
address = form.get("address", "")
view_key = form.get("view_key", "")
valid_view_key = False
if not username:
await flash("must provide a username")
return redirect("/setup")
if not password:
await flash("must provide a password")
return redirect("/setup")
if not address:
await flash("must provide an LWS admin address")
return redirect("/setup")
if not view_key:
await flash("must provide an LWS admin view_key")
return redirect("/setup")
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("/setup")
if not valid_view_key:
await flash("Invalid view key provided for address")
return redirect("/setup")
pw_hash = bcrypt.generate_password_hash(password).decode("utf-8")
admin = User.create(
username=username,
password=pw_hash,
address=address,
view_key=view_key
)
login_user(AuthUser(admin.id))
return redirect("/")
return await render_template("setup.html")

@ -0,0 +1,49 @@
import monero.seed
from quart import Blueprint, redirect, request, flash, render_template
from quart_auth import login_required
from lws.factory import bcrypt
from lws.models import Wallet, User
from lws import config
bp = Blueprint('meta', 'meta')
@bp.route("/")
@login_required
async def index():
admin_exists = User.select().first()
if not admin_exists:
return redirect("/setup")
wallets = Wallet.select().order_by(Wallet.date.desc())
return await render_template(
"index.html",
config=config,
wallets=wallets
)
@bp.route("/utils")
async def utils():
return await render_template("utils/index.html")
@bp.route("/utils/mnemonic", methods=["GET", "POST"])
async def utils_mnemonic():
form = await request.form
if form:
seed = form.get("seed", "")
if not seed:
await flash("must provide mnemonic seed")
return redirect("/utils/mnemonic")
try:
s = monero.seed.Seed(seed)
return await render_template(
"utils/mnemonic.html",
results=s
)
except Exception as e:
print(f"failed to read mnemonic seed: {e}")
await flash("failed to parse mnemonic seed")
return await render_template("utils/mnemonic.html")

@ -0,0 +1,94 @@
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
bp = Blueprint('wallet', 'wallet')
@bp.route("/wallets")
@login_required
async def list():
wallets = Wallet.select().order_by(Wallet.id.asc())
return await render_template("wallet/list.html", wallets=wallets)
@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)
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")
@bp.route("/wallet/<id>")
@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/<id>/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}")
# / - redirect to /setup if user not setup, to /login if not authenticated
# /setup - first time setup user account, encrypted session
# /login - log into encrypted session
# /wallet/add - add a wallet to LWS
# /wallet/:id - show wallet details (balances, txes, etc)
# /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

@ -6,7 +6,7 @@
<p>LWS Admin: {{ config.LWS_ADMIN_URL }}</p>
<p>LWS RPC: {{ config.LWS_URL }}</p>
<p>{{ wallets.count() }} wallet{% if wallets.count() > 1 %}s{% endif %} being tracked</p>
<a href="/wallets" class="button outline primary">Manage Wallets</a>
<a href="{{ url_for('wallet.list') }}" class="button outline primary">Manage Wallets</a>
</div>
{% endblock %}

@ -2,10 +2,10 @@
{% block content %}
<h1>Manage Wallets</h1>
<a class="button outline primary" href="/wallet/add">Add a Wallet</a>
<a class="button outline primary" href="{{ url_for('wallet.add') }}">Add a Wallet</a>
<div id="wallets" class="content">
{% for wallet in wallets %}
<p>{{ wallet.id }} - <a href="{{ url_for('wallet_show', id=wallet.id) }}">{{ wallet.name }}</a> - {{ wallet.description }} - {{ wallet.date }}</p>
<p>{{ wallet.id }} - <a href="{{ url_for('wallet.show', id=wallet.id) }}">{{ wallet.name }}</a> - {{ wallet.description }} - {{ wallet.date }}</p>
{% endfor %}
</div>
<style>

@ -23,5 +23,5 @@
}
-->
<a href="{{ url_for('wallet_rescan', id=wallet.id) }}"><button>rescan</button></a>
<a href="{{ url_for('wallet.rescan', id=wallet.id) }}"><button>rescan</button></a>
{% endblock %}

23
lws-web/poetry.lock generated

@ -708,21 +708,21 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
[[package]]
name = "requests"
version = "2.29.0"
version = "2.30.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"},
{file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"},
{file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"},
{file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
@ -742,20 +742,21 @@ files = [
[[package]]
name = "urllib3"
version = "1.26.15"
version = "2.0.2"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
python-versions = ">=3.7"
files = [
{file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
{file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
{file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"},
{file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "varint"

@ -24,4 +24,4 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
start = "lws.app:run"
start = "lws.app:app.run"

Loading…
Cancel
Save