Coverage for /Users/antonigmitruk/golf/src/golf/core/builder_auth.py: 0%

1"""Authentication integration for the Golf MCP build process.

2

3 This module adds support for injecting authentication configuration

4 into the generated FastMCP application during the build process using

5 FastMCP 2.11+ built-in auth providers.

6"""

7

8 from golf.auth import get_auth_config, is_auth_configured

9 from golf.auth.api_key import get_api_key_config

10 from golf.auth.providers import AuthConfig

11

12

13 def generate_auth_code(

14 server_name: str,

15 host: str = "localhost",

16 port: int = 3000,

17 https: bool = False,

18 opentelemetry_enabled: bool = False,

19 transport: str = "streamable-http",

20) -> dict:

21 """Generate authentication components for the FastMCP app using modern

22 auth providers.

23

24 Returns a dictionary with:

25 - imports: List of import statements

26 - setup_code: Auth setup code (provider configuration, etc.)

27 - fastmcp_args: Dict of arguments to add to FastMCP constructor

28 - has_auth: Whether auth is configured

29 """

30 # Check for API key configuration first

31 api_key_config = get_api_key_config()

32 if api_key_config:

33 return generate_api_key_auth_components(server_name, opentelemetry_enabled, transport)

34

35 # Check for modern auth configuration

36 auth_config = get_auth_config()

37 if not auth_config:

38 # If no auth config, return empty components

39 return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False}

40

41 # Validate that we have a modern auth config

42 if not isinstance(auth_config, AuthConfig):

43 raise ValueError(

44 f"Invalid auth configuration type: {type(auth_config).__name__}. "

45 "Golf 0.2.x requires modern auth configurations (JWTAuthConfig, "

46 "StaticTokenConfig, OAuthServerConfig, or RemoteAuthConfig). "

47 "Please update your auth.py file."

48 )

49

50 # Generate modern auth components with embedded configuration

51 auth_imports = [

52 "import os",

53 "import sys",

54 "from golf.auth.factory import create_auth_provider",

55 "from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig",

56 ]

57

58 # Embed the auth configuration directly in the generated code

59 # Convert the auth config to its string representation for embedding

60 auth_config_repr = repr(auth_config)

61

62 setup_code_lines = [

63 "# Modern FastMCP 2.11+ authentication setup with embedded configuration",

64 f"auth_config = {auth_config_repr}",

65 "try:",

66 " auth_provider = create_auth_provider(auth_config)",

67 " print(f'Authentication configured with {auth_config.provider_type} provider')",

68 "except Exception as e:",

69 " print(f'Authentication setup failed: {e}', file=sys.stderr)",

70 " auth_provider = None",

71 "",

72 ]

73

74 # FastMCP constructor arguments - FastMCP 2.11+ uses auth parameter

75 fastmcp_args = {"auth": "auth_provider"}

76

77 return {

78 "imports": auth_imports,

79 "setup_code": setup_code_lines,

80 "fastmcp_args": fastmcp_args,

81 "has_auth": True,

82 }

83

84

85 def generate_api_key_auth_components(

86 server_name: str,

87 opentelemetry_enabled: bool = False,

88 transport: str = "streamable-http",

89) -> dict:

90 """Generate authentication components for API key authentication.

91

92 Returns a dictionary with:

93 - imports: List of import statements

94 - setup_code: Auth setup code (middleware setup)

95 - fastmcp_args: Dict of arguments to add to FastMCP constructor

96 - has_auth: Whether auth is configured

97 """

98 api_key_config = get_api_key_config()

99 if not api_key_config:

100 return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False}

101

102 auth_imports = [

103 "# API key authentication setup",

104 "from golf.auth.api_key import get_api_key_config, configure_api_key",

105 "from golf.auth import set_api_key",

106 "from starlette.middleware.base import BaseHTTPMiddleware",

107 "from starlette.requests import Request",

108 "from starlette.responses import JSONResponse",

109 "import os",

110 ]

111

112 setup_code_lines = [

113 "# Recreate API key configuration from auth.py",

114 "configure_api_key(",

115 f" header_name={repr(api_key_config.header_name)},",

116 f" header_prefix={repr(api_key_config.header_prefix)},",

117 f" required={repr(api_key_config.required)}",

118 ")",

119 "",

120 "# Simplified API key middleware that validates presence",

121 "class ApiKeyMiddleware(BaseHTTPMiddleware):",

122 " async def dispatch(self, request: Request, call_next):",

123 " # Debug mode from environment",

124 " debug = os.environ.get('GOLF_API_KEY_DEBUG', '').lower() == 'true'",

125 " ",

126 " # Skip auth for monitoring endpoints",

127 " path = request.url.path",

128 " if path in ['/metrics', '/health']:",

129 " return await call_next(request)",

130 " ",

131 " api_key_config = get_api_key_config()",

132 " ",

133 " if api_key_config:",

134 " # Extract API key from the configured header",

135 " header_name = api_key_config.header_name",

136 " header_prefix = api_key_config.header_prefix",

137 " ",

138 " # Case-insensitive header lookup",

139 " api_key = None",

140 " for k, v in request.headers.items():",

141 " if k.lower() == header_name.lower():",

142 " api_key = v",

143 " break",

144 " ",

145 " # Process the API key if found",

146 " if api_key:",

147 " # Strip prefix if configured",

148 " if header_prefix and api_key.startswith(header_prefix):",

149 " api_key = api_key[len(header_prefix):]",

150 " ",

151 " # Store the API key in request state for tools to access",

152 " request.state.api_key = api_key",

153 " ",

154 " # Also store in context variable for tools",

155 " set_api_key(api_key)",

156 " ",

157 " # Check if API key is required but missing",

158 " if api_key_config.required and not api_key:",

159 " return JSONResponse(",

160 " {'error': 'unauthorized', "

161 "'detail': f'Missing required {header_name} header'},"

162 " status_code=401,",

163 " headers={'WWW-Authenticate': f'{header_name} realm=\"MCP Server\"'}",

164 " )",

165 " ",

166 " # Continue with the request",

167 " return await call_next(request)",

168 "",

169 ]

170

171 # API key auth is handled via middleware, not FastMCP constructor args

172 fastmcp_args = {}

173

174 return {

175 "imports": auth_imports,

176 "setup_code": setup_code_lines,

177 "fastmcp_args": fastmcp_args,

178 "has_auth": True,

179 }

180

181

182 def generate_auth_routes() -> str:

183 """Generate code for auth routes in the FastMCP app.

184

185 Auth providers (RemoteAuthProvider, OAuthProvider) provide OAuth metadata routes

186 that need to be added to the server.

187 """

188 # API key auth doesn't need special routes

189 api_key_config = get_api_key_config()

190 if api_key_config:

191 return ""

192

193 # Check if auth is configured

194 if not is_auth_configured():

195 return ""

196

197 # Auth providers provide OAuth metadata routes that need to be added to the server

198 return """

199# Add OAuth metadata routes from auth provider

200 if auth_provider and hasattr(auth_provider, 'get_routes'):

201 auth_routes = auth_provider.get_routes()

202 if auth_routes:

203 # Add routes to FastMCP's additional HTTP routes list

204 try:

205 mcp._additional_http_routes.extend(auth_routes)

206 print(f"Added {len(auth_routes)} OAuth metadata routes")

207 except Exception as e:

208 print(f"Warning: Failed to add OAuth routes: {e}")

209"""