From ffba2b3b7bc3600c40a7467e68209a6887051d56 Mon Sep 17 00:00:00 2001 From: Fluffy-Bean Date: Mon, 8 May 2023 01:00:40 +0100 Subject: [PATCH] Add account page and login system --- run.sh | 22 ++++++++++++++ server/auth.py | 55 ++++++++++++++++++++++++++++------- server/models.py | 16 +++++----- server/static/gen/styles.css | 2 +- server/static/style.sass | 55 ++++++++++++++++++----------------- server/templates/about.html | 5 ++++ server/templates/account.html | 10 +++++++ server/templates/auth.html | 8 ++--- server/templates/base.html | 23 ++++++++------- server/templates/scores.html | 12 ++++++++ server/views.py | 9 ++++-- 11 files changed, 154 insertions(+), 63 deletions(-) create mode 100755 run.sh create mode 100644 server/templates/about.html create mode 100644 server/templates/account.html diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0d4aeff --- /dev/null +++ b/run.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Check if migrations folder exists +if [ ! -d "./migrations" ]; +then + echo "Creating tables..." + flask --app server db init +fi + +# Check if there are any changes to the database +if flask --app server db check | grep "No changes detected"; +then + echo "No database changes detected" +else + echo "Database changes detected! Migrating..." + flask --app server db migrate + flask --app server db upgrade +fi + +# Start server!!!! +echo "Starting server..." +# gunicorn --bind highscore:8080 server:app \ No newline at end of file diff --git a/server/auth.py b/server/auth.py index bc660f4..2e8e125 100644 --- a/server/auth.py +++ b/server/auth.py @@ -1,11 +1,11 @@ import re from flask import Blueprint, render_template, request, flash, redirect, url_for -from flask_login import current_user, login_required -from werkzeug.security import generate_password_hash +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 cache, login_manager, db -from server.models import Users +from server.extensions import db +from server.models import Users, Tokens blueprint = Blueprint('auth', __name__) @@ -15,10 +15,12 @@ blueprint = Blueprint('auth', __name__) def auth(): return render_template('auth.html') + @blueprint.route('/account', methods=['GET']) @login_required def account(): - return render_template('account.html') + token_list = Tokens.query.filter_by(holder=current_user.id).all() + return render_template('account.html', token_list=token_list) @blueprint.route('/register', methods=['POST']) @@ -46,11 +48,44 @@ def register(): flash(err, "error") return redirect(url_for("auth.auth")) - register_user = Users( - username=username, - password=generate_password_hash(password, method="sha256"), - ) + register_user = Users(username=username, password=generate_password_hash(password, method="scrypt")) db.session.add(register_user) db.session.commit() - return redirect(url_for("view.index")) \ No newline at end of file + flash("Successfully registered!", "success") + return redirect(url_for("auth.auth")) + + +@blueprint.route('/login', methods=['POST']) +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") + + error = [] + + # Validate the form + 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 there are errors, return them + if error: + for err in error: + flash(err, "error") + return redirect(url_for("auth.account")) + + login_user(user, remember=True) + return redirect(url_for("views.index")) + + +@blueprint.route('/logout', methods=['GET']) +@login_required +def logout(): + logout_user() + return redirect(url_for("views.index")) diff --git a/server/models.py b/server/models.py index c4c793d..3e7c218 100644 --- a/server/models.py +++ b/server/models.py @@ -2,6 +2,7 @@ Database models for the server """ from uuid import uuid4 +from flask_login import UserMixin from server.extensions import db @@ -19,17 +20,17 @@ class Scores(db.Model): username = db.Column(db.String(32), nullable=True) score = db.Column(db.Float, nullable=False) - difficulty = db.Column(db.String, nullable=False) + difficulty = db.Column(db.Integer, nullable=False) scored_at = db.Column( db.DateTime, nullable=False, - server_default=db.func.utcnow(), + server_default=db.func.now(), ) scorer = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) -class Users(db.Model): +class Users(db.Model, UserMixin): """ User table """ @@ -39,12 +40,11 @@ class Users(db.Model): alt_id = db.Column(db.String, nullable=False, unique=True, default=str(uuid4())) username = db.Column(db.String(32), unique=True, nullable=False) - email = db.Column(db.String, unique=True, nullable=False) - password = db.Column(db.String(128), nullable=False) + password = db.Column(db.String, nullable=False) joined_at = db.Column( db.DateTime, nullable=False, - server_default=db.func.utcnow(), # pylint: disable=E1102 + server_default=db.func.now(), ) scores = db.relationship('Scores', backref='user', lazy=True) @@ -61,10 +61,10 @@ class Tokens(db.Model): __tablename__ = "tokens" id = db.Column(db.Integer, primary_key=True) + holder = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) token = db.Column(db.String, nullable=False, unique=True, default=str(uuid4())) created_at = db.Column( db.DateTime, nullable=False, - server_default=db.func.utcnow(), # pylint: disable=E1102 + server_default=db.func.now(), ) - holder = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) diff --git a/server/static/gen/styles.css b/server/static/gen/styles.css index eba9d0b..9133795 100644 --- a/server/static/gen/styles.css +++ b/server/static/gen/styles.css @@ -1 +1 @@ -@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");:root{--black:21,21,21;--white:232,227,227;--primary:213,214,130;--secondary:185,77,77;--gold:255,222,70;--silver:229,220,206;--bronze:193,145,69;--darkBlue:9,9,39}*{box-sizing:border-box;font-family:'Merriweather',serif}html{margin:0;padding:0}body{margin:0;padding:0;display:flex;flex-direction:row;background-color:RGB(var(--darkBlue));color:RGB(var(--white))}.background{width:100%;height:100%;object-fit:cover;position:absolute;z-index:1}.app{margin:0 auto;padding:0;width:800px;min-height:100vh;position:relative;display:flex;flex-direction:column;background-color:rgba(var(--darkBlue),0.7);z-index:2}.app>table{width:100%}header{padding:1rem;background-color:RGBA(var(--darkBlue),0.7)}header>img{margin-bottom:2rem;width:100%;height:auto;text-align:center}header>nav{margin:0;padding:0;height:3rem;display:flex;flex-direction:row;justify-content:center}header>nav>span{width:100%}header>nav>a{margin:auto .25rem;padding:.5rem 1rem;text-decoration:none;white-space:nowrap;color:RGB(var(--primary))}header>nav>a.button{text-decoration:none;background-color:transparent;color:RGB(var(--white));border-radius:2px;transition:background-color .2s ease-in-out,box-shadow .2s ease-in-out,transform .2s ease-in-out}header>nav>a.button:hover{background-color:RGBA(var(--white),0.3);transform:translateY(-0.1rem)}header>nav>a.button.primary{text-decoration:none;background-color:transparent;color:RGB(var(--primary));border-radius:2px;transition:background-color .2s ease-in-out,box-shadow .2s ease-in-out,transform .2s ease-in-out}header>nav>a.button.primary:hover{background-color:RGBA(var(--primary),0.3);transform:translateY(-0.1rem)}header>nav>a.button.secondary{text-decoration:none;background-color:transparent;color:RGB(var(--secondary));border-radius:2px;transition:background-color .2s ease-in-out,box-shadow .2s ease-in-out,transform .2s ease-in-out}header>nav>a.button.secondary:hover{background-color:RGBA(var(--secondary),0.3);transform:translateY(-0.1rem)}.flash{display:flex;flex-direction:column;justify-content:center;align-items:center}.flash>p{margin:.4rem 0 0;padding:.75rem 1rem;width:100%;position:relative;border-left:RGB(var(--secondary)) .25rem solid;background-color:RGB(var(--darkBlue));color:RGB(var(--secondary))}main{padding:1rem;height:100%;display:flex;flex-direction:column}main .center-text{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center}main .center-text>h2{margin:0;text-align:center;font-size:2em;color:RGB(var(--white))}main .center-text>p{margin:0;text-align:center;font-size:1em;color:RGB(var(--white))}main .auth{margin:auto 0;padding:1rem;display:flex;flex-direction:column;background-color:rgba(var(--darkBlue),0.7);border-radius:2px}main .auth>h2{margin:0 0 1rem 0;font-size:1.3em;color:RGB(var(--white))}main form{display:flex;flex-direction:column}main form>input{margin:0 0 1rem 0;padding:.75rem 1rem;border:1px solid RGB(var(--white));border-radius:2px;background-color:RGB(var(--darkBlue));color:RGB(var(--white))}main form>input:focus{outline:none;border-color:RGB(var(--primary))}main form>input.error{border-color:RGB(var(--secondary))}main form>button{margin:0;padding:.75rem 1rem;font-weight:bolder;border:transparent;border-radius:2px;background-color:RGB(var(--primary));color:RGB(var(--black))}main form>button:focus-visible,main form>button:hover{outline:none;background-color:RGBA(var(--primary),0.3);color:RGB(var(--primary))}main form>button.disabled{pointer-events:none;opacity:.5}main form>button.secondary{background-color:RGB(var(--secondary));color:RGB(var(--black))}main form>button.secondary:focus-visible,main form>button.secondary:hover{background-color:RGBA(var(--secondary),0.3);color:RGB(var(--secondary))}footer{padding:.5rem 1rem;width:100%;display:flex;flex-direction:row;background-color:RGBA(var(--darkBlue),0.7)}footer>p{margin:0;width:100%;text-align:center;font-size:.8em;white-space:nowrap;color:RGB(var(--white))}footer>p>a{color:RGB(var(--secondary));text-decoration:none}footer>p>a:hover{text-decoration:underline} \ No newline at end of file +@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");:root{--black:21,21,21;--white:232,227,227;--primary:213,214,130;--secondary:185,77,77;--gold:255,222,70;--silver:229,220,206;--bronze:193,145,69;--darkBlue:9,9,39}*{box-sizing:border-box;font-family:'Merriweather',serif}html{margin:0;padding:0}body{margin:0;padding:0;display:flex;flex-direction:row;background-color:RGB(var(--darkBlue));color:RGB(var(--white))}.background{width:100%;height:100%;object-fit:cover;position:absolute;z-index:1}.app{margin:0 auto;padding:0;width:800px;min-height:100vh;position:relative;display:flex;flex-direction:column;background-color:rgba(var(--darkBlue),0.7);z-index:2}.app>table{width:100%}header{padding:1rem;background-color:RGBA(var(--darkBlue),0.7)}header>img{margin-bottom:1rem;width:100%;height:auto;text-align:center}nav{margin-top:.3rem;padding:0;display:flex;flex-direction:row;justify-content:center}nav>span{width:100%}nav>a{margin:auto .15rem;padding:.5rem 1rem;text-decoration:none;white-space:nowrap;font-size:.9em;color:RGB(var(--primary))}nav>a.button{text-decoration:none;background-color:RGBA(var(--white),0.02);color:RGB(var(--white));border-radius:2px;transition:background-color .2s ease-in-out,transform .1s ease-in-out}nav>a.button:hover{background-color:RGBA(var(--white),0.3);transform:translateY(-0.1rem)}nav>a.button.primary{text-decoration:none;background-color:RGBA(var(--primary),0.02);color:RGB(var(--primary));border-radius:2px;transition:background-color .2s ease-in-out,transform .1s ease-in-out}nav>a.button.primary:hover{background-color:RGBA(var(--primary),0.3);transform:translateY(-0.1rem)}nav>a.button.secondary{text-decoration:none;background-color:RGBA(var(--secondary),0.02);color:RGB(var(--secondary));border-radius:2px;transition:background-color .2s ease-in-out,transform .1s ease-in-out}nav>a.button.secondary:hover{background-color:RGBA(var(--secondary),0.3);transform:translateY(-0.1rem)}nav>a>i{font-size:1.25em;display:block}.flash{display:flex;flex-direction:column;justify-content:center;align-items:center}.flash>p{margin:.4rem 0 0;padding:.75rem 1rem;width:100%;position:relative;border-left:RGB(var(--secondary)) .25rem solid;background-color:RGB(var(--darkBlue));color:RGB(var(--secondary))}main{padding:1rem;height:100%;display:flex;flex-direction:column}main .center-text{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center}main .center-text>h2{margin:0;text-align:center;font-size:2em;color:RGB(var(--white))}main .center-text>p{margin:0;text-align:center;font-size:1em;color:RGB(var(--white))}main .auth{margin-bottom:1rem;padding:1rem;display:flex;flex-direction:column;background-color:rgba(var(--darkBlue),0.7);border-radius:2px}main .auth>h2{margin:0 0 1rem 0;font-size:1.3em;color:RGB(var(--white))}main form{display:flex;flex-direction:column}main form>input{margin:0 0 1rem 0;padding:.75rem 1rem;border:1px solid RGB(var(--white));border-radius:2px;background-color:RGB(var(--darkBlue));color:RGB(var(--white))}main form>input:focus{outline:none;border-color:RGB(var(--primary))}main form>input.error{border-color:RGB(var(--secondary))}main form>button{margin:0;padding:.75rem 1rem;font-weight:bolder;border:transparent;border-radius:2px;background-color:RGB(var(--primary));color:RGB(var(--black))}main form>button:focus-visible,main form>button:hover{outline:none;background-color:RGBA(var(--primary),0.3);color:RGB(var(--primary))}main form>button.disabled{pointer-events:none;opacity:.5}main form>button.secondary{background-color:RGB(var(--secondary));color:RGB(var(--black))}main form>button.secondary:focus-visible,main form>button.secondary:hover{background-color:RGBA(var(--secondary),0.3);color:RGB(var(--secondary))}footer{padding:.5rem 1rem;width:100%;display:flex;flex-direction:row;background-color:RGBA(var(--darkBlue),0.7)}footer>p{margin:0;width:100%;text-align:center;font-size:.8em;white-space:nowrap;color:RGB(var(--white))}footer>p>a{color:RGB(var(--secondary));text-decoration:none}footer>p>a:hover{text-decoration:underline} \ No newline at end of file diff --git a/server/static/style.sass b/server/static/style.sass index 596505d..753c1f2 100644 --- a/server/static/style.sass +++ b/server/static/style.sass @@ -9,16 +9,14 @@ $darkBlue: var(--darkBlue) @mixin button($color) text-decoration: none - background-color: transparent + background-color: RGBA($color, 0.02) color: RGB($color) border-radius: 2px - // box-shadow: 0 0 0 0 RGBA($color, 0) - transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out + transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out &:hover background-color: RGBA($color, 0.3) transform: translateY(-0.1rem) - // box-shadow: 0 0.1rem 0.4rem 0.1rem RGBA($color, 0.2) @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') @@ -79,41 +77,44 @@ header background-color: RGBA($darkBlue, 0.7) > img - margin-bottom: 2rem + margin-bottom: 1rem width: 100% height: auto text-align: center - > nav - margin: 0 - padding: 0 +nav + margin-top: 0.3rem + padding: 0 - height: 3rem + display: flex + flex-direction: row + justify-content: center - display: flex - flex-direction: row - justify-content: center + > span + width: 100% - > span - width: 100% + > a + margin: auto 0.15rem + padding: 0.5rem 1rem - > a - margin: auto 0.25rem - padding: 0.5rem 1rem + text-decoration: none + white-space: nowrap + font-size: 0.9em - text-decoration: none - white-space: nowrap + color: RGB($primary) - color: RGB($primary) + &.button + @include button($white) - &.button - @include button($white) + &.primary + @include button($primary) - &.primary - @include button($primary) + &.secondary + @include button($secondary) - &.secondary - @include button($secondary) + > i + font-size: 1.25em + display: block .flash display: flex @@ -160,7 +161,7 @@ main color: RGB($white) .auth - margin: auto 0 + margin-bottom: 1rem padding: 1rem display: flex diff --git a/server/templates/about.html b/server/templates/about.html new file mode 100644 index 0000000..58d4ab2 --- /dev/null +++ b/server/templates/about.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} +{% block content %} +

