Security hardening

This commit is contained in:
2025-12-27 17:51:00 +01:00
parent 3f26416bdd
commit f631f61bad
12 changed files with 347 additions and 2 deletions
+73 -1
View File
@@ -2,21 +2,63 @@ import os
import uuid
import yaml
import re
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from flask import Flask, render_template, request, redirect, url_for, flash, session
from flask_mail import Mail, Message
from flask_wtf.csrf import CSRFProtect, CSRFError
from werkzeug.utils import secure_filename
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
mail = Mail(app)
csrf = CSRFProtect(app)
# Ensure applications folder exists
Path(app.config['APPLICATIONS_FOLDER']).mkdir(exist_ok=True)
# Rate limiting helper
def check_rate_limit():
"""
Check if user is submitting forms too quickly.
Returns (allowed: bool, wait_seconds: int)
"""
now = datetime.now()
last_submit_str = session.get('last_submission_time')
if last_submit_str:
try:
last_submit = datetime.fromisoformat(last_submit_str)
elapsed = (now - last_submit).total_seconds()
if elapsed < app.config['RATE_LIMIT_SECONDS']:
wait_seconds = int(app.config['RATE_LIMIT_SECONDS'] - elapsed) + 1
return False, wait_seconds
except (ValueError, TypeError):
# Invalid timestamp, allow submission
pass
# Update last submission time
session['last_submission_time'] = now.isoformat()
return True, 0
# Error handlers
@app.errorhandler(CSRFError)
def handle_csrf_error(e):
"""Handle CSRF validation errors."""
flash('Sicherheitsfehler: Die Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.', 'error')
return redirect(url_for('page1_email')), 400
@app.errorhandler(429)
def handle_rate_limit_error(e):
"""Handle rate limit errors."""
return str(e), 429
def get_application_path(session_id):
"""Get the path to an application folder"""
return os.path.join(app.config['APPLICATIONS_FOLDER'], session_id)
@@ -137,6 +179,12 @@ def page1_email():
@app.route('/apply/submit-email', methods=['POST'])
def submit_email():
"""Process email submission and create session"""
# Check rate limit
allowed, wait_seconds = check_rate_limit()
if not allowed:
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
return redirect(url_for('page1_email')), 429
email = request.form.get('email', '').strip()
job_name = request.form.get('job_name', 'Offene Position')
@@ -193,6 +241,12 @@ def page2_personal(session_id):
@app.route('/apply/<session_id>/submit-personal', methods=['POST'])
def submit_personal(session_id):
"""Process personal information submission"""
# Check rate limit
allowed, wait_seconds = check_rate_limit()
if not allowed:
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
return redirect(url_for('page2_personal', session_id=session_id)), 429
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -276,6 +330,12 @@ def page3_motivation(session_id):
@app.route('/apply/<session_id>/submit-motivation', methods=['POST'])
def submit_motivation(session_id):
"""Process motivation questions submission"""
# Check rate limit
allowed, wait_seconds = check_rate_limit()
if not allowed:
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
return redirect(url_for('page3_motivation', session_id=session_id)), 429
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -340,6 +400,12 @@ def page4_upload(session_id):
@app.route('/apply/<session_id>/upload-file', methods=['POST'])
def upload_file(session_id):
"""Handle file upload"""
# Check rate limit
allowed, wait_seconds = check_rate_limit()
if not allowed:
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
return redirect(url_for('page4_upload', session_id=session_id)), 429
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -437,6 +503,12 @@ def remove_file(session_id, file_index):
@app.route('/apply/<session_id>/submit-application', methods=['POST'])
def submit_application(session_id):
"""Submit final application"""
# Check rate limit
allowed, wait_seconds = check_rate_limit()
if not allowed:
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
return redirect(url_for('page4_upload', session_id=session_id)), 429
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')