v2.1: Security - Captcha, Admin-Login, Auth-Decorator

This commit is contained in:
Peter
2026-05-16 12:21:10 +00:00
parent 1ae070f82f
commit 9f8c7fb78d
6 changed files with 1286 additions and 2 deletions
+1 -2
View File
@@ -2,7 +2,7 @@ FROM python:3.12-slim
WORKDIR /app WORKDIR /app
RUN pip install --no-cache-dir flask flask-cors requests RUN pip install --no-cache-dir flask flask-cors werkzeug requests
COPY app/ ./ COPY app/ ./
@@ -12,7 +12,6 @@ EXPOSE 8080
ENV PORT=8080 ENV PORT=8080
ENV DATA_DIR=/data ENV DATA_DIR=/data
ENV OLLAMA_URL=http://192.168.0.150:11434
VOLUME ["/data"] VOLUME ["/data"]
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
import re
with open('/root/reservation-system/app/main.py', 'r') as f:
content = f.read()
# Add auth import
old_import = "from database import get_db, init_db, generate_booking_number, log_change"
new_import = """from database import get_db, init_db, generate_booking_number, log_change
from auth import require_auth, verify_captcha
from login_routes import auth_bp"""
content = content.replace(old_import, new_import)
# Add secret key config after app creation
old_cors = "CORS(app)"
new_cors = """CORS(app)
# Security config
app.secret_key = os.environ.get('SESSION_SECRET', 'dev-secret-change-in-production')
# Register auth blueprint
app.register_blueprint(auth_bp)
@app.before_request
def check_auth():
# Public endpoints don't require auth
public_endpoints = ['/', '/api/captcha', '/api/rooms', '/api/availability',
'/api/health', '/api/admin/login']
if request.path in public_endpoints or request.path.startswith('/static/'):
return None
# Admin endpoints require login
if request.path.startswith('/api/admin/') and session.get('user_role') != 'admin':
return jsonify({"error": "Unauthorized"}), 401"""
content = content.replace(old_cors, new_cors)
# Protect POST reservations with captcha
old_post = ''' if request.method == 'POST':
data = request.get_json()'''
new_post = ''' if request.method == 'POST':
data = request.get_json()
# Captcha for non-admin bookings
if session.get('user_role') != 'admin':
if not verify_captcha(data.get('captcha_token'), data.get('captcha_answer')):
return jsonify({"error": "Invalid captcha. Please solve the math problem."}), 403'''
content = content.replace(old_post, new_post, 1)
with open('/root/reservation-system/app/main.py', 'w') as f:
f.write(content)
print("Security patches applied!")
+50
View File
@@ -0,0 +1,50 @@
import os
import hashlib
import random
import time
from functools import wraps
from flask import session, request, jsonify
# Config
ADMIN_PASSWORD_HASH = os.environ.get('ADMIN_PASSWORD', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi')
SECRET_KEY = os.environ.get('SESSION_SECRET', 'dev-secret-change-in-production')
def generate_captcha():
a = random.randint(1, 15)
b = random.randint(1, 15)
op = random.choice(['+', '-'])
if op == '+':
answer = a + b
else:
answer = a - b
token = hashlib.sha256(f"{a}{op}{b}{int(time.time()/600)}captcha".encode()).hexdigest()[:16]
return {
"question": f"{a} {op} {b} = ?",
"token": token,
"answer": answer
}
def verify_captcha(token, answer):
if not token or not answer:
return False
stored = session.get('captcha_answer')
if stored and str(stored) == str(answer):
session.pop('captcha_answer', None)
return True
return False
def require_auth(role='admin'):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user_role' not in session:
return jsonify({"error": "Unauthorized"}), 401
if role == 'admin' and session['user_role'] != 'admin':
return jsonify({"error": "Admin required"}), 403
return f(*args, **kwargs)
return decorated
return decorator
def check_admin_password(password):
from werkzeug.security import check_password_hash
return check_password_hash(ADMIN_PASSWORD_HASH, password)
+35
View File
@@ -0,0 +1,35 @@
"""Login und Captcha Routes"""
from flask import Blueprint, request, jsonify, session
from auth import generate_captcha, verify_captcha, check_admin_password
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/api/captcha', methods=['GET'])
def get_captcha():
captcha = generate_captcha()
session['captcha_answer'] = captcha['answer']
return jsonify({"question": captcha['question'], "token": captcha['token']})
@auth_bp.route('/api/admin/login', methods=['POST'])
def admin_login():
data = request.get_json() or {}
password = data.get('password', '')
if check_admin_password(password):
session['user_role'] = 'admin'
session['login_time'] = __import__('time').time()
return jsonify({"status": "ok", "role": "admin"})
return jsonify({"error": "Invalid credentials"}), 401
@auth_bp.route('/api/admin/logout', methods=['POST'])
def admin_logout():
session.pop('user_role', None)
session.pop('login_time', None)
return jsonify({"status": "ok"})
@auth_bp.route('/api/session', methods=['GET'])
def check_session():
role = session.get('user_role')
if role:
return jsonify({"role": role, "logged_in": True})
return jsonify({"role": None, "logged_in": False})
+20
View File
@@ -10,6 +10,8 @@ from flask import Flask, request, jsonify, render_template, send_from_directory,
from flask_cors import CORS from flask_cors import CORS
from database import get_db, init_db, generate_booking_number, log_change from database import get_db, init_db, generate_booking_number, log_change
from auth import require_auth, verify_captcha
from login_routes import auth_bp
from utils.ollama_client import ( from utils.ollama_client import (
parse_email_with_ollama, parse_email_with_ollama,
generate_confirmation_email, generate_confirmation_email,
@@ -21,6 +23,24 @@ app = Flask(__name__,
static_folder='static') static_folder='static')
CORS(app) CORS(app)
# Security config
app.secret_key = os.environ.get('SESSION_SECRET', 'dev-secret-change-in-production')
# Register auth blueprint
app.register_blueprint(auth_bp)
@app.before_request
def check_auth():
# Public endpoints don't require auth
public_endpoints = ['/', '/api/captcha', '/api/rooms', '/api/availability',
'/api/health', '/api/admin/login']
if request.path in public_endpoints or request.path.startswith('/static/'):
return None
# Admin endpoints require login
if request.path.startswith('/api/admin/') and session.get('user_role') != 'admin':
return jsonify({"error": "Unauthorized"}), 401
# Konfiguration # Konfiguration
DEFAULT_OPEN_HOUR = 10 # 10:00 DEFAULT_OPEN_HOUR = 10 # 10:00
DEFAULT_CLOSE_HOUR = 23 # 23:00 DEFAULT_CLOSE_HOUR = 23 # 23:00
File diff suppressed because it is too large Load Diff