What is The Front Rooms?

+

Project Redacted

+{% endblock %} \ No newline at end of file diff --git a/server/templates/account.html b/server/templates/account.html new file mode 100644 index 0000000..e2c3e54 --- /dev/null +++ b/server/templates/account.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% block content %} +

Hello, {{ current_user.username }}!

+ Logout + +

Tokens

+ {% for token in token_list %} +

{{ token.token }}

+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/server/templates/auth.html b/server/templates/auth.html index b31b6e4..6cdc005 100644 --- a/server/templates/auth.html +++ b/server/templates/auth.html @@ -2,21 +2,19 @@ {% block content %}

Login

-
- + +
-

or

-

Register

- +
{% endblock %} \ No newline at end of file diff --git a/server/templates/base.html b/server/templates/base.html index 049be4e..21802af 100644 --- a/server/templates/base.html +++ b/server/templates/base.html @@ -5,30 +5,33 @@ Front Rooms Highscores + {% assets "styles" %} {% endassets %} - The Front Rooms pause menu + The Front Rooms pause menu
- The Front Rooms logo + The Front Rooms logo + + + {% block nav %}{% endblock %}
diff --git a/server/templates/scores.html b/server/templates/scores.html index c62e0fb..4bc24cb 100644 --- a/server/templates/scores.html +++ b/server/templates/scores.html @@ -1,4 +1,16 @@ {% extends "base.html" %} +{% block nav %} + +{% endblock %} {% block content %} {% if scores %} diff --git a/server/views.py b/server/views.py index 9196a2a..60b41fe 100644 --- a/server/views.py +++ b/server/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, request, render_template +from flask import Blueprint, request, render_template from flask_wtf import FlaskForm from wtforms import StringField, IntegerField from wtforms.validators import DataRequired @@ -20,7 +20,7 @@ class ScoreForm(FlaskForm): @blueprint.route('/') -@cache.cached(timeout=60) +# @cache.cached(timeout=60) def index(): difficulty = request.args.get('diff', 0) @@ -32,6 +32,11 @@ def index(): return render_template('scores.html', top_scores=top_scores) +@blueprint.route('/about') +def about(): + return render_template('about.html') + + @blueprint.route('/post', methods=['POST']) def post():