From f188af4abff1ea2f896afed9a527e9123dd29c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20J=C3=B6nsson?= Date: Mon, 17 Nov 2025 10:30:47 +0100 Subject: [PATCH] added csrf protection & fixed minor functionality on admin page --- app.py | 36 +++++++++++++++++++++++-- requirements.txt | 1 + templates/admin.html | 60 +++++++++++++++++++++++++++++++++++++++++ templates/base.html | 3 +++ templates/login.html | 1 + templates/register.html | 1 + 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 templates/admin.html diff --git a/app.py b/app.py index 173a2c4..1351c0a 100644 --- a/app.py +++ b/app.py @@ -1,10 +1,12 @@ -from flask import Flask, render_template, redirect, url_for, request +from flask import Flask, render_template, redirect, url_for, request, abort +from flask_wtf.csrf import CSRFProtect from flask_sqlalchemy import SQLAlchemy -from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user +from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user from werkzeug.security import generate_password_hash, check_password_hash import os app = Flask(__name__) +csrf = CSRFProtect(app) app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"] from urllib.parse import quote_plus @@ -38,6 +40,7 @@ class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), unique=True) password = db.Column(db.String(200)) # Increased for password hash + admin = db.Column(db.Boolean, default=False) @login_manager.user_loader @@ -81,6 +84,11 @@ def register(): password = generate_password_hash(password) new_user = User(email=email, password=password) + + # Automatically make first user admin + if User.query.count() == 0: + new_user.admin = True + db.session.add(new_user) db.session.commit() @@ -99,3 +107,27 @@ def dashboard(): def logout(): logout_user() return redirect(url_for("login")) + +@app.route("/admin", methods=["GET", "POST"]) +@login_required +def admin(): + if not current_user.admin: + abort(403) + + if request.method == "POST": + action = request.form.get("action") + user_id = request.form.get("user_id") + user = User.query.get(user_id) + + if user and user != current_user: # Prevent self-modification + if action == "promote": + user.admin = True + elif action == "demote": + user.admin = False + elif action == "delete": + db.session.delete(user) + + db.session.commit() + + users = User.query.all() + return render_template("admin.html", users=users) diff --git a/requirements.txt b/requirements.txt index 6b602d2..71ab25c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ flask==3.0.0 flask-sqlalchemy==3.1.1 flask-login==0.6.3 +flask-wtf==1.2.1 werkzeug==3.0.0 gunicorn==21.2.0 psycopg2-binary==2.9.9 diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..7991ed6 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} + +{% block title %}Admin Panel{% endblock %} + +{% block content %} +
+
+
+

Admin Dashboard

+
+
+
User Management
+
+ + + + + + + + + + + {% for user in users %} + + + + + + + {% endfor %} + +
IDEmailAdmin StatusActions
{{ user.id }}{{ user.email }}{% if user.admin %}Admin{% else %}User{% endif %} + {% if user.admin %} +
+ + + + +
+ {% else %} +
+ + + + +
+ {% endif %} +
+ + + + +
+
+
+
+
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html index 3fe9ec8..5d7103c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -30,6 +30,9 @@ diff --git a/templates/login.html b/templates/login.html index 8c18d12..bf592d5 100644 --- a/templates/login.html +++ b/templates/login.html @@ -9,6 +9,7 @@

Sign In

+
diff --git a/templates/register.html b/templates/register.html index a42c26f..74dc4bb 100644 --- a/templates/register.html +++ b/templates/register.html @@ -9,6 +9,7 @@

Create Account

+