fix(board): Add CSRF tokens to publish forms and handle CSRFError
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Both agenda and protocol publish forms were missing CSRF tokens,
causing 'CSRF Token is missing' raw error. Adds hidden csrf_token
inputs and a global CSRFError handler that shows a friendly flash
message instead of raw system error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-04 11:47:02 +01:00
parent 5395262fdf
commit 86d4951917
2 changed files with 16 additions and 2 deletions

8
app.py
View File

@ -1256,6 +1256,14 @@ def not_found(error):
return render_template('errors/404.html'), 404 return render_template('errors/404.html'), 404
from flask_wtf.csrf import CSRFError
@app.errorhandler(CSRFError)
def handle_csrf_error(e):
flash('Sesja wygasła lub formularz został nieprawidłowo przesłany. Spróbuj ponownie.', 'warning')
return redirect(request.referrer or url_for('index'))
def send_registration_notification(user_info): def send_registration_notification(user_info):
"""Send email notification when a new user registers""" """Send email notification when a new user registers"""
try: try:

View File

@ -120,6 +120,10 @@
background: #059669; background: #059669;
} }
.inline-form {
display: inline;
}
.btn-back { .btn-back {
background: var(--bg-secondary); background: var(--bg-secondary);
color: var(--text-secondary); color: var(--text-secondary);
@ -461,7 +465,8 @@
</a> </a>
{% if meeting.status == 'draft' %} {% if meeting.status == 'draft' %}
<form action="{{ url_for('board.meeting_publish_agenda', meeting_id=meeting.id) }}" method="POST" style="display: inline;"> <form action="{{ url_for('board.meeting_publish_agenda', meeting_id=meeting.id) }}" method="POST" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn-action btn-publish" onclick="return confirm('Czy na pewno chcesz opublikować program posiedzenia?')"> <button type="submit" class="btn-action btn-publish" onclick="return confirm('Czy na pewno chcesz opublikować program posiedzenia?')">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/> <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
@ -470,7 +475,8 @@
</button> </button>
</form> </form>
{% elif meeting.status in ['agenda_published', 'protocol_draft'] %} {% elif meeting.status in ['agenda_published', 'protocol_draft'] %}
<form action="{{ url_for('board.meeting_publish_protocol', meeting_id=meeting.id) }}" method="POST" style="display: inline;"> <form action="{{ url_for('board.meeting_publish_protocol', meeting_id=meeting.id) }}" method="POST" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn-action btn-publish" onclick="return confirm('Czy na pewno chcesz opublikować protokół?')"> <button type="submit" class="btn-action btn-publish" onclick="return confirm('Czy na pewno chcesz opublikować protokół?')">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/> <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>