mdb-engine 0.4.9__tar.gz → 0.4.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mdb_engine-0.4.9/mdb_engine.egg-info → mdb_engine-0.4.11}/PKG-INFO +1 -1
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/__init__.py +7 -5
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/csrf.py +8 -3
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/engine.py +215 -32
- {mdb_engine-0.4.9 → mdb_engine-0.4.11/mdb_engine.egg-info}/PKG-INFO +1 -1
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/pyproject.toml +1 -1
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/setup.py +1 -1
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/LICENSE +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/MANIFEST.in +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/ARCHITECTURE.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/audit.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/base.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/casbin_factory.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/casbin_models.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/config_defaults.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/config_helpers.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/cookie_utils.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/decorators.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/dependencies.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/helpers.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/integration.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/jwt.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/middleware.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/oso_factory.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/provider.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/rate_limiter.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/restrictions.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/session_manager.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/shared_middleware.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/shared_users.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/token_lifecycle.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/token_store.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/users.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/utils.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/generate.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/migrate.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/show.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/validate.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/main.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/utils.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/config.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/constants.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/app_registration.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/app_secrets.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/connection.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/encryption.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/index_management.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/manifest.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/ray_integration.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/seeding.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/service_initialization.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/types.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/abstraction.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/connection.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/query_validator.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/resource_limiter.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/scoped_wrapper.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/dependencies.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/container.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/providers.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/scopes.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/dependencies.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/service.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/exceptions.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/helpers.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/manager.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/service.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/health.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/logging.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/metrics.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/base.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/mongo.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/unit_of_work.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/README.md +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/websockets.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/utils/__init__.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/utils/mongo.py +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/SOURCES.txt +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/dependency_links.txt +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/entry_points.txt +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/requires.txt +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/top_level.txt +0 -0
- {mdb_engine-0.4.9 → mdb_engine-0.4.11}/setup.cfg +0 -0
|
@@ -82,11 +82,13 @@ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
|
|
|
82
82
|
from .utils import clean_mongo_doc, clean_mongo_docs
|
|
83
83
|
|
|
84
84
|
__version__ = (
|
|
85
|
-
"0.4.
|
|
86
|
-
# -
|
|
87
|
-
# -
|
|
88
|
-
# -
|
|
89
|
-
# -
|
|
85
|
+
"0.4.11" # Automatic WebSocket support improvements
|
|
86
|
+
# - Improved CORS config merging with proper wildcard handling
|
|
87
|
+
# - WebSocket route verification after registration
|
|
88
|
+
# - Startup verification logging for CORS config and WebSocket routes
|
|
89
|
+
# - Enhanced CSRF middleware error messages with path and CORS status
|
|
90
|
+
# - Better logging throughout WebSocket registration process
|
|
91
|
+
# - All WebSocket multi-app SSO features now work automatically
|
|
90
92
|
)
|
|
91
93
|
|
|
92
94
|
__all__ = [
|
|
@@ -210,14 +210,15 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|
|
210
210
|
are registered on parent app), then falls back to request host.
|
|
211
211
|
"""
|
|
212
212
|
try:
|
|
213
|
-
#
|
|
213
|
+
# For WebSocket routes on parent app, request.app is parent app
|
|
214
|
+
# Parent app has merged CORS config from all child apps
|
|
214
215
|
cors_config = getattr(request.app.state, "cors_config", None)
|
|
215
216
|
if cors_config and cors_config.get("allow_origins"):
|
|
216
217
|
origins = cors_config["allow_origins"]
|
|
217
218
|
if origins:
|
|
218
219
|
return origins if isinstance(origins, list) else [origins]
|
|
219
|
-
except (AttributeError, TypeError, KeyError):
|
|
220
|
-
|
|
220
|
+
except (AttributeError, TypeError, KeyError) as e:
|
|
221
|
+
logger.debug(f"Could not read CORS config from app.state: {e}")
|
|
221
222
|
|
|
222
223
|
# Fallback: Check if this is a multi-app setup and try to find mounted app's CORS config
|
|
223
224
|
try:
|
|
@@ -249,6 +250,7 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|
|
249
250
|
origin = f"{scheme}://{host}"
|
|
250
251
|
return [origin]
|
|
251
252
|
except (AttributeError, TypeError):
|
|
253
|
+
# Return empty list if we can't determine origin (will reject)
|
|
252
254
|
return []
|
|
253
255
|
|
|
254
256
|
def _validate_websocket_origin(self, request: Request) -> bool:
|
|
@@ -275,9 +277,12 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|
|
275
277
|
if origin == allowed or origin.rstrip("/") == allowed.rstrip("/"):
|
|
276
278
|
return True
|
|
277
279
|
|
|
280
|
+
cors_config = getattr(request.app.state, "cors_config", None)
|
|
281
|
+
cors_enabled = cors_config.get("enabled", False) if cors_config else False
|
|
278
282
|
logger.warning(
|
|
279
283
|
f"WebSocket upgrade rejected - invalid Origin: {origin} "
|
|
280
284
|
f"(allowed: {allowed_origins}, app: {getattr(request.app, 'title', 'unknown')}, "
|
|
285
|
+
f"path: {request.url.path}, CORS enabled: {cors_enabled}, "
|
|
281
286
|
f"has_cors_config: {hasattr(request.app.state, 'cors_config')})"
|
|
282
287
|
)
|
|
283
288
|
return False
|
|
@@ -2212,16 +2212,42 @@ class MongoDBEngine:
|
|
|
2212
2212
|
# Merge allow_origins lists
|
|
2213
2213
|
child_origins = child_cors.get("allow_origins", [])
|
|
2214
2214
|
parent_origins = parent_cors.get("allow_origins", [])
|
|
2215
|
-
|
|
2215
|
+
|
|
2216
|
+
# CRITICAL: Handle wildcard origins correctly
|
|
2217
|
+
# If any child or parent has wildcard, merged config gets wildcard
|
|
2218
|
+
if "*" in child_origins:
|
|
2219
|
+
merged_origins = ["*"] # Wildcard takes precedence
|
|
2220
|
+
elif "*" in parent_origins:
|
|
2221
|
+
merged_origins = ["*"] # Keep wildcard if already set
|
|
2222
|
+
else:
|
|
2223
|
+
# Merge unique origins
|
|
2224
|
+
merged_origins = list(set(parent_origins + child_origins))
|
|
2225
|
+
if not merged_origins:
|
|
2226
|
+
merged_origins = ["*"] # Default to wildcard if empty
|
|
2227
|
+
|
|
2228
|
+
# CRITICAL: If ANY child app requires credentials, parent must allow them
|
|
2229
|
+
# This is essential for SSO cookie-based authentication
|
|
2230
|
+
child_requires_credentials = child_cors.get("allow_credentials", False)
|
|
2231
|
+
parent_allows_credentials = parent_cors.get("allow_credentials", False)
|
|
2232
|
+
merged_allow_credentials = (
|
|
2233
|
+
child_requires_credentials or parent_allows_credentials
|
|
2234
|
+
)
|
|
2235
|
+
|
|
2216
2236
|
parent_app.state.cors_config = {
|
|
2217
2237
|
**parent_cors,
|
|
2218
2238
|
**child_cors,
|
|
2219
|
-
"allow_origins": merged_origins
|
|
2239
|
+
"allow_origins": merged_origins,
|
|
2240
|
+
# If ANY child requires credentials, parent gets True (for SSO)
|
|
2241
|
+
"allow_credentials": merged_allow_credentials,
|
|
2220
2242
|
}
|
|
2221
2243
|
else:
|
|
2222
2244
|
# Parent has no CORS config, use child's
|
|
2223
2245
|
parent_app.state.cors_config = child_cors
|
|
2224
|
-
logger.
|
|
2246
|
+
logger.info(
|
|
2247
|
+
f"✅ Merged CORS config from '{slug}': "
|
|
2248
|
+
f"origins={parent_app.state.cors_config.get('allow_origins')}, "
|
|
2249
|
+
f"credentials={parent_app.state.cors_config.get('allow_credentials')}"
|
|
2250
|
+
)
|
|
2225
2251
|
|
|
2226
2252
|
async def _register_websocket_routes(
|
|
2227
2253
|
parent_app: "FastAPI",
|
|
@@ -2232,6 +2258,7 @@ class MongoDBEngine:
|
|
|
2232
2258
|
"""Register WebSocket routes on parent app for a child app."""
|
|
2233
2259
|
websockets_config = child_manifest.get("websockets")
|
|
2234
2260
|
if not websockets_config:
|
|
2261
|
+
logger.debug(f"No WebSocket configuration found for app '{slug}'")
|
|
2235
2262
|
return
|
|
2236
2263
|
|
|
2237
2264
|
try:
|
|
@@ -2239,6 +2266,9 @@ class MongoDBEngine:
|
|
|
2239
2266
|
|
|
2240
2267
|
from ..routing.websockets import create_websocket_endpoint
|
|
2241
2268
|
|
|
2269
|
+
registered_count = 0
|
|
2270
|
+
failed_count = 0
|
|
2271
|
+
|
|
2242
2272
|
for endpoint_name, endpoint_config in websockets_config.items():
|
|
2243
2273
|
ws_path = endpoint_config.get("path", f"/{endpoint_name}")
|
|
2244
2274
|
# Combine mount prefix with WebSocket path
|
|
@@ -2259,24 +2289,65 @@ class MongoDBEngine:
|
|
|
2259
2289
|
|
|
2260
2290
|
ping_interval = endpoint_config.get("ping_interval", 30)
|
|
2261
2291
|
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2292
|
+
try:
|
|
2293
|
+
# Create WebSocket handler
|
|
2294
|
+
handler = create_websocket_endpoint(
|
|
2295
|
+
app_slug=slug,
|
|
2296
|
+
path=ws_path,
|
|
2297
|
+
endpoint_name=endpoint_name,
|
|
2298
|
+
handler=None,
|
|
2299
|
+
require_auth=require_auth,
|
|
2300
|
+
ping_interval=ping_interval,
|
|
2301
|
+
)
|
|
2302
|
+
|
|
2303
|
+
# Register on parent app with full path
|
|
2304
|
+
ws_router = APIRouter()
|
|
2305
|
+
ws_router.websocket(full_ws_path)(handler)
|
|
2306
|
+
parent_app.include_router(ws_router)
|
|
2271
2307
|
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2308
|
+
logger.info(
|
|
2309
|
+
f"✅ Registered WebSocket route '{full_ws_path}' "
|
|
2310
|
+
f"for mounted app '{slug}' (mounted at '{path_prefix}', "
|
|
2311
|
+
f"auth: {require_auth}, ping: {ping_interval}s)"
|
|
2312
|
+
)
|
|
2276
2313
|
|
|
2314
|
+
# Verify route was actually registered
|
|
2315
|
+
registered_routes = [
|
|
2316
|
+
r
|
|
2317
|
+
for r in parent_app.routes
|
|
2318
|
+
if hasattr(r, "path") and full_ws_path in str(getattr(r, "path", ""))
|
|
2319
|
+
]
|
|
2320
|
+
if registered_routes:
|
|
2321
|
+
registered_count += 1
|
|
2322
|
+
logger.debug(
|
|
2323
|
+
f"✅ Verified WebSocket route '{full_ws_path}' "
|
|
2324
|
+
f"registered for '{slug}'"
|
|
2325
|
+
)
|
|
2326
|
+
else:
|
|
2327
|
+
failed_count += 1
|
|
2328
|
+
logger.warning(
|
|
2329
|
+
f"⚠️ WebSocket route '{full_ws_path}' not found after registration "
|
|
2330
|
+
f"for '{slug}' - route may not be accessible"
|
|
2331
|
+
)
|
|
2332
|
+
except (ValueError, TypeError, AttributeError, RuntimeError) as e:
|
|
2333
|
+
failed_count += 1
|
|
2334
|
+
logger.error(
|
|
2335
|
+
f"Failed to register WebSocket route '{full_ws_path}' "
|
|
2336
|
+
f"for mounted app '{slug}': {e}",
|
|
2337
|
+
exc_info=True,
|
|
2338
|
+
)
|
|
2339
|
+
|
|
2340
|
+
# Summary logging
|
|
2341
|
+
total_routes = len(websockets_config)
|
|
2342
|
+
if registered_count > 0:
|
|
2277
2343
|
logger.info(
|
|
2278
|
-
f"✅
|
|
2279
|
-
f"
|
|
2344
|
+
f"✅ WebSocket registration summary for '{slug}': "
|
|
2345
|
+
f"{registered_count}/{total_routes} routes registered successfully"
|
|
2346
|
+
)
|
|
2347
|
+
if failed_count > 0:
|
|
2348
|
+
logger.warning(
|
|
2349
|
+
f"⚠️ WebSocket registration issues for '{slug}': "
|
|
2350
|
+
f"{failed_count}/{total_routes} routes failed to register"
|
|
2280
2351
|
)
|
|
2281
2352
|
except ImportError:
|
|
2282
2353
|
logger.warning(
|
|
@@ -2654,6 +2725,38 @@ class MongoDBEngine:
|
|
|
2654
2725
|
# This ensures the state reflects the final mounted_apps list
|
|
2655
2726
|
app.state.mounted_apps = mounted_apps
|
|
2656
2727
|
|
|
2728
|
+
# VERIFICATION: Log final configuration state
|
|
2729
|
+
logger.info("=" * 60)
|
|
2730
|
+
logger.info("MDB-Engine Multi-App Configuration Verification")
|
|
2731
|
+
logger.info("=" * 60)
|
|
2732
|
+
|
|
2733
|
+
# Verify CORS config
|
|
2734
|
+
cors_config = getattr(app.state, "cors_config", None)
|
|
2735
|
+
if cors_config:
|
|
2736
|
+
logger.info(
|
|
2737
|
+
f"✅ CORS Config: enabled={cors_config.get('enabled')}, "
|
|
2738
|
+
f"origins={cors_config.get('allow_origins')}, "
|
|
2739
|
+
f"credentials={cors_config.get('allow_credentials')}"
|
|
2740
|
+
)
|
|
2741
|
+
else:
|
|
2742
|
+
logger.warning("⚠️ No CORS config found on parent app")
|
|
2743
|
+
|
|
2744
|
+
# Verify WebSocket routes
|
|
2745
|
+
ws_routes = [
|
|
2746
|
+
r for r in app.routes if hasattr(r, "path") and "/ws" in str(getattr(r, "path", ""))
|
|
2747
|
+
]
|
|
2748
|
+
if ws_routes:
|
|
2749
|
+
logger.info(f"✅ Found {len(ws_routes)} WebSocket route(s):")
|
|
2750
|
+
for route in ws_routes:
|
|
2751
|
+
route_path = getattr(route, "path", "unknown")
|
|
2752
|
+
logger.info(f" 🔌 {route_path}")
|
|
2753
|
+
else:
|
|
2754
|
+
logger.warning(
|
|
2755
|
+
"⚠️ No WebSocket routes found - check manifest.json websockets config"
|
|
2756
|
+
)
|
|
2757
|
+
|
|
2758
|
+
logger.info("=" * 60)
|
|
2759
|
+
|
|
2657
2760
|
yield
|
|
2658
2761
|
|
|
2659
2762
|
# Shutdown is handled by parent app
|
|
@@ -2670,11 +2773,14 @@ class MongoDBEngine:
|
|
|
2670
2773
|
# Set default CORS config on parent app for WebSocket origin validation
|
|
2671
2774
|
# This ensures CSRF middleware can validate WebSocket origins even if child apps
|
|
2672
2775
|
# don't configure CORS
|
|
2776
|
+
# NOTE: allow_credentials defaults to False here, but will be set to True
|
|
2777
|
+
# during merge if any child app requires it (essential for SSO cookie-based auth)
|
|
2673
2778
|
from ..auth.config_helpers import CORS_DEFAULTS
|
|
2674
2779
|
|
|
2675
2780
|
parent_app.state.cors_config = CORS_DEFAULTS.copy()
|
|
2676
2781
|
parent_app.state.cors_config["enabled"] = True
|
|
2677
2782
|
parent_app.state.cors_config["allow_origins"] = ["*"] # Default to allow all for WebSocket
|
|
2783
|
+
# Keep allow_credentials as False initially - will be merged from child apps
|
|
2678
2784
|
logger.debug("Set default CORS config on parent app for WebSocket origin validation")
|
|
2679
2785
|
|
|
2680
2786
|
# Store app reference in engine for get_mounted_apps()
|
|
@@ -2716,22 +2822,99 @@ class MongoDBEngine:
|
|
|
2716
2822
|
logger.info("CSRFMiddleware added to parent app for WebSocket origin validation")
|
|
2717
2823
|
|
|
2718
2824
|
# Add shared CORS middleware if configured
|
|
2719
|
-
#
|
|
2825
|
+
# NOTE: We create a dynamic CORS middleware that reads from app.state.cors_config
|
|
2826
|
+
# This allows the config to be updated after child apps are mounted and merged
|
|
2720
2827
|
try:
|
|
2721
|
-
from
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2828
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
2829
|
+
from starlette.requests import Request
|
|
2830
|
+
from starlette.responses import Response
|
|
2831
|
+
|
|
2832
|
+
class DynamicCORSMiddleware(BaseHTTPMiddleware):
|
|
2833
|
+
"""
|
|
2834
|
+
Dynamic CORS middleware that reads config from app.state.cors_config.
|
|
2835
|
+
|
|
2836
|
+
This allows CORS config to be updated after child apps are mounted
|
|
2837
|
+
and their configs are merged, which is essential for SSO multi-app
|
|
2838
|
+
setups where allow_credentials must be True for cookie-based auth.
|
|
2839
|
+
"""
|
|
2840
|
+
|
|
2841
|
+
async def dispatch(self, request: Request, call_next):
|
|
2842
|
+
# Read CORS config from app.state (may have been merged from child apps)
|
|
2843
|
+
cors_config = getattr(request.app.state, "cors_config", {})
|
|
2844
|
+
|
|
2845
|
+
if not cors_config.get("enabled", False):
|
|
2846
|
+
# CORS not enabled, pass through
|
|
2847
|
+
return await call_next(request)
|
|
2848
|
+
|
|
2849
|
+
# Handle preflight OPTIONS request
|
|
2850
|
+
if request.method == "OPTIONS":
|
|
2851
|
+
origin = request.headers.get("origin")
|
|
2852
|
+
allowed_origins = cors_config.get("allow_origins", ["*"])
|
|
2853
|
+
allow_credentials = cors_config.get("allow_credentials", False)
|
|
2854
|
+
|
|
2855
|
+
# Check if origin is allowed
|
|
2856
|
+
origin_allowed = False
|
|
2857
|
+
if "*" in allowed_origins:
|
|
2858
|
+
origin_allowed = True
|
|
2859
|
+
elif origin in allowed_origins:
|
|
2860
|
+
origin_allowed = True
|
|
2861
|
+
|
|
2862
|
+
if origin_allowed:
|
|
2863
|
+
headers = {
|
|
2864
|
+
"Access-Control-Allow-Methods": ", ".join(
|
|
2865
|
+
cors_config.get("allow_methods", ["*"])
|
|
2866
|
+
),
|
|
2867
|
+
"Access-Control-Allow-Headers": ", ".join(
|
|
2868
|
+
cors_config.get("allow_headers", ["*"])
|
|
2869
|
+
),
|
|
2870
|
+
"Access-Control-Max-Age": str(cors_config.get("max_age", 3600)),
|
|
2871
|
+
}
|
|
2872
|
+
if allow_credentials:
|
|
2873
|
+
headers["Access-Control-Allow-Credentials"] = "true"
|
|
2874
|
+
if origin:
|
|
2875
|
+
headers["Access-Control-Allow-Origin"] = origin
|
|
2876
|
+
|
|
2877
|
+
expose_headers = cors_config.get("expose_headers", [])
|
|
2878
|
+
if expose_headers:
|
|
2879
|
+
headers["Access-Control-Expose-Headers"] = ", ".join(expose_headers)
|
|
2880
|
+
|
|
2881
|
+
return Response(status_code=200, headers=headers)
|
|
2882
|
+
else:
|
|
2883
|
+
return Response(status_code=403)
|
|
2884
|
+
|
|
2885
|
+
# Handle actual request
|
|
2886
|
+
response = await call_next(request)
|
|
2887
|
+
|
|
2888
|
+
# Add CORS headers to response
|
|
2889
|
+
origin = request.headers.get("origin")
|
|
2890
|
+
allowed_origins = cors_config.get("allow_origins", ["*"])
|
|
2891
|
+
allow_credentials = cors_config.get("allow_credentials", False)
|
|
2892
|
+
|
|
2893
|
+
# Check if origin is allowed
|
|
2894
|
+
origin_allowed = False
|
|
2895
|
+
if "*" in allowed_origins:
|
|
2896
|
+
origin_allowed = True
|
|
2897
|
+
elif origin and origin in allowed_origins:
|
|
2898
|
+
origin_allowed = True
|
|
2899
|
+
|
|
2900
|
+
if origin_allowed:
|
|
2901
|
+
if origin:
|
|
2902
|
+
response.headers["Access-Control-Allow-Origin"] = origin
|
|
2903
|
+
if allow_credentials:
|
|
2904
|
+
response.headers["Access-Control-Allow-Credentials"] = "true"
|
|
2905
|
+
|
|
2906
|
+
expose_headers = cors_config.get("expose_headers", [])
|
|
2907
|
+
if expose_headers:
|
|
2908
|
+
response.headers["Access-Control-Expose-Headers"] = ", ".join(
|
|
2909
|
+
expose_headers
|
|
2910
|
+
)
|
|
2911
|
+
|
|
2912
|
+
return response
|
|
2913
|
+
|
|
2914
|
+
parent_app.add_middleware(DynamicCORSMiddleware)
|
|
2915
|
+
logger.debug(
|
|
2916
|
+
"Dynamic CORS middleware added for parent app (reads from app.state.cors_config)"
|
|
2733
2917
|
)
|
|
2734
|
-
logger.debug("CORS middleware added for parent app")
|
|
2735
2918
|
except ImportError:
|
|
2736
2919
|
logger.warning("CORS middleware not available")
|
|
2737
2920
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|