From b19dedc5687171c24a48e5770ed06178694b68af Mon Sep 17 00:00:00 2001 From: user Date: Thu, 22 Jun 2023 11:54:15 +0000 Subject: [PATCH 1/5] make forms less problematic --- TFR/server/__init__.py | 8 ++++---- TFR/server/api.py | 21 ++++++++------------- TFR/server/auth.py | 24 ++++++++++-------------- TFR/server/config.py | 1 - TFR/server/models.py | 7 +++---- TFR/server/templates/auth.html | 8 ++++---- TFR/server/templates/base.html | 5 ++++- TFR/server/templates/settings.html | 7 +++++++ TFR/server/views.py | 4 ++-- 9 files changed, 42 insertions(+), 43 deletions(-) diff --git a/TFR/server/__init__.py b/TFR/server/__init__.py index 1f42466..077f7e4 100644 --- a/TFR/server/__init__.py +++ b/TFR/server/__init__.py @@ -4,10 +4,10 @@ from flask import Flask, render_template, abort from flask_assets import Bundle from werkzeug.exceptions import HTTPException -from server.extensions import db, migrate, cache, assets, login_manager -from server.models import Users -from server.config import MIGRATION_DIR, INSTANCE_DIR -from server import views, auth, api, filters +from .config import MIGRATION_DIR, INSTANCE_DIR +from .extensions import db, migrate, cache, assets, login_manager +from .models import Users +from . import views, auth, api, filters app = Flask(__name__, instance_path=INSTANCE_DIR) diff --git a/TFR/server/api.py b/TFR/server/api.py index 171a4c2..b873962 100644 --- a/TFR/server/api.py +++ b/TFR/server/api.py @@ -5,13 +5,10 @@ from flask import Blueprint, request, jsonify from flask_login import login_required, current_user from werkzeug.security import check_password_hash -from server.models import Scores, Sessions, Users -from server.extensions import db -from server.config import ( - GAME_VERSION, - GAME_VERSIONS, +from .models import Scores, Sessions, Users +from .extensions import db +from .config import ( GAME_DIFFICULTIES, - USER_MAX_TOKENS, MAX_SEARCH_RESULTS, USER_REGEX, ) @@ -82,7 +79,7 @@ def post(): @blueprint.route("/search", methods=["GET"]) def search(): - search_arg = request.args.get("q") + search_arg = request.args.get("q").strip() if not search_arg: return "No search query provided!", 400 @@ -98,16 +95,15 @@ def search(): @blueprint.route("/login", methods=["POST"]) def login(): - username = request.form["username"].strip() - password = request.form["password"].strip() - device = request.form["device"].strip() + username = request.form.get("username", None).strip() + password = request.form.get("password", None).strip() + device = request.form.get("device", "Unknown").strip() username_regex = re.compile(USER_REGEX) if not username or not username_regex.match(username) or not password: return "Username or Password is incorrect!", 400 user = Users.query.filter_by(username=username).first() - if not user or not check_password_hash(user.password, password): return "Username or Password is incorrect!", 400 @@ -125,10 +121,9 @@ def login(): @blueprint.route("/authenticate", methods=["POST"]) def authenticate(): - auth_key = request.form["auth_key"].strip() + auth_key = request.form.get("session", None).strip() session = Sessions.query.filter_by(auth_key=auth_key).first() - if not session: return "Invalid session", 400 diff --git a/TFR/server/auth.py b/TFR/server/auth.py index 9132d7f..607d126 100644 --- a/TFR/server/auth.py +++ b/TFR/server/auth.py @@ -2,12 +2,12 @@ import re import uuid from flask import Blueprint, render_template, request, flash, redirect, url_for -from flask_login import login_required, login_user, logout_user, current_user +from flask_login import login_user from werkzeug.security import generate_password_hash, check_password_hash -from server.extensions import db -from server.models import Users, Sessions -from server.config import USER_REGEX, USER_EMAIL_REGEX +from .extensions import db +from .models import Users +from .config import USER_REGEX blueprint = Blueprint("auth", __name__) @@ -21,23 +21,20 @@ def auth(): @blueprint.route("/register", methods=["POST"]) def register(): # Get the form data - username = request.form["username"].strip() - email = request.form["email"].strip() - password = request.form["password"].strip() + username = request.form.get("username", None).strip() + password = request.form.get("password", None).strip() + confirm = request.form.get("confirm", None).strip() username_regex = re.compile(USER_REGEX) - email_regex = re.compile(USER_EMAIL_REGEX) error = [] # Validate the form if not username or not username_regex.match(username): error.append("Username is invalid! Must be alphanumeric, and can contain ._-") - if not email or not email_regex.match(email): - error.append("Email is invalid! Must be email format") - if not password: - error.append("Password is empty!") - elif len(password) < 8: + if not password or len(password) < 8: error.append("Password is too short! Must be at least 8 characters long.") + if not confirm or password != confirm: + error.append("Passwords do not match!") if Users.query.filter_by(username=username).first(): error.append("Username already exists!") @@ -50,7 +47,6 @@ def register(): register_user = Users( alt_id=str(uuid.uuid4()), username=username, - email=generate_password_hash(email, method="scrypt"), password=generate_password_hash(password, method="scrypt"), ) db.session.add(register_user) diff --git a/TFR/server/config.py b/TFR/server/config.py index 5d109ae..9a6a266 100644 --- a/TFR/server/config.py +++ b/TFR/server/config.py @@ -5,7 +5,6 @@ GAME_VERSION = "alpha" GAME_VERSIONS = ["alpha"] GAME_DIFFICULTIES = [0, 1, 2, 3, 4] -USER_MAX_TOKENS = 3 USER_REGEX = r"\b[A-Za-z0-9._-]+\b" USER_EMAIL_REGEX = r"[^@]+@[^@]+\.[^@]+" diff --git a/TFR/server/models.py b/TFR/server/models.py index 9a32e8a..2c15299 100644 --- a/TFR/server/models.py +++ b/TFR/server/models.py @@ -1,10 +1,9 @@ """ Database models for the server """ -import uuid from flask_login import UserMixin -from server.extensions import db -from server.config import GAME_VERSION +from .extensions import db +from .config import GAME_VERSION class Scores(db.Model): @@ -115,7 +114,7 @@ class Users(db.Model, UserMixin): alt_id = db.Column(db.String, nullable=False, unique=True) username = db.Column(db.String(32), unique=True, nullable=False) - email = db.Column(db.String, unique=True, nullable=False) + email = db.Column(db.String) password = db.Column(db.String, nullable=False) joined_at = db.Column( diff --git a/TFR/server/templates/auth.html b/TFR/server/templates/auth.html index 148966a..e18379a 100644 --- a/TFR/server/templates/auth.html +++ b/TFR/server/templates/auth.html @@ -28,13 +28,13 @@ - - + + - - + + diff --git a/TFR/server/templates/base.html b/TFR/server/templates/base.html index 51bcdc2..79d0be4 100644 --- a/TFR/server/templates/base.html +++ b/TFR/server/templates/base.html @@ -50,7 +50,10 @@ {% if current_user.is_authenticated %} - {{ current_user.username }} + + {{ current_user.username }} + {% if not current_user.email %}{% endif %} + {% else %} {% endif %} diff --git a/TFR/server/templates/settings.html b/TFR/server/templates/settings.html index 6d6e72e..bc08f03 100644 --- a/TFR/server/templates/settings.html +++ b/TFR/server/templates/settings.html @@ -1,5 +1,12 @@ {% extends "base.html" %} {% block content %} + {% if not current_user.email %} +
+

No Email set

+

If you forget your password, you will not be able to recover your account.

+
+ {% endif %} +

Hello, {{ current_user.username }}!

Sample text

diff --git a/TFR/server/views.py b/TFR/server/views.py index 65fb89a..3cdc99c 100644 --- a/TFR/server/views.py +++ b/TFR/server/views.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, render_template, abort, flash, redirect, url_for from flask_login import login_required, current_user, logout_user -from server.models import Scores, Users, Sessions -from server.config import GAME_VERSION, MAX_TOP_SCORES +from .models import Scores, Users, Sessions +from .config import GAME_VERSION, MAX_TOP_SCORES blueprint = Blueprint("views", __name__) From 300c80fcd5db5bfedf22f710d4993b979d8fa109 Mon Sep 17 00:00:00 2001 From: Fluffy Date: Thu, 22 Jun 2023 13:19:00 +0000 Subject: [PATCH 2/5] Yeet useless tables Yeet useless data paths in dockerfile Add order into templates file --- TFR/Dockerfile | 1 - TFR/server/__init__.py | 5 +-- TFR/server/api.py | 1 - TFR/server/auth.py | 7 ++- TFR/server/config.py | 3 -- TFR/server/filters.py | 6 +-- TFR/server/models.py | 25 ++--------- TFR/server/templates/auth.html | 43 ------------------- TFR/server/templates/base.html | 33 ++++++-------- TFR/server/templates/macros/input.html | 15 +++++++ TFR/server/templates/navigation.html | 15 +++++++ TFR/server/templates/{ => views}/about.html | 0 TFR/server/templates/views/auth.html | 25 +++++++++++ TFR/server/templates/{ => views}/scores.html | 0 .../templates/{ => views}/settings.html | 0 TFR/server/views.py | 10 +++-- docker-compose.yml | 3 +- 17 files changed, 86 insertions(+), 106 deletions(-) delete mode 100644 TFR/server/templates/auth.html create mode 100644 TFR/server/templates/macros/input.html create mode 100644 TFR/server/templates/navigation.html rename TFR/server/templates/{ => views}/about.html (100%) create mode 100644 TFR/server/templates/views/auth.html rename TFR/server/templates/{ => views}/scores.html (100%) rename TFR/server/templates/{ => views}/settings.html (100%) diff --git a/TFR/Dockerfile b/TFR/Dockerfile index 67491da..130c6aa 100644 --- a/TFR/Dockerfile +++ b/TFR/Dockerfile @@ -2,7 +2,6 @@ FROM alpine:latest EXPOSE 8000 -# RUN apt update && apt install -y python3 python3-pip postgresql-client RUN apk update && apk add python3 py3-pip postgresql-client WORKDIR /data diff --git a/TFR/server/__init__.py b/TFR/server/__init__.py index 077f7e4..e910a85 100644 --- a/TFR/server/__init__.py +++ b/TFR/server/__init__.py @@ -4,17 +4,16 @@ from flask import Flask, render_template, abort from flask_assets import Bundle from werkzeug.exceptions import HTTPException -from .config import MIGRATION_DIR, INSTANCE_DIR from .extensions import db, migrate, cache, assets, login_manager from .models import Users from . import views, auth, api, filters -app = Flask(__name__, instance_path=INSTANCE_DIR) +app = Flask(__name__) app.config.from_pyfile("config.py") db.init_app(app) -migrate.init_app(app, db, directory=MIGRATION_DIR) +migrate.init_app(app, db) with app.app_context(): db.create_all() diff --git a/TFR/server/api.py b/TFR/server/api.py index b873962..1513d4a 100644 --- a/TFR/server/api.py +++ b/TFR/server/api.py @@ -59,7 +59,6 @@ def post(): if int(difficulty) not in GAME_DIFFICULTIES: return "Invalid difficulty!" - session_data = Sessions.query.filter_by(auth_key=session_key).first() if not session_data: return "Authentication failed!" diff --git a/TFR/server/auth.py b/TFR/server/auth.py index 607d126..272e5ff 100644 --- a/TFR/server/auth.py +++ b/TFR/server/auth.py @@ -15,7 +15,7 @@ blueprint = Blueprint("auth", __name__) @blueprint.route("/auth", methods=["GET"]) def auth(): - return render_template("auth.html") + return render_template("views/auth.html") @blueprint.route("/register", methods=["POST"]) @@ -59,10 +59,9 @@ def register(): @blueprint.route("/login", methods=["POST"]) def login(): # Get the form data - username = request.form["username"].strip() - password = request.form["password"].strip() + username = request.form.get("username", None).strip() + password = request.form.get("password", None).strip() username_regex = re.compile(USER_REGEX) - error = [] # Validate the form diff --git a/TFR/server/config.py b/TFR/server/config.py index 9a6a266..b612d79 100644 --- a/TFR/server/config.py +++ b/TFR/server/config.py @@ -23,9 +23,6 @@ SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{user}:{password}@{host}:5432/ SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_POOL_RECYCLE = 621 -MIGRATION_DIR = "/data/storage/migrations" -INSTANCE_DIR = "/data/storage/instance" - """ # SQLite SECRET_KEY = "dev" diff --git a/TFR/server/filters.py b/TFR/server/filters.py index 213c535..ed4a030 100644 --- a/TFR/server/filters.py +++ b/TFR/server/filters.py @@ -2,12 +2,12 @@ import datetime from flask import Blueprint -blueprint = Blueprint('filters', __name__, template_folder='templates') +blueprint = Blueprint("filters", __name__, template_folder="templates") @blueprint.app_template_filter() def format_result(dttm): - dttm = str(dttm).split('.') + dttm = str(dttm).split(".") time = datetime.timedelta(seconds=int(dttm[0])) microtime = dttm[1][:3] - return f'{time}:{microtime}' + return f"{time}:{microtime}" diff --git a/TFR/server/models.py b/TFR/server/models.py index 2c15299..70b6551 100644 --- a/TFR/server/models.py +++ b/TFR/server/models.py @@ -9,9 +9,6 @@ from .config import GAME_VERSION class Scores(db.Model): """ Post table - Scores supports anonymous posting, and instead just wants to post a score, - then the username must be provided. Otherwise, it's grabbed from the user - table """ id = db.Column(db.Integer, primary_key=True) @@ -75,25 +72,6 @@ class PasswordReset(db.Model): ) -class Permissions(db.Model): - """ - Permissions table - """ - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True)) - - user_ban = db.Column(db.Boolean, default=False) - user_warn = db.Column(db.Boolean, default=False) - - score_removal = db.Column(db.Boolean, default=False) - score_edit = db.Column(db.Boolean, default=False) - - admin_panel = db.Column(db.Boolean, default=False) - admin_promote = db.Column(db.Boolean, default=False) - admin_demote = db.Column(db.Boolean, default=False) - - class ProfileTags(db.Model): """ Profile Tags table @@ -112,6 +90,9 @@ class Users(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) alt_id = db.Column(db.String, nullable=False, unique=True) + superuser = db.Column(db.Boolean, default=False) + + picture = db.Column(db.String) username = db.Column(db.String(32), unique=True, nullable=False) email = db.Column(db.String) diff --git a/TFR/server/templates/auth.html b/TFR/server/templates/auth.html deleted file mode 100644 index e18379a..0000000 --- a/TFR/server/templates/auth.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "base.html" %} -{% block content %} -
-

Login

-

Welcome back!

-
- - - - - - - - - - - -
-
- -
-

Register

-

Don't have an account?

-
- - - - - - - - - - - - - - - - -
-
-{% endblock %} \ No newline at end of file diff --git a/TFR/server/templates/base.html b/TFR/server/templates/base.html index 79d0be4..bc2ac5d 100644 --- a/TFR/server/templates/base.html +++ b/TFR/server/templates/base.html @@ -1,7 +1,7 @@ - Front Rooms Highscores + {% block title %}Front Rooms Highscores{% endblock %} @@ -16,10 +16,9 @@ {% assets "scripts" %}{% endassets %} -
-

Start typing to see results...

-
+

Start typing to see results...

+ The Front Rooms Level select render @@ -28,38 +27,30 @@
+ {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} -

{{ message }}

+

+ + {{ message }} +

{% endfor %} {% endif %} {% endwith %}
+ The Front Rooms logo - - - + {% block nav %}{% endblock %}
diff --git a/TFR/server/templates/macros/input.html b/TFR/server/templates/macros/input.html new file mode 100644 index 0000000..f8a7e80 --- /dev/null +++ b/TFR/server/templates/macros/input.html @@ -0,0 +1,15 @@ +{% macro text(id, name, type="text", required=False, minlength=0) %} + + + + + +{% endmacro %} \ No newline at end of file diff --git a/TFR/server/templates/navigation.html b/TFR/server/templates/navigation.html new file mode 100644 index 0000000..f2401ec --- /dev/null +++ b/TFR/server/templates/navigation.html @@ -0,0 +1,15 @@ + diff --git a/TFR/server/templates/about.html b/TFR/server/templates/views/about.html similarity index 100% rename from TFR/server/templates/about.html rename to TFR/server/templates/views/about.html diff --git a/TFR/server/templates/views/auth.html b/TFR/server/templates/views/auth.html new file mode 100644 index 0000000..b5fdf5a --- /dev/null +++ b/TFR/server/templates/views/auth.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% from "macros/input.html" import text %} + +{% block content %} +
+

