mirror of
https://github.com/Fluffy-Bean/GameExpo23.git
synced 2025-05-14 14:22:16 +00:00
Deleting accounts
Resetting password Profile settings Fixing random shit
This commit is contained in:
parent
300c80fcd5
commit
e08b31a430
13 changed files with 317 additions and 89 deletions
|
@ -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
135
TFR/server/account.py
Normal 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")
|
|
@ -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:
|
||||
|
|
|
@ -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"))
|
||||
|
|
67
TFR/server/static/sass/profile-settings.sass
Normal file
67
TFR/server/static/sass/profile-settings.sass
Normal 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
|
|
@ -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"
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
61
TFR/server/templates/views/account_settings.html
Normal file
61
TFR/server/templates/views/account_settings.html
Normal 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 %}
|
17
TFR/server/templates/views/delete_account.html
Normal file
17
TFR/server/templates/views/delete_account.html
Normal 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 %}
|
15
TFR/server/templates/views/reset_password.html
Normal file
15
TFR/server/templates/views/reset_password.html
Normal 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 %}
|
|
@ -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 %}
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue