app

Flask application factory for AIFT.

Provides the create_app() factory function that initialises the Flask application, loads configuration from config.yaml, sets the upload size limit, registers all HTTP route blueprints, and configures CSRF protection.

A Python version guard runs at import time so that downstream code can assume a supported interpreter.

Attributes:
  • CSRF_HEADER: Name of the HTTP header used to transmit the CSRF token.
  • CSRF_SAFE_METHODS: HTTP methods exempt from CSRF validation (read-only methods that do not modify server state).
  1"""Flask application factory for AIFT.
  2
  3Provides the :func:`create_app` factory function that initialises the Flask
  4application, loads configuration from ``config.yaml``, sets the upload size
  5limit, registers all HTTP route blueprints, and configures CSRF protection.
  6
  7A Python version guard runs at import time so that downstream code can
  8assume a supported interpreter.
  9
 10Attributes:
 11    CSRF_HEADER: Name of the HTTP header used to transmit the CSRF token.
 12    CSRF_SAFE_METHODS: HTTP methods exempt from CSRF validation (read-only
 13        methods that do not modify server state).
 14"""
 15
 16from __future__ import annotations
 17
 18import secrets
 19from pathlib import Path
 20
 21from runtime_compat import assert_supported_python_version
 22
 23assert_supported_python_version()
 24
 25from flask import Flask, jsonify, request
 26
 27from .config import PROJECT_ROOT, load_config
 28from .routes import register_routes
 29
 30__all__ = [
 31    "create_app",
 32    "ai_providers",
 33    "analyzer",
 34    "audit",
 35    "case_logging",
 36    "chat",
 37    "config",
 38    "hasher",
 39    "parser",
 40    "reporter",
 41    "routes",
 42    "version",
 43]
 44
 45CSRF_HEADER = "X-CSRF-Token"
 46CSRF_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
 47
 48
 49def create_app(config_path: str | None = None) -> Flask:
 50    """Create and configure the Flask application instance.
 51
 52    Loads AIFT configuration (merging defaults, YAML, and environment
 53    variables), stores it in ``app.config["AIFT_CONFIG"]``, configures the
 54    maximum upload size, generates a per-process CSRF token, installs CSRF
 55    validation middleware, and registers all HTTP routes.
 56
 57    Args:
 58        config_path: Optional path to a YAML configuration file.  When
 59            *None*, the default ``config.yaml`` in the project root is used.
 60
 61    Returns:
 62        A fully configured :class:`~flask.Flask` application instance.
 63    """
 64    app = Flask(__name__, template_folder="../templates", static_folder="../static")
 65    aift_config = load_config(config_path)
 66    # Store the resolved absolute path so all downstream code uses it consistently.
 67    resolved_config_path = str(Path(config_path).resolve()) if config_path is not None else str(PROJECT_ROOT / "config.yaml")
 68    app.config["AIFT_CONFIG"] = aift_config
 69    app.config["AIFT_CONFIG_PATH"] = resolved_config_path
 70
 71    # Generate a per-process CSRF token for protecting state-changing requests.
 72    app.config["CSRF_TOKEN"] = secrets.token_hex(32)
 73
 74    _register_csrf_protection(app)
 75    register_routes(app)
 76
 77    return app
 78
 79
 80def _register_csrf_protection(app: Flask) -> None:
 81    """Install a ``before_request`` hook that validates the CSRF token.
 82
 83    All requests whose method is not in :data:`CSRF_SAFE_METHODS` must
 84    include a valid ``X-CSRF-Token`` header matching the token stored in
 85    ``app.config["CSRF_TOKEN"]``.  Requests to the CSRF token endpoint
 86    itself (``/api/csrf-token``) are exempt so the frontend can obtain the
 87    token.
 88
 89    Args:
 90        app: The Flask application to attach the hook to.
 91    """
 92
 93    @app.before_request
 94    def _enforce_csrf() -> tuple | None:
 95        """Reject state-changing requests that lack a valid CSRF token.
 96
 97        Returns:
 98            A 403 JSON error response tuple when validation fails, or
 99            ``None`` to allow the request to proceed.
100        """
101        if request.method in CSRF_SAFE_METHODS:
102            return None
103        if request.path == "/api/csrf-token":
104            return None
105        token = request.headers.get(CSRF_HEADER, "")
106        if not secrets.compare_digest(token, app.config["CSRF_TOKEN"]):
107            return jsonify({"error": "CSRF token missing or invalid."}), 403
108        return None
109
110    @app.get("/api/csrf-token")
111    def _get_csrf_token() -> tuple:
112        """Return the CSRF token so the frontend can include it in requests.
113
114        Returns:
115            A JSON response containing the CSRF token with a 200 status.
116        """
117        return jsonify({"csrf_token": app.config["CSRF_TOKEN"]}), 200
def create_app(config_path: str | None = None) -> flask.app.Flask:
50def create_app(config_path: str | None = None) -> Flask:
51    """Create and configure the Flask application instance.
52
53    Loads AIFT configuration (merging defaults, YAML, and environment
54    variables), stores it in ``app.config["AIFT_CONFIG"]``, configures the
55    maximum upload size, generates a per-process CSRF token, installs CSRF
56    validation middleware, and registers all HTTP routes.
57
58    Args:
59        config_path: Optional path to a YAML configuration file.  When
60            *None*, the default ``config.yaml`` in the project root is used.
61
62    Returns:
63        A fully configured :class:`~flask.Flask` application instance.
64    """
65    app = Flask(__name__, template_folder="../templates", static_folder="../static")
66    aift_config = load_config(config_path)
67    # Store the resolved absolute path so all downstream code uses it consistently.
68    resolved_config_path = str(Path(config_path).resolve()) if config_path is not None else str(PROJECT_ROOT / "config.yaml")
69    app.config["AIFT_CONFIG"] = aift_config
70    app.config["AIFT_CONFIG_PATH"] = resolved_config_path
71
72    # Generate a per-process CSRF token for protecting state-changing requests.
73    app.config["CSRF_TOKEN"] = secrets.token_hex(32)
74
75    _register_csrf_protection(app)
76    register_routes(app)
77
78    return app

Create and configure the Flask application instance.

Loads AIFT configuration (merging defaults, YAML, and environment variables), stores it in app.config["AIFT_CONFIG"], configures the maximum upload size, generates a per-process CSRF token, installs CSRF validation middleware, and registers all HTTP routes.

Arguments:
  • config_path: Optional path to a YAML configuration file. When None, the default config.yaml in the project root is used.
Returns:

A fully configured ~flask.Flask application instance.