Login

+

Welcome back!

+
+ {{ text(id="login-username", name="username", required=True) }} + {{ text(id="login-password", name="password", type="password", required=True) }} + +
+
+ +
+

Register

+

Don't have an account?

+
+ {{ text(id="register-username", name="username", required=True) }} + {{ text(id="register-password", name="password", type="password", required=True, minlength=8) }} + {{ text(id="register-confirm", name="confirm", type="password", required=True, minlength=8) }} + +
+
+{% endblock %} \ No newline at end of file diff --git a/TFR/server/templates/scores.html b/TFR/server/templates/views/scores.html similarity index 100% rename from TFR/server/templates/scores.html rename to TFR/server/templates/views/scores.html diff --git a/TFR/server/templates/settings.html b/TFR/server/templates/views/settings.html similarity index 100% rename from TFR/server/templates/settings.html rename to TFR/server/templates/views/settings.html diff --git a/TFR/server/views.py b/TFR/server/views.py index 3cdc99c..80e428d 100644 --- a/TFR/server/views.py +++ b/TFR/server/views.py @@ -27,13 +27,17 @@ def index(): scores = scores.order_by(Scores.score.asc()).limit(MAX_TOP_SCORES).all() return render_template( - "scores.html", scores=scores, diff=int(diff_arg), ver=ver_arg, user=user_arg + "views/scores.html", + scores=scores, + diff=int(diff_arg), + ver=ver_arg, + user=user_arg ) @blueprint.route("/about") def about(): - return render_template("about.html") + return render_template("views/about.html") @blueprint.route("/settings", methods=["GET"]) @@ -51,4 +55,4 @@ def settings(): flash("Insert password change function", "error") sessions = Sessions.query.filter_by(user_id=current_user.id).all() - return render_template("settings.html", sessions=sessions) + return render_template("views/settings.html", sessions=sessions) diff --git a/docker-compose.yml b/docker-compose.yml index bad0391..7de16b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,8 +36,7 @@ services: build: TFR restart: unless-stopped volumes: - - ./TFR/storage:/data/storage - - ./TFR/logs:/data/logs + - ./TFR/storage/migrations:/data/migrations environment: FLASK_KEY: ${THE_FRONT_ROOMS_SECRETE_KEY} DB_USER: ${POSTGRES_USER} From e08b31a430b23c993bffe26e0cab33f565d1178d Mon Sep 17 00:00:00 2001 From: Fluffy Date: Thu, 22 Jun 2023 17:31:36 +0000 Subject: [PATCH 3/5] Deleting accounts Resetting password Profile settings Fixing random shit --- TFR/server/__init__.py | 3 +- TFR/server/account.py | 135 ++++++++++++++++++ TFR/server/api.py | 12 +- TFR/server/auth.py | 2 +- TFR/server/static/sass/profile-settings.sass | 67 +++++++++ TFR/server/static/sass/style.sass | 1 + TFR/server/templates/macros/input.html | 8 +- TFR/server/templates/navigation.html | 4 +- .../templates/views/account_settings.html | 61 ++++++++ .../templates/views/delete_account.html | 17 +++ .../templates/views/reset_password.html | 15 ++ TFR/server/templates/views/settings.html | 54 ------- TFR/server/views.py | 27 +--- 13 files changed, 317 insertions(+), 89 deletions(-) create mode 100644 TFR/server/account.py create mode 100644 TFR/server/static/sass/profile-settings.sass create mode 100644 TFR/server/templates/views/account_settings.html create mode 100644 TFR/server/templates/views/delete_account.html create mode 100644 TFR/server/templates/views/reset_password.html delete mode 100644 TFR/server/templates/views/settings.html diff --git a/TFR/server/__init__.py b/TFR/server/__init__.py index e910a85..053fc46 100644 --- a/TFR/server/__init__.py +++ b/TFR/server/__init__.py @@ -6,7 +6,7 @@ from werkzeug.exceptions import HTTPException from .extensions import db, migrate, cache, assets, login_manager from .models import Users -from . import views, auth, api, filters +from . import views, account, auth, api, filters app = Flask(__name__) @@ -36,6 +36,7 @@ assets.register("styles", styles) cache.init_app(app) app.register_blueprint(views.blueprint) +app.register_blueprint(account.blueprint) app.register_blueprint(auth.blueprint) app.register_blueprint(api.blueprint) app.register_blueprint(filters.blueprint) diff --git a/TFR/server/account.py b/TFR/server/account.py new file mode 100644 index 0000000..519f378 --- /dev/null +++ b/TFR/server/account.py @@ -0,0 +1,135 @@ +import uuid +import re + +from flask import Blueprint, request, render_template, flash, redirect, url_for +from flask_login import login_required, current_user, logout_user +from werkzeug.security import generate_password_hash, check_password_hash + +from .config import USER_REGEX, USER_EMAIL_REGEX +from .models import Users, Sessions, Scores, ProfileTags, PasswordReset +from .extensions import db + + +blueprint = Blueprint("account", __name__, url_prefix="/account") + + +@blueprint.route("/settings", methods=["GET", "POST"]) +@login_required +def settings(): + if request.method == "POST": + username = request.form.get("username", "").strip() + email = request.form.get("email", "").strip() + password = request.form.get("password", "").strip() + + user_regex = re.compile(USER_REGEX) + email_regex = re.compile(USER_EMAIL_REGEX) + error = [] + + user = Users.query.filter_by(username=current_user.username).first() + + if not check_password_hash(user.password, password): + flash("Password is incorrect!", "error") + return redirect(url_for('account.settings')) + + if username: + if user_regex.match(username): + user.username = username + else: + error.append("Username is invalid!") + if email: + if email_regex.match(email): + user.email = email + else: + error.append("Email is invalid!") + + if error: + for err in error: + flash(err, "error") + return redirect(url_for("account.settings")) + + db.session.commit() + + flash("Successfully updated account!", "success") + return redirect(url_for("account.settings")) + else: + action = request.args.get("action", None) + + if action == "logout": + logout_user() + flash("Successfully logged out!", "success") + return redirect(url_for("views.index")) + + sessions = Sessions.query.filter_by(user_id=current_user.id).all() + return render_template("views/account_settings.html", sessions=sessions) + + +@blueprint.route("/reset-password", methods=["GET", "POST"]) +@login_required +def password_reset(): + if request.method == "POST": + current = request.form.get("current", "").strip() + new = request.form.get("new", "").strip() + confirm = request.form.get("confirm", "").strip() + error = [] + + user = Users.query.filter_by(username=current_user.username).first() + + if not current or not new or not confirm: + error.append("Please fill out all fields!") + if not check_password_hash(user.password, current): + error.append("Current password is incorrect!") + if len(new) < 8: + error.append("New password is too short! Must be at least 8 characters long.") + if new != confirm: + error.append("New passwords do not match!") + + if error: + for err in error: + flash(err, "error") + return redirect(url_for("account.password_reset")) + + user.password = generate_password_hash(new, method="scrypt") + user.alt_id = str(uuid.uuid4()) + db.session.commit() + + flash("Successfully changed password!", "success") + logout_user() + return redirect(url_for("auth.auth")) + else: + return render_template("views/reset_password.html") + + +@blueprint.route("/delete-account", methods=["GET", "POST"]) +@login_required +def delete_account(): + if request.method == "POST": + username = request.form.get("username", "").strip() + password = request.form.get("password", "").strip() + error = [] + + user = Users.query.filter_by(username=current_user.username).first() + + if username != user.username: + error.append("Username does not match!") + if not password: + error.append("Please fill out all fields!") + if not check_password_hash(user.password, password): + error.append("Password is incorrect!") + + if error: + for err in error: + flash(err, "error") + return redirect(url_for("account.delete_account")) + + db.session.query(Sessions).filter_by(user_id=current_user.id).delete() + db.session.query(Scores).filter_by(user_id=current_user.id).delete() + db.session.query(ProfileTags).filter_by(user_id=current_user.id).delete() + db.session.query(PasswordReset).filter_by(user_id=current_user.id).delete() + db.session.delete(user) + db.session.commit() + + flash("Successfully deleted account!", "success") + logout_user() + return redirect(url_for("auth.auth")) + else: + return render_template("views/delete_account.html") diff --git a/TFR/server/api.py b/TFR/server/api.py index 1513d4a..47b91e3 100644 --- a/TFR/server/api.py +++ b/TFR/server/api.py @@ -20,7 +20,7 @@ blueprint = Blueprint("api", __name__, url_prefix="/api") @blueprint.route("/tokens", methods=["POST"]) @login_required def tokens(): - session_id = request.form["session_id"] + session_id = request.form.get("session", "").strip() if not session_id: return jsonify({"error": "No Session provided!"}), 400 @@ -40,8 +40,8 @@ def tokens(): @blueprint.route("/post", methods=["POST"]) def post(): - session_key = request.form.get("session", None) - version = request.form.get("version", "alpha") + session_key = request.form.get("session", "").strip() + version = request.form.get("version", "alpha").strip() difficulty = request.form.get("difficulty", 0) score = request.form.get("score", 0) @@ -94,8 +94,8 @@ def search(): @blueprint.route("/login", methods=["POST"]) def login(): - username = request.form.get("username", None).strip() - password = request.form.get("password", None).strip() + username = request.form.get("username", "").strip() + password = request.form.get("password", "").strip() device = request.form.get("device", "Unknown").strip() username_regex = re.compile(USER_REGEX) @@ -120,7 +120,7 @@ def login(): @blueprint.route("/authenticate", methods=["POST"]) def authenticate(): - auth_key = request.form.get("session", None).strip() + auth_key = request.form.get("session", "").strip() session = Sessions.query.filter_by(auth_key=auth_key).first() if not session: diff --git a/TFR/server/auth.py b/TFR/server/auth.py index 272e5ff..762aab6 100644 --- a/TFR/server/auth.py +++ b/TFR/server/auth.py @@ -81,4 +81,4 @@ def login(): login_user(user, remember=True) flash("Successfully logged in!", "success") - return redirect(url_for("views.index")) + return redirect(url_for("account.settings")) diff --git a/TFR/server/static/sass/profile-settings.sass b/TFR/server/static/sass/profile-settings.sass new file mode 100644 index 0000000..2d729ed --- /dev/null +++ b/TFR/server/static/sass/profile-settings.sass @@ -0,0 +1,67 @@ +.profile-settings + display: flex + flex-direction: row + gap: 0.5rem + + .picture + margin: 0 + width: 13rem + + position: relative + display: flex + flex-direction: column + + > img + height: 13rem + width: 13rem + + object-fit: cover + + background-color: RGBA($white, 0.02) + border-bottom: 1px solid RGBA($white, 0.1) + border-radius: 2px 2px 0 0 + + transition: opacity 0.1s ease-in-out + + > input + height: 100% + width: 100% + + position: absolute + top: 0 + left: 0 + + opacity: 0 + + > label + padding: 0.5rem 0.7rem + width: 100% + + text-decoration: none + text-align: center + white-space: nowrap + font-size: 0.9em + + background-color: RGBA($white, 0.02) + color: RGB($white) + border-radius: 0 0 2px 2px + + transition: background-color 0.1s ease-in-out + + &:hover + cursor: pointer + + > img + opacity: 0.8 + + > label + background-color: RGBA($white, 0.04) + + .other + width: 100% + height: 100% + + display: flex + flex-direction: column + justify-content: flex-start + gap: 0.5rem diff --git a/TFR/server/static/sass/style.sass b/TFR/server/static/sass/style.sass index f80bb22..fd0004f 100644 --- a/TFR/server/static/sass/style.sass +++ b/TFR/server/static/sass/style.sass @@ -16,6 +16,7 @@ $bronze: var(--bronze) --bronze: 193, 145, 69 @import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700&display=swap') +@import "profile-settings" @import "block" @import "button" @import "hint" diff --git a/TFR/server/templates/macros/input.html b/TFR/server/templates/macros/input.html index f8a7e80..1f44540 100644 --- a/TFR/server/templates/macros/input.html +++ b/TFR/server/templates/macros/input.html @@ -1,15 +1,19 @@ -{% macro text(id, name, type="text", required=False, minlength=0) %} +{% macro text(id, name, type="text", required=False, minlength=0, value="") %} -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/TFR/server/templates/navigation.html b/TFR/server/templates/navigation.html index f2401ec..e349325 100644 --- a/TFR/server/templates/navigation.html +++ b/TFR/server/templates/navigation.html @@ -5,9 +5,9 @@ {% if current_user.is_authenticated %} - + {{ current_user.username }} - {% if not current_user.email %}{% endif %} + {% if not current_user.email %}{% endif %} {% else %} diff --git a/TFR/server/templates/views/account_settings.html b/TFR/server/templates/views/account_settings.html new file mode 100644 index 0000000..1d82ab6 --- /dev/null +++ b/TFR/server/templates/views/account_settings.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% from "macros/input.html" import text %} + +{% block content %} +
+

