diff --git a/src/app.py b/src/app.py index d04a342..03f6cf5 100644 --- a/src/app.py +++ b/src/app.py @@ -124,6 +124,19 @@ if _mcp_enabled: ) import src.mcp.tools # noqa: E402,F401 # l'import enregistre les @mcp_enabled + # Les routes /_mcp existent maintenant : exempter du CSRF (POST JSON-RPC + # externe sans jeton) puis brancher le garde d'abonnement. + from src.mcp.auth import init_mcp_auth # noqa: E402 + + if _auth_csrf is not None: + for _rule in app.server.url_map.iter_rules(): + if _rule.rule.startswith("/_mcp"): + _vf = app.server.view_functions.get(_rule.endpoint) + if _vf is not None: + _auth_csrf.exempt(_vf) + + init_mcp_auth(app.server) + from src.subscriptions.setup import init_subscriptions # noqa: E402 init_subscriptions(app.server) diff --git a/tests/mcp/test_app_wiring.py b/tests/mcp/test_app_wiring.py new file mode 100644 index 0000000..66bb2b2 --- /dev/null +++ b/tests/mcp/test_app_wiring.py @@ -0,0 +1,26 @@ +import importlib + + +def test_mcp_endpoint_guarded_and_csrf_exempt(monkeypatch, tmp_path): + # DB éphémère + secrets requis par init_auth/init_subscriptions. + monkeypatch.setenv("USERS_DB_PATH", str(tmp_path / "users.test.sqlite")) + monkeypatch.setenv("SECRET_KEY", "test-secret-key") + monkeypatch.setenv("APP_BASE_URL", "http://localhost:8050") + monkeypatch.setenv("DASH_MCP_ENABLED", "true") + + from src.auth import db as auth_db + + auth_db.reset_conn_for_tests() + + import src.app as app_module + + app_module = importlib.reload(app_module) + client = app_module.app.server.test_client() + + # Pas de jeton : le garde renvoie 401 (et NON une erreur CSRF 400/403), + # ce qui prouve exemption CSRF + garde câblés sur /_mcp. + resp = client.post("/_mcp", json={"jsonrpc": "2.0", "method": "ping", "id": 1}) + assert resp.status_code == 401 + assert resp.headers.get("WWW-Authenticate") == 'Bearer realm="colibre-mcp"' + + auth_db.reset_conn_for_tests()