- Improved CSRF handling

- Wordpress plugin for Evie Chat
This commit is contained in:
Josako
2024-08-13 14:31:29 +02:00
parent ab38dd7540
commit a237db339a
14 changed files with 944 additions and 23 deletions

View File

@@ -53,6 +53,10 @@ def create_app(config_file=None):
register_extensions(app)
# Configure CSRF protection
app.config['WTF_CSRF_CHECK_DEFAULT'] = False # Disable global CSRF protection
app.config['WTF_CSRF_TIME_LIMIT'] = None # Remove time limit for CSRF tokens
app.celery = make_celery(app.name, app.config)
init_celery(app.celery, app)
@@ -88,6 +92,12 @@ def create_app(config_file=None):
}
return jsonify(response), 500
@app.before_request
def before_request():
# app.logger.debug(f"Before request - Session ID: {session.sid}")
app.logger.debug(f"Before request - Session data: {session}")
app.logger.debug(f"Before request - Request headers: {request.headers}")
# Register API
register_api(app)

View File

@@ -27,9 +27,18 @@ def access_forbidden(error):
return render_template('error/403.html')
def key_error_handler(error):
# Check if the KeyError is specifically for 'tenant'
if str(error) == "'tenant'":
return redirect(prefixed_url_for('security.login'))
# For other KeyErrors, you might want to log the error and return a generic error page
return render_template('error/generic.html', error_message="An unexpected error occurred"), 500
def register_error_handlers(app):
app.register_error_handler(404, not_found_error)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(401, not_authorised_error)
app.register_error_handler(403, not_authorised_error)
app.register_error_handler(KeyError, key_error_handler)

View File

@@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block title %}Unexpected Error{% endblock %}
{% block content_title %}Internal Server error{% endblock %}
{% block content_description %}Something unexpected happened! The administrator has been notified.{% endblock %}
{% block content %}
<p><a href="{{ url_for('basic_bp.index') }}">Return home</a></p>
{% endblock %}

View File

@@ -20,6 +20,13 @@
{# </p>#}
<p>{{ login_user_form.submit() }}</p>
</form>
<!-- Debugging information -->
{# <div style="margin-top: 20px; border: 1px solid #ccc; padding: 10px;">#}
{# <h4>Debugging Information:</h4>#}
{# <p>CSRF Token: {{ login_user_form.csrf_token.current_token }}</p>#}
{# <p>Session ID: {{ session.sid }}</p>#}
{# </div>#}
{% endblock %}
{% block content_footer %}
First time here? Forgot your password?

View File

@@ -1,5 +1,6 @@
from flask import request, render_template, Blueprint, session, current_app, jsonify
from flask_security import roles_required, roles_accepted
from flask_wtf.csrf import generate_csrf
from .basic_forms import SessionDefaultsForm
@@ -59,3 +60,14 @@ def set_user_timezone():
def health():
return jsonify({'status': 'ok'}), 200
@basic_bp.route('/check_csrf', methods=['GET'])
def check_csrf():
csrf_token = generate_csrf()
return jsonify({
'csrf_token_in_session': session.get('csrf_token'),
'generated_csrf_token': csrf_token,
'session_id': session.sid if hasattr(session, 'sid') else None,
'session_data': dict(session)
})

View File

@@ -3,6 +3,7 @@ from flask import Blueprint, render_template, redirect, request, flash, current_
from flask_security import current_user, login_required, login_user, logout_user
from flask_security.utils import verify_and_update_password, get_message, do_flash, config_value, hash_password
from flask_security.forms import LoginForm
from flask_wtf.csrf import CSRFError, generate_csrf
from urllib.parse import urlparse
from datetime import datetime as dt, timezone as tz
@@ -45,27 +46,41 @@ def login():
form = LoginForm()
if form.validate_on_submit():
current_app.logger.debug(f'Validating login form: {form.email.data}')
user = User.query.filter_by(email=form.email.data).first()
if user is None or not verify_and_update_password(form.password.data, user):
flash('Invalid username or password', 'danger')
if request.method == 'POST':
current_app.logger.debug(f"Starting login procedure for {form.email.data}")
try:
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None or not verify_and_update_password(form.password.data, user):
flash('Invalid username or password', 'danger')
current_app.logger.debug(f'Failed to login user')
return redirect(prefixed_url_for('security_bp.login'))
if login_user(user):
current_app.logger.info(f'Login successful! Current User is {current_user.email}')
db.session.commit()
if current_user.has_roles('Super User'):
return redirect(prefixed_url_for('user_bp.select_tenant'))
else:
return redirect(prefixed_url_for('user_bp.tenant_overview'))
else:
flash('Invalid username or password', 'danger')
current_app.logger.debug(f'Failed to login user {user.email}')
abort(401)
else:
current_app.logger.debug(f'Invalid login form: {form.errors}')
except CSRFError:
current_app.logger.warning('CSRF token mismatch during login attempt')
flash('Your session has expired. Please try logging in again.', 'danger')
return redirect(prefixed_url_for('security_bp.login'))
if login_user(user):
current_app.logger.info(f'Login successful! Current User is {current_user.email}')
db.session.commit()
if current_user.has_roles('Super User'):
return redirect(prefixed_url_for('user_bp.select_tenant'))
else:
return redirect(prefixed_url_for('user_bp.tenant_overview'))
else:
flash('Invalid username or password', 'danger')
current_app.logger.debug(f'Failed to login user {user.email}')
abort(401)
else:
current_app.logger.debug(f'Invalid login form: {form.errors}')
if request.method == 'GET':
csrf_token = generate_csrf()
current_app.logger.debug(f'Generated new CSRF token: {csrf_token}')
# current_app.logger.debug(f"Login route completed - Session ID: {session.sid}")
current_app.logger.debug(f"Login route completed - Session data: {session}")
return render_template('security/login_user.html', login_user_form=form)