Files
RollCalcPython/app.py
T
2026-07-07 16:12:12 +02:00

180 lines
5.9 KiB
Python

"""
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", # ⚠️ ÄNDERN!
"naue": "naue_access_2026", # ⚠️ ÄNDERN!
"cniehues": "beta_test_2026", # ⚠️ ÄNDERN!
"lvollmert": "Geheim!", # ⚠️ ÄNDERN!
"mtazl": "rollcalc", # ⚠️ ÄNDERN!
"controlling": "beta2026" # ⚠️ ÄNDERN!
}
# Logging für Auditing
LOG_FILE = "access_log.json"
BUILD_INFO_FILE = "build_info.json"
UNKNOWN_BUILD_INFO = {
"version": "unknown",
"branch": "unknown",
"commit": "unknown",
"timestamp": "unknown"
}
# ============================================================================
# BUILD INFO
# ============================================================================
def load_build_info():
"""Load deployment/build metadata for templates."""
try:
with open(BUILD_INFO_FILE, "r") as f:
data = json.load(f)
except Exception as e:
print(f"[Build Info Error] {e}")
return UNKNOWN_BUILD_INFO.copy()
if not isinstance(data, dict):
return UNKNOWN_BUILD_INFO.copy()
build_info = UNKNOWN_BUILD_INFO.copy()
for key in build_info:
value = data.get(key)
if isinstance(value, str) and value.strip():
build_info[key] = value.strip()
return build_info
BUILD_INFO = load_build_info()
@app.context_processor
def inject_build_info():
"""Make build metadata available in all templates."""
return {"build_info": BUILD_INFO}
# ============================================================================
# AUTHENTICATION
# ============================================================================
@auth.verify_password
def verify_password(username, password):
"""Verify HTTP Basic Auth credentials"""
if username in BETA_USERS and BETA_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": "14.1"}), 200
@app.route("/api/user", methods=["GET"])
@auth.login_required
def get_user():
"""Get current authenticated user info"""
user = auth.current_user()
return jsonify({
"username": user,
"authenticated": True,
"timestamp": datetime.now().isoformat()
}), 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
# ============================================================================
# STARTUP
# ============================================================================
if __name__ == "__main__":
os.makedirs("templates", exist_ok=True)
os.makedirs("static", exist_ok=True)
print("""
╔═══════════════════════════════════════════════════════════╗
║ Naue Roll Calculator - V14 + QoL Update ║
║ HTTP Basic Auth Enabled ║
║ ║
║ ⚠️ BEFORE PRODUCTION: Update passwords in app.py ║
║ ║
║ Starten auf: http://localhost:5000 ║
╚═══════════════════════════════════════════════════════════╝
""")
app.run(
host="0.0.0.0",
port=5000,
debug=False,
use_reloader=False
)