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 SCRYPT_PARAMS = {"n": 16384, "r": 8, "p": 1} HEX_CHARS = set("0123456789ABCDEF") GUEST_CAPTCHA = Captcha.from_string( 5, "m...cena....m...ebneb.clm", ".........................", True ) GUIDE_CAPTCHAS = { "Layer 01": [ Captcha.from_string(2, "a...", "...."), Captcha.from_string(2, "aaa.", "01.."), Captcha.from_string(2, "aaaa", "...."), ], "Layer 02": [ Captcha.from_string(2, "b...", "...."), Captcha.from_string(2, "b.b.", "0.1."), Captcha.from_string(2, "bbbb", "00.."), ], "Layer 03": [ Captcha.from_string(2, "c...", "...."), Captcha.from_string(2, "c...", ".11."), Captcha.from_string(2, "c.c.", "1.1."), ], "Layer 04": [ Captcha.from_string(2, "ac..", "01.."), Captcha.from_string(2, "ab..", "...."), Captcha.from_string(2, "bba.", "...."), ], "Layer 05": [ Captcha.from_string(2, "d...", "1..."), Captcha.from_string(2, "d...", "..1."), Captcha.from_string(2, "d..d", "...1"), ], "Layer 06": [ Captcha.from_string(3, "c.c.c.c.c", "........."), Captcha.from_string(3, "c.b...b.a", "........."), Captcha.from_string(3, "d...d...d", "........."), ], "Layer 11": [ Captcha.from_string(3, "m.m......", "10.10.00."), Captcha.from_string(3, "m.......m", "10.10.01."), Captcha.from_string(3, "m.m...m.m", "1.1...2.2"), ], "Layer 12": [ Captcha.from_string(3, "m.m...n.n", "1.1...2.2"), Captcha.from_string(3, "m.n...n.m", "1.1...2.2"), Captcha.from_string(3, "m.m...n.n", "1.2....0."), ], "Layer 13": [ Captcha.from_string(4, "m....m..........", "11..10.........."), Captcha.from_string(4, "mmnn......cc...c", "01230...0...0..."), Captcha.from_string(4, "mn............nm", "1...11.........."), Captcha.from_string(4, "mn..nm........nm", "................"), ], "Layer 14": [ Captcha.from_string(4, "m..a.m.........d", "11..10.......0.."), Captcha.from_string(4, "m..a.m.........d", "11..10.......2.."), Captcha.from_string(4, "m..a.md........d", "11..10.......0.."), Captcha.from_string(4, "m.bn.mc.....n..d", "11..10.......2.."), ], "Layer 21": [ Captcha.from_string(3, "....v....", "0101.10.0"), Captcha.from_string(3, "....v....", "1101.10.0"), Captcha.from_string(3, "v.......v", "1101020.."), ], "Layer 22": [ Captcha.from_string(3, "v....v.v.", "0......1."), Captcha.from_string(3, "vm.....m.", ".1.....0."), Captcha.from_string(3, "v..m.m.v.", ".....1.0."), ], "Layer 23": [ Captcha.from_string(3, "....l....", "...010..."), Captcha.from_string(3, ".l..l..l.", ".1..0..1."), Captcha.from_string(3, "..l.l.l..", ".1011...."), ], "Layer 24": [ Captcha.from_string(3, "...cl...d", "....1...."), Captcha.from_string(3, "l...d...l", "........."), Captcha.from_string(3, "l...a.b.l", "........."), ], "Layer 31": [ Captcha.from_string(3, "r...c...r", "31.312.1."), Captcha.from_string(3, "r.b.c.b.r", "........."), Captcha.from_string(3, "raa.d.a.r", "........."), ], "Layer 32": [ Captcha.from_string(3, "....e....", "0001020.0"), Captcha.from_string(3, "....e....", "....2...."), Captcha.from_string(3, "e.e...e.e", "........."), ], "Layer 33": [ Captcha.from_string(3, "ebebb.e.e", ".1.23...."), Captcha.from_string(3, "eaeac.e.e", "........."), Captcha.from_string(3, "eaebede.e", "........."), ], "Layer 34": [ Captcha.from_string(4, "er...em..me.dbre", "................"), ], } 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 app = Flask(__name__) with open("./config.json", "r", encoding="utf-8") as file: config = json.load(file) app.config["SECRET_KEY"] = config["fsk"] LIGHT_URL = f"http://192.168.1.10/api/{config['light']}" del config app.teardown_appcontext(teardown) @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