Deleting accounts

Resetting password
Profile settings
Fixing random shit
This commit is contained in:
Michał Gdula 2023-06-22 17:31:36 +00:00
parent 300c80fcd5
commit e08b31a430
13 changed files with 317 additions and 89 deletions

View file

@ -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)

135
TFR/server/account.py Normal file
View file

@ -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")

View file

@ -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:

View file

@ -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"))

View file

@ -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

View file

@ -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"

View file

@ -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="") %}
<span class="text-input">
<label for="{{ id }}">
{{ name|title }}
{% if required %}
<span style="color: rgb(var(--secondary)) !important;">*</span>
{% endif %}
</label>
<input
type="{{ type }}"
name="{{ name }}"
id="{{ id }}"
value="{{ value }}"
{% if required %}required{% endif %}
minlength="{{ minlength }}"
>
</span>
{% endmacro %}
{% endmacro %}

View file

@ -5,9 +5,9 @@
<span class="spacer"></span>
{% if current_user.is_authenticated %}
<a href="{{ url_for('views.settings') }}" class="button primary">
<a href="{{ url_for('account.settings') }}" class="button primary">
{{ current_user.username }}
{% if not current_user.email %}<i class="ph ph-warning"></i>{% endif %}
{% if not current_user.email %}<i class="ph ph-warning" style="margin-left: 0.2rem !important;"></i>{% endif %}
</a>
{% else %}
<a href="{{ url_for('auth.auth') }}" class="button primary"><i class="ph ph-user-circle"></i></a>

View file

@ -0,0 +1,61 @@
{% extends "base.html" %}
{% from "macros/input.html" import text %}
{% block content %}
<div class="block">
<h2 style="margin-bottom: 1rem;">Profile Settings</h2>
<form action="{{ url_for('account.settings') }}" method="POST">
<div class="profile-settings">
<div class="picture">
<img src="{{ url_for('static', filename='images/error/2.jpg') }}" alt="Profile picture">
<label for="profile-picture">Profile Picture</label>
<input type="file" name="picture" id="profile-picture">
</div>
<div class="other">
{{ 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) }}
</div>
</div>
<button type="submit" class="button primary">Save changes</button>
</form>
</div>
<div class="block">
<h2>Sessions</h2>
<p>Devices and games that you logged into. If you're looking to log out all website users, reset your password instead.</p>
<div class="table">
<table>
<tr>
<th></th>
<th>Device</th>
<th>Created</th>
<th>Last Used</th>
</tr>
{% for session in sessions %}
<tr id="sess-{{ session.id }}">
<td><button onclick="yeetSession({{ session.id }})" class="button secondary"><i class="ph ph-trash"></i></button></td>
<td>{{ session.device_type }}</td>
<td>{{ session.created_at.strftime('%Y-%m-%d') }}</td>
<td>{{ session.last_used.strftime('%Y-%m-%d') }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="block secondary">
<h2>Danger Zone</h2>
<p>Be careful!</p>
<a href="{{ url_for('account.delete_account') }}" class="button secondary" style="margin-bottom: 0.5rem">Delete Account</a>
<a href="{{ url_for('account.password_reset') }}" class="button secondary" style="margin-bottom: 0.5rem">Reset Password</a>
<a href="{{ url_for('account.settings', action='logout') }}" class="button secondary">Logout</a>
</div>
{% if not current_user.email %}
<script>
addFlashMessage("No Email set. If you loose your account, it will not be possible to recover it!", "error")
</script>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% from "macros/input.html" import text %}
{% block content %}
<div class="block secondary">
<h2>Delete account</h2>
<p>
Deleting your account will delete <span style="color: rgb(var(--secondary)) !important;">EVERYTHING</span> on your account, including <span style="color: rgb(var(--secondary)) !important;">ALL</span> your ever submitted scores.
There is <span style="color: rgb(var(--secondary)) !important;">NO WAY</span> to recover your account from this, are you sure you want todo this?
</p>
<form action="{{ url_for('account.delete_account') }}" method="POST">
{{ text(id="username", name="username", required=True) }}
{{ text(id="password", name="password", type="password", required=True) }}
<button type="submit" class="button secondary">Delete account forever</button>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% from "macros/input.html" import text %}
{% block content %}
<div class="block secondary">
<h2>Password Reset</h2>
<p>Forgotten your current password? Go here [insert password reset tool link]</p>
<form action="{{ url_for('account.password_reset') }}" method="POST">
{{ 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) }}
<button type="submit" class="button secondary">Reset</button>
</form>
</div>
{% endblock %}

View file

@ -1,54 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% if not current_user.email %}
<div class="block secondary">
<h2>No Email set</h2>
<p>If you forget your password, you will not be able to recover your account.</p>
</div>
{% endif %}
<div class="block">
<h2>Hello, {{ current_user.username }}!</h2>
<p>Sample text</p>
</div>
<div class="block">
<h2>Sessions</h2>
<p>Devices and games that you logged into. If you're looking to logout all website users, reset your password instead.</p>
<div class="table">
<table>
<tr>
<th></th>
<th>Device</th>
<th>Created</th>
<th>Last Used</th>
</tr>
{% if sessions %}
{% for session in sessions %}
<tr id="sess-{{ session.id }}">
<td><button onclick="yeetSession({{ session.id }})" class="button secondary"><i class="ph ph-trash"></i></button></td>
<td>{{ session.device_type }}</td>
<td>{{ session.created_at.strftime('%Y-%m-%d') }}</td>
<td>{{ session.last_used.strftime('%Y-%m-%d') }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{% endif %}
</table>
</div>
</div>
<div class="block secondary">
<h2>Danger Zone</h2>
<p>Be careful!</p>
<a href="{{ url_for('views.settings', action='delete') }}" class="button secondary" style="margin-bottom: 0.5rem">Delete Account</a>
<a href="{{ url_for('views.settings', action='password') }}" class="button secondary" style="margin-bottom: 0.5rem">Reset Password</a>
<a href="{{ url_for('views.settings', action='logout') }}" class="button secondary">Logout</a>
</div>
{% endblock %}

View file

@ -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)