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.
Files changed (104) hide show
  1. {mdb_engine-0.4.9/mdb_engine.egg-info → mdb_engine-0.4.11}/PKG-INFO +1 -1
  2. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/__init__.py +7 -5
  3. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/csrf.py +8 -3
  4. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/engine.py +215 -32
  5. {mdb_engine-0.4.9 → mdb_engine-0.4.11/mdb_engine.egg-info}/PKG-INFO +1 -1
  6. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/pyproject.toml +1 -1
  7. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/setup.py +1 -1
  8. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/LICENSE +0 -0
  9. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/MANIFEST.in +0 -0
  10. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/README.md +0 -0
  11. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/README.md +0 -0
  12. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/ARCHITECTURE.md +0 -0
  13. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/README.md +0 -0
  14. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/__init__.py +0 -0
  15. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/audit.py +0 -0
  16. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/base.py +0 -0
  17. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/casbin_factory.py +0 -0
  18. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/casbin_models.py +0 -0
  19. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/config_defaults.py +0 -0
  20. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/config_helpers.py +0 -0
  21. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/cookie_utils.py +0 -0
  22. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/decorators.py +0 -0
  23. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/dependencies.py +0 -0
  24. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/helpers.py +0 -0
  25. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/integration.py +0 -0
  26. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/jwt.py +0 -0
  27. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/middleware.py +0 -0
  28. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/oso_factory.py +0 -0
  29. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/provider.py +0 -0
  30. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/rate_limiter.py +0 -0
  31. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/restrictions.py +0 -0
  32. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/session_manager.py +0 -0
  33. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/shared_middleware.py +0 -0
  34. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/shared_users.py +0 -0
  35. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/token_lifecycle.py +0 -0
  36. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/token_store.py +0 -0
  37. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/users.py +0 -0
  38. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/auth/utils.py +0 -0
  39. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/__init__.py +0 -0
  40. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/__init__.py +0 -0
  41. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/generate.py +0 -0
  42. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/migrate.py +0 -0
  43. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/show.py +0 -0
  44. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/commands/validate.py +0 -0
  45. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/main.py +0 -0
  46. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/cli/utils.py +0 -0
  47. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/config.py +0 -0
  48. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/constants.py +0 -0
  49. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/README.md +0 -0
  50. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/__init__.py +0 -0
  51. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/app_registration.py +0 -0
  52. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/app_secrets.py +0 -0
  53. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/connection.py +0 -0
  54. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/encryption.py +0 -0
  55. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/index_management.py +0 -0
  56. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/manifest.py +0 -0
  57. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/ray_integration.py +0 -0
  58. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/seeding.py +0 -0
  59. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/service_initialization.py +0 -0
  60. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/core/types.py +0 -0
  61. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/README.md +0 -0
  62. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/__init__.py +0 -0
  63. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/abstraction.py +0 -0
  64. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/connection.py +0 -0
  65. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/query_validator.py +0 -0
  66. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/resource_limiter.py +0 -0
  67. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/database/scoped_wrapper.py +0 -0
  68. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/dependencies.py +0 -0
  69. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/__init__.py +0 -0
  70. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/container.py +0 -0
  71. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/providers.py +0 -0
  72. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/di/scopes.py +0 -0
  73. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/README.md +0 -0
  74. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/__init__.py +0 -0
  75. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/dependencies.py +0 -0
  76. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/embeddings/service.py +0 -0
  77. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/exceptions.py +0 -0
  78. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/README.md +0 -0
  79. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/__init__.py +0 -0
  80. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/helpers.py +0 -0
  81. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/indexes/manager.py +0 -0
  82. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/README.md +0 -0
  83. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/__init__.py +0 -0
  84. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/memory/service.py +0 -0
  85. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/README.md +0 -0
  86. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/__init__.py +0 -0
  87. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/health.py +0 -0
  88. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/logging.py +0 -0
  89. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/observability/metrics.py +0 -0
  90. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/__init__.py +0 -0
  91. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/base.py +0 -0
  92. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/mongo.py +0 -0
  93. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/repositories/unit_of_work.py +0 -0
  94. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/README.md +0 -0
  95. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/__init__.py +0 -0
  96. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/routing/websockets.py +0 -0
  97. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/utils/__init__.py +0 -0
  98. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine/utils/mongo.py +0 -0
  99. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/SOURCES.txt +0 -0
  100. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/dependency_links.txt +0 -0
  101. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/entry_points.txt +0 -0
  102. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/requires.txt +0 -0
  103. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/mdb_engine.egg-info/top_level.txt +0 -0
  104. {mdb_engine-0.4.9 → mdb_engine-0.4.11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdb-engine
3
- Version: 0.4.9
3
+ Version: 0.4.11
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
6
  Author: Fabian Valle
@@ -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.9" # Fix: WebSocket + CSRF + Multi-App architecture
86
- # - CSRF middleware now added to parent app when child apps use shared auth
87
- # - CORS config properly merged from child apps to parent app
88
- # - WebSocket origin validation uses parent app's CORS config
89
- # - Comprehensive integration tests added
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
- # Check current app's CORS config (parent app for WebSocket routes in multi-app)
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
- pass
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
- merged_origins = list(set(parent_origins + child_origins))
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 if merged_origins else ["*"],
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.debug(f"✅ Merged CORS config from child app '{slug}' to parent app")
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
- # Create WebSocket handler
2263
- handler = create_websocket_endpoint(
2264
- app_slug=slug,
2265
- path=ws_path,
2266
- endpoint_name=endpoint_name,
2267
- handler=None,
2268
- require_auth=require_auth,
2269
- ping_interval=ping_interval,
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
- # Register on parent app with full path
2273
- ws_router = APIRouter()
2274
- ws_router.websocket(full_ws_path)(handler)
2275
- parent_app.include_router(ws_router)
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"✅ Registered WebSocket route '{full_ws_path}' "
2279
- f"for mounted app '{slug}' (mounted at '{path_prefix}')"
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
- # (Individual apps can add their own CORS, but parent-level is useful)
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 fastapi.middleware.cors import CORSMiddleware
2722
-
2723
- # Use CORS config from parent app state (set above)
2724
- cors_origins = parent_app.state.cors_config.get("allow_origins", ["*"])
2725
- cors_credentials = parent_app.state.cors_config.get("allow_credentials", True)
2726
-
2727
- parent_app.add_middleware(
2728
- CORSMiddleware,
2729
- allow_origins=cors_origins,
2730
- allow_credentials=cors_credentials,
2731
- allow_methods=["*"],
2732
- allow_headers=["*"],
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdb-engine
3
- Version: 0.4.9
3
+ Version: 0.4.11
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
6
  Author: Fabian Valle
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mdb-engine"
7
- version = "0.4.9"
7
+ version = "0.4.11"
8
8
  description = "MongoDB Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -14,7 +14,7 @@ if readme_file.exists():
14
14
 
15
15
  setup(
16
16
  name="mdb-engine",
17
- version="0.4.8",
17
+ version="0.4.11",
18
18
  description="MongoDB Engine",
19
19
  long_description=long_description,
20
20
  long_description_content_type="text/markdown",
File without changes
File without changes
File without changes
File without changes