Profile Settings

+
+
+
+ Profile picture + + +
+
+ {{ text(id="profile-username", name="username", value=current_user.username) }} + {{ text(id="profile-email", name="email") }} + {{ text(id="profile-password", name="password", type="password", required=True, minlength=8) }} +
+
+ +
+
+ +
+

Sessions

+

Devices and games that you logged into. If you're looking to log out all website users, reset your password instead.

+
+ + + + + + + + {% for session in sessions %} + + + + + + + {% endfor %} +
DeviceCreatedLast Used
{{ session.device_type }}{{ session.created_at.strftime('%Y-%m-%d') }}{{ session.last_used.strftime('%Y-%m-%d') }}
+
+
+ +
+

Danger Zone

+

Be careful!

+ Delete Account + Reset Password + Logout +
+ + + {% if not current_user.email %} + + {% endif %} +{% endblock %} diff --git a/TFR/server/templates/views/delete_account.html b/TFR/server/templates/views/delete_account.html new file mode 100644 index 0000000..bb4f42e --- /dev/null +++ b/TFR/server/templates/views/delete_account.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% from "macros/input.html" import text %} + +{% block content %} +
+

Delete account

+

+ Deleting your account will delete EVERYTHING on your account, including ALL your ever submitted scores. + There is NO WAY to recover your account from this, are you sure you want todo this? +

