all repos — myprecious @ 841bbbc4384b5ef741729464115a0dd54fa717cb

A lightweight web service to backup precious game saves.

registration prototype
Andronaco Marco marco.andronaco@olivetti.com
Mon, 17 Jul 2023 22:15:51 +0200
commit

841bbbc4384b5ef741729464115a0dd54fa717cb

parent

4f12dee46f1c595f99cb80b659909d4ce88d7dd5

A .vscode/launch.json

@@ -0,0 +1,33 @@

+{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: <https://go.microsoft.com/fwlink/?linkid=830387> + "version": "0.2.0", + "configurations": [ + { + "name": "myprecious", + "type": "python", + "request": "launch", + "cwd": "${workspaceFolder}", + "module": "poetry", + "python": "C:\\Users\\07501300\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\myprecious-MjU9b_nO-py3.11", + "args": [ + "run", + "python", + "-m",//"waitress-serve", "", "0.0.0.0", "--port", "5000", "main:app" + "waitress-serve", + "--host", + "0.0.0.0", + "--port", + "5000", + "main:app" + ], + "justMyCode": true, + "stopOnEntry": false, + "console": "integratedTerminal", + "env": { + "DEBUG_SWITCH": "True" + } + } + ] + }
M migrations/init.sqlmigrations/init.sql

@@ -4,3 +4,9 @@ username text unique not null,

password text not null, email text ); + +create table if not exists queue ( + username text primary key, + password text not null, + email text +)
M myprecious/Constants.pymyprecious/Constants.py

@@ -16,6 +16,9 @@ DEFAULT_ADMIN_PW = os.getenv("DEFAULT_ADMIN_PW", "admin")

DEFAULT_ADMIN_EMAIL = os.getenv("DEFAULT_ADMIN_EMAIL", "") # other constants +MIN_PW_LENGTH = 5 +MIN_USERNAME_LENGTH = 3 +MAX_LENGTH = 20 MISSING_COVER_URL = "https://placehold.co/100?text=no%20cover" BASE_DIRECTORY = "data" CONTENT_DIRECTORY = "content"
A myprecious/Db.py

@@ -0,0 +1,54 @@

+ +import uuid, hashlib, sqlite3 +import Constants as c + +def hash(password: str): + salt = uuid.uuid4().hex + return hashlib.sha512(password + salt).hexdigest(), salt + +def db_query(query, parameters): + with sqlite3.connect(c.DB_PATH) as db_connection: + curs = db_connection.cursor() + curs.execute(query, parameters) + return curs + +def db_query_one(query, parameters): + curs = db_query(query, parameters) + try: + return list(curs.fetchone()) + except TypeError: + return None + +def run_sql(sql_path: str): + with open(sql_path, 'r', encoding="utf-8") as f: + with sqlite3.connect(c.DB_PATH) as con: + curs = con.cursor() + sql = f.read() + curs.executescript(sql) + +def add_user_to_queue(username, password, email): + res = get_user_from_username(username) + if res is not None: + return None + query_str = "insert or ignore into queue (username, password, email) values (?,?,?);" + query_param = [username, password, email] + return db_query(query_str, query_param) + +def add_user(username, password, email): + query_str = "insert or ignore into login (username, password, email) values (?,?,?);" + query_param = [username, password, email] + return db_query(query_str, query_param) + +def accept_user(username): + r = get_user_from_username(username, "queue") + return add_user(r[0], r[1], r[2]) + +def get_user_from_username(username: str, table="login"): + return db_query_one(f"SELECT * FROM { table } where username = (?)", [username]) + +def get_user_from_id(id: int): + return db_query_one("SELECT * from login where user_id = (?)", [id]) + +def init_db(): + run_sql(c.MIGRATIONS_INIT_PATH) + add_user(c.DEFAULT_ADMIN_USER, c.DEFAULT_ADMIN_PW, c.DEFAULT_ADMIN_EMAIL)
M myprecious/__main__.pymyprecious/__main__.py

@@ -2,8 +2,8 @@ from flask import Flask, request, render_template, url_for, redirect

from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user from werkzeug.utils import secure_filename from contextlib import suppress -import os, json, base64, sqlite3 -import hashlib, uuid +from Db import init_db, get_user_from_username, get_user_from_id, add_user_to_queue +import os, json, base64 import Constants as c if c.DEBUG_SWITCH: from GamesApiTest import search_game

@@ -13,36 +13,6 @@

