Security hardening
This commit is contained in:
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user