Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions backend/core/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ async def dispatch(self, request: Request, call_next) -> Response:
# Log request
logger.info(f"Request: {request.method} {request.url.path}")

# Skip auth for static files and configured auth endpoint
if request.url.path.startswith('/static') or request.url.path == self.auth_redirect_url:
# Skip auth for static files, health check, and configured auth endpoint
if (request.url.path.startswith('/static') or
request.url.path == '/api/health' or
request.url.path == self.auth_redirect_url):
return await call_next(request)

# Validate proxy secret if enabled (skip in debug mode for local development)
Expand Down Expand Up @@ -115,7 +117,10 @@ async def dispatch(self, request: Request, call_next) -> Response:
# Distinguish between API endpoints (return 401) and browser endpoints (redirect)
if request.url.path.startswith('/api/'):
logger.warning(f"Missing authentication for API endpoint: {request.url.path}")
raise HTTPException(status_code=401, detail="Unauthorized")
return JSONResponse(
status_code=401,
content={"detail": "Unauthorized"}
)
else:
logger.warning(f"Missing {self.auth_header_name}, redirecting to {self.auth_redirect_url}")
return RedirectResponse(url=self.auth_redirect_url, status_code=302)
Expand Down
7 changes: 5 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from routes.config_routes import router as config_router
from routes.admin_routes import admin_router
from routes.files_routes import router as files_router
from routes.health_routes import router as health_router
from version import VERSION

# Load environment variables from the parent directory
load_dotenv(dotenv_path="../.env")
Expand Down Expand Up @@ -111,8 +113,8 @@ async def lifespan(app: FastAPI):
# Create FastAPI app with minimal setup
app = FastAPI(
title="Chat UI Backend",
description="Basic chat backend with modular architecture",
version="2.0.0",
description="Basic chat backend with modular architecture",
version=VERSION,
lifespan=lifespan,
)

Expand Down Expand Up @@ -141,6 +143,7 @@ async def lifespan(app: FastAPI):
app.include_router(config_router)
app.include_router(admin_router)
app.include_router(files_router)
app.include_router(health_router)

# Serve frontend build (Vite)
project_root = Path(__file__).resolve().parents[1]
Expand Down
40 changes: 40 additions & 0 deletions backend/routes/health_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Health check routes for service monitoring and load balancing.

Provides simple health check endpoint for monitoring tools, orchestrators,
and load balancers to verify service availability.
"""

import logging
from datetime import datetime, timezone
from typing import Dict, Any

from fastapi import APIRouter

from version import VERSION

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api", tags=["health"])


@router.get("/health")
async def health_check() -> Dict[str, Any]:
"""Health check endpoint for service monitoring.

Returns basic service status information. This endpoint does not require
authentication and is intended for use by load balancers, monitoring
systems, and orchestration platforms.

Returns:
Dictionary containing:
- status: Service health status ("healthy")
- service: Service name
- version: Service version
- timestamp: Current UTC timestamp in ISO-8601 format
"""
return {
"status": "healthy",
"service": "atlas-ui-3-backend",
"version": VERSION,
"timestamp": datetime.now(timezone.utc).isoformat()
}
49 changes: 49 additions & 0 deletions backend/tests/test_health_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Unit tests for health check endpoint."""

from starlette.testclient import TestClient
from datetime import datetime

from main import app


def test_health_endpoint_returns_200():
"""Test that health endpoint returns 200 status."""
client = TestClient(app)
resp = client.get("/api/health")
assert resp.status_code == 200


def test_health_endpoint_no_auth_required():
"""Test that health endpoint works without authentication."""
client = TestClient(app)
# No X-User-Email header provided
resp = client.get("/api/health")
assert resp.status_code == 200


def test_health_endpoint_response_structure():
"""Test that health endpoint returns correct response structure."""
client = TestClient(app)
resp = client.get("/api/health")
assert resp.status_code == 200

data = resp.json()

# Verify all required fields are present
assert "status" in data
assert "service" in data
assert "version" in data
assert "timestamp" in data

# Verify field values
assert data["status"] == "healthy"
assert data["service"] == "atlas-ui-3-backend"

# This version number can change, so just check it's a non-empty string
assert isinstance(data["version"], str) and len(data["version"]) > 0

# Verify timestamp is valid ISO-8601 format
try:
datetime.fromisoformat(data["timestamp"])
except ValueError:
assert False, f"Invalid timestamp format: {data['timestamp']}"
32 changes: 32 additions & 0 deletions backend/tests/test_middleware_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,35 @@ def ping(request: Request):
assert resp.status_code == 200
assert resp.json()["user"] == "debug@example.com"


def test_health_endpoint_bypasses_auth():
"""Test that /api/health endpoint bypasses authentication middleware."""
app = FastAPI()

@app.get("/api/health")
def health():
return {"status": "healthy"}

@app.get("/api/other")
def other():
return {"data": "test"}

# Add an /auth route to receive redirects
@app.get("/auth")
def auth():
return {"login": True}

# Add middleware with auth required (debug_mode=False)
app.add_middleware(AuthMiddleware, debug_mode=False)
client = TestClient(app)

# Health endpoint should work without auth header
health_resp = client.get("/api/health")
assert health_resp.status_code == 200
assert health_resp.json()["status"] == "healthy"

# Other API endpoints should still require auth (return 401)
other_resp = client.get("/api/other")
assert other_resp.status_code == 401
assert "Unauthorized" in other_resp.json()["detail"]

6 changes: 6 additions & 0 deletions backend/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Application version constant.

Single source of truth for the application version number.
"""

VERSION = "0.1.0"
4 changes: 4 additions & 0 deletions docs/02_admin_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,10 @@ It is essential to configure the location where the `app.jsonl` file is stored,
```
* **Default**: If this variable is not set, the application will attempt to create a `logs` directory in the project's root, which may not be desirable or possible in a production deployment. Ensure the specified directory exists and the application has the necessary permissions to write to it.

## Health Monitoring (Added 2025-11-21)

The application provides a public health check endpoint at `/api/health` specifically designed for monitoring tools, load balancers, and orchestration platforms. This endpoint requires no authentication and returns a JSON response containing the service status, version, and current timestamp in ISO-8601 format. You can integrate this endpoint into your monitoring infrastructure (such as Kubernetes liveness/readiness probes, AWS ELB health checks, or Prometheus monitoring) to verify that the backend service is running and responding correctly. The endpoint is lightweight and does not check database connectivity or external dependencies, making it ideal for high-frequency health polling without impacting application performance. For more detailed system status information that includes configuration and component health, admin users can access the `/admin/system-status` endpoint, which requires authentication and admin group membership.

Comment on lines +581 to +582
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This documentation paragraph is quite long and dense (581 characters in a single paragraph). Consider breaking it into multiple paragraphs or a bulleted list for better readability. For example:

## Health Monitoring (Added 2025-11-21)

The application provides a public health check endpoint at `/api/health` specifically designed for monitoring tools, load balancers, and orchestration platforms. 

**Key Features:**
- No authentication required
- Returns JSON with service status, version, and ISO-8601 timestamp
- Lightweight - no database or external dependency checks
- Ideal for high-frequency health polling

**Integration Examples:**
You can integrate this endpoint into monitoring infrastructure such as:
- Kubernetes liveness/readiness probes
- AWS ELB/ALB health checks  
- Prometheus monitoring

**Advanced Monitoring:**
For more detailed system status information that includes configuration and component health, admin users can access the `/admin/system-status` endpoint, which requires authentication and admin group membership.
Suggested change
The application provides a public health check endpoint at `/api/health` specifically designed for monitoring tools, load balancers, and orchestration platforms. This endpoint requires no authentication and returns a JSON response containing the service status, version, and current timestamp in ISO-8601 format. You can integrate this endpoint into your monitoring infrastructure (such as Kubernetes liveness/readiness probes, AWS ELB health checks, or Prometheus monitoring) to verify that the backend service is running and responding correctly. The endpoint is lightweight and does not check database connectivity or external dependencies, making it ideal for high-frequency health polling without impacting application performance. For more detailed system status information that includes configuration and component health, admin users can access the `/admin/system-status` endpoint, which requires authentication and admin group membership.
The application provides a public health check endpoint at `/api/health` specifically designed for monitoring tools, load balancers, and orchestration platforms.
**Key Features:**
- No authentication required
- Returns JSON with service status, version, and ISO-8601 timestamp
- Lightweight: does not check database connectivity or external dependencies
- Ideal for high-frequency health polling without impacting application performance
**Integration Examples:**
You can integrate this endpoint into your monitoring infrastructure, such as:
- Kubernetes liveness/readiness probes
- AWS ELB/ALB health checks
- Prometheus monitoring
**Advanced Monitoring:**
For more detailed system status information, including configuration and component health, admin users can access the `/admin/system-status` endpoint, which requires authentication and admin group membership.

Copilot uses AI. Check for mistakes.
## LLM Configuration (`llmconfig.yml`)

The `llmconfig.yml` file is where you define all the Large Language Models that the application can use. The application uses the `LiteLLM` library, which allows it to connect to a wide variety of LLM providers.
Expand Down
3 changes: 2 additions & 1 deletion docs/archive/endpoint_summary.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Endpoint Summary

Generated: 2025-08-09
Branch: feature/s3-file-storage
Last Updated: 2025-11-21

## 1. Frontend-Used Endpoints
(HTTP method inferred from usage / backend definition.)
Expand Down Expand Up @@ -47,6 +47,7 @@ Branch: feature/s3-file-storage
- GET /api/debug/servers
- GET /healthz
- GET /api/files/health
- GET /api/health (service health check for monitoring/load balancers, added 2025-11-21)
- DELETE /api/feedback/{feedback_id}
- GET /api/feedback/stats

Expand Down
Loading