+
+ {{ text(id="username", name="username", required=True) }} + {{ text(id="password", name="password", type="password", required=True) }} + +
+
+{% endblock %} diff --git a/TFR/server/templates/views/reset_password.html b/TFR/server/templates/views/reset_password.html new file mode 100644 index 0000000..85e21e8 --- /dev/null +++ b/TFR/server/templates/views/reset_password.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% from "macros/input.html" import text %} + +{% block content %} +
+

Password Reset

+

Forgotten your current password? Go here [insert password reset tool link]

+
+ {{ text(id="current-password", name="current", type="password", required=True) }} + {{ text(id="new-password", name="new", type="password", required=True) }} + {{ text(id="confirm-password", name="confirm", type="password", required=True) }} + +
+
+{% endblock %} diff --git a/TFR/server/templates/views/settings.html b/TFR/server/templates/views/settings.html deleted file mode 100644 index bc08f03..0000000 --- a/TFR/server/templates/views/settings.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base.html" %} -{% block content %} - {% if not current_user.email %} -
-

No Email set

-

If you forget your password, you will not be able to recover your account.

-
- {% endif %} - -
-

Hello, {{ current_user.username }}!

-

Sample text

-
- -
-

Sessions

-

Devices and games that you logged into. If you're looking to logout all website users, reset your password instead.

