""" MCLogger – Flask Web-Panel Multi-Tenant mit Gruppen, Rollen & verschlüsselten DB-Zugangsdaten. Coolify-kompatibel: alle Einstellungen via ENV. """ import secrets from datetime import datetime from flask import Flask, abort, render_template, request, session, url_for from config import Config from panel_db import init_databases, get_user_groups from roles import can_manage_group from blueprints.auth import auth from blueprints.site_admin import site_admin from blueprints.group_admin import group_admin from blueprints.panel import panel def create_app() -> Flask: app = Flask(__name__) app.secret_key = Config.SECRET_KEY app.config.update( SESSION_COOKIE_HTTPONLY=Config.SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SAMESITE=Config.SESSION_COOKIE_SAMESITE, SESSION_COOKIE_SECURE=Config.SESSION_COOKIE_SECURE, ) Config.validate_security() # Blueprints registrieren app.register_blueprint(auth) app.register_blueprint(site_admin) app.register_blueprint(group_admin) app.register_blueprint(panel) # Panel-Datenbank-Tabellen anlegen try: init_databases() except Exception as e: app.logger.warning(f"DB-Initialisierung fehlgeschlagen (noch nicht konfiguriert?): {e}") # ── Template-Filter ─────────────────────────────────────── def _get_or_create_csrf_token() -> str: token = session.get("_csrf_token") if not token: token = secrets.token_urlsafe(32) session["_csrf_token"] = token return token @app.before_request def enforce_csrf(): if request.method not in {"POST", "PUT", "PATCH", "DELETE"}: return session_token = session.get("_csrf_token") request_token = request.form.get("_csrf_token") or request.headers.get("X-CSRF-Token") if not session_token or not request_token or session_token != request_token: abort(400) @app.after_request def set_security_headers(resp): resp.headers.setdefault("X-Content-Type-Options", "nosniff") resp.headers.setdefault("X-Frame-Options", "DENY") resp.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin") resp.headers.setdefault("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data:; font-src 'self' https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors 'none';") return resp @app.route("/privacy-policy") def privacy_policy(): from config import Config return render_template( "privacy_policy.html", last_updated="April 14, 2026", invite_expiry_hours=Config.INVITE_EXPIRY_HOURS, ) @app.errorhandler(400) def bad_request(_): return "Bad request", 400 @app.errorhandler(404) def not_found(_): uid = session.get("user_id") is_site_admin = bool(session.get("is_site_admin")) role = session.get("role") links = [] if not uid: links = [ {"label": "Login", "href": url_for("auth.login"), "btn": "btn-success"}, {"label": "Site Admin Login", "href": url_for("auth.admin_login"), "btn": "btn-outline-danger"}, ] elif is_site_admin and not session.get("group_id"): links = [ {"label": "Site Admin Dashboard", "href": url_for("site_admin.dashboard"), "btn": "btn-danger"}, ] else: links.append({"label": "Panel Dashboard", "href": url_for("panel.dashboard"), "btn": "btn-success"}) if is_site_admin: links.append({"label": "Site Admin", "href": url_for("site_admin.dashboard"), "btn": "btn-outline-danger"}) if can_manage_group(role) and not is_site_admin: links.append({"label": "Group Admin", "href": url_for("group_admin.dashboard"), "btn": "btn-outline-warning"}) return render_template( "404.html", requested_path=request.path, request_method=request.method, links=links, is_logged_in=bool(uid), is_site_admin=is_site_admin, role=role, ), 404 @app.template_filter("fmt_duration") def fmt_duration(seconds): if seconds is None: return "—" seconds = int(seconds) h = seconds // 3600 m = (seconds % 3600) // 60 s = seconds % 60 if h: return f"{h}h {m}m" elif m: return f"{m}m {s}s" return f"{s}s" @app.template_filter("fmt_dt") def fmt_dt(dt): if dt is None: return "—" if isinstance(dt, str): return dt return dt.strftime("%d.%m.%Y %H:%M:%S") @app.context_processor def inject_globals(): uid = session.get("user_id") try: groups = get_user_groups(uid) if uid else [] except Exception: groups = [] return { "now": datetime.now(), "app_version": "2.0.0", "author": "SimolZimol", "user_groups": groups, "csrf_token": _get_or_create_csrf_token, } return app app = create_app() if __name__ == "__main__": app.run(host=Config.HOST, port=Config.PORT, debug=Config.DEBUG)