From dcfd7871ed2104361437ed6ee9de5308eacfcea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gdula?= Date: Mon, 12 Jun 2023 17:37:59 +0300 Subject: [PATCH] Remove Token login and switch to Sessions --- TFR/server/api.py | 135 +++++++++++++++++------------- TFR/server/auth.py | 11 +-- TFR/server/config.py | 1 + TFR/server/models.py | 15 +++- TFR/server/static/js/main.js | 44 +++------- TFR/server/templates/account.html | 28 ++++--- 6 files changed, 121 insertions(+), 113 deletions(-) diff --git a/TFR/server/api.py b/TFR/server/api.py index e64fa35..5aa8de5 100644 --- a/TFR/server/api.py +++ b/TFR/server/api.py @@ -3,86 +3,77 @@ import shortuuid from flask import Blueprint, request, jsonify from flask_login import login_required, current_user -from server.models import Tokens, Scores, Users +from server.models import Scores, Sessions, Users from server.extensions import db -from server.config import GAME_VERSION, GAME_VERSIONS, GAME_DIFFICULTIES, USER_MAX_TOKENS, MAX_SEARCH_RESULTS +from server.config import GAME_VERSION, GAME_VERSIONS, GAME_DIFFICULTIES, USER_MAX_TOKENS, MAX_SEARCH_RESULTS, USER_REGEX blueprint = Blueprint("api", __name__, url_prefix="/api") -@blueprint.route("/tokens", methods=["DELETE", "POST"]) +@blueprint.route("/tokens", methods=["POST"]) @login_required def tokens(): - if request.method == "DELETE": - token_id = request.form["token_id"] - if not token_id: - return jsonify({"error": "No token ID provided!"}), 400 + session_id = request.form["session_id"] - token = Tokens.query.filter_by(id=token_id).first() - if not token: - return jsonify({"error": "Token not found!"}), 404 - if token.user_id != current_user.id: - return jsonify({"error": "You do not own this token!"}), 403 + if not session_id: + return jsonify({"error": "No Session provided!"}), 400 - db.session.delete(token) - db.session.commit() + session = Sessions.query.filter_by(id=session_id).first() - return jsonify({"success": "Token deleted!"}), 200 - elif request.method == "POST": - if len(Tokens.query.filter_by(user_id=current_user.id).all()) >= USER_MAX_TOKENS: - return jsonify({"error": f"You already have {USER_MAX_TOKENS} tokens!"}), 403 + if not session: + return jsonify({"error": "Session not found!"}), 404 + if session.user_id != current_user.id: + return jsonify({"error": "You do not own this session!"}), 403 - new_string = str(shortuuid.ShortUUID().random(length=20)) - token = Tokens(token=new_string, user_id=current_user.id) - db.session.add(token) - db.session.commit() + db.session.delete(session) + db.session.commit() - return jsonify({"success": "Token added!"}), 200 + return jsonify({"success": "Session deleted!"}) @blueprint.route("/post", methods=["POST"]) def post(): form = request.form - errors = [] + error = [] if not form: - errors += "No form data provided!" - if not form["token"]: - errors += "No token provided!" + error.append("No form data provided!") + if not form["session"]: + error.append("No session key provided!") if not form["version"]: - errors += "No version provided!" + error.append("No version provided!") - if errors: - return jsonify(errors), 400 + if error: + return jsonify(error), 400 try: int(form["score"]) int(form["difficulty"]) except TypeError: - errors += "Invalid score and difficulty must be valid numbers!" + error.append("Invalid score and difficulty must be valid numbers!") if int(form["difficulty"]) not in GAME_DIFFICULTIES: - errors += "Invalid difficulty!" + error.append("Invalid difficulty!") - token_data = Tokens.query.filter_by(token=form["token"]).first() - if not token_data: - errors += "Authentication failed!" + session_data = Sessions.query.filter_by(auth_key=form["session"]).first() + if not session_data: + error.append("Authentication failed!") - if errors: - return jsonify(errors), 400 + if error: + return jsonify(error), 400 score = Scores( score=int(form["score"]), difficulty=int(form["difficulty"]), version=form["version"], - user_id=token_data.user_id, + user_id=session_data.user_id, ) db.session.add(score) db.session.commit() - return "Success!", 200 + return "Success!" @blueprint.route("/search", methods=["GET"]) @@ -92,26 +83,52 @@ def search(): if not search_arg: return "No search query provided!", 400 - users = Users.query.filter(Users.username.contains(search)).limit(MAX_SEARCH_RESULTS).all() + users = Users.query.filter(Users.username.icontains(search_arg)).limit(MAX_SEARCH_RESULTS).all() - return jsonify([user.username for user in users]), 200 + return jsonify([user.username for user in users]) -# @blueprint.route("/login", methods=["POST"]) -# def login(): -# username = request.form["username"] -# password = request.form["password"] -# errors = [] -# -# if not username: -# errors += "Empty Username" -# if not password: -# errors += "Empty Password" -# -# if errors: -# return jsonify(errors), 400 -# -# user = Users.query.filter(username=username).first() -# if not user: -# errors += "No user found" -# +@blueprint.route("/login", methods=["POST"]) +def login(): + username = request.form["username"].strip() + password = request.form["password"].strip() + device = request.form["device"].strip() + username_regex = re.compile(USER_REGEX) + + error = [] + + if not username or not username_regex.match(username) or not password: + error.append("Username or Password is incorrect!") + + user = Users.query.filter_by(username=username).first() + + if not user or not check_password_hash(user.password, password): + error.append("Username or Password is incorrect!") + + if error: + return jsonify(error), 400 + + session = Sessions( + user_id=user.id, + auth_key=str(shortuuid.ShortUUID().random(length=32)), + id_address=request.remote_addr, + device_type=device + ) + db.session.add(session) + db.session.commit() + + return str(session.auth_key) + + +@blueprint.route("/authenticate", methods=["POST"]) +def authenticate(): + auth_key = request.form["auth_key"].strip() + + session = Sessions.query.filter_by(auth_key=auth_key).first() + + if not session: + return "Invalid session", 400 + + user_data = Users.query.filter_by(id=session.user_id).first() + + return jsonify({'username':user_data.username}) diff --git a/TFR/server/auth.py b/TFR/server/auth.py index 8856f38..befe6ab 100644 --- a/TFR/server/auth.py +++ b/TFR/server/auth.py @@ -6,7 +6,8 @@ from flask_login import login_required, login_user, logout_user, current_user from werkzeug.security import generate_password_hash, check_password_hash from server.extensions import db -from server.models import Users, Tokens +from server.models import Users, Sessions +from server.config import USER_REGEX blueprint = Blueprint("auth", __name__) @@ -31,8 +32,8 @@ def account(): if action == "password": flash("Insert password change function", "error") - token_list = Tokens.query.filter_by(user_id=current_user.id).all() - return render_template("account.html", token_list=token_list) + sessions = Sessions.query.filter_by(user_id=current_user.id).all() + return render_template("account.html", sessions=sessions) @blueprint.route("/register", methods=["POST"]) @@ -40,7 +41,7 @@ def register(): # Get the form data username = request.form["username"].strip() password = request.form["password"].strip() - username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b") + username_regex = re.compile(USER_REGEX) error = [] @@ -79,7 +80,7 @@ def login(): # Get the form data username = request.form["username"].strip() password = request.form["password"].strip() - username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b") + username_regex = re.compile(USER_REGEX) error = [] diff --git a/TFR/server/config.py b/TFR/server/config.py index fb532cd..c588107 100644 --- a/TFR/server/config.py +++ b/TFR/server/config.py @@ -6,6 +6,7 @@ GAME_VERSIONS = ["alpha"] GAME_DIFFICULTIES = [0, 1, 2, 3, 4] USER_MAX_TOKENS = 3 +USER_REGEX = r"\b[A-Za-z0-9._-]+\b" MAX_TOP_SCORES = 15 MAX_SEARCH_RESULTS = 5 diff --git a/TFR/server/models.py b/TFR/server/models.py index 614d612..e897659 100644 --- a/TFR/server/models.py +++ b/TFR/server/models.py @@ -33,18 +33,25 @@ class Scores(db.Model): user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True)) -class Tokens(db.Model): +class Sessions(db.Model): """ - Token table + Sessions table """ id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("users.id", use_alter=True)) - token = db.Column(db.String, nullable=False, unique=True) + auth_key = db.Column(db.String, nullable=False, unique=True) + ip_address = db.Column(db.String) + device_type = db.Column(db.String) created_at = db.Column( db.DateTime, nullable=False, server_default=db.func.now(), ) + last_used = db.Column( + db.DateTime, + nullable=False, + server_default=db.func.now(), + ) class Users(db.Model, UserMixin): @@ -64,7 +71,7 @@ class Users(db.Model, UserMixin): scores = db.relationship("Scores", backref=db.backref('users', lazy=True)) - tokens = db.relationship("Tokens", backref=db.backref('users', lazy=True)) + tokens = db.relationship("Sessions", backref=db.backref('users', lazy=True)) def get_id(self): return str(self.alt_id) diff --git a/TFR/server/static/js/main.js b/TFR/server/static/js/main.js index 743abb2..7bee6f4 100644 --- a/TFR/server/static/js/main.js +++ b/TFR/server/static/js/main.js @@ -11,44 +11,22 @@ function addFlashMessage(message, type='success') { document.querySelector('.flash').appendChild(flask); } -function ajax(url, form, callback, method='POST') { - console.log(form) - fetch(url, { - method: method, +function yeetSession(id) { + let form = new FormData(); + form.append('session_id', id); + + fetch('/api/tokens', { + method: 'POST', body: form, }) - .then(response => response.json()) - .then(data => callback(data)) - .catch(error => addFlashMessage(error.error, 'error')); -} - -function deleteToken(id) { - let form = new FormData(); - form.append('token_id', id); - - ajax('/api/tokens', form, (data) => { + .then(response => response.json()) + .then(data => { if (data.success) { addFlashMessage(data.success, 'success'); - document.querySelector(`#token-${id}`).remove(); + document.querySelector(`#sess-${id}`).remove(); } else { addFlashMessage(data.error, 'error'); } - }, 'DELETE'); -} - -function addToken() { - ajax('/api/tokens', null, (data) => { - if (data.success) { - window.location.reload(); - } else { - addFlashMessage(data.error, 'error'); - } - }); -} - -function viewToken(id) { - let token = document.querySelector(`#token-${id}`); - let hidden = token.children[2]; - - hidden.classList.toggle('hidden'); + }) + .catch(error => addFlashMessage(error.error, 'error')); } diff --git a/TFR/server/templates/account.html b/TFR/server/templates/account.html index a49719b..c26590a 100644 --- a/TFR/server/templates/account.html +++ b/TFR/server/templates/account.html @@ -9,18 +9,22 @@ {% endblock %} {% block content %}
-

Tokens

-

These are your API tokens. Used to link the uploaded scores with your account.

- - {% for token in token_list %} - - - - - - {% endfor %} -
- +

Sessions

+

Devices and games that you logged into, yeet them if it wasn't you who logged in!

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

No sessions active. If you're looking to logout all website users, reset your password.

+ {% endif %}