From 7dea07a30f3654a412bf9b477c7b32ddacc3f936 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Mon, 13 Jul 2026 14:04:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20gate=20abonnement=20+=20=C3=A9cran?= =?UTF-8?q?s=20de=20consentement=20OAuth=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- src/mcp/oauth/consent.py | 37 +++++++++++++++++++++++++++++++++ tests/mcp/test_oauth_consent.py | 29 ++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/mcp/oauth/consent.py create mode 100644 tests/mcp/test_oauth_consent.py diff --git a/src/mcp/oauth/consent.py b/src/mcp/oauth/consent.py new file mode 100644 index 0000000..ca59f4a --- /dev/null +++ b/src/mcp/oauth/consent.py @@ -0,0 +1,37 @@ +from urllib.parse import urlparse + +from markupsafe import escape + +from src.subscriptions.db import has_active_subscription +from src.utils import TOUS_ABONNES + + +def subscription_ok(user_id: int) -> bool: + return bool(TOUS_ABONNES or has_active_subscription(user_id)) + + +def render_subscription_required() -> str: + return ( + "" + "Abonnement requis — colibre" + "

Abonnement requis

" + "

Le connecteur MCP colibre nécessite un abonnement actif.

" + '

Gérer mon abonnement

' + "" + ) + + +def render_consent(client_name: str, redirect_uri: str, scope: str) -> str: + host = urlparse(redirect_uri).netloc or redirect_uri + return ( + "" + "Autoriser l'accès — colibre" + f"

Autoriser {escape(client_name)} ?

" + f"

{escape(client_name)} demande à lire les données " + "colibre en votre nom via le connecteur MCP.

" + f"

Vous serez redirigé vers {escape(host)}.

" + '
' + ' ' + '' + "
" + ) diff --git a/tests/mcp/test_oauth_consent.py b/tests/mcp/test_oauth_consent.py new file mode 100644 index 0000000..da16f88 --- /dev/null +++ b/tests/mcp/test_oauth_consent.py @@ -0,0 +1,29 @@ +from src.mcp.oauth import consent + + +def test_subscription_ok_tous_abonnes(monkeypatch): + monkeypatch.setattr("src.mcp.oauth.consent.TOUS_ABONNES", True) + assert consent.subscription_ok(999) is True + + +def test_subscription_ok_delegates(monkeypatch): + monkeypatch.setattr("src.mcp.oauth.consent.TOUS_ABONNES", False) + monkeypatch.setattr( + "src.mcp.oauth.consent.has_active_subscription", lambda uid: uid == 7 + ) + assert consent.subscription_ok(7) is True + assert consent.subscription_ok(8) is False + + +def test_render_consent_shows_redirect_host(): + html = consent.render_consent( + "Claude", "https://claude.ai/api/mcp/auth_callback", "mcp" + ) + assert "claude.ai" in html + assert "Claude" in html + assert 'name="confirm"' in html + + +def test_render_subscription_required_links_abonnement(): + html = consent.render_subscription_required() + assert "/compte/abonnement" in html