import json from hashlib import scrypt from os import urandom from hmac import compare_digest import sqlite3 from flask import ( Flask, render_template, request, redirect, url_for, session, g, flash, ) from werkzeug.exceptions import HTTPException from captcha import Captcha from presets import GUEST_CAPTCHA, GUIDE_CAPTCHAS SCRYPT_PARAMS = {"n": 16384, "r": 8, "p": 1} HEX_CHARS = set("0123456789ABCDEF") def get_db(): if "db" not in g: g.db = sqlite3.connect("akasha.db", detect_types=sqlite3.PARSE_DECLTYPES) g.db.row_factory = sqlite3.Row return g.db def teardown(e=None): if (db := g.pop("db", None)) is not None: db.close() def has_captcha(captcha_id): return captcha_id in session.get("captcha", []) def add_captcha(captcha_id): if "captcha" not in session: session["captcha"] = [] if captcha_id not in session["captcha"]: session["captcha"].append(captcha_id) session.modified = True def make_app(): global LIGHT_URL app = Flask(__name__) with sqlite3.connect("akasha.db", detect_types=sqlite3.PARSE_DECLTYPES) as temp_db: cur = temp_db.cursor() cur.execute("SELECT value FROM config WHERE key='fsk'") app.config["SECRET_KEY"] = cur.fetchone()[0] cur.execute("SELECT value FROM config WHERE key='light'") LIGHT_URL = f"http://192.168.1.10/api/{cur.fetchone()[0]}" app.teardown_appcontext(teardown) return app app = make_app() @app.context_processor def context_processor(): return dict(has_captcha=has_captcha) @app.errorhandler(HTTPException) def handle_bad_request(e): if request.endpoint != "static": # TODO: Create an actual error page. if "username" not in session: flash("Guest access is temporarily restricted. Please log in to continue.") return redirect(url_for("login")) flash(f"Error {e.code}: {e.name}") flash("You have been redirected to the main page.") return redirect(url_for("index")) return e @app.route("/") def index(): db = get_db() cur = db.cursor() cur.execute("SELECT * FROM news ORDER BY id DESC LIMIT 1") results = cur.fetchone() return render_template("index.html", news=results) @app.route("/login", methods=["GET", "POST"]) def login(): db = get_db() if request.method == "POST": username = request.form.get("username") password = request.form.get("password") if not username or not password: flash("Error: Empty username or password.") return redirect(url_for("login")) if request.form.get("register") == "1": cur = db.cursor() cur.execute("SELECT 1 FROM users WHERE username=?", (username,)) if cur.fetchone(): flash("Error: User already exists.") return redirect(url_for("login")) salt = urandom(32) hash = scrypt(password.encode("utf-8"), salt=salt, **SCRYPT_PARAMS) cur.execute( "INSERT INTO users (username, hash, salt) VALUES (?, ?, ?)", (username, hash, salt), ) flash("Success: User registered.") db.commit() else: cur = db.cursor() cur.execute("SELECT hash, salt FROM users WHERE username=?", (username,)) results = cur.fetchone() if not results: flash("Error: Invalid username or password.") return redirect(url_for("login")) expected_hash, salt = results actual_hash = scrypt(password.encode("utf-8"), salt=salt, **SCRYPT_PARAMS) if not compare_digest(expected_hash, actual_hash): flash("Error: Invalid username or password.") return redirect(url_for("login")) session["username"] = username flash(f"Success: Logged in as {username}.") return redirect(url_for("index")) return render_template("login.html") @app.route("/guest") def guest_login(): if has_captcha(GUEST_CAPTCHA.id) and "username" not in session: session["username"] = "guest" flash(f"Success: Logged in as guest.") return redirect(url_for("index")) return render_template("guest.html", captcha=GUEST_CAPTCHA) @app.route("/eguide") def eguide(): return render_template("eguide.html", captchas=GUIDE_CAPTCHAS) @app.route("/logout") def logout(): if "username" in session: del session["username"] if "captcha" in session: del session["captcha"] flash("Success: You have been logged out.") return redirect(url_for("index")) @app.route("/hyde") def hydematch(): if "username" not in session: flash( "The Drosera Springs Wrestling Database is only available to logged-in users." ) return redirect(url_for("login")) db = get_db() cur = db.cursor() cur.execute("SELECT * FROM matches ORDER BY id DESC") results = cur.fetchall() return render_template("hyde.html", matches=results) @app.route("/news") def news(): db = get_db() cur = db.cursor() cur.execute("SELECT * FROM news ORDER BY id DESC") results = cur.fetchall() return render_template("news.html", news=results) @app.route("/captcha") def captcha(): size = request.args.get("size", "") label = request.args.get("label", "") lock = request.args.get("lock", "") answer = request.args.get("answer", "") captcha = Captcha.safe_validate(size, label, lock, answer) if captcha: add_captcha(captcha.id) return {"valid": True}, 200 return {"valid": False}, 400