Add multi-token authentication with client labels
- Replace single api_token with api_tokens dict (label: token pairs) - Add context-aware logging to track which client made each request - Implement token label lookup with secure comparison - Add logging middleware to inject token labels into request context - Update logging format to display [label] in all log messages - Fix WebSocket authentication to use new multi-token system - Update CLI --show-token to display all tokens with labels - Update config generation to use api_tokens format - Update README with multi-token documentation - Update config.example.yaml with multiple token examples Benefits: - Easy identification of clients in logs (Home Assistant, mobile, web UI, etc.) - Per-client token management and revocation - Better security and auditability Example log output: 2026-02-06 03:36:20,806 - [home_assistant] - WebSocket client connected Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,24 +7,38 @@ from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from . import __version__
|
||||
from .auth import get_token_label, token_label_var
|
||||
from .config import settings, generate_default_config, get_config_dir
|
||||
from .routes import audio_router, health_router, media_router, scripts_router
|
||||
from .services import get_media_controller
|
||||
from .services.websocket_manager import ws_manager
|
||||
|
||||
|
||||
class TokenLabelFilter(logging.Filter):
|
||||
"""Add token label to log records."""
|
||||
|
||||
def filter(self, record):
|
||||
record.token_label = token_label_var.get("unknown")
|
||||
return True
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Configure application logging."""
|
||||
"""Configure application logging with token labels."""
|
||||
# Create filter and handler
|
||||
token_filter = TokenLabelFilter()
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.addFilter(token_filter)
|
||||
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, settings.log_level.upper()),
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
handlers=[logging.StreamHandler(sys.stdout)],
|
||||
format="%(asctime)s - %(name)s - [%(token_label)s] - %(levelname)s - %(message)s",
|
||||
handlers=[handler],
|
||||
)
|
||||
|
||||
|
||||
@@ -34,7 +48,10 @@ async def lifespan(app: FastAPI):
|
||||
setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info(f"Media Server starting on {settings.host}:{settings.port}")
|
||||
logger.info(f"API Token: {settings.api_token[:8]}...")
|
||||
|
||||
# Log all configured tokens
|
||||
for label, token in settings.api_tokens.items():
|
||||
logger.info(f"API Token [{label}]: {token[:8]}...")
|
||||
|
||||
# Start WebSocket status monitor
|
||||
controller = get_media_controller()
|
||||
@@ -66,6 +83,31 @@ def create_app() -> FastAPI:
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Add token logging middleware
|
||||
@app.middleware("http")
|
||||
async def token_logging_middleware(request: Request, call_next):
|
||||
"""Extract token label and set in context for logging."""
|
||||
token_label = "unknown"
|
||||
|
||||
# Try Authorization header
|
||||
auth_header = request.headers.get("authorization", "")
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:]
|
||||
label = get_token_label(token)
|
||||
if label:
|
||||
token_label = label
|
||||
|
||||
# Try query parameter (for artwork endpoint)
|
||||
elif "token" in request.query_params:
|
||||
token = request.query_params["token"]
|
||||
label = get_token_label(token)
|
||||
if label:
|
||||
token_label = label
|
||||
|
||||
token_label_var.set(token_label)
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
# Register routers
|
||||
app.include_router(audio_router)
|
||||
app.include_router(health_router)
|
||||
@@ -122,8 +164,10 @@ def main():
|
||||
return
|
||||
|
||||
if args.show_token:
|
||||
print(f"API Token: {settings.api_token}")
|
||||
print(f"Config directory: {get_config_dir()}")
|
||||
print(f"\nAPI Tokens:")
|
||||
for label, token in settings.api_tokens.items():
|
||||
print(f" {label:20} {token}")
|
||||
return
|
||||
|
||||
uvicorn.run(
|
||||
|
||||
Reference in New Issue
Block a user