You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
5.0 KiB
Python

from os import environ as env
from secrets import token_urlsafe
1 year ago
import asyncio
import monero.address
from dotenv import load_dotenv
from quart import Quart, render_template, redirect, request, flash
from quart_auth import (
AuthUser, AuthManager, current_user, login_required, login_user, logout_user, Unauthorized
)
from quart_bcrypt import Bcrypt
from quart_session import Session
1 year ago
from lws.models import Admin, Wallet
1 year ago
load_dotenv()
1 year ago
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
Session(app)
AuthManager(app)
bcrypt = Bcrypt(app)
1 year ago
@app.route("/")
@login_required
1 year ago
async def index():
admin_exists = Admin.select().first()
if not admin_exists:
return redirect("/setup")
1 year ago
return await render_template("index.html")
@app.route("/login", methods=["GET", "POST"])
async def login():
if not Admin.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 = Admin.select().where(Admin.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("/setup", methods=["GET", "POST"])
async def setup():
if Admin.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 = Admin.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")
# bcrypt.check_password_hash(pw_hash, "hunter2") # returns True
# / - 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
@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()