-
- - - - - - - - {% if sessions %} - {% for session in sessions %} - - - - - - - {% endfor %} - {% else %} - - - - - - - {% endif %} -
DeviceCreatedLast Used
{{ session.device_type }}{{ session.created_at.strftime('%Y-%m-%d') }}{{ session.last_used.strftime('%Y-%m-%d') }}
-
-
- -
-

Danger Zone

-

Be careful!

- Delete Account - Reset Password - Logout -
-{% endblock %} diff --git a/TFR/server/views.py b/TFR/server/views.py index 80e428d..d6b2405 100644 --- a/TFR/server/views.py +++ b/TFR/server/views.py @@ -1,6 +1,5 @@ -from flask import Blueprint, request, render_template, abort, flash, redirect, url_for -from flask_login import login_required, current_user, logout_user -from .models import Scores, Users, Sessions +from flask import Blueprint, request, render_template, abort +from .models import Scores, Users from .config import GAME_VERSION, MAX_TOP_SCORES @@ -13,7 +12,7 @@ def index(): ver_arg = request.args.get("ver", GAME_VERSION) user_arg = request.args.get("user", None) - scores = Scores.query.filter_by(difficulty=diff_arg) + scores = Scores.query.filter_by(difficulty=diff_arg).order_by(Scores.score.asc()) if ver_arg: scores = scores.filter_by(version=ver_arg) @@ -24,7 +23,7 @@ def index(): else: abort(404, "User not found") - scores = scores.order_by(Scores.score.asc()).limit(MAX_TOP_SCORES).all() + scores = scores.limit(MAX_TOP_SCORES).all() return render_template( "views/scores.html", @@ -38,21 +37,3 @@ def index(): @blueprint.route("/about") def about(): return render_template("views/about.html") - - -@blueprint.route("/settings", methods=["GET"]) -@login_required -def settings(): - action = request.args.get("action", None) - - if action == "logout": - logout_user() - flash("Successfully logged out!", "success") - return redirect(url_for("views.index")) - if action == "delete": - flash("Insert delete function", "error") - if action == "password": - flash("Insert password change function", "error") - - sessions = Sessions.query.filter_by(user_id=current_user.id).all() - return render_template("views/settings.html", sessions=sessions) From e1d22d502d15e50f40e6d4a9b464ce6f456a0395 Mon Sep 17 00:00:00 2001 From: Fluffy Date: Thu, 22 Jun 2023 20:45:16 +0000 Subject: [PATCH 4/5] Show user only once on top scores --- TFR/server/static/sass/button.sass | 1 + TFR/server/static/sass/profile-settings.sass | 18 +++++++++++++--- TFR/server/views.py | 22 ++++++++++++++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/TFR/server/static/sass/button.sass b/TFR/server/static/sass/button.sass index d7920ab..3f2e9e5 100644 --- a/TFR/server/static/sass/button.sass +++ b/TFR/server/static/sass/button.sass @@ -51,6 +51,7 @@ min-width: 6rem text-decoration: none + text-align: end white-space: nowrap font-size: 0.9em diff --git a/TFR/server/static/sass/profile-settings.sass b/TFR/server/static/sass/profile-settings.sass index 2d729ed..fcc5c99 100644 --- a/TFR/server/static/sass/profile-settings.sass +++ b/TFR/server/static/sass/profile-settings.sass @@ -5,15 +5,15 @@ .picture margin: 0 - width: 13rem + width: 10rem position: relative display: flex flex-direction: column > img - height: 13rem - width: 13rem + height: 10rem + width: 10rem object-fit: cover @@ -65,3 +65,15 @@ flex-direction: column justify-content: flex-start gap: 0.5rem +@media (max-width: 621px) + .profile-settings + flex-direction: column + gap: 1rem + + .picture + margin: 0 auto + width: 13rem + + > img + height: 13rem + width: 13rem diff --git a/TFR/server/views.py b/TFR/server/views.py index d6b2405..297ecf0 100644 --- a/TFR/server/views.py +++ b/TFR/server/views.py @@ -1,6 +1,9 @@ from flask import Blueprint, request, render_template, abort +from sqlalchemy import func + from .models import Scores, Users from .config import GAME_VERSION, MAX_TOP_SCORES +from .extensions import db blueprint = Blueprint("views", __name__) @@ -12,18 +15,29 @@ def index(): ver_arg = request.args.get("ver", GAME_VERSION) user_arg = request.args.get("user", None) - scores = Scores.query.filter_by(difficulty=diff_arg).order_by(Scores.score.asc()) + scores = db.session.query(Scores).filter_by(difficulty=diff_arg) + + subquery = ( + db.session.query(Scores.user_id, func.min(Scores.score).label('min')) + .group_by(Scores.user_id) + .subquery() + ) if ver_arg: scores = scores.filter_by(version=ver_arg) - if user_arg: + + if not user_arg: + scores = ( + scores.join(subquery, Scores.user_id == subquery.c.user_id) + .filter(Scores.score == subquery.c.min) + ) + else: if user := Users.query.filter_by(username=user_arg).first(): scores = scores.filter_by(user_id=user.id) - print(user.id) else: abort(404, "User not found") - scores = scores.limit(MAX_TOP_SCORES).all() + scores = scores.order_by(Scores.score.asc()).limit(MAX_TOP_SCORES).all() return render_template( "views/scores.html", From 361d76f530bd926326b348852ad2084b0213ef29 Mon Sep 17 00:00:00 2001 From: Fluffy Date: Thu, 22 Jun 2023 21:00:35 +0000 Subject: [PATCH 5/5] Start adding user profiles along searches --- TFR/server/templates/views/scores.html | 14 +++++++++++++- TFR/server/views.py | 10 ++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/TFR/server/templates/views/scores.html b/TFR/server/templates/views/scores.html index 6dcf1f4..9f30f88 100644 --- a/TFR/server/templates/views/scores.html +++ b/TFR/server/templates/views/scores.html @@ -22,7 +22,13 @@ - + @@ -30,6 +36,12 @@ {% endblock %} {% block content %} + {% if user %} +
+

{{ user.username }}

+
+ {% endif %} + {% if scores %}
diff --git a/TFR/server/views.py b/TFR/server/views.py index 297ecf0..0abe51f 100644 --- a/TFR/server/views.py +++ b/TFR/server/views.py @@ -12,8 +12,9 @@ blueprint = Blueprint("views", __name__) @blueprint.route("/") def index(): diff_arg = request.args.get("diff", 0) - ver_arg = request.args.get("ver", GAME_VERSION) - user_arg = request.args.get("user", None) + ver_arg = request.args.get("ver", GAME_VERSION).strip() + user_arg = request.args.get("user", "").strip() + user = None scores = db.session.query(Scores).filter_by(difficulty=diff_arg) @@ -32,7 +33,8 @@ def index(): .filter(Scores.score == subquery.c.min) ) else: - if user := Users.query.filter_by(username=user_arg).first(): + user = Users.query.filter_by(username=user_arg).first() + if user: scores = scores.filter_by(user_id=user.id) else: abort(404, "User not found") @@ -44,7 +46,7 @@ def index(): scores=scores, diff=int(diff_arg), ver=ver_arg, - user=user_arg + user=user )