Refactor database connection handling in LocalOAuthProvider to use async context manager

This commit is contained in:
2026-10-28 23:46:26 -04:00
parent 91b7f312aa
commit 7e9f488a5f
+22 -17
View File
@@ -8,8 +8,9 @@ import html
import json import json
import secrets import secrets
import time import time
from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, AsyncIterator
from urllib.parse import urlencode from urllib.parse import urlencode
import aiosqlite import aiosqlite
@@ -110,13 +111,17 @@ class LocalOAuthProvider:
) )
await db.commit() await db.commit()
async def _connect(self) -> aiosqlite.Connection: @asynccontextmanager
async def _connect(self) -> AsyncIterator[aiosqlite.Connection]:
db = await aiosqlite.connect(self.database_path) db = await aiosqlite.connect(self.database_path)
db.row_factory = aiosqlite.Row db.row_factory = aiosqlite.Row
return db try:
yield db
finally:
await db.close()
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None: async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT metadata_json FROM oauth_clients WHERE client_id = ?", (client_id,) "SELECT metadata_json FROM oauth_clients WHERE client_id = ?", (client_id,)
) as cursor: ) as cursor:
@@ -129,7 +134,7 @@ class LocalOAuthProvider:
if not client_info.client_id: if not client_info.client_id:
raise ValueError("OAuth client registration did not include a client ID") raise ValueError("OAuth client registration did not include a client ID")
data = client_info.model_dump_json() data = client_info.model_dump_json()
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT COUNT(*) AS n FROM oauth_clients WHERE client_id != ?", "SELECT COUNT(*) AS n FROM oauth_clients WHERE client_id != ?",
(client_info.client_id,), (client_info.client_id,),
@@ -158,7 +163,7 @@ class LocalOAuthProvider:
raise AuthorizeError("invalid_target", "This authorization server only issues tokens for this MCP endpoint") raise AuthorizeError("invalid_target", "This authorization server only issues tokens for this MCP endpoint")
flow_id = secrets.token_urlsafe(32) flow_id = secrets.token_urlsafe(32)
async with await self._connect() as db: async with self._connect() as db:
now = int(time.time()) now = int(time.time())
await db.execute("DELETE FROM oauth_flows WHERE expires_at < ?", (now,)) await db.execute("DELETE FROM oauth_flows WHERE expires_at < ?", (now,))
await db.execute("DELETE FROM oauth_codes WHERE expires_at < ?", (now,)) await db.execute("DELETE FROM oauth_codes WHERE expires_at < ?", (now,))
@@ -185,7 +190,7 @@ class LocalOAuthProvider:
return f"{self.issuer_url}/login?{urlencode({'flow': flow_id})}" return f"{self.issuer_url}/login?{urlencode({'flow': flow_id})}"
async def get_flow(self, flow_id: str) -> dict[str, Any] | None: async def get_flow(self, flow_id: str) -> dict[str, Any] | None:
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT * FROM oauth_flows WHERE flow_id = ?", (flow_id,) "SELECT * FROM oauth_flows WHERE flow_id = ?", (flow_id,)
) as cursor: ) as cursor:
@@ -202,7 +207,7 @@ class LocalOAuthProvider:
async def login_is_limited(self, client_ip: str) -> bool: async def login_is_limited(self, client_ip: str) -> bool:
cutoff = int(time.time()) - LOGIN_WINDOW_SECONDS cutoff = int(time.time()) - LOGIN_WINDOW_SECONDS
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT COUNT(*) AS n FROM oauth_login_failures WHERE client_ip = ? AND failed_at >= ?", "SELECT COUNT(*) AS n FROM oauth_login_failures WHERE client_ip = ? AND failed_at >= ?",
(client_ip, cutoff), (client_ip, cutoff),
@@ -213,7 +218,7 @@ class LocalOAuthProvider:
async def record_login_failure(self, client_ip: str) -> None: async def record_login_failure(self, client_ip: str) -> None:
now = int(time.time()) now = int(time.time())
cutoff = now - LOGIN_WINDOW_SECONDS cutoff = now - LOGIN_WINDOW_SECONDS
async with await self._connect() as db: async with self._connect() as db:
await db.execute("DELETE FROM oauth_login_failures WHERE failed_at < ?", (cutoff,)) await db.execute("DELETE FROM oauth_login_failures WHERE failed_at < ?", (cutoff,))
await db.execute( await db.execute(
"INSERT INTO oauth_login_failures (client_ip, failed_at) VALUES (?, ?)", "INSERT INTO oauth_login_failures (client_ip, failed_at) VALUES (?, ?)",
@@ -222,7 +227,7 @@ class LocalOAuthProvider:
await db.commit() await db.commit()
async def clear_login_failures(self, client_ip: str) -> None: async def clear_login_failures(self, client_ip: str) -> None:
async with await self._connect() as db: async with self._connect() as db:
await db.execute("DELETE FROM oauth_login_failures WHERE client_ip = ?", (client_ip,)) await db.execute("DELETE FROM oauth_login_failures WHERE client_ip = ?", (client_ip,))
await db.commit() await db.commit()
@@ -238,7 +243,7 @@ class LocalOAuthProvider:
code_value = secrets.token_urlsafe(48) code_value = secrets.token_urlsafe(48)
now = int(time.time()) now = int(time.time())
async with await self._connect() as db: async with self._connect() as db:
await db.execute( await db.execute(
"""INSERT INTO oauth_codes """INSERT INTO oauth_codes
(code, client_id, redirect_uri, redirect_uri_provided_explicitly, (code, client_id, redirect_uri, redirect_uri_provided_explicitly,
@@ -267,7 +272,7 @@ class LocalOAuthProvider:
async def load_authorization_code( async def load_authorization_code(
self, client: OAuthClientInformationFull, authorization_code: str self, client: OAuthClientInformationFull, authorization_code: str
) -> AuthorizationCode | None: ) -> AuthorizationCode | None:
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT * FROM oauth_codes WHERE code = ? AND client_id = ?", "SELECT * FROM oauth_codes WHERE code = ? AND client_id = ?",
(authorization_code, client.client_id), (authorization_code, client.client_id),
@@ -295,7 +300,7 @@ class LocalOAuthProvider:
now = int(time.time()) now = int(time.time())
access = secrets.token_urlsafe(48) access = secrets.token_urlsafe(48)
refresh = secrets.token_urlsafe(48) refresh = secrets.token_urlsafe(48)
async with await self._connect() as db: async with self._connect() as db:
await db.execute("BEGIN IMMEDIATE") await db.execute("BEGIN IMMEDIATE")
async with db.execute( async with db.execute(
"SELECT expires_at FROM oauth_codes WHERE code = ? AND client_id = ?", "SELECT expires_at FROM oauth_codes WHERE code = ? AND client_id = ?",
@@ -335,7 +340,7 @@ class LocalOAuthProvider:
self, client: OAuthClientInformationFull, refresh_token: str self, client: OAuthClientInformationFull, refresh_token: str
) -> RefreshToken | None: ) -> RefreshToken | None:
now = int(time.time()) now = int(time.time())
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"""SELECT * FROM oauth_tokens """SELECT * FROM oauth_tokens
WHERE refresh_token = ? AND client_id = ? AND refresh_expires_at >= ?""", WHERE refresh_token = ? AND client_id = ? AND refresh_expires_at >= ?""",
@@ -365,7 +370,7 @@ class LocalOAuthProvider:
raise TokenError("invalid_scope", "A refresh token cannot grant additional permissions") raise TokenError("invalid_scope", "A refresh token cannot grant additional permissions")
access = secrets.token_urlsafe(48) access = secrets.token_urlsafe(48)
refresh = secrets.token_urlsafe(48) refresh = secrets.token_urlsafe(48)
async with await self._connect() as db: async with self._connect() as db:
await db.execute("BEGIN IMMEDIATE") await db.execute("BEGIN IMMEDIATE")
async with db.execute( async with db.execute(
"""SELECT * FROM oauth_tokens WHERE refresh_token = ? AND client_id = ? """SELECT * FROM oauth_tokens WHERE refresh_token = ? AND client_id = ?
@@ -406,7 +411,7 @@ class LocalOAuthProvider:
async def load_access_token(self, token: str) -> AccessToken | None: async def load_access_token(self, token: str) -> AccessToken | None:
now = int(time.time()) now = int(time.time())
async with await self._connect() as db: async with self._connect() as db:
async with db.execute( async with db.execute(
"SELECT * FROM oauth_tokens WHERE access_token = ? AND access_expires_at >= ?", "SELECT * FROM oauth_tokens WHERE access_token = ? AND access_expires_at >= ?",
(token, now), (token, now),
@@ -425,7 +430,7 @@ class LocalOAuthProvider:
async def revoke_token(self, token: AccessToken | RefreshToken) -> None: async def revoke_token(self, token: AccessToken | RefreshToken) -> None:
column = "access_token" if isinstance(token, AccessToken) else "refresh_token" column = "access_token" if isinstance(token, AccessToken) else "refresh_token"
async with await self._connect() as db: async with self._connect() as db:
await db.execute(f"DELETE FROM oauth_tokens WHERE {column} = ?", (token.token,)) await db.execute(f"DELETE FROM oauth_tokens WHERE {column} = ?", (token.token,))
await db.commit() await db.commit()