mirror of
https://github.com/tmdinosaurcenter/kiosk-guestbook.git
synced 2026-06-04 01:50:03 -06:00
feat: add CSRF protection to all POST forms
Installs Flask-WTF and enables CSRFProtect globally. Adds csrf_token hidden fields to all four POST forms (login, delete entry, add user, delete user, and the public guestbook form). Exempts the API endpoint which uses header-based key auth instead.
This commit is contained in:
@@ -13,6 +13,7 @@ from flask_limiter.util import get_remote_address
|
|||||||
from flask_login import (
|
from flask_login import (
|
||||||
LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
)
|
)
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
@@ -28,6 +29,7 @@ if not _secret_key:
|
|||||||
app.secret_key = _secret_key
|
app.secret_key = _secret_key
|
||||||
|
|
||||||
limiter = Limiter(get_remote_address, app=app, default_limits=[])
|
limiter = Limiter(get_remote_address, app=app, default_limits=[])
|
||||||
|
csrf = CSRFProtect(app)
|
||||||
|
|
||||||
app.config.update(
|
app.config.update(
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
@@ -467,6 +469,7 @@ def admin_users_delete(user_id):
|
|||||||
|
|
||||||
@app.route('/api/guests', methods=['GET'])
|
@app.route('/api/guests', methods=['GET'])
|
||||||
@limiter.limit("100 per hour")
|
@limiter.limit("100 per hour")
|
||||||
|
@csrf.exempt
|
||||||
def api_guests():
|
def api_guests():
|
||||||
api_key = request.headers.get('X-API-Key')
|
api_key = request.headers.get('X-API-Key')
|
||||||
if api_key != os.environ.get("API_KEY"):
|
if api_key != os.environ.get("API_KEY"):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
Flask>=3.1.3
|
Flask>=3.1.3
|
||||||
|
Flask-WTF>=1.2
|
||||||
Werkzeug>=3.0.6
|
Werkzeug>=3.0.6
|
||||||
Flask-Limiter>=3.0
|
Flask-Limiter>=3.0
|
||||||
Flask-Login>=0.6
|
Flask-Login>=0.6
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
{% if current_user.role != 'viewer' %}
|
{% if current_user.role != 'viewer' %}
|
||||||
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
|
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
|
||||||
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
|
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<div class="alert alert-danger py-2">{{ error }}</div>
|
<div class="alert alert-danger py-2">{{ error }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="POST" action="{{ url_for('admin_login', next=request.args.get('next', '')) }}">
|
<form method="POST" action="{{ url_for('admin_login', next=request.args.get('next', '')) }}">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="username" class="form-label">Username</label>
|
<label for="username" class="form-label">Username</label>
|
||||||
<input type="text" id="username" name="username" class="form-control"
|
<input type="text" id="username" name="username" class="form-control"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<div class="card-header">Add User</div>
|
<div class="card-header">Add User</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" action="{{ url_for('admin_users_add') }}">
|
<form method="POST" action="{{ url_for('admin_users_add') }}">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input type="text" name="username" class="form-control" placeholder="Username" required />
|
<input type="text" name="username" class="form-control" placeholder="Username" required />
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<form method="POST" action="{{ url_for('admin_users_delete', user_id=u[0]) }}"
|
<form method="POST" action="{{ url_for('admin_users_delete', user_id=u[0]) }}"
|
||||||
onsubmit="return confirm('Remove user {{ u[1] }}?')">
|
onsubmit="return confirm('Remove user {{ u[1] }}?')">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<button type="submit" class="btn btn-danger btn-sm">Remove</button>
|
<button type="submit" class="btn btn-danger btn-sm">Remove</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" action="/" class="mb-4">
|
<form method="post" action="/" class="mb-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="first_name" class="form-label">First Name(s):</label>
|
<label for="first_name" class="form-label">First Name(s):</label>
|
||||||
<input type="text" class="form-control" id="first_name" name="first_name" required />
|
<input type="text" class="form-control" id="first_name" name="first_name" required />
|
||||||
|
|||||||
Reference in New Issue
Block a user