37 lines
1023 B
Python
37 lines
1023 B
Python
"""CSRF protection (session-scoped token, no external dependency)."""
|
|
|
|
import hmac
|
|
import logging
|
|
import secrets
|
|
|
|
from flask import abort, request, session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_csrf_token():
|
|
"""Return the per-session CSRF token, generating one on first use."""
|
|
token = session.get("_csrf_token")
|
|
if not token:
|
|
token = secrets.token_hex(32)
|
|
session["_csrf_token"] = token
|
|
return token
|
|
|
|
|
|
def validate_csrf():
|
|
"""Abort 403 if the form CSRF token doesn't match the session token."""
|
|
expected = session.get("_csrf_token")
|
|
provided = request.form.get("csrf_token", "")
|
|
if not expected or not provided or not hmac.compare_digest(expected, provided):
|
|
logger.warning(
|
|
"CSRF validation failed for %s from %s",
|
|
request.path,
|
|
request.remote_addr,
|
|
)
|
|
abort(403)
|
|
|
|
|
|
def init_app(app):
|
|
"""Expose csrf_token() to Jinja templates as a global."""
|
|
app.jinja_env.globals["csrf_token"] = get_csrf_token
|