This commit is contained in:
pika 2025-03-31 17:48:23 +02:00
parent f7f28b35ec
commit eedc354160
6 changed files with 56 additions and 6 deletions

View file

@ -20,13 +20,14 @@ def create_app(config_name="development"):
app.config['SECRET_KEY'] = secrets.token_hex(32)
# Initialize extensions
from app.core.extensions import db, migrate, login_manager, bcrypt, limiter, csrf
from app.core.extensions import db, migrate, login_manager, bcrypt, limiter
from app.core.csrf_utils import init_csrf
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
bcrypt.init_app(app)
csrf.init_app(app)
init_csrf(app)
limiter.init_app(app)
# Initialize login manager
@ -101,4 +102,21 @@ def create_app(config_name="development"):
def forbidden(e):
return render_template("errors/403.html", title="Forbidden"), 403
# Session configuration
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_FILE_DIR'] = os.path.join(os.getcwd(), 'instance/sessions')
app.config['SESSION_PERMANENT'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 hour in seconds
# Ensure the sessions directory exists
os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
# Debug CSRF issues
@app.after_request
def after_request(response):
if app.debug: # Only in development
print(f"Session contains CSRF token: {'csrf_token' in session}")
print(f"CSRF header name: {app.config.get('WTF_CSRF_HEADERS')}")
return response
return app

16
app/core/csrf_utils.py Normal file
View file

@ -0,0 +1,16 @@
from flask_wtf.csrf import CSRFProtect
# Single global instance of CSRFProtect
csrf = CSRFProtect()
def init_csrf(app):
"""Initialize CSRF protection with proper configuration"""
# Ensure cookies work in Docker environment
app.config['WTF_CSRF_ENABLED'] = True
app.config['WTF_CSRF_TIME_LIMIT'] = 3600 # 1 hour
app.config['SESSION_COOKIE_SECURE'] = False # Set to True if using HTTPS
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
# Initialize CSRF protection
csrf.init_app(app)

View file

@ -4,7 +4,7 @@ from flask_login import LoginManager
from flask_bcrypt import Bcrypt
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_wtf.csrf import CSRFProtect
from app.core.csrf_utils import csrf # Import from centralized location
# Initialize extensions
db = SQLAlchemy()
@ -15,7 +15,7 @@ login_manager.login_message = "Please log in to access this page."
login_manager.login_message_category = "info"
bcrypt = Bcrypt()
csrf = CSRFProtect()
# csrf is now imported from csrf_utils, not defined here
limiter = Limiter(
key_func=get_remote_address, default_limits=["200 per day", "50 per hour"]
)

View file

@ -4,10 +4,9 @@ from werkzeug.security import generate_password_hash, check_password_hash
from app.core.extensions import db
from app.core.auth import User
import re
from flask_wtf.csrf import CSRFProtect
from app.core.csrf_utils import csrf # Import from centralized location
bp = Blueprint("auth", __name__, url_prefix="/auth")
csrf = CSRFProtect()
@bp.route("/login", methods=["GET", "POST"])

View file

@ -0,0 +1,4 @@
<form method="POST" action="...">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<!-- Form fields -->
</form>

View file

@ -25,6 +25,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}">
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/favicon.png') }}">
<meta name="csrf-token" content="{{ csrf_token() }}">
{% block styles %}{% endblock %}
<script>
// Check for saved theme preference or respect OS preference
@ -602,6 +603,18 @@
to { opacity: 0; }
}
</script>
<script>
// Add CSRF token to all AJAX requests
$(document).ready(function () {
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", $('meta[name="csrf-token"]').attr('content'));
}
}
});
});
</script>
{% block scripts %}{% endblock %}
</body>