app = Flask(__name__) login_manager = LoginManager(app) -def hash(password: str): - salt = uuid.uuid4().hex - return hashlib.sha512(password + salt).hexdigest(), salt - -def db_query_one(query, parameters): - with sqlite3.connect(c.DB_PATH) as db_connection: - curs = db_connection.cursor() - curs.execute(query, parameters) - try: - return list(curs.fetchone()) - except TypeError: - return None - -def run_sql(sql_path: str): - with open(sql_path, 'r', encoding="utf-8") as f: - with sqlite3.connect(c.DB_PATH) as con: - curs = con.cursor() - sql = f.read() - curs.executescript(sql) - -def add_user(username, password, email): - query_str = "insert or ignore into login (username, password, email) values (?,?,?);" - query_param = [username, password, email] - return db_query_one(query_str, query_param) - -def init_db(): - run_sql(c.MIGRATIONS_INIT_PATH) - add_user(c.DEFAULT_ADMIN_USER, c.DEFAULT_ADMIN_PW, c.DEFAULT_ADMIN_EMAIL) - - class User(UserMixin): def __init__(self, user_id, username, password, email): self.id = user_id

@@ -77,15 +47,15 @@ temp_base64 = base64.b64encode(temp_bytes)

temp_obj["info"] = temp_base64.decode("utf-8") return temp_obj -def handle_game(game): - return [ handle_platform(game, platform) for platform in game["platforms"] ] +def handle_response(response): + return [ [ handle_platform(game, platform) for platform in game["platforms"] ] for game in response ] def collapse_list_of_lists(l): return [ item for sublist in l for item in sublist ] @login_manager.user_loader def load_user(user_id): - lu = db_query_one("SELECT * from login where user_id = (?)", [user_id]) + lu = get_user_from_id(user_id) if lu is None: return None return construct_user(lu[0], lu[1], lu[2], lu[3])

@@ -110,7 +80,7 @@ remember = bool(form["remember"])

except KeyError: remember = False - r = db_query_one("SELECT * FROM login where username = (?)", [username]) + r = get_user_from_username(username) if r is None: return render_template("login.html", user=current_user, last_user=username) user = construct_user(r[0], r[1], r[2], r[3])

@@ -132,7 +102,21 @@

if request.method == "GET": return render("register.html") - return render("register.html") + form = request.form + username = form["username"].lower() + email = form["email"].lower() + password = form["password"] + + if len(password) < c.MIN_PW_LENGTH or len(username) < c.MIN_USERNAME_LENGTH: + return render_template("register.html", user=current_user, error="Your username or password is too short.") + + if len(password) > c.MAX_LENGTH or len(username) > c.MAX_LENGTH: + return render_template("register.html", user=current_user, error="Your username or password is too long.") + + res = add_user_to_queue(username, password, email) + if res == None: + return render_template("register.html", user=current_user, error="This username is already registered.") + return render("register_done.html") @app.route('/logout') def route_logout():

@@ -149,7 +133,7 @@

query = request.form["query"] search_response = search_game(query) - games = collapse_list_of_lists([ handle_game(x) for x in search_response ]) + games = collapse_list_of_lists(handle_response(search_response)) return render_template("search.html", user=current_user, games=games, query=query) @app.route('/upload', methods=['GET', 'POST'])
M myprecious/templates/register.htmlmyprecious/templates/register.html

@@ -8,4 +8,7 @@ <input type="email" name="email" placeholder="e-mail (optional)"/>

<input type="password" name="password" placeholder="password" /> <input type="submit" value="register" /> </form> +{% if error %} +<p class="error">{{ error }}</p> +{% endif %} {% endblock %}
A myprecious/templates/register_done.html

@@ -0,0 +1,5 @@

+{% extends "base.html" %} +{% block title %}done{% endblock %} +{% block content %} +<p>Your registration request has been taken into account and will likely be processed in a few days.</p> +{% endblock %}
M poetry.lockpoetry.lock

@@ -552,6 +552,21 @@ socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]

zstd = ["zstandard (>=0.18.0)"] [[package]] +name = "waitress" +version = "2.1.2" +description = "Waitress WSGI server" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "waitress-2.1.2-py3-none-any.whl", hash = "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a"}, + {file = "waitress-2.1.2.tar.gz", hash = "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"}, +] + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["coverage (>=5.0)", "pytest", "pytest-cover"] + +[[package]] name = "werkzeug" version = "2.3.6" description = "The comprehensive WSGI web application library."

@@ -588,4 +603,4 @@

[metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "13973aa9456db6c61a975db23b3842c2f9f37092b2d43dffd5283ee22e2ee5db" +content-hash = "040a197af5e285bc8c828b20498b6c8bd09ccc1b231404f9e1ca08d49aafd1cd"
M pyproject.tomlpyproject.toml

@@ -15,6 +15,7 @@ wtforms = "^3.0.1"

flask-wtf = "^1.1.1" igdb-api-v4 = "^0.2.0" python-dotenv = "^1.0.0" +waitress = "^2.1.2" [build-system]