mdb-engine 0.4.10__tar.gz → 0.4.12__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.10/mdb_engine.egg-info → mdb_engine-0.4.12}/PKG-INFO +1 -1
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/__init__.py +5 -5
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/csrf.py +8 -3
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/engine.py +137 -25
- {mdb_engine-0.4.10 → mdb_engine-0.4.12/mdb_engine.egg-info}/PKG-INFO +1 -1
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/pyproject.toml +1 -1
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/setup.py +1 -1
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/LICENSE +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/MANIFEST.in +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/ARCHITECTURE.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/audit.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/base.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/casbin_factory.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/casbin_models.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/config_defaults.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/config_helpers.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/cookie_utils.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/decorators.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/dependencies.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/helpers.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/integration.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/jwt.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/middleware.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/oso_factory.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/provider.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/rate_limiter.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/restrictions.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/session_manager.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/shared_middleware.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/shared_users.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/token_lifecycle.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/token_store.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/users.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/auth/utils.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/commands/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/commands/generate.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/commands/migrate.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/commands/show.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/commands/validate.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/main.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/cli/utils.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/config.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/constants.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/app_registration.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/app_secrets.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/connection.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/encryption.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/index_management.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/manifest.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/ray_integration.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/seeding.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/service_initialization.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/core/types.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/abstraction.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/connection.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/query_validator.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/resource_limiter.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/database/scoped_wrapper.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/dependencies.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/di/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/di/container.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/di/providers.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/di/scopes.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/embeddings/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/embeddings/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/embeddings/dependencies.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/embeddings/service.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/exceptions.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/indexes/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/indexes/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/indexes/helpers.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/indexes/manager.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/memory/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/memory/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/memory/service.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/observability/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/observability/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/observability/health.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/observability/logging.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/observability/metrics.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/repositories/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/repositories/base.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/repositories/mongo.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/repositories/unit_of_work.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/routing/README.md +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/routing/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/routing/websockets.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/utils/__init__.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine/utils/mongo.py +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine.egg-info/SOURCES.txt +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine.egg-info/dependency_links.txt +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine.egg-info/entry_points.txt +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine.egg-info/requires.txt +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/mdb_engine.egg-info/top_level.txt +0 -0
- {mdb_engine-0.4.10 → mdb_engine-0.4.12}/setup.cfg +0 -0
|
@@ -82,11 +82,11 @@ 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.12" # Fix CSRF middleware rejecting WebSocket connections
|
|
86
|
+
# - Skip CSRF middleware on child apps in multi-app setups (parent handles it)
|
|
87
|
+
# - Merge child app public routes into parent CSRF exempt list
|
|
88
|
+
# - WebSocket connections now work correctly in multi-app SSO setups
|
|
89
|
+
# - Security maintained: parent app CSRF middleware protects all routes
|
|
90
90
|
)
|
|
91
91
|
|
|
92
92
|
__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
|
|
@@ -1499,8 +1499,9 @@ class MongoDBEngine:
|
|
|
1499
1499
|
|
|
1500
1500
|
# Add CSRF middleware (after auth - auto-enabled for shared mode)
|
|
1501
1501
|
# CSRF protection is enabled by default for shared auth mode
|
|
1502
|
+
# SKIP for sub-apps in multi-app setups - parent app handles CSRF
|
|
1502
1503
|
csrf_config = auth_config.get("csrf_protection", True if auth_mode == "shared" else False)
|
|
1503
|
-
if csrf_config:
|
|
1504
|
+
if csrf_config and not is_sub_app: # Don't add CSRF to child apps
|
|
1504
1505
|
from ..auth.csrf import create_csrf_middleware
|
|
1505
1506
|
|
|
1506
1507
|
csrf_middleware = create_csrf_middleware(
|
|
@@ -1508,6 +1509,11 @@ class MongoDBEngine:
|
|
|
1508
1509
|
)
|
|
1509
1510
|
app.add_middleware(csrf_middleware)
|
|
1510
1511
|
logger.info(f"CSRFMiddleware added for '{slug}'")
|
|
1512
|
+
elif csrf_config and is_sub_app:
|
|
1513
|
+
logger.debug(
|
|
1514
|
+
f"CSRFMiddleware skipped for child app '{slug}' - "
|
|
1515
|
+
f"parent app handles CSRF protection for WebSocket routes"
|
|
1516
|
+
)
|
|
1511
1517
|
|
|
1512
1518
|
# Add security middleware (HSTS, headers)
|
|
1513
1519
|
security_config = auth_config.get("security", {})
|
|
@@ -2127,17 +2133,33 @@ class MongoDBEngine:
|
|
|
2127
2133
|
"Path prefix validation failed:\n" + "\n".join(f" - {e}" for e in errors)
|
|
2128
2134
|
)
|
|
2129
2135
|
|
|
2130
|
-
# Check if any app uses shared auth
|
|
2136
|
+
# Check if any app uses shared auth and collect public routes for CSRF exemption
|
|
2131
2137
|
has_shared_auth = False
|
|
2138
|
+
all_public_routes = [
|
|
2139
|
+
"/health",
|
|
2140
|
+
"/docs",
|
|
2141
|
+
"/openapi.json",
|
|
2142
|
+
"/_mdb/routes",
|
|
2143
|
+
] # Base exempt routes
|
|
2132
2144
|
for app_config in apps:
|
|
2133
2145
|
try:
|
|
2134
2146
|
manifest_path = app_config["manifest"]
|
|
2147
|
+
path_prefix = app_config.get("path_prefix", f"/{app_config.get('slug')}")
|
|
2135
2148
|
with open(manifest_path) as f:
|
|
2136
2149
|
app_manifest_pre = json.load(f)
|
|
2137
2150
|
auth_config = app_manifest_pre.get("auth", {})
|
|
2138
2151
|
if auth_config.get("mode") == "shared":
|
|
2139
2152
|
has_shared_auth = True
|
|
2140
|
-
|
|
2153
|
+
# Collect public routes with path prefix for CSRF exemption
|
|
2154
|
+
child_public_routes = auth_config.get("public_routes", [])
|
|
2155
|
+
for route in child_public_routes:
|
|
2156
|
+
# Add path prefix to make route absolute on parent app
|
|
2157
|
+
if route.startswith("/"):
|
|
2158
|
+
prefixed_route = f"{path_prefix.rstrip('/')}{route}"
|
|
2159
|
+
else:
|
|
2160
|
+
prefixed_route = f"{path_prefix.rstrip('/')}/{route}"
|
|
2161
|
+
if prefixed_route not in all_public_routes:
|
|
2162
|
+
all_public_routes.append(prefixed_route)
|
|
2141
2163
|
except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
|
|
2142
2164
|
logger.warning(f"Could not check auth mode for app '{app_config.get('slug')}': {e}")
|
|
2143
2165
|
|
|
@@ -2212,7 +2234,18 @@ class MongoDBEngine:
|
|
|
2212
2234
|
# Merge allow_origins lists
|
|
2213
2235
|
child_origins = child_cors.get("allow_origins", [])
|
|
2214
2236
|
parent_origins = parent_cors.get("allow_origins", [])
|
|
2215
|
-
|
|
2237
|
+
|
|
2238
|
+
# CRITICAL: Handle wildcard origins correctly
|
|
2239
|
+
# If any child or parent has wildcard, merged config gets wildcard
|
|
2240
|
+
if "*" in child_origins:
|
|
2241
|
+
merged_origins = ["*"] # Wildcard takes precedence
|
|
2242
|
+
elif "*" in parent_origins:
|
|
2243
|
+
merged_origins = ["*"] # Keep wildcard if already set
|
|
2244
|
+
else:
|
|
2245
|
+
# Merge unique origins
|
|
2246
|
+
merged_origins = list(set(parent_origins + child_origins))
|
|
2247
|
+
if not merged_origins:
|
|
2248
|
+
merged_origins = ["*"] # Default to wildcard if empty
|
|
2216
2249
|
|
|
2217
2250
|
# CRITICAL: If ANY child app requires credentials, parent must allow them
|
|
2218
2251
|
# This is essential for SSO cookie-based authentication
|
|
@@ -2225,16 +2258,17 @@ class MongoDBEngine:
|
|
|
2225
2258
|
parent_app.state.cors_config = {
|
|
2226
2259
|
**parent_cors,
|
|
2227
2260
|
**child_cors,
|
|
2228
|
-
"allow_origins": merged_origins
|
|
2261
|
+
"allow_origins": merged_origins,
|
|
2229
2262
|
# If ANY child requires credentials, parent gets True (for SSO)
|
|
2230
2263
|
"allow_credentials": merged_allow_credentials,
|
|
2231
2264
|
}
|
|
2232
2265
|
else:
|
|
2233
2266
|
# Parent has no CORS config, use child's
|
|
2234
2267
|
parent_app.state.cors_config = child_cors
|
|
2235
|
-
logger.
|
|
2236
|
-
f"✅ Merged CORS config from
|
|
2237
|
-
f"
|
|
2268
|
+
logger.info(
|
|
2269
|
+
f"✅ Merged CORS config from '{slug}': "
|
|
2270
|
+
f"origins={parent_app.state.cors_config.get('allow_origins')}, "
|
|
2271
|
+
f"credentials={parent_app.state.cors_config.get('allow_credentials')}"
|
|
2238
2272
|
)
|
|
2239
2273
|
|
|
2240
2274
|
async def _register_websocket_routes(
|
|
@@ -2246,6 +2280,7 @@ class MongoDBEngine:
|
|
|
2246
2280
|
"""Register WebSocket routes on parent app for a child app."""
|
|
2247
2281
|
websockets_config = child_manifest.get("websockets")
|
|
2248
2282
|
if not websockets_config:
|
|
2283
|
+
logger.debug(f"No WebSocket configuration found for app '{slug}'")
|
|
2249
2284
|
return
|
|
2250
2285
|
|
|
2251
2286
|
try:
|
|
@@ -2253,6 +2288,9 @@ class MongoDBEngine:
|
|
|
2253
2288
|
|
|
2254
2289
|
from ..routing.websockets import create_websocket_endpoint
|
|
2255
2290
|
|
|
2291
|
+
registered_count = 0
|
|
2292
|
+
failed_count = 0
|
|
2293
|
+
|
|
2256
2294
|
for endpoint_name, endpoint_config in websockets_config.items():
|
|
2257
2295
|
ws_path = endpoint_config.get("path", f"/{endpoint_name}")
|
|
2258
2296
|
# Combine mount prefix with WebSocket path
|
|
@@ -2273,24 +2311,65 @@ class MongoDBEngine:
|
|
|
2273
2311
|
|
|
2274
2312
|
ping_interval = endpoint_config.get("ping_interval", 30)
|
|
2275
2313
|
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2314
|
+
try:
|
|
2315
|
+
# Create WebSocket handler
|
|
2316
|
+
handler = create_websocket_endpoint(
|
|
2317
|
+
app_slug=slug,
|
|
2318
|
+
path=ws_path,
|
|
2319
|
+
endpoint_name=endpoint_name,
|
|
2320
|
+
handler=None,
|
|
2321
|
+
require_auth=require_auth,
|
|
2322
|
+
ping_interval=ping_interval,
|
|
2323
|
+
)
|
|
2324
|
+
|
|
2325
|
+
# Register on parent app with full path
|
|
2326
|
+
ws_router = APIRouter()
|
|
2327
|
+
ws_router.websocket(full_ws_path)(handler)
|
|
2328
|
+
parent_app.include_router(ws_router)
|
|
2285
2329
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2330
|
+
logger.info(
|
|
2331
|
+
f"✅ Registered WebSocket route '{full_ws_path}' "
|
|
2332
|
+
f"for mounted app '{slug}' (mounted at '{path_prefix}', "
|
|
2333
|
+
f"auth: {require_auth}, ping: {ping_interval}s)"
|
|
2334
|
+
)
|
|
2335
|
+
|
|
2336
|
+
# Verify route was actually registered
|
|
2337
|
+
registered_routes = [
|
|
2338
|
+
r
|
|
2339
|
+
for r in parent_app.routes
|
|
2340
|
+
if hasattr(r, "path") and full_ws_path in str(getattr(r, "path", ""))
|
|
2341
|
+
]
|
|
2342
|
+
if registered_routes:
|
|
2343
|
+
registered_count += 1
|
|
2344
|
+
logger.debug(
|
|
2345
|
+
f"✅ Verified WebSocket route '{full_ws_path}' "
|
|
2346
|
+
f"registered for '{slug}'"
|
|
2347
|
+
)
|
|
2348
|
+
else:
|
|
2349
|
+
failed_count += 1
|
|
2350
|
+
logger.warning(
|
|
2351
|
+
f"⚠️ WebSocket route '{full_ws_path}' not found after registration "
|
|
2352
|
+
f"for '{slug}' - route may not be accessible"
|
|
2353
|
+
)
|
|
2354
|
+
except (ValueError, TypeError, AttributeError, RuntimeError) as e:
|
|
2355
|
+
failed_count += 1
|
|
2356
|
+
logger.error(
|
|
2357
|
+
f"Failed to register WebSocket route '{full_ws_path}' "
|
|
2358
|
+
f"for mounted app '{slug}': {e}",
|
|
2359
|
+
exc_info=True,
|
|
2360
|
+
)
|
|
2290
2361
|
|
|
2362
|
+
# Summary logging
|
|
2363
|
+
total_routes = len(websockets_config)
|
|
2364
|
+
if registered_count > 0:
|
|
2291
2365
|
logger.info(
|
|
2292
|
-
f"✅
|
|
2293
|
-
f"
|
|
2366
|
+
f"✅ WebSocket registration summary for '{slug}': "
|
|
2367
|
+
f"{registered_count}/{total_routes} routes registered successfully"
|
|
2368
|
+
)
|
|
2369
|
+
if failed_count > 0:
|
|
2370
|
+
logger.warning(
|
|
2371
|
+
f"⚠️ WebSocket registration issues for '{slug}': "
|
|
2372
|
+
f"{failed_count}/{total_routes} routes failed to register"
|
|
2294
2373
|
)
|
|
2295
2374
|
except ImportError:
|
|
2296
2375
|
logger.warning(
|
|
@@ -2668,6 +2747,38 @@ class MongoDBEngine:
|
|
|
2668
2747
|
# This ensures the state reflects the final mounted_apps list
|
|
2669
2748
|
app.state.mounted_apps = mounted_apps
|
|
2670
2749
|
|
|
2750
|
+
# VERIFICATION: Log final configuration state
|
|
2751
|
+
logger.info("=" * 60)
|
|
2752
|
+
logger.info("MDB-Engine Multi-App Configuration Verification")
|
|
2753
|
+
logger.info("=" * 60)
|
|
2754
|
+
|
|
2755
|
+
# Verify CORS config
|
|
2756
|
+
cors_config = getattr(app.state, "cors_config", None)
|
|
2757
|
+
if cors_config:
|
|
2758
|
+
logger.info(
|
|
2759
|
+
f"✅ CORS Config: enabled={cors_config.get('enabled')}, "
|
|
2760
|
+
f"origins={cors_config.get('allow_origins')}, "
|
|
2761
|
+
f"credentials={cors_config.get('allow_credentials')}"
|
|
2762
|
+
)
|
|
2763
|
+
else:
|
|
2764
|
+
logger.warning("⚠️ No CORS config found on parent app")
|
|
2765
|
+
|
|
2766
|
+
# Verify WebSocket routes
|
|
2767
|
+
ws_routes = [
|
|
2768
|
+
r for r in app.routes if hasattr(r, "path") and "/ws" in str(getattr(r, "path", ""))
|
|
2769
|
+
]
|
|
2770
|
+
if ws_routes:
|
|
2771
|
+
logger.info(f"✅ Found {len(ws_routes)} WebSocket route(s):")
|
|
2772
|
+
for route in ws_routes:
|
|
2773
|
+
route_path = getattr(route, "path", "unknown")
|
|
2774
|
+
logger.info(f" 🔌 {route_path}")
|
|
2775
|
+
else:
|
|
2776
|
+
logger.warning(
|
|
2777
|
+
"⚠️ No WebSocket routes found - check manifest.json websockets config"
|
|
2778
|
+
)
|
|
2779
|
+
|
|
2780
|
+
logger.info("=" * 60)
|
|
2781
|
+
|
|
2671
2782
|
yield
|
|
2672
2783
|
|
|
2673
2784
|
# Shutdown is handled by parent app
|
|
@@ -2723,10 +2834,11 @@ class MongoDBEngine:
|
|
|
2723
2834
|
from ..auth.csrf import create_csrf_middleware
|
|
2724
2835
|
|
|
2725
2836
|
# Create CSRF middleware with default config (will use parent app's CORS config)
|
|
2726
|
-
# Exempt routes that don't need CSRF (health checks,
|
|
2837
|
+
# Exempt routes that don't need CSRF (health checks, public routes from child apps)
|
|
2838
|
+
# all_public_routes includes base routes + child app public routes with path prefixes
|
|
2727
2839
|
parent_csrf_config = {
|
|
2728
2840
|
"csrf_protection": True,
|
|
2729
|
-
"public_routes":
|
|
2841
|
+
"public_routes": all_public_routes,
|
|
2730
2842
|
}
|
|
2731
2843
|
csrf_middleware = create_csrf_middleware(parent_csrf_config)
|
|
2732
2844
|
parent_app.add_middleware(csrf_middleware)
|
|
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
|