wip
This commit is contained in:
parent
f7f28b35ec
commit
eedc354160
6 changed files with 56 additions and 6 deletions
|
@ -20,13 +20,14 @@ def create_app(config_name="development"):
|
||||||
app.config['SECRET_KEY'] = secrets.token_hex(32)
|
app.config['SECRET_KEY'] = secrets.token_hex(32)
|
||||||
|
|
||||||
# Initialize extensions
|
# 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)
|
db.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
bcrypt.init_app(app)
|
bcrypt.init_app(app)
|
||||||
csrf.init_app(app)
|
init_csrf(app)
|
||||||
limiter.init_app(app)
|
limiter.init_app(app)
|
||||||
|
|
||||||
# Initialize login manager
|
# Initialize login manager
|
||||||
|
@ -101,4 +102,21 @@ def create_app(config_name="development"):
|
||||||
def forbidden(e):
|
def forbidden(e):
|
||||||
return render_template("errors/403.html", title="Forbidden"), 403
|
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
|
return app
|
||||||
|
|
16
app/core/csrf_utils.py
Normal file
16
app/core/csrf_utils.py
Normal 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)
|
|
@ -4,7 +4,7 @@ from flask_login import LoginManager
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from flask_limiter import Limiter
|
from flask_limiter import Limiter
|
||||||
from flask_limiter.util import get_remote_address
|
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
|
# Initialize extensions
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
@ -15,7 +15,7 @@ login_manager.login_message = "Please log in to access this page."
|
||||||
login_manager.login_message_category = "info"
|
login_manager.login_message_category = "info"
|
||||||
|
|
||||||
bcrypt = Bcrypt()
|
bcrypt = Bcrypt()
|
||||||
csrf = CSRFProtect()
|
# csrf is now imported from csrf_utils, not defined here
|
||||||
limiter = Limiter(
|
limiter = Limiter(
|
||||||
key_func=get_remote_address, default_limits=["200 per day", "50 per hour"]
|
key_func=get_remote_address, default_limits=["200 per day", "50 per hour"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,10 +4,9 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from app.core.extensions import db
|
from app.core.extensions import db
|
||||||
from app.core.auth import User
|
from app.core.auth import User
|
||||||
import re
|
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")
|
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||||
csrf = CSRFProtect()
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/login", methods=["GET", "POST"])
|
@bp.route("/login", methods=["GET", "POST"])
|
||||||
|
|
4
app/templates/dashboard/any_form_template.html
Normal file
4
app/templates/dashboard/any_form_template.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<form method="POST" action="...">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
|
<!-- Form fields -->
|
||||||
|
</form>
|
|
@ -25,6 +25,7 @@
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}">
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/favicon.png') }}">
|
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/favicon.png') }}">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
{% block styles %}{% endblock %}
|
{% block styles %}{% endblock %}
|
||||||
<script>
|
<script>
|
||||||
// Check for saved theme preference or respect OS preference
|
// Check for saved theme preference or respect OS preference
|
||||||
|
@ -602,6 +603,18 @@
|
||||||
to { opacity: 0; }
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
</script>
|
</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 %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue