auth service completed and tested

This commit is contained in:
2025-01-14 19:16:24 +03:00
parent 08b1815156
commit 486fadbfb3
33 changed files with 1325 additions and 248 deletions

View File

@@ -0,0 +1,37 @@
"""
FastAPI Application Entry Point
This module initializes and configures the FastAPI application with:
- CORS middleware for cross-origin requests
- Request timing middleware for performance monitoring
- Custom exception handlers for consistent error responses
- Prometheus instrumentation for metrics
- API routers for endpoint organization
"""
import uvicorn
import routers
from create_file import create_app
from prometheus_fastapi_instrumentator import Instrumentator
from app_handler import setup_middleware, get_uvicorn_config
print("Loading app.py module...")
# Initialize FastAPI application
app = create_app(routers=routers)
# Setup Prometheus metrics
Instrumentator().instrument(app=app).expose(app=app)
# Configure middleware and exception handlers
setup_middleware(app)
if __name__ == "__main__":
print("Starting server from __main__...")
# Run the application with Uvicorn
uvicorn_config = get_uvicorn_config()
print(f"Using config: {uvicorn_config}")
uvicorn.Server(uvicorn.Config(**uvicorn_config)).run()

View File

@@ -0,0 +1,121 @@
"""
FastAPI Application Handler Module
This module contains all the handler functions for configuring and setting up the FastAPI application:
- CORS middleware configuration
- Exception handlers setup
- Uvicorn server configuration
"""
from typing import Dict, Any
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from ErrorHandlers.bases import (
BaseErrorModelClass,
StatusesModelClass,
LanguageModelClass,
)
from ErrorHandlers import statuses
from middleware.auth_middleware import MiddlewareModule
def setup_cors_middleware(app: FastAPI) -> None:
"""
Configure CORS middleware for the application.
Args:
app: FastAPI application instance
"""
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
"""
Handle HTTP exceptions and return formatted error responses.
Args:
request: FastAPI request object
exc: HTTP exception instance
Returns:
JSONResponse: Formatted error response
"""
error_code = getattr(exc, "error_code", None)
if error_code:
status_code = StatusesModelClass.retrieve_error_by_code(error_code)
error_message = LanguageModelClass.retrieve_error_by_code(
error_code, request.headers.get("accept-language", "en")
)
else:
status_code = exc.status_code
error_message = str(exc.detail)
return JSONResponse(
status_code=status_code,
content={"detail": error_message, "error_code": error_code},
)
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
"""
Handle generic exceptions and return formatted error responses.
Args:
request: FastAPI request object
exc: Exception instance
Returns:
JSONResponse: Formatted error response
"""
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"detail": "Internal server error", "error_code": "INTERNAL_ERROR"},
)
def setup_exception_handlers(app: FastAPI) -> None:
"""
Configure custom exception handlers for the application.
Args:
app: FastAPI application instance
"""
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(Exception, generic_exception_handler)
def setup_middleware(app: FastAPI) -> None:
"""
Configure all middleware for the application.
Args:
app: FastAPI application instance
"""
setup_cors_middleware(app)
app.add_middleware(MiddlewareModule.RequestTimingMiddleware)
setup_exception_handlers(app)
def get_uvicorn_config() -> Dict[str, Any]:
"""
Get Uvicorn server configuration.
Returns:
Dict[str, Any]: Uvicorn configuration dictionary
"""
return {
"app": "app:app",
"host": "0.0.0.0",
"port": 41575,
"log_level": "info",
"reload": True,
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,21 @@
"""
Base FastAPI application configuration.
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
def create_app() -> FastAPI:
app = FastAPI(title="API Service")
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
return app

View File

@@ -0,0 +1,152 @@
"""
FastAPI Application Factory Module
This module provides functionality to create and configure a FastAPI application with:
- Custom OpenAPI schema configuration
- Security scheme configuration for Bearer authentication
- Automatic router registration
- Response class configuration
- Security requirements for protected endpoints
"""
from types import ModuleType
from typing import Any, Dict, List, Optional, Union
from fastapi import FastAPI, APIRouter
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.openapi.utils import get_openapi
from fastapi.routing import APIRoute
from AllConfigs.main import MainConfig as Config
from middleware.auth_middleware import MiddlewareModule
def setup_security_schema() -> Dict[str, Any]:
"""
Configure security schema for the OpenAPI documentation.
Returns:
Dict[str, Any]: Security schema configuration
"""
return {
"Bearer": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Enter the token",
}
}
def configure_route_security(
path: str,
method: str,
schema: Dict[str, Any],
protected_paths: List[str]
) -> None:
"""
Configure security requirements for a specific route.
Args:
path: Route path
method: HTTP method
schema: OpenAPI schema to modify
protected_paths: List of paths that require authentication
"""
if path in protected_paths:
if "paths" not in schema:
schema["paths"] = {}
if path not in schema["paths"]:
schema["paths"][path] = {}
if method not in schema["paths"][path]:
schema["paths"][path][method] = {}
schema["paths"][path][method]["security"] = [{"Bearer": []}]
def get_routers(routers_module: ModuleType) -> List[APIRouter]:
"""
Extract all routers from the routers module.
Args:
routers_module: Module containing router definitions
Returns:
List[APIRouter]: List of router instances
"""
routers = []
for attr_name in dir(routers_module):
attr = getattr(routers_module, attr_name)
if isinstance(attr, APIRouter):
routers.append(attr)
return routers
def create_app(routers: ModuleType) -> FastAPI:
"""
Create and configure a FastAPI application.
Args:
routers: Module containing router definitions
Returns:
FastAPI: Configured FastAPI application instance
"""
# Initialize FastAPI app
app = FastAPI(
title=Config.TITLE,
description=Config.DESCRIPTION,
default_response_class=JSONResponse,
)
# Add home route that redirects to API documentation
@app.get("/", include_in_schema=False, summary=str(Config.DESCRIPTION))
async def home() -> RedirectResponse:
"""Redirect root path to API documentation."""
return RedirectResponse(url="/docs")
# Get all routers
router_instances = get_routers(routers)
# Find protected paths
protected_paths = []
for router in router_instances:
for route in router.routes:
if isinstance(route, APIRoute):
# Check if the route has auth_required decorator
if any(d.__name__ == 'auth_required' for d in route.dependencies):
protected_paths.append(route.path)
# Include routers
for router in router_instances:
app.include_router(router)
# Configure custom OpenAPI schema
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title=Config.TITLE,
version="1.0.0",
description=Config.DESCRIPTION,
routes=app.routes,
)
# Add security schemes
openapi_schema["components"] = {"securitySchemes": setup_security_schema()}
# Configure security for each route
for route in app.routes:
if isinstance(route, APIRoute):
configure_route_security(
route.path,
route.methods.pop().lower(),
openapi_schema,
protected_paths
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
return app

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,179 @@
"""
Authentication and Authorization middleware for FastAPI applications.
This module provides authentication decorator for protecting endpoints
and a middleware for request timing measurements.
"""
from time import perf_counter
from typing import Callable, Optional, Dict, Any, Tuple
from functools import wraps
from fastapi import HTTPException, Request, Response, status
from starlette.middleware.base import BaseHTTPMiddleware
from AllConfigs.Token.config import Auth
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
class MiddlewareModule:
"""
Module containing authentication and middleware functionality.
This class provides:
- Token extraction and validation
- Authentication decorator for endpoints
- Request timing middleware
"""
@staticmethod
def get_access_token(request: Request) -> Tuple[str, str]:
"""
Extract access token from request headers.
Args:
request: FastAPI request object
Returns:
Tuple[str, str]: A tuple containing (scheme, token)
Raises:
HTTPExceptionApi: If token is missing or malformed
"""
auth_header = request.headers.get(Auth.ACCESS_TOKEN_TAG)
if not auth_header:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No authorization header",
)
try:
scheme, token = auth_header.split()
if scheme.lower() != "bearer":
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication scheme",
)
return scheme, token
except ValueError:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token format"
)
@staticmethod
async def validate_token(token: str) -> Dict[str, Any]:
"""
Validate the authentication token.
Args:
token: JWT token to validate
Returns:
Dict[str, Any]: User data extracted from token
Raises:
HTTPExceptionApi: If token is invalid
"""
try:
# TODO: Implement your token validation logic
# Example:
# return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
return {"user_id": "test", "role": "user"} # Placeholder
except Exception as e:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Token validation failed: {str(e)}",
)
@classmethod
def auth_required(cls, func: Callable) -> Callable:
"""
Decorator for protecting FastAPI endpoints with authentication.
Usage:
@router.get("/protected")
@MiddlewareModule.auth_required
async def protected_endpoint(request: Request):
user = request.state.user # Access authenticated user data
return {"message": "Protected content"}
@router.get("/public") # No decorator = public endpoint
async def public_endpoint():
return {"message": "Public content"}
Args:
func: The FastAPI route handler function to protect
Returns:
Callable: Wrapped function that checks authentication before execution
"""
@wraps(func)
async def wrapper(request: Request, *args, **kwargs):
try:
# Get token from header
_, token = cls.get_access_token(request)
# Validate token and get user data
token_data = await cls.validate_token(token)
# Add user data to request state for use in endpoint
request.state.user = token_data
# Call the original endpoint function
return await func(request, *args, **kwargs)
except HTTPExceptionApi:
raise
except Exception as e:
raise HTTPExceptionApi(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Authentication failed: {str(e)}",
)
return wrapper
class RequestTimingMiddleware(BaseHTTPMiddleware):
"""
Middleware for measuring and logging request timing.
Only handles timing, no authentication.
"""
async def dispatch(self, request: Request, call_next: Callable) -> Response:
"""
Process each request through the middleware.
Args:
request: FastAPI request object
call_next: Next middleware in the chain
Returns:
Response: Processed response with timing headers
"""
start_time = perf_counter()
# Process the request
response = await call_next(request)
# Add timing information to response headers
self._add_timing_headers(response, start_time)
return response
@staticmethod
def _add_timing_headers(response: Response, start_time: float) -> None:
"""
Add request timing information to response headers.
Args:
response: FastAPI response object
start_time: Time when request processing started
"""
end_time = perf_counter()
elapsed = (end_time - start_time) * 1000 # Convert to milliseconds
response.headers.update(
{
"request-start": f"{start_time:.6f}",
"request-end": f"{end_time:.6f}",
"request-duration": f"{elapsed:.2f}ms",
}
)

View File

@@ -0,0 +1,209 @@
"""
OpenAPI Schema Creator Module
This module provides functionality to create and customize OpenAPI documentation:
- Custom security schemes (Bearer Auth, API Key)
- Response schemas and examples
- Tag management and descriptions
- Error responses and validation
- Custom documentation extensions
"""
from typing import Any, Dict, List, Optional, Set
from fastapi import FastAPI, APIRouter
from fastapi.openapi.utils import get_openapi
from AllConfigs.main import MainConfig as Config
class OpenAPISchemaCreator:
"""
OpenAPI schema creator and customizer for FastAPI applications.
"""
def __init__(self, app: FastAPI):
"""
Initialize the OpenAPI schema creator.
Args:
app: FastAPI application instance
"""
self.app = app
self.protected_paths: Set[str] = set()
self.tags_metadata = self._create_tags_metadata()
@staticmethod
def _create_tags_metadata() -> List[Dict[str, str]]:
"""
Create metadata for API tags.
Returns:
List[Dict[str, str]]: List of tag metadata
"""
return [
{
"name": "Authentication",
"description": "Operations related to user authentication and authorization",
},
{
"name": "Users",
"description": "User management and profile operations",
},
# Add more tags as needed
]
def _create_security_schemes(self) -> Dict[str, Any]:
"""
Create security scheme definitions.
Returns:
Dict[str, Any]: Security scheme configurations
"""
return {
"Bearer Auth": {
"type": "apiKey",
"in": "header",
"name": "evyos-session-key",
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
},
"API Key": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API key for service authentication",
},
}
def _create_common_responses(self) -> Dict[str, Any]:
"""
Create common response schemas.
Returns:
Dict[str, Any]: Common response configurations
"""
return {
"401": {
"description": "Unauthorized - Authentication failed or not provided",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"detail": {"type": "string"},
"error_code": {"type": "string"},
},
},
"example": {
"detail": "Invalid authentication credentials",
"error_code": "INVALID_CREDENTIALS",
},
}
},
},
"403": {
"description": "Forbidden - Insufficient permissions",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/HTTPValidationError"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/HTTPValidationError"}
}
},
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"detail": {"type": "string"},
"error_code": {"type": "string"},
},
},
"example": {
"detail": "Internal server error occurred",
"error_code": "INTERNAL_ERROR",
},
}
},
},
}
def configure_route_security(
self, path: str, method: str, schema: Dict[str, Any]
) -> None:
"""
Configure security requirements for a specific route.
Args:
path: Route path
method: HTTP method
schema: OpenAPI schema to modify
"""
if path not in Config.INSECURE_PATHS:
schema["paths"][path][method]["security"] = [
{"Bearer Auth": []},
{"API Key": []},
]
schema["paths"][path][method]["responses"].update(
self._create_common_responses()
)
def create_schema(self) -> Dict[str, Any]:
"""
Create the complete OpenAPI schema.
Returns:
Dict[str, Any]: Complete OpenAPI schema
"""
openapi_schema = get_openapi(
title=Config.TITLE,
description=Config.DESCRIPTION,
version="1.0.0",
routes=self.app.routes,
tags=self.tags_metadata,
)
# Add security schemes
if "components" not in openapi_schema:
openapi_schema["components"] = {}
openapi_schema["components"]["securitySchemes"] = self._create_security_schemes()
# Configure route security and responses
for route in self.app.routes:
if isinstance(route, APIRoute) and route.include_in_schema:
path = str(route.path)
methods = [method.lower() for method in route.methods]
for method in methods:
self.configure_route_security(path, method, openapi_schema)
# Add custom documentation extensions
openapi_schema["x-documentation"] = {
"postman_collection": "/docs/postman",
"swagger_ui": "/docs",
"redoc": "/redoc",
}
return openapi_schema
def create_openapi_schema(app: FastAPI) -> Dict[str, Any]:
"""
Create OpenAPI schema for a FastAPI application.
Args:
app: FastAPI application instance
Returns:
Dict[str, Any]: Complete OpenAPI schema
"""
creator = OpenAPISchemaCreator(app)
return creator.create_schema()

View File

@@ -0,0 +1,5 @@
from .base_router import test_route
__all__ = [
"test_route"
]

View File

@@ -0,0 +1,22 @@
"""
Base router configuration and setup.
"""
from fastapi import APIRouter, Request
from middleware.auth_middleware import MiddlewareModule
# Create test router
test_route = APIRouter(prefix="/test", tags=["Test"])
@test_route.get("/health")
@MiddlewareModule.auth_required
async def health_check(request: Request):
return {"status": "healthy", "message": "Service is running"}
@test_route.get("/ping")
async def ping_test():
return {"ping": "pong", "service": "base-router"}
# Initialize and include test routes
def init_test_routes():
return test_route

View File

@@ -0,0 +1,30 @@
FROM python:3.9-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy all required directories
COPY DockerApiServices/AllApiNeeds /app/
COPY ApiLibrary /app/ApiLibrary
COPY ApiValidations /app/ApiValidations
COPY AllConfigs /app/AllConfigs
COPY ErrorHandlers /app/ErrorHandlers
COPY Schemas /app/Schemas
COPY Services /app/Services
# Install Python dependencies
COPY DockerApiServices/requirements.txt /app/
RUN pip install --upgrade pip && pip install --no-cache-dir -r /app/requirements.txt
# Copy application code
COPY . .
# Set Python path to include app directory
ENV PYTHONPATH=/app
# Run the application using the configured uvicorn server
CMD ["python", "app.py"]

View File

@@ -0,0 +1,15 @@
fastapi==0.104.1
uvicorn==0.24.0.post1
pydantic==2.10.5
sqlalchemy==2.0.37
psycopg2-binary==2.9.10
python-dateutil==2.9.0.post0
motor==3.3.2
redis==5.2.1
pytest==7.4.4
pytest-asyncio==0.21.2
pytest-cov==4.1.0
coverage==7.6.10
arrow==1.3.0
redmail==0.6.0
sqlalchemy-mixins==2.0.5

View File

@@ -0,0 +1,23 @@
FROM python:3.9-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY pyproject.toml .
RUN pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-dev
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run the application
CMD ["uvicorn", "EventServiceApi.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,59 @@
# Docker Services Guide
This repository contains multiple microservices that can be run using Docker Compose.
## Quick Start (With Cache)
For regular development when dependencies haven't changed:
```bash
# Build and run Auth Service
docker compose -f ../docker-compose-services.yml up auth-service
# Build and run Event Service
docker compose -f ../docker-compose-services.yml up event-service
# Build and run Validation Service
docker compose -f ../docker-compose-services.yml up validation-service
# Build and run all services
docker compose -f ../docker-compose-services.yml up
```
## Clean Build (No Cache)
Use these commands when changing Dockerfile or dependencies:
```bash
# Auth Service
docker compose -f ../docker-compose-services.yml build --no-cache auth-service && docker compose -f ../docker-compose-services.yml up auth-service
# Event Service
docker compose -f ../docker-compose-services.yml build --no-cache event-service && docker compose -f ../docker-compose-services.yml up event-service
# Validation Service
docker compose -f ../docker-compose-services.yml build --no-cache validation-service && docker compose -f ../docker-compose-services.yml up validation-service
# All Services
docker compose -f ../docker-compose-services.yml build --no-cache && docker compose -f ../docker-compose-services.yml up
```
## Service Ports
- Auth Service: `http://localhost:8000`
- `/test/health` - Protected health check endpoint (requires authentication)
- `/test/ping` - Public ping endpoint
- Event Service: `http://localhost:8001`
- Validation Service: `http://localhost:8002`
## Development Notes
- Use clean build (--no-cache) when:
- Changing Dockerfile
- Updating dependencies
- Experiencing caching issues
- Use regular build (with cache) when:
- Only changing application code
- For faster development iterations
- Run in detached mode:
```bash
docker compose -f ../docker-compose-services.yml up -d auth-service
```
- Stop services:
```bash
docker compose -f ../docker-compose-services.yml down
```

View File

@@ -0,0 +1,23 @@
FROM python:3.9-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY pyproject.toml .
RUN pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-dev
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run the application
CMD ["uvicorn", "ValidationServiceApi.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,76 @@
[tool.poetry]
name = "wag-management-api-services"
version = "0.1.1"
description = "WAG Management API Service"
authors = ["Karatay Berkay <karatay.berkay@evyos.com.tr>"]
[tool.poetry.dependencies]
python = "^3.9"
# FastAPI and Web
fastapi = "^0.104.1"
uvicorn = "^0.24.0"
pydantic = "^2.5.2"
# MongoDB
motor = "3.3.2" # Pinned version
pymongo = "4.5.0" # Pinned version to match motor
# PostgreSQL
sqlalchemy = "^2.0.23"
sqlalchemy-mixins = "^2.0.5"
psycopg2-binary = "^2.9.9"
# Redis
redis = "^5.0.1"
arrow = "^1.3.0"
# Email
redmail = "^0.6.0"
# Testing
pytest = "^7.4.3"
pytest-asyncio = "^0.21.1"
pytest-cov = "^4.1.0"
# Utilities
python-dateutil = "^2.8.2"
typing-extensions = "^4.8.0"
[tool.poetry.group.dev.dependencies]
black = "^23.11.0"
isort = "^5.12.0"
mypy = "^1.7.1"
flake8 = "^6.1.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
line-length = 88
target-version = ['py39']
include = '\.pyi?$'
[tool.isort]
profile = "black"
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
line_length = 88
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --cov=Services"
testpaths = [
"Ztest",
]
python_files = ["test_*.py"]
asyncio_mode = "auto"

View File

@@ -0,0 +1,17 @@
fastapi==0.104.1
uvicorn==0.24.0.post1
pydantic==2.10.5
sqlalchemy==2.0.37
psycopg2-binary==2.9.10
python-dateutil==2.9.0.post0
motor==3.3.2
redis==5.2.1
pytest==7.4.4
pytest-asyncio==0.21.2
pytest-cov==4.1.0
coverage==7.6.10
arrow==1.3.0
redmail==0.6.0
sqlalchemy-mixins==2.0.5
prometheus-client==0.19.0
prometheus-fastapi-instrumentator==6.1.0

View File

@@ -1,12 +1,29 @@
What to do with services?
WAG Management API Microservices Setup
*
1. Authentication Service (Port 8000)
- User authentication and authorization
- JWT token management
- Role-based access control
- Uses PostgreSQL for user data
*
2. Event Service (Port 8001)
- Event processing and handling
- Message queue integration
- Real-time notifications
- Uses MongoDB for event storage
*
3. Validation Service (Port 8002)
- Request validation
- Data sanitization
- Schema validation
- Uses Redis for caching
*
*
To run the services:
```bash
docker compose up --build
```
Access services at:
- Auth Service: http://localhost:8000
- Event Service: http://localhost:8001
- Validation Service: http://localhost:8002