from flask import Flask, request, render_template, jsonify, redirect, send_from_directory from backend.db_config import lade_db_config, speichere_db_config, teste_verbindung, initialisiere_admin_user from datetime import datetime, timedelta from jwt import decode, ExpiredSignatureError, InvalidTokenError import time import os import jwt import logging from logging.handlers import RotatingFileHandler app = Flask(__name__) app.template_folder = "templates" SECRET_KEY = "bitte_hier_dein_geheimes_passwort_setzen" # Achtung: später z. B. aus .env CONFIG_PATH = "config/db_config.json" MAX_WAIT = 30 # In Sekunden WAIT_INTERVAL = 10 # Logging-Zielpfade log_dir = "Tools" os.makedirs(log_dir, exist_ok=True) # App-Log (alles ab INFO) app_log_handler = RotatingFileHandler( os.path.join(log_dir, "app.log"), maxBytes=1_000_000, backupCount=3 ) app_log_handler.setLevel(logging.INFO) app_log_handler.setFormatter(logging.Formatter( "%(asctime)s [%(levelname)s] %(message)s")) # Error-Log (nur ERROR) error_log_handler = RotatingFileHandler( os.path.join(log_dir, "error.log"), maxBytes=1_000_000, backupCount=3 ) error_log_handler.setLevel(logging.ERROR) error_log_handler.setFormatter(logging.Formatter( "%(asctime)s [%(levelname)s] %(message)s")) # Logging-Grundkonfiguration logging.basicConfig( level=logging.INFO, handlers=[ app_log_handler, error_log_handler, logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def ist_konfiguriert(): return os.path.exists(CONFIG_PATH) def versuche_verbindung_mit_warten(): elapsed = 0 if not ist_konfiguriert(): logger.warning("⚠️ Noch keine Konfiguration gefunden.") return False config = lade_db_config() while not teste_verbindung(config) and elapsed < MAX_WAIT: logger.info(f"🔄 DB nicht erreichbar – warte {WAIT_INTERVAL}s...") time.sleep(WAIT_INTERVAL) elapsed += WAIT_INTERVAL if elapsed >= MAX_WAIT: logger.error("❌ Maximale Wartezeit überschritten – DB nicht erreichbar.") return elapsed < MAX_WAIT @app.route('/api/status') def status(): if not ist_konfiguriert(): return jsonify({"status": "init", "db_connected": False}) elif teste_verbindung(lade_db_config()): return jsonify({"status": "ready", "db_connected": True}) else: return jsonify({"status": "error", "db_connected": False}) @app.route('/setup', methods=['GET', 'POST']) def setup(): if request.method == 'POST': db_config = { "host": request.form['host'], "port": int(request.form['port']), "user": request.form['user'], "password": request.form['password'], "database": request.form['database'] } speichere_db_config(db_config) if teste_verbindung(db_config): initialisiere_admin_user(db_config) logger.info("✅ Setup erfolgreich, Admin-User erstellt.") return redirect('/') else: logger.error("❌ Setup fehlgeschlagen – DB-Verbindung nicht möglich.") return "Verbindung fehlgeschlagen. Bitte zurück und prüfen.", 500 return render_template('setup.html') @app.route('/', defaults={'path': ''}) @app.route('/') def serve_react(path): if not ist_konfiguriert() or not teste_verbindung(lade_db_config()): return redirect('/setup') # setup und API dürfen nicht von React überdeckt werden if path.startswith('setup') or path.startswith('api'): return redirect(f'/{path}') # Pfad zu frontend/dist absolut auflösen dist_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'frontend', 'dist')) file_path = os.path.join(dist_dir, path) if path and os.path.exists(file_path): return send_from_directory(dist_dir, path) else: return send_from_directory(dist_dir, 'index.html') @app.route('/api/login', methods=['POST']) def login(): from mysql.connector import connect from werkzeug.security import check_password_hash data = request.get_json() username = data.get('username') password = data.get('password') try: config = lade_db_config() conn = connect(**config) cursor = conn.cursor(dictionary=True) cursor.execute("SELECT * FROM users WHERE username = %s", (username,)) user = cursor.fetchone() cursor.close() conn.close() if user and check_password_hash(user['password'], password): logger.info(f"✅ Login erfolgreich: {username}") payload = { "username": user['username'], "role": user['role'], "exp": datetime.utcnow() + timedelta(minutes=60) } token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") return jsonify({ "token": token, "role": user['role'] }) logger.warning(f"⛔ Login fehlgeschlagen: {username}") return jsonify({"message": "Login fehlgeschlagen"}), 401 except Exception as e: logger.error(f"[Login-Fehler] {e}") return jsonify({"message": "Serverfehler"}), 500 @app.route('/api/logout', methods=['POST']) def logout(): return jsonify({"message": "Logout erfolgreich"}) def verify_token(): auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): logger.warning("🔐 Kein gültiger Bearer-Header") return None token = auth_header.replace("Bearer ", "") try: decoded = decode(token, SECRET_KEY, algorithms=["HS256"]) return decoded except ExpiredSignatureError: logger.warning("🔐 Token abgelaufen") return None except InvalidTokenError: logger.warning("🔐 Ungültiger Token") return None @app.route('/api/hash/md5', methods=['POST']) def hash_md5(): logger.info("🔁 /api/hash/md5 aufgerufen") user = verify_token() if not user: logger.warning("⛔ Nicht autorisiert") return jsonify({"message": "Nicht autorisiert"}), 401 try: data = request.get_json() logger.debug(f"📩 Payload: {data}") password = data.get("password", "") import hashlib result = hashlib.md5(password.encode()).hexdigest() logger.info(f"✅ Hash erstellt von {user['username']}") return jsonify({ "md5": result, "by": user['username'] }) except Exception as e: logger.error(f"❌ Fehler beim Hashen: {e}") return jsonify({"message": "Fehler beim Hashen"}), 500 if __name__ == '__main__': os.makedirs("config", exist_ok=True) app.run(host='0.0.0.0', port=5000, debug=True) # debug=True nur für Entwicklung, nicht in Produktion verwenden!