diff --git a/.dockerignore b/.dockerignore index 1cda462..819c9bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,5 @@ data/db/* data/static/avatars/* !data/static/avatars/default.webp + +.local/ diff --git a/.gitignore b/.gitignore index 0b936da..a5221e4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ data/static/avatars/* !data/static/avatars/default.webp config/secrets.prod.env + +.local/ diff --git a/app/__init__.py b/app/__init__.py index 496f30b..1bd97ce 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,43 @@ from flask import Flask from dotenv import load_dotenv +from .models import Avatars, Users +from .auth import digest +from .constants import PermissionLevel import os +import time +import secrets + +def create_default_avatar(): + if Avatars.count() == 0: + print("Creating default avatar reference") + Avatars.create({ + "file_path": "/static/avatars/default.webp", + "uploaded_at": int(time.time()) + }) + +def create_admin(): + username = "admin" + if Users.count({"username": username}) == 0: + print("!!!!!Creating admin account!!!!!") + password_length = 16 + password = secrets.token_urlsafe(password_length) + hashed = digest(password) + Users.create({ + "username": username, + "password_hash": hashed, + "permission": PermissionLevel.ADMIN.value, + }) + print(f"!!!!!Administrator account created, use '{username}' as the login and '{password}' as the password. This will only be shown once!!!!!") + +def create_deleted_user(): + username = "DeletedUser" + if Users.count({"username": username}) == 0: + print("Creating DeletedUser") + Users.create({ + "username": username, + "password_hash": "", + "permission": PermissionLevel.SYSTEM.value, + }) def create_app(): app = Flask(__name__) @@ -22,6 +59,10 @@ def create_app(): create_tables() run_migrations() + create_default_avatar() + create_admin() + create_deleted_user() + from app.routes.app import bp as app_bp app.register_blueprint(app_bp) diff --git a/app/auth.py b/app/auth.py index 47a5d28..a4e7275 100644 --- a/app/auth.py +++ b/app/auth.py @@ -2,7 +2,7 @@ from argon2 import PasswordHasher ph = PasswordHasher() -def hash_password(password): +def digest(password): return ph.hash(password) def verify(expected, given): diff --git a/app/constants.py b/app/constants.py new file mode 100644 index 0000000..136fdce --- /dev/null +++ b/app/constants.py @@ -0,0 +1,8 @@ +from enum import Enum + +class PermissionLevel(Enum): + GUEST = 0 + USER = 1 + MODERATOR = 2 + SYSTEM = 3 + ADMIN = 4 diff --git a/app/db.py b/app/db.py index db8d9ba..6e41e50 100644 --- a/app/db.py +++ b/app/db.py @@ -200,6 +200,16 @@ class Model: return None + @classmethod + def count(cls, conditions = None): + qb = db.QueryBuilder(cls.table).select("COUNT(*) AS c") + if conditions is not None: + qb.where(conditions) + + result = qb.first() + return result["c"] if result else 0 + + def update(self, data): qb = db.QueryBuilder(self.table)\ .where({"id": self._data["id"]}) diff --git a/app/models.py b/app/models.py index f0987c4..f539199 100644 --- a/app/models.py +++ b/app/models.py @@ -1 +1,25 @@ from .db import Model + +class Users(Model): + table = "users" + +class Topics(Model): + table = "topics" + +class Threads(Model): + table = "threads" + +class Posts(Model): + table = "posts" + +class PostHistory(Model): + table = "post_history" + +class Sessions(Model): + table = "sessions" + +class Avatars(Model): + table = "avatars" + +class Subscriptions(Model): + table = "subscriptions"