security: replace open OAuth with Forgejo-backed authentication
Uses FastMCP OAuthProxy to proxy OAuth to Forgejo (git.georgsen.dk). Only users who can authenticate with Forgejo get MCP access. DCR is still used for client registration, but authorization requires Forgejo login. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
50c3b116f7
commit
5d08e15b0f
1 changed files with 45 additions and 6 deletions
|
|
@ -5,27 +5,66 @@ import logging
|
|||
from datetime import datetime, timezone
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.in_memory import InMemoryOAuthProvider
|
||||
from fastmcp.server.auth.auth import ClientRegistrationOptions
|
||||
import contextlib
|
||||
import httpx
|
||||
from fastmcp.server.auth import TokenVerifier, AccessToken
|
||||
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
from .db import Database
|
||||
from .config import get_group_chat_id
|
||||
from .config import get_group_chat_id, load_credentials
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
db: Database | None = None
|
||||
|
||||
oauth = InMemoryOAuthProvider(
|
||||
FORGEJO_URL = "https://git.georgsen.dk"
|
||||
|
||||
|
||||
class ForgejoTokenVerifier(TokenVerifier):
|
||||
"""Verify OAuth tokens against Forgejo's API."""
|
||||
|
||||
def __init__(self, forgejo_url: str = FORGEJO_URL):
|
||||
super().__init__(required_scopes=None)
|
||||
self.forgejo_url = forgejo_url
|
||||
|
||||
async def verify_token(self, token: str) -> AccessToken | None:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
resp = await client.get(
|
||||
f"{self.forgejo_url}/api/v1/user",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return None
|
||||
user = resp.json()
|
||||
return AccessToken(
|
||||
token=token,
|
||||
client_id=str(user["id"]),
|
||||
scopes=[],
|
||||
expires_at=None,
|
||||
claims={"sub": str(user["id"]), "login": user.get("login")},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Forgejo token verification failed: {e}")
|
||||
return None
|
||||
|
||||
|
||||
creds = load_credentials()
|
||||
auth = OAuthProxy(
|
||||
upstream_authorization_endpoint=f"{FORGEJO_URL}/login/oauth/authorize",
|
||||
upstream_token_endpoint=f"{FORGEJO_URL}/login/oauth/access_token",
|
||||
upstream_client_id=creds["FORGEJO_OAUTH_CLIENT_ID"],
|
||||
upstream_client_secret=creds["FORGEJO_OAUTH_CLIENT_SECRET"],
|
||||
token_verifier=ForgejoTokenVerifier(),
|
||||
base_url="https://mcp.georgsen.dk",
|
||||
client_registration_options=ClientRegistrationOptions(enabled=True),
|
||||
)
|
||||
|
||||
mcp = FastMCP(
|
||||
name="homelab-bridge",
|
||||
auth=oauth,
|
||||
auth=auth,
|
||||
instructions=(
|
||||
"This MCP server bridges claude.ai to a homelab Telegram group chat. "
|
||||
"Use pull_updates to read conversation history (supports cursor-based pagination). "
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue