mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
feat(auth): add _build_google_auth() builder with stdio warning
Adds _build_google_auth() to server.py that reads Google OAuth settings and returns a configured GoogleProvider instance or None when unconfigured. Includes warning for stdio transport incompatibility and conditional jwt_signing_key passthrough. 4 new TDD tests in tests/test_auth_builder.py.
This commit is contained in:
95
tests/test_auth_builder.py
Normal file
95
tests/test_auth_builder.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Tests for _build_google_auth() in server.py."""
|
||||
|
||||
import importlib
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
def test_build_google_auth_returns_none_when_unconfigured(monkeypatch):
|
||||
"""Returns None when Google OAuth env vars are absent."""
|
||||
monkeypatch.delenv("GOOGLE_CLIENT_ID", raising=False)
|
||||
monkeypatch.delenv("GOOGLE_CLIENT_SECRET", raising=False)
|
||||
monkeypatch.delenv("UNRAID_MCP_BASE_URL", raising=False)
|
||||
|
||||
import unraid_mcp.config.settings as s
|
||||
|
||||
importlib.reload(s)
|
||||
|
||||
from unraid_mcp.server import _build_google_auth
|
||||
|
||||
result = _build_google_auth()
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_build_google_auth_returns_provider_when_configured(monkeypatch):
|
||||
"""Returns GoogleProvider instance when all required vars are set."""
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_ID", "test-id.apps.googleusercontent.com")
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_SECRET", "GOCSPX-test-secret")
|
||||
monkeypatch.setenv("UNRAID_MCP_BASE_URL", "http://10.1.0.2:6970")
|
||||
monkeypatch.setenv("UNRAID_MCP_JWT_SIGNING_KEY", "x" * 32)
|
||||
|
||||
import unraid_mcp.config.settings as s
|
||||
|
||||
importlib.reload(s)
|
||||
|
||||
mock_provider = MagicMock()
|
||||
mock_provider_class = MagicMock(return_value=mock_provider)
|
||||
|
||||
with patch("unraid_mcp.server.GoogleProvider", mock_provider_class):
|
||||
from unraid_mcp.server import _build_google_auth
|
||||
|
||||
result = _build_google_auth()
|
||||
|
||||
assert result is mock_provider
|
||||
mock_provider_class.assert_called_once_with(
|
||||
client_id="test-id.apps.googleusercontent.com",
|
||||
client_secret="GOCSPX-test-secret",
|
||||
base_url="http://10.1.0.2:6970",
|
||||
jwt_signing_key="x" * 32,
|
||||
)
|
||||
|
||||
|
||||
def test_build_google_auth_omits_jwt_key_when_empty(monkeypatch):
|
||||
"""jwt_signing_key is omitted (not passed as empty string) when not set."""
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_ID", "test-id.apps.googleusercontent.com")
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_SECRET", "GOCSPX-test-secret")
|
||||
monkeypatch.setenv("UNRAID_MCP_BASE_URL", "http://10.1.0.2:6970")
|
||||
monkeypatch.delenv("UNRAID_MCP_JWT_SIGNING_KEY", raising=False)
|
||||
|
||||
import unraid_mcp.config.settings as s
|
||||
|
||||
importlib.reload(s)
|
||||
|
||||
mock_provider_class = MagicMock(return_value=MagicMock())
|
||||
|
||||
with patch("unraid_mcp.server.GoogleProvider", mock_provider_class):
|
||||
from unraid_mcp.server import _build_google_auth
|
||||
|
||||
_build_google_auth()
|
||||
|
||||
call_kwargs = mock_provider_class.call_args.kwargs
|
||||
assert "jwt_signing_key" not in call_kwargs
|
||||
|
||||
|
||||
def test_build_google_auth_warns_on_stdio_transport(monkeypatch):
|
||||
"""Logs a warning when Google auth is configured but transport is stdio."""
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_ID", "test-id.apps.googleusercontent.com")
|
||||
monkeypatch.setenv("GOOGLE_CLIENT_SECRET", "GOCSPX-test-secret")
|
||||
monkeypatch.setenv("UNRAID_MCP_BASE_URL", "http://10.1.0.2:6970")
|
||||
monkeypatch.setenv("UNRAID_MCP_TRANSPORT", "stdio")
|
||||
|
||||
import unraid_mcp.config.settings as s
|
||||
|
||||
importlib.reload(s)
|
||||
|
||||
warning_messages: list[str] = []
|
||||
|
||||
from unraid_mcp.server import _build_google_auth
|
||||
|
||||
with (
|
||||
patch("unraid_mcp.server.GoogleProvider", MagicMock(return_value=MagicMock())),
|
||||
patch("unraid_mcp.server.logger") as mock_logger,
|
||||
):
|
||||
mock_logger.warning.side_effect = lambda msg, *a, **kw: warning_messages.append(msg)
|
||||
_build_google_auth()
|
||||
|
||||
assert any("stdio" in m.lower() for m in warning_messages)
|
||||
@@ -7,6 +7,7 @@ separate modules for configuration, core functionality, subscriptions, and tools
|
||||
import sys
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.google import GoogleProvider
|
||||
from fastmcp.server.middleware.caching import CallToolSettings, ResponseCachingMiddleware
|
||||
from fastmcp.server.middleware.error_handling import ErrorHandlingMiddleware
|
||||
from fastmcp.server.middleware.logging import LoggingMiddleware
|
||||
@@ -68,6 +69,53 @@ cache_middleware = ResponseCachingMiddleware(
|
||||
get_prompt_settings={"enabled": False},
|
||||
)
|
||||
|
||||
|
||||
def _build_google_auth() -> "GoogleProvider | None":
|
||||
"""Build GoogleProvider when OAuth env vars are configured, else return None.
|
||||
|
||||
Returns None (no auth) when GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET are absent,
|
||||
preserving backward compatibility for existing unprotected setups.
|
||||
"""
|
||||
from .config.settings import (
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
UNRAID_MCP_BASE_URL,
|
||||
UNRAID_MCP_JWT_SIGNING_KEY,
|
||||
UNRAID_MCP_TRANSPORT,
|
||||
is_google_auth_configured,
|
||||
)
|
||||
|
||||
if not is_google_auth_configured():
|
||||
return None
|
||||
|
||||
if UNRAID_MCP_TRANSPORT == "stdio":
|
||||
logger.warning(
|
||||
"Google OAuth is configured but UNRAID_MCP_TRANSPORT=stdio. "
|
||||
"OAuth requires HTTP transport (streamable-http or sse). "
|
||||
"Auth will be applied but may not work as expected."
|
||||
)
|
||||
|
||||
kwargs: dict[str, str] = {
|
||||
"client_id": GOOGLE_CLIENT_ID,
|
||||
"client_secret": GOOGLE_CLIENT_SECRET,
|
||||
"base_url": UNRAID_MCP_BASE_URL,
|
||||
}
|
||||
if UNRAID_MCP_JWT_SIGNING_KEY:
|
||||
kwargs["jwt_signing_key"] = UNRAID_MCP_JWT_SIGNING_KEY
|
||||
else:
|
||||
logger.warning(
|
||||
"UNRAID_MCP_JWT_SIGNING_KEY is not set. FastMCP will derive a key automatically, "
|
||||
"but tokens may be invalidated on server restart. "
|
||||
"Set UNRAID_MCP_JWT_SIGNING_KEY to a stable secret."
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Google OAuth enabled — base_url={UNRAID_MCP_BASE_URL}, "
|
||||
f"redirect_uri={UNRAID_MCP_BASE_URL}/auth/callback"
|
||||
)
|
||||
return GoogleProvider(**kwargs)
|
||||
|
||||
|
||||
# Initialize FastMCP instance
|
||||
mcp = FastMCP(
|
||||
name="Unraid MCP Server",
|
||||
|
||||
Reference in New Issue
Block a user