security: add CSRF protection to all forms

- Add Flask-WTF dependency for CSRF protection
- Initialize CSRFProtect in app.py
- Add CSRF tokens to all POST forms in templates
- Exempt /order JSON API endpoint (uses API key instead)

This protects against Cross-Site Request Forgery attacks on all
admin and user management operations.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 08:01:22 +01:00
parent 12eece0226
commit e062a1e836
8 changed files with 20 additions and 0 deletions

1
wawi/templates/ausbuchen.html Normal file → Executable file
View File

@@ -4,6 +4,7 @@
<h2>Ausbuchen: {{ item.artikel }} ({{ item.groesse }})</h2>
<div class="note">Aktueller Bestand: <strong>{{ item.gezaehlt }}</strong></div>
<form method="post" onsubmit="return confirm('Wirklich ausbuchen?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-grid">
<label>
Menge

1
wawi/templates/edit.html Normal file → Executable file
View File

@@ -3,6 +3,7 @@
<div class="card form-card">
<h2>{{ "Artikel bearbeiten" if item else "Neuen Artikel anlegen" }}</h2>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-grid">
<label>
Artikel

2
wawi/templates/index.html Normal file → Executable file
View File

@@ -67,10 +67,12 @@
<td class="actions">
<a class="btn icon" href="{{ url_for('bp.edit_item', item_id=r.id) }}" title="Bearbeiten" aria-label="Bearbeiten"><span></span></a>
<form method="post" action="{{ url_for('bp.verkauf', item_id=r.id) }}" onsubmit="return confirm('Wirklich 1 Stück als verkauft buchen?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn icon" type="submit" title="Verkauf +1" aria-label="Verkauf +1"><span>🛒</span></button>
</form>
<a class="btn icon" href="{{ url_for('bp.ausbuchen', item_id=r.id) }}" title="Ausbuchen" aria-label="Ausbuchen"><span></span></a>
<form method="post" action="{{ url_for('bp.delete_item', item_id=r.id) }}" onsubmit="return confirm('Wirklich löschen? Dieser Vorgang kann nicht rückgängig gemacht werden.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn icon danger" type="submit" title="Löschen" aria-label="Löschen"><span>🗑</span></button>
</form>
</td>

1
wawi/templates/login.html Normal file → Executable file
View File

@@ -6,6 +6,7 @@
<div class="note">Benutzername oder Passwort ist falsch.</div>
{% endif %}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-grid">
<label>
Benutzer

2
wawi/templates/orders.html Normal file → Executable file
View File

@@ -45,9 +45,11 @@
<td class="actions">
{% if not o.done and not o.canceled %}
<form method="post" action="{{ url_for('bp.complete_order', order_id=o.id) }}" onsubmit="return confirm('Bestellung als erledigt markieren?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn small" type="submit">Erledigt</button>
</form>
<form method="post" action="{{ url_for('bp.cancel_order', order_id=o.id) }}" onsubmit="return confirm('Bestellung wirklich stornieren?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn small danger" type="submit">Stornieren</button>
</form>
{% else %}

3
wawi/templates/users.html Normal file → Executable file
View File

@@ -15,6 +15,7 @@
{% endif %}
{% endwith %}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-grid">
<label>
Benutzername
@@ -47,9 +48,11 @@
<td>{{ u.created_at }}</td>
<td class="actions">
<form method="post" action="{{ url_for('bp.reset_user_password', user_id=u.id) }}" onsubmit="return confirm('Passwort für diesen Benutzer wirklich zurücksetzen?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn small" type="submit">Passwort neu</button>
</form>
<form method="post" action="{{ url_for('bp.delete_user', user_id=u.id) }}" onsubmit="return confirm('Benutzer wirklich löschen?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="btn small danger" type="submit">Löschen</button>
</form>
</td>