Security, code quality and frontend improvements

- Move SECRET_KEY out of docker-compose into .env (env_file), add .env.example
- Add flask-limiter with 10 req/min on login route; introduce util/limiter.py
- Replace direct mysql.connector.connect() calls with MySQLConnectionPool via util/db_pool.py
- Fix deprecated datetime.utcnow() -> datetime.now(timezone.utc) in auth/login.py
- Remove dead /api/scripts 410 route from admin.py
- Add MD5 security warning in Md5Tool.jsx
- Add ErrorBoundary component and wrap App.jsx
- Expand README with setup guide, screenshot and project structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-04-24 13:52:53 +02:00
parent 8e2c2d740e
commit 80ec5eca7b
12 changed files with 232 additions and 75 deletions
+10 -25
View File
@@ -1,8 +1,7 @@
from flask import Blueprint, request, jsonify
from mysql.connector import connect
from werkzeug.security import generate_password_hash
from auth.token import verify_token
from util.db_config import load_config
from util.db_pool import get_connection
from util.logger import logger
admin_bp = Blueprint("admin", __name__)
@@ -47,8 +46,7 @@ def list_users():
if err:
return err
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor(dictionary=True)
_ensure_tables(cur)
cur.execute("SELECT id, username, role FROM users ORDER BY username ASC")
@@ -73,8 +71,7 @@ def create_user():
if not username or not password:
return jsonify({"message": "Username und Passwort erforderlich"}), 400
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor(dictionary=True)
_ensure_tables(cur)
cur.execute("SELECT id FROM users WHERE username=%s", (username,))
@@ -108,8 +105,7 @@ def update_user(user_id):
if role is None and password is None:
return jsonify({"message": "Nichts zu aktualisieren"}), 400
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor()
_ensure_tables(cur)
if role:
@@ -135,8 +131,7 @@ def delete_user(user_id):
if err:
return err
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor()
_ensure_tables(cur)
# Schutz: Admin darf sich nicht selbst löschen
@@ -170,8 +165,7 @@ def list_websites_admin():
if err:
return err
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor(dictionary=True)
_ensure_tables(cur)
cur.execute("SELECT id, name, url, description FROM websites ORDER BY name ASC")
@@ -196,8 +190,7 @@ def create_website():
if not name or not url:
return jsonify({"message": "Name und URL erforderlich"}), 400
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor()
_ensure_tables(cur)
cur.execute(
@@ -221,8 +214,7 @@ def update_website(item_id):
return err
data = request.get_json() or {}
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor()
_ensure_tables(cur)
cur.execute(
@@ -244,8 +236,7 @@ def delete_website(item_id):
if err:
return err
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor()
_ensure_tables(cur)
cur.execute("DELETE FROM websites WHERE id=%s", (item_id,))
@@ -266,8 +257,7 @@ def list_websites_public():
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
cfg = load_config()
conn = connect(**cfg)
conn = get_connection()
cur = conn.cursor(dictionary=True)
_ensure_tables(cur)
cur.execute("SELECT id, name, url, description FROM websites ORDER BY name ASC")
@@ -278,8 +268,3 @@ def list_websites_public():
except Exception as e:
logger.error(f"[Public list_websites] {e}")
return jsonify({"message": "Serverfehler"}), 500
@admin_bp.route("/api/scripts", methods=["GET"])
def list_scripts_public():
return jsonify({"message": "Scripts wurden entfernt"}), 410