Skip to content

Commit a092617

Browse files
committed
Merge remote-tracking branch 'origin/main' into copilot/fix-access-denied-error
2 parents adfa64d + d7864e6 commit a092617

36 files changed

+923
-699
lines changed

.env.example

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ MOCK_RAG=true
1010
PORT=8000
1111
APP_NAME=Chat UI 13
1212

13+
# Authentication configuration
14+
# Header name to extract authenticated username from reverse proxy
15+
# Different reverse proxy setups use different header names (e.g., X-User-Email, X-Authenticated-User, X-Remote-User)
16+
# Default: X-User-Email
17+
# AUTH_USER_HEADER=X-User-Email
18+
1319
# Agent mode configuration
1420
AGENT_MAX_STEPS=10
1521
AGENT_DEFAULT_ENABLED=true
@@ -56,9 +62,9 @@ USE_NEW_FRONTEND=true
5662
#############################################
5763
FEATURE_WORKSPACES_ENABLED=false # Workspace selector (none configured yet)
5864
FEATURE_RAG_ENABLED=false # RAG sources (MOCK_RAG currently true)
59-
FEATURE_TOOLS_ENABLED=false # MCP / tools panel
60-
FEATURE_MARKETPLACE_ENABLED=false # Marketplace browsing (disabled)
61-
FEATURE_FILES_PANEL_ENABLED=false # Uploaded/session files panel
65+
FEATURE_TOOLS_ENABLED=true # MCP / tools panel
66+
FEATURE_MARKETPLACE_ENABLED=true # Marketplace browsing (disabled)
67+
FEATURE_FILES_PANEL_ENABLED=true # Uploaded/session files panel
6268
FEATURE_CHAT_HISTORY_ENABLED=false # Previous chat history list
6369
FEATURE_COMPLIANCE_LEVELS_ENABLED=false # Compliance level filtering for MCP servers and data sources
6470

.github/copilot-instructions.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ frontend/ React 19 + Vite + Tailwind; state via contexts (Chat/WS/Marketplace)
3434
- Feature flags (AppSettings): FEATURE_TOOLS_ENABLED, FEATURE_RAG_MCP_ENABLED, FEATURE_COMPLIANCE_LEVELS_ENABLED, FEATURE_AGENT_MODE_AVAILABLE.
3535

3636
## MCP + RAG conventions
37-
- MCP servers live in mcp.json (tools/prompts) and mcp-rag.json (RAG-only inventory). Fields: groups, is_exclusive, transport|type, url|command/cwd, compliance_level.
37+
- MCP servers live in mcp.json (tools/prompts) and mcp-rag.json (RAG-only inventory). Fields: groups, transport|type, url|command/cwd, compliance_level.
3838
- Transport detection order: explicit transport → command (stdio) → URL protocol (http/sse) → type fallback.
3939
- Tool names exposed to LLM are fully-qualified: server_toolName. “canvas_canvas” is a pseudo-tool always available.
4040
- RAG over MCP tools expected: rag_discover_resources, rag_get_raw_results, optional rag_get_synthesized_results. RAG resources and servers may include complianceLevel.
@@ -68,4 +68,8 @@ frontend/ React 19 + Vite + Tailwind; state via contexts (Chat/WS/Marketplace)
6868
- Add a RAG provider: edit config/overrides/mcp-rag.json; ensure it exposes rag_* tools; UI consumes /api/config.rag_servers.
6969
- Change agent loop: set AGENT_LOOP_STRATEGY to react | think-act | act; ChatService uses app_settings.agent_loop_strategy.
7070

71-
Common pitfalls: “uv not found” → install uv; frontend not loading → npm run build; missing tools → check MCP transport/URL and server logs; empty lists → check auth groups and compliance filtering.
71+
Common pitfalls: “uv not found” → install uv; frontend not loading → npm run build; missing tools → check MCP transport/URL and server logs; empty lists → check auth groups and compliance filtering.
72+
73+
# Style
74+
75+
No emojis please

.github/workflows/build-artifacts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Set up Node.js
2222
uses: actions/setup-node@v4
2323
with:
24-
node-version: '18'
24+
node-version: '22.12'
2525
cache: 'npm'
2626
cache-dependency-path: frontend/package-lock.json
2727

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,4 @@ jobs:
7373
build-args: |
7474
VITE_APP_NAME=Chat UI
7575
tags: ${{ steps.meta.outputs.tags }}
76-
labels: ${{ steps.meta.outputs.labels }}
76+
labels: ${{ steps.meta.outputs.labels }}

Dockerfile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ WORKDIR /app
77
# Create non-root user
88
RUN groupadd -r appuser && useradd -r -g appuser appuser
99

10-
# Install system dependencies including Python and Node.js
11-
RUN dnf update -y && dnf install -y python3 python3-pip python3-virtualenv nodejs npm curl hostname sudo && dnf clean all
10+
# Install system dependencies including Python
11+
RUN dnf update -y && dnf install -y python3 python3-pip python3-virtualenv curl hostname sudo && dnf clean all
12+
13+
# Install Node.js 20.x from NodeSource
14+
RUN curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - && \
15+
dnf install -y nodejs && \
16+
dnf clean all
1217

1318
# Install uv for better Python dependency management
1419
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \

Dockerfile-test

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ WORKDIR /app
77
# Create non-root user
88
RUN groupadd -r appuser && useradd -r -g appuser appuser
99

