Selcan Yukcu commited on
Commit
92d7038
·
1 Parent(s): 7f82b3d

feat(auth): implement Keycloak authentication and configuration

Browse files
Files changed (3) hide show
  1. main.py +7 -10
  2. utils/auth.py +34 -0
  3. utils/config.py +16 -1
main.py CHANGED
@@ -3,13 +3,13 @@ from contextlib import asynccontextmanager
3
  import httpx
4
  from fastmcp import FastMCP, Context
5
  from loguru import logger
6
- from dataclasses import dataclass
7
  import uuid
8
  import os
9
  import sys
10
 
11
  from utils.config import API_CONFIG
12
  from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest
 
13
 
14
  from fastmcp.server.dependencies import get_http_request
15
  from starlette.requests import Request
@@ -19,18 +19,15 @@ from dotenv import load_dotenv
19
 
20
  load_dotenv()
21
 
22
-
23
- @dataclass
24
- class DSContext:
25
- """Typed context for DS MCP server"""
26
-
27
- client: httpx.AsyncClient
28
-
29
-
30
  @asynccontextmanager
31
  async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]:
32
  client = httpx.AsyncClient(timeout=API_CONFIG["timeout"])
33
- ctx = DSContext(client=client)
 
 
 
 
 
34
 
35
  try:
36
  yield ctx
 
3
  import httpx
4
  from fastmcp import FastMCP, Context
5
  from loguru import logger
 
6
  import uuid
7
  import os
8
  import sys
9
 
10
  from utils.config import API_CONFIG
11
  from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest
12
+ from utils.auth import DSContext, authenticate
13
 
14
  from fastmcp.server.dependencies import get_http_request
15
  from starlette.requests import Request
 
19
 
20
  load_dotenv()
21
 
 
 
 
 
 
 
 
 
22
  @asynccontextmanager
23
  async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]:
24
  client = httpx.AsyncClient(timeout=API_CONFIG["timeout"])
25
+ token = await authenticate()
26
+
27
+ if token:
28
+ client.headers.update({"Authorization": f"Bearer {token}"})
29
+
30
+ ctx = DSContext(client=client, access_token=token)
31
 
32
  try:
33
  yield ctx
utils/auth.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import httpx
3
+ from dataclasses import dataclass
4
+
5
+ from utils.config import KEYCLOAK_CONFIG, TOKEN_ENDPOINT
6
+
7
+
8
+ @dataclass
9
+ class DSContext:
10
+ """Typed context for DS MCP server"""
11
+
12
+ client: httpx.AsyncClient
13
+
14
+
15
+
16
+ async def authenticate() -> Optional[str]:
17
+ """Authenticate with Keycloak and return access token"""
18
+ data = {
19
+ "grant_type": KEYCLOAK_CONFIG["grant_type"],
20
+ "client_id": KEYCLOAK_CONFIG["client_id"],
21
+ "username": KEYCLOAK_CONFIG["username"],
22
+ "password": KEYCLOAK_CONFIG["password"],
23
+ }
24
+
25
+ if KEYCLOAK_CONFIG["client_secret"]:
26
+ data["client_secret"] = KEYCLOAK_CONFIG["client_secret"]
27
+
28
+ async with httpx.AsyncClient() as client:
29
+ response = await client.post(TOKEN_ENDPOINT, data=data)
30
+ if response.status_code == 200:
31
+ return response.json().get("access_token")
32
+ else:
33
+ print(f"Authentication failed: {response.status_code} - {response.text}")
34
+ return None
utils/config.py CHANGED
@@ -11,4 +11,19 @@ API_CONFIG = {
11
  "api_searchspace_endpoint": str(os.getenv("DS_API_SEARCHSPACE_ENDPOINT", "/mcp_search_space_routing")),
12
  "hf_access_token": os.getenv("HF_ACCESS_TOKEN", None),
13
  "timeout": int(os.getenv("TIMEOUT", 120)),
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  "api_searchspace_endpoint": str(os.getenv("DS_API_SEARCHSPACE_ENDPOINT", "/mcp_search_space_routing")),
12
  "hf_access_token": os.getenv("HF_ACCESS_TOKEN", None),
13
  "timeout": int(os.getenv("TIMEOUT", 120)),
14
+ }
15
+
16
+
17
+ # Keycloak Configuration
18
+ KEYCLOAK_CONFIG = {
19
+ "base_url": os.getenv("KEYCLOAK_BASE_URL", "https://training.test.orbitant.dev"),
20
+ "realm": os.getenv("KEYCLOAK_REALM", "orbitant-realm"),
21
+ "client_id": os.getenv("KEYCLOAK_CLIENT_ID", "orbitant-backend-client"),
22
+ "client_secret": os.getenv("KEYCLOAK_CLIENT_SECRET", " "),
23
+ "grant_type": os.getenv("KEYCLOAK_GRANT_TYPE", "password"),
24
+ "username": os.getenv("KEYCLOAK_USERNAME", "admin"),
25
+ "password": os.getenv("KEYCLOAK_PASSWORD", "admin"),
26
+ }
27
+
28
+ # Token Endpoint
29
+ TOKEN_ENDPOINT = f"{KEYCLOAK_CONFIG['base_url']}/realms/{KEYCLOAK_CONFIG['realm']}/protocol/openid-connect/token"