From 1f384d1d03221c3dca3bf7d270470c0e54339daf Mon Sep 17 00:00:00 2001 From: kimi Date: Sat, 21 Mar 2026 10:55:53 -0400 Subject: [PATCH] feat: Add CORS config for Matrix frontend origin - Add matrix_frontend_url setting to config.py (default: empty/disabled) - Update _get_cors_origins() to include matrix_frontend_url when set - Add allow_origin_regex for Tailscale IPs (100.x.x.x pattern) - Existing CORS behavior unchanged when matrix_frontend_url is empty Fixes #679 --- src/config.py | 6 ++++++ src/dashboard/app.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/config.py b/src/config.py index b959a4e8..efb53c42 100644 --- a/src/config.py +++ b/src/config.py @@ -149,6 +149,12 @@ class Settings(BaseSettings): "http://127.0.0.1:8000", ] + # ── Matrix Frontend Integration ──────────────────────────────────────── + # URL of the Matrix frontend (Replit/Tailscale) for CORS. + # When set, this origin is added to CORS allowed_origins. + # Example: "http://100.124.176.28:8080" or "https://alexanderwhitestone.com" + matrix_frontend_url: str = "" # Empty = disabled + # Trusted hosts for the Host header check (TrustedHostMiddleware). # Set TRUSTED_HOSTS as a comma-separated list. Wildcards supported (e.g. "*.ts.net"). # Defaults include localhost + Tailscale MagicDNS. Add your Tailscale IP if needed. diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 868a5b61..3bf6ac3f 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -10,6 +10,7 @@ Key improvements: import asyncio import json import logging +import re from contextlib import asynccontextmanager from pathlib import Path @@ -520,17 +521,40 @@ app = FastAPI( def _get_cors_origins() -> list[str]: - """Get CORS origins from settings, rejecting wildcards in production.""" - origins = settings.cors_origins + """Get CORS origins from settings, rejecting wildcards in production. + + Adds matrix_frontend_url when configured. Always allows Tailscale IPs + (100.x.x.x range) for development convenience. + """ + origins = list(settings.cors_origins) + + # Strip wildcards in production (security) if "*" in origins and not settings.debug: logger.warning( "Wildcard '*' in CORS_ORIGINS stripped in production — " "set explicit origins via CORS_ORIGINS env var" ) origins = [o for o in origins if o != "*"] + + # Add Matrix frontend URL if configured + if settings.matrix_frontend_url: + url = settings.matrix_frontend_url.strip() + if url and url not in origins: + origins.append(url) + logger.debug("Added Matrix frontend to CORS: %s", url) + return origins +# Pattern to match Tailscale IPs (100.x.x.x) for CORS origin regex +_TAILSCALE_IP_PATTERN = re.compile(r"^https?://100\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?$") + + +def _is_tailscale_origin(origin: str) -> bool: + """Check if origin is a Tailscale IP (100.x.x.x range).""" + return bool(_TAILSCALE_IP_PATTERN.match(origin)) + + # Add dedicated middleware in correct order # 1. Logging (outermost to capture everything) app.add_middleware(RequestLoggingMiddleware, skip_paths=["/health"]) @@ -552,6 +576,7 @@ app.add_middleware( app.add_middleware( CORSMiddleware, allow_origins=_get_cors_origins(), + allow_origin_regex=r"https?://100\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?", allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["Content-Type", "Authorization"], -- 2.43.0