10-
# Install system dependencies including Node.js
10+
# Install system dependencies
1111
RUN dnf update -y && dnf install -y \
1212
python3 \
1313
python3-pip \
1414
python3-virtualenv \
15-
nodejs \
16-
npm \
1715
curl \
1816
hostname \
1917
wget \
2018
ca-certificates \
2119
dos2unix \
2220
&& dnf clean all
2321

22+
# Install Node.js 20.x from NodeSource
23+
RUN curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - && \
24+
dnf install -y nodejs && \
25+
dnf clean all
26+
2427
# Install uv for better Python dependency management
2528
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
2629
mkdir -p /root/.local/bin

backend/core/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async def is_user_in_group(user_id: str, group_id: str) -> bool:
6161

6262

6363
def get_user_from_header(x_email_header: Optional[str]) -> Optional[str]:
64-
"""Extract user email from X-User-Email header."""
64+
"""Extract user email from authentication header value."""
6565
if not x_email_header:
6666
return None
6767
return x_email_header.strip()

backend/core/middleware.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
class AuthMiddleware(BaseHTTPMiddleware):
1818
"""Middleware to handle authentication and logging."""
1919

20-
def __init__(self, app, debug_mode: bool = False):
20+
def __init__(self, app, debug_mode: bool = False, auth_header_name: str = "X-User-Email"):
2121
super().__init__(app)
2222
self.debug_mode = debug_mode
23+
self.auth_header_name = auth_header_name
2324

2425
async def dispatch(self, request: Request, call_next) -> Response:
2526
# Log request
@@ -50,11 +51,11 @@ async def dispatch(self, request: Request, call_next) -> Response:
5051
else:
5152
logger.warning("Invalid capability token provided")
5253

53-
# Check authentication via X-User-Email header
54+
# Check authentication via configured header (default: X-User-Email)
5455
user_email = None
5556
if self.debug_mode:
56-
# In debug mode, honor X-User-Email header if provided, otherwise use config test user
57-
x_email_header = request.headers.get('X-User-Email')
57+
# In debug mode, honor auth header if provided, otherwise use config test user
58+
x_email_header = request.headers.get(self.auth_header_name)
5859
if x_email_header:
5960
user_email = get_user_from_header(x_email_header)
6061
else:
@@ -63,7 +64,7 @@ async def dispatch(self, request: Request, call_next) -> Response:
6364
user_email = config_manager.app_settings.test_user
6465
# logger.info(f"Debug mode: using user {user_email}")
6566
else:
66-
x_email_header = request.headers.get('X-User-Email')
67+
x_email_header = request.headers.get(self.auth_header_name)
6768
user_email = get_user_from_header(x_email_header)
6869

6970
if not user_email:
@@ -72,7 +73,7 @@ async def dispatch(self, request: Request, call_next) -> Response:
7273
logger.warning(f"Missing authentication for API endpoint: {request.url.path}")
7374
raise HTTPException(status_code=401, detail="Unauthorized")
7475
else:
75-
logger.warning("Missing X-User-Email, redirecting to auth")
76+
logger.warning(f"Missing {self.auth_header_name}, redirecting to auth")
7677
return RedirectResponse(url="/auth", status_code=302)
7778

7879
# Add user to request state

backend/main.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ async def lifespan(app: FastAPI):
124124
"""
125125
app.add_middleware(SecurityHeadersMiddleware)
126126
app.add_middleware(RateLimitMiddleware)
127-
app.add_middleware(AuthMiddleware, debug_mode=config.app_settings.debug_mode)
127+
app.add_middleware(
128+
AuthMiddleware,
129+
debug_mode=config.app_settings.debug_mode,
130+
auth_header_name=config.app_settings.auth_user_header
131+
)
128132

129133
# Include essential routes (add files API)
130134
app.include_router(config_router)
@@ -187,9 +191,12 @@ async def websocket_endpoint(websocket: WebSocket):
187191
2. Reverse proxy intercepts WebSocket handshake (HTTP Upgrade request)
188192
3. Reverse proxy delegates to authentication service
189193
4. Auth service validates JWT/session from cookies or headers
190-
5. If valid: Auth service returns X-User-Email header
191-
6. Reverse proxy forwards connection to this app with X-User-Email header
194+
5. If valid: Auth service returns authenticated user header
195+
6. Reverse proxy forwards connection to this app with authenticated user header
192196
7. This app trusts the header (already validated by auth service)
197+
198+
The header name is configurable via AUTH_USER_HEADER environment variable
199+
(default: X-User-Email). This allows flexibility for different reverse proxy setups.
193200
194201
SECURITY REQUIREMENTS:
195202
- This app MUST ONLY be accessible via reverse proxy
@@ -200,7 +207,7 @@ async def websocket_endpoint(websocket: WebSocket):
200207
(otherwise attackers can inject headers: X-User-Email: [email protected])
201208
202209
DEVELOPMENT vs PRODUCTION:
203-
- Production: Extracts user from X-User-Email header (set by reverse proxy)
210+
- Production: Extracts user from configured auth header (set by reverse proxy)
204211
- Development: Falls back to 'user' query parameter (INSECURE, local only)
205212
206213
See docs/security_architecture.md for complete architecture details.

backend/modules/config/cli.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ def list_servers(args) -> None:
8585
print(f" URL: {server.url}")
8686
if server.groups:
8787
print(f" Groups: {', '.join(server.groups)}")
88-
if server.is_exclusive:
89-
print(" Exclusive: ⚠️ Yes")
9088
print()
9189

9290

0 commit comments

Comments
 (0)