From b4efbc8f3fdc3388c486b17e7c0e53acb21ed75b Mon Sep 17 00:00:00 2001 From: Matthew Welch Date: Mon, 22 Mar 2021 16:23:52 -0700 Subject: [PATCH] Added user authentication for the admin site Added error pages for 404 and 500 codes Added site icon --- admin/admin.py | 7 ++++- admin/templates/admin/base.html | 1 + config.py | 1 + database.py | 39 ++++++++++++++++++++++++-- main.py | 25 ++++++++++++++--- static/favicon.ico | Bin 0 -> 165015 bytes templates/base.html | 1 + templates/error.html | 8 ++++++ templates/security/login_user.html | 22 +++++++++++++++ templates/security/register_user.html | 22 +++++++++++++++ 10 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 static/favicon.ico create mode 100644 templates/error.html create mode 100644 templates/security/login_user.html create mode 100644 templates/security/register_user.html diff --git a/admin/admin.py b/admin/admin.py index 301e5ba..ec27447 100644 --- a/admin/admin.py +++ b/admin/admin.py @@ -1,5 +1,6 @@ import json -from flask import render_template, Blueprint, request, jsonify +from flask import render_template, Blueprint, request, jsonify, redirect, url_for +from flask_security import roles_required, auth_required import database from database import get_all_questions, get_question @@ -8,22 +9,26 @@ Admin = Blueprint("admin", __name__, template_folder="templates") @Admin.route("/admin/") +@roles_required("admin") def index(): return render_template("admin/index.html", title="admin") @Admin.route("/admin/questions/") +@roles_required("admin") def questions(): return render_template("admin/question_list.html") @Admin.route("/admin/questions/edit/") +@roles_required("admin") def edit_question(question_id): question: database.AllQuestions = get_question(database.AllQuestions, question_id) return render_template("admin/edit_question.html", question=question) @Admin.route("/admin/question_query") +@roles_required("admin") def query_questions(): offset = request.args.get("offset", type=int) limit = request.args.get("limit", type=int) diff --git a/admin/templates/admin/base.html b/admin/templates/admin/base.html index 5bf2938..9c5d268 100644 --- a/admin/templates/admin/base.html +++ b/admin/templates/admin/base.html @@ -3,6 +3,7 @@ Admin + {% block head %}{% endblock %} diff --git a/config.py b/config.py index e05ca65..3c62f4b 100644 --- a/config.py +++ b/config.py @@ -13,6 +13,7 @@ class Config: DB_HOST = environ.get("DB_HOST") DB_PORT = environ.get("DB_PORT") DB_NAME = environ.get("DB_NAME") + SECURITY_REGISTERABLE = True class Production(Config): diff --git a/database.py b/database.py index afdfac9..f4bebaa 100644 --- a/database.py +++ b/database.py @@ -1,9 +1,10 @@ from flask import current_app, g +from flask_security import UserMixin, RoleMixin import sqlalchemy from typing import Union, Optional, Literal, Type, List, Tuple import random import os -from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY +from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY, Boolean, UnicodeText, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import sessionmaker, relationship, scoped_session @@ -16,6 +17,38 @@ engine = create_engine(config.DB_URL) session_factory = sessionmaker(bind=engine) Session = scoped_session(session_factory) Base = declarative_base() +Base.query = Session.query_property() + + +class User(Base, UserMixin): + __tablename__ = "users" + user_id = Column(Integer, primary_key=True) + email = Column(String, unique=True, nullable=False) + username = Column(String, unique=True) + password = Column(String, nullable=False) + active = Column(Boolean, nullable=False) + last_login_at = Column(DateTime()) + current_login_at = Column(DateTime()) + last_login_ip = Column(String(100)) + current_login_ip = Column(String(100)) + login_count = Column(Integer) + fs_uniquifier = Column(String, unique=True, nullable=False) + roles = relationship("Role", secondary="users_roles") + + +class Role(Base, RoleMixin): + __tablename__ = "roles" + role_id = Column(Integer, primary_key=True) + name = Column(String, unique=True) + description = Column(String) + permissions = Column(UnicodeText) + + +class UsersRoles(Base): + __tablename__ = "users_roles" + user_role_id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey("users.user_id")) + role_id = Column(Integer, ForeignKey("roles.role_id")) class AllQuestions(Base): @@ -91,6 +124,9 @@ class MultipleChoice(Base): return f"" +Base.metadata.create_all(engine) + + def add_multiple_choice_question(question, answer, addresses, difficulty, hint, wrong_answers): session: sqlalchemy.orm.session.Session = Session() question_id = session.query(AllQuestions).count() @@ -140,7 +176,6 @@ def get_random_hidden_answer(difficulty: Optional[Literal[1, 2, 3]] = None) -> H def get_random_multiple_choice(difficulty: Optional[Literal[1, 2, 3]] = None) -> MultipleChoice: session: sqlalchemy.orm.session.Session = Session() - print(type(session)) if difficulty is not None: return session.query(MultipleChoice).filter(MultipleChoice.difficulty == difficulty).order_by(func.random()).first() return session.query(MultipleChoice).order_by(func.random()).first() diff --git a/main.py b/main.py index 8fa6ec5..191f54c 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,15 @@ import os -from flask import Flask, render_template, request, jsonify -from flask_security import Security +from flask import request, render_template, jsonify, Flask +from flask_security import Security, SQLAlchemySessionUserDatastore, RegisterForm import database from admin import admin app = Flask(__name__) -app.register_blueprint(admin.Admin) environment_configuration = os.environ['CONFIGURATION_SETUP'] app.config.from_object(environment_configuration) +user_datastore = SQLAlchemySessionUserDatastore(database.Session, database.User, database.Role) +security = Security(app, user_datastore) +app.register_blueprint(admin.Admin) @app.route("/") @@ -31,7 +33,7 @@ def hidden_answer_category(): @app.route("/category/multiple_choice") def multiple_choice_category(): - easy = database.get_random_multiple_choice(3) + easy = database.get_random_multiple_choice(1) easy.randomize_answer_list() medium = database.get_random_multiple_choice(2) medium.randomize_answer_list() @@ -54,5 +56,20 @@ def check_answer(): return jsonify(database.check_answer(question_id, answer)) +@app.errorhandler(404) +def error_404(e): + print(e) + return render_template("error.html", error_msg="The requested page can not be found.", error_code=404), 404 + + +@app.errorhandler(500) +def error_404(e): + print(e) + msg = "There was an error with the server." + if app.config["DEBUG"]: + msg = e + return render_template("error.html", error_msg=msg, error_code=500), 500 + + if __name__ == "__main__": app.run() diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..45e4858f7e1d211b1fd4090777e335ebcaaa4e8e GIT binary patch literal 165015 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 L2ELs=Mp6DBWZ@*F literal 0 HcmV?d00001 diff --git a/templates/base.html b/templates/base.html index 7cc991e..7fa98c3 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,6 +3,7 @@ Quiz The Word - {{ title }} + diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..000ea20 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block body %} +
+

{{ error_code }}

+

{{ error_msg }}

+
+{% endblock %} \ No newline at end of file diff --git a/templates/security/login_user.html b/templates/security/login_user.html new file mode 100644 index 0000000..cb50f86 --- /dev/null +++ b/templates/security/login_user.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block body %} +
+
+ {{ login_user_form.hidden_tag() }} +
+ + +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/security/register_user.html b/templates/security/register_user.html new file mode 100644 index 0000000..35c7e3c --- /dev/null +++ b/templates/security/register_user.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block body %} +
+
+ {{ register_user_form.hidden_tag() }} +
+ + +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} \ No newline at end of file