feat(mcp): migrations OAuth/usage + câblage app.py (#114)

Ajoute les migrations 0008-0011 (oauth_clients, oauth_codes,
oauth_tokens, mcp_usage) et câble le serveur d'autorisation OAuth 2.1
dans le bloc DASH_MCP_ENABLED de app.py : init des schémas oauth/usage,
purge périodique, enregistrement des routes /oauth et /.well-known, et
exemption CSRF de ces routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-07-13 14:38:33 +02:00
parent 8258b92f98
commit 34a8470792
3 changed files with 89 additions and 0 deletions
+23
View File
@@ -137,6 +137,29 @@ if _mcp_enabled:
init_mcp_auth(app.server)
# Serveur d'autorisation OAuth 2.1 (scope B2, #114) : découverte, DCR,
# authorize/token, révocation. Réutilise la session flask_login.
from src.mcp import usage # noqa: E402
from src.mcp.oauth import routes as oauth_routes # noqa: E402
from src.mcp.oauth import store as oauth_store # noqa: E402
_users_db = os.environ["USERS_DB_PATH"]
oauth_store.init_schema(_users_db)
usage.init_schema(_users_db)
usage.purge_older_than(_users_db)
oauth_routes.init_oauth(app.server)
# Exempter les routes OAuth du CSRF (POST externes JSON-RPC/form sans jeton).
if _auth_csrf is not None:
for _rule in app.server.url_map.iter_rules():
if _rule.rule.startswith("/oauth") or _rule.rule.startswith(
"/.well-known/oauth"
):
_vf = app.server.view_functions.get(_rule.endpoint)
if _vf is not None:
_auth_csrf.exempt(_vf)
from src.subscriptions.setup import init_subscriptions # noqa: E402
init_subscriptions(app.server)
+28
View File
@@ -46,6 +46,34 @@ _MIGRATIONS: list[tuple[str, str]] = [
"0007_add_kind_to_api_tokens",
"ALTER TABLE api_tokens ADD COLUMN kind TEXT NOT NULL DEFAULT 'api'",
),
(
"0008_create_oauth_clients",
"CREATE TABLE IF NOT EXISTS oauth_clients ("
"client_id TEXT PRIMARY KEY, client_metadata TEXT NOT NULL, "
"created_at TEXT NOT NULL)",
),
(
"0009_create_oauth_codes",
"CREATE TABLE IF NOT EXISTS oauth_codes ("
"code_hash TEXT PRIMARY KEY, client_id TEXT NOT NULL, user_id INTEGER NOT NULL, "
"redirect_uri TEXT, code_challenge TEXT, code_challenge_method TEXT, "
"scope TEXT, resource TEXT, expires_at TEXT NOT NULL, used INTEGER NOT NULL DEFAULT 0)",
),
(
"0010_create_oauth_tokens",
"CREATE TABLE IF NOT EXISTS oauth_tokens ("
"id INTEGER PRIMARY KEY, access_token_hash TEXT NOT NULL UNIQUE, "
"refresh_token_hash TEXT UNIQUE, client_id TEXT NOT NULL, user_id INTEGER NOT NULL, "
"scope TEXT, resource TEXT NOT NULL, issued_at TEXT NOT NULL, "
"access_expires_at TEXT NOT NULL, refresh_expires_at TEXT, revoked_at TEXT, "
"count_total INTEGER NOT NULL DEFAULT 0, last_used_at TEXT)",
),
(
"0011_create_mcp_usage",
"CREATE TABLE IF NOT EXISTS mcp_usage ("
"id INTEGER PRIMARY KEY, user_id INTEGER, token_id INTEGER, "
"kind TEXT NOT NULL, created_at TEXT NOT NULL)",
),
]