242 lines
8.1 KiB
Plaintext
242 lines
8.1 KiB
Plaintext
"""
|
|
Naue Roll Calculator - Beta
|
|
Flask app with HTTP Basic Authentication
|
|
"""
|
|
|
|
from flask import Flask, render_template, request, send_file, send_from_directory, jsonify
|
|
from flask_httpauth import HTTPBasicAuth
|
|
from functools import wraps
|
|
import os
|
|
from datetime import datetime
|
|
import json
|
|
|
|
app = Flask(__name__)
|
|
auth = HTTPBasicAuth()
|
|
|
|
# ============================================================================
|
|
# CONFIGURATION
|
|
# ============================================================================
|
|
|
|
# Benutzer für Beta-Phase (in Produktion aus env-Variablen laden!)
|
|
BETA_USERS = {
|
|
"beta": "rollcalc_beta_2026", # Ändere das Passwort!
|
|
"naue": "naue_access_2026", # Zweiter User optional
|
|
"cniehues": "beta_test_2026", # Christian Niehues EDD
|
|
"lvollmert": "Geheim!", # Lars Vollmert EDD
|
|
"mtazl": "rollcalc", # Martin Tazl EDD
|
|
"controlling": "beta2026" # Lars Krückemeier und Lea-Marie
|
|
}
|
|
|
|
# Optional: Admin-Features (z.B. Logs, Stats)
|
|
ADMIN_USERS = {
|
|
"admin": "admin_secure_pwd_2026"
|
|
}
|
|
|
|
# Logging für Auditing
|
|
LOG_FILE = "access_log.json"
|
|
|
|
# ============================================================================
|
|
# AUTHENTICATION
|
|
# ============================================================================
|
|
|
|
@auth.verify_password
|
|
def verify_password(username, password):
|
|
"""Verify HTTP Basic Auth credentials"""
|
|
all_users = {**BETA_USERS, **ADMIN_USERS}
|
|
if username in all_users and all_users[username] == password:
|
|
return username
|
|
return None
|
|
|
|
def log_access(username, endpoint, method, status=200):
|
|
"""Log all access attempts for audit trail"""
|
|
log_entry = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"username": username,
|
|
"endpoint": endpoint,
|
|
"method": method,
|
|
"status": status
|
|
}
|
|
try:
|
|
logs = []
|
|
if os.path.exists(LOG_FILE):
|
|
with open(LOG_FILE, "r") as f:
|
|
logs = json.load(f)
|
|
logs.append(log_entry)
|
|
with open(LOG_FILE, "w") as f:
|
|
json.dump(logs, f, indent=2)
|
|
except Exception as e:
|
|
print(f"[Logging Error] {e}")
|
|
|
|
# ============================================================================
|
|
# ROUTES
|
|
# ============================================================================
|
|
|
|
@app.route("/", methods=["GET"])
|
|
@auth.login_required
|
|
def index():
|
|
"""Main calculator page - requires authentication"""
|
|
log_access(auth.current_user(), "/", "GET")
|
|
return render_template("roll_calculator.html")
|
|
|
|
@app.route("/static/<path:filename>", methods=["GET"])
|
|
@auth.login_required
|
|
def serve_static(filename):
|
|
"""Serve static files (CSS, JS, JSON) - protected"""
|
|
log_access(auth.current_user(), f"/static/{filename}", "GET")
|
|
return send_from_directory("static", filename)
|
|
|
|
@app.route("/api/health", methods=["GET"])
|
|
@auth.login_required
|
|
def health_check():
|
|
"""Simple health check endpoint"""
|
|
return jsonify({"status": "ok", "version": "1.0.0-beta"}), 200
|
|
|
|
@app.route("/api/user", methods=["GET"])
|
|
@auth.login_required
|
|
def get_user():
|
|
"""Get current authenticated user info"""
|
|
user = auth.current_user()
|
|
is_admin = user in ADMIN_USERS
|
|
return jsonify({
|
|
"username": user,
|
|
"authenticated": True,
|
|
"is_admin": is_admin,
|
|
"timestamp": datetime.now().isoformat()
|
|
}), 200
|
|
|
|
# Admin-only endpoint (optional)
|
|
@app.route("/admin/logs", methods=["GET"])
|
|
@auth.login_required
|
|
def get_logs():
|
|
"""View access logs - admin only"""
|
|
user = auth.current_user()
|
|
if user not in ADMIN_USERS:
|
|
log_access(user, "/admin/logs", "GET", 403)
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
log_access(user, "/admin/logs", "GET")
|
|
logs = []
|
|
if os.path.exists(LOG_FILE):
|
|
with open(LOG_FILE, "r") as f:
|
|
logs = json.load(f)
|
|
return jsonify({"logs": logs}), 200
|
|
|
|
# ============================================================================
|
|
# ERROR HANDLERS
|
|
# ============================================================================
|
|
|
|
@app.errorhandler(401)
|
|
def unauthorized(e):
|
|
"""Handle 401 Unauthorized - browser will prompt for credentials"""
|
|
return jsonify({"error": "Unauthorized - please provide valid credentials"}), 401
|
|
|
|
@app.errorhandler(404)
|
|
def not_found(e):
|
|
"""Handle 404 Not Found"""
|
|
return jsonify({"error": "Not found"}), 404
|
|
|
|
@app.errorhandler(500)
|
|
def internal_error(e):
|
|
"""Handle 500 Internal Server Error"""
|
|
return jsonify({"error": "Internal server error"}), 500
|
|
|
|
# ============================================================================
|
|
# ADMIN ROUTES
|
|
# ============================================================================
|
|
|
|
@app.route("/admin", methods=["GET"])
|
|
@auth.login_required
|
|
def admin_dashboard():
|
|
"""Admin Dashboard - nur für Admin-Benutzer"""
|
|
user = auth.current_user()
|
|
if user not in ADMIN_USERS:
|
|
log_access(user, "/admin", "GET", 403)
|
|
return jsonify({"error": "Unauthorized - Admin access required"}), 403
|
|
|
|
log_access(user, "/admin", "GET")
|
|
return render_template("admin.html")
|
|
|
|
|
|
@app.route("/api/admin/stats", methods=["GET"])
|
|
@auth.login_required
|
|
def get_admin_stats():
|
|
"""Statistiken für Admin Dashboard"""
|
|
user = auth.current_user()
|
|
if user not in ADMIN_USERS:
|
|
log_access(user, "/api/admin/stats", "GET", 403)
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
log_access(user, "/api/admin/stats", "GET")
|
|
|
|
logs = []
|
|
if os.path.exists(LOG_FILE):
|
|
try:
|
|
with open(LOG_FILE, "r") as f:
|
|
logs = json.load(f)
|
|
except json.JSONDecodeError:
|
|
logs = []
|
|
|
|
total_requests = len(logs)
|
|
unique_users = len(set(log.get("username", "unknown") for log in logs))
|
|
failed_logins = len([l for l in logs if l.get("status") == 401])
|
|
|
|
return jsonify({
|
|
"total_requests": total_requests,
|
|
"unique_users": unique_users,
|
|
"failed_logins": failed_logins,
|
|
"timestamp": datetime.now().isoformat()
|
|
}), 200
|
|
|
|
|
|
@app.route("/api/admin/logs", methods=["GET"])
|
|
@auth.login_required
|
|
def get_admin_logs():
|
|
"""Access Logs für Admin Dashboard"""
|
|
user = auth.current_user()
|
|
if user not in ADMIN_USERS:
|
|
log_access(user, "/api/admin/logs", "GET", 403)
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
log_access(user, "/api/admin/logs", "GET")
|
|
|
|
logs = []
|
|
if os.path.exists(LOG_FILE):
|
|
try:
|
|
with open(LOG_FILE, "r") as f:
|
|
logs = json.load(f)
|
|
except json.JSONDecodeError:
|
|
logs = []
|
|
|
|
logs_sorted = sorted(logs, key=lambda x: x.get("timestamp", ""), reverse=True)
|
|
return jsonify({"logs": logs_sorted[-500:]}), 200
|
|
|
|
# ============================================================================
|
|
# STARTUP
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
# Erstelle templates-Verzeichnis falls nicht vorhanden
|
|
os.makedirs("templates", exist_ok=True)
|
|
os.makedirs("static", exist_ok=True)
|
|
|
|
print("""
|
|
╔═══════════════════════════════════════════════════════════╗
|
|
║ Naue Roll Calculator - Beta ║
|
|
║ HTTP Basic Auth Enabled ║
|
|
║ ║
|
|
║ Test credentials: ║
|
|
║ User: beta / Password: rollcalc_beta_2026 ║
|
|
║ User: admin / Password: admin_secure_pwd_2026 ║
|
|
║ ║
|
|
║ Starten auf: http://localhost:5000 ║
|
|
║ ⚠️ ÄNDERE DIE PASSWÖRTER VOR PRODUKTIVBEREITSTELLUNG! ║
|
|
╚═══════════════════════════════════════════════════════════╝
|
|
""")
|
|
|
|
app.run(
|
|
host="0.0.0.0",
|
|
port=5000,
|
|
debug=False, # In Produktion: False
|
|
use_reloader=True
|
|
)
|