mdb-engine 0.4.5__py3-none-any.whl → 0.4.8__py3-none-any.whl
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/__init__.py +2 -2
- mdb_engine/auth/csrf.py +64 -1
- mdb_engine/core/engine.py +67 -0
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/METADATA +1 -1
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/RECORD +9 -9
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/WHEEL +0 -0
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.4.5.dist-info → mdb_engine-0.4.8.dist-info}/top_level.txt +0 -0
mdb_engine/__init__.py
CHANGED
|
@@ -82,8 +82,8 @@ 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
|
-
#
|
|
85
|
+
"0.4.8" # Fix: WebSocket routes now work correctly with mounted apps
|
|
86
|
+
# WebSocket routes are registered on parent app with full mount path prefix
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
__all__ = [
|
mdb_engine/auth/csrf.py
CHANGED
|
@@ -195,6 +195,62 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|
|
195
195
|
return True
|
|
196
196
|
return False
|
|
197
197
|
|
|
198
|
+
def _is_websocket_upgrade(self, request: Request) -> bool:
|
|
199
|
+
"""Check if request is a WebSocket upgrade request."""
|
|
200
|
+
upgrade_header = request.headers.get("upgrade", "").lower()
|
|
201
|
+
return upgrade_header == "websocket"
|
|
202
|
+
|
|
203
|
+
def _get_allowed_origins(self, request: Request) -> list[str]:
|
|
204
|
+
"""Get allowed origins from app state (CORS config) or use request host as fallback."""
|
|
205
|
+
try:
|
|
206
|
+
cors_config = getattr(request.app.state, "cors_config", None)
|
|
207
|
+
if cors_config and cors_config.get("allow_origins"):
|
|
208
|
+
return cors_config["allow_origins"]
|
|
209
|
+
except (AttributeError, TypeError, KeyError):
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
host = request.url.hostname
|
|
214
|
+
scheme = request.url.scheme
|
|
215
|
+
port = request.url.port
|
|
216
|
+
if port and port not in [80, 443]:
|
|
217
|
+
origin = f"{scheme}://{host}:{port}"
|
|
218
|
+
else:
|
|
219
|
+
origin = f"{scheme}://{host}"
|
|
220
|
+
return [origin]
|
|
221
|
+
except (AttributeError, TypeError):
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
def _validate_websocket_origin(self, request: Request) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Validate Origin header for WebSocket upgrade requests.
|
|
227
|
+
|
|
228
|
+
Primary defense against Cross-Site WebSocket Hijacking (CSWSH).
|
|
229
|
+
Returns True if Origin is valid, False otherwise.
|
|
230
|
+
"""
|
|
231
|
+
origin = request.headers.get("origin")
|
|
232
|
+
if not origin:
|
|
233
|
+
logger.warning(f"WebSocket upgrade missing Origin header: {request.url.path}")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
allowed_origins = self._get_allowed_origins(request)
|
|
237
|
+
|
|
238
|
+
for allowed in allowed_origins:
|
|
239
|
+
if allowed == "*":
|
|
240
|
+
logger.warning(
|
|
241
|
+
"WebSocket Origin validation using wildcard '*' - "
|
|
242
|
+
"not recommended for production"
|
|
243
|
+
)
|
|
244
|
+
return True
|
|
245
|
+
if origin == allowed or origin.rstrip("/") == allowed.rstrip("/"):
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
logger.warning(
|
|
249
|
+
f"WebSocket upgrade rejected - invalid Origin: {origin} "
|
|
250
|
+
f"(allowed: {allowed_origins})"
|
|
251
|
+
)
|
|
252
|
+
return False
|
|
253
|
+
|
|
198
254
|
async def dispatch(
|
|
199
255
|
self,
|
|
200
256
|
request: Request,
|
|
@@ -206,7 +262,14 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|
|
206
262
|
path = request.url.path
|
|
207
263
|
method = request.method
|
|
208
264
|
|
|
209
|
-
|
|
265
|
+
if self._is_websocket_upgrade(request):
|
|
266
|
+
if not self._validate_websocket_origin(request):
|
|
267
|
+
return JSONResponse(
|
|
268
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
269
|
+
content={"detail": "Invalid origin for WebSocket connection"},
|
|
270
|
+
)
|
|
271
|
+
return await call_next(request)
|
|
272
|
+
|
|
210
273
|
if self._is_exempt(path):
|
|
211
274
|
return await call_next(request)
|
|
212
275
|
|
mdb_engine/core/engine.py
CHANGED
|
@@ -2375,6 +2375,73 @@ class MongoDBEngine:
|
|
|
2375
2375
|
|
|
2376
2376
|
# Mount child app at path prefix
|
|
2377
2377
|
app.mount(path_prefix, child_app)
|
|
2378
|
+
|
|
2379
|
+
# CRITICAL FIX: Register WebSocket routes on parent app with full path
|
|
2380
|
+
# FastAPI's app.mount() doesn't handle WebSocket routes correctly,
|
|
2381
|
+
# so we need to register them on the parent app with the mount prefix
|
|
2382
|
+
# Get WebSocket config from manifest directly (app registration happens
|
|
2383
|
+
# asynchronously in lifespan, so config may not be available yet)
|
|
2384
|
+
websockets_config = app_manifest_data.get("websockets")
|
|
2385
|
+
if websockets_config:
|
|
2386
|
+
try:
|
|
2387
|
+
from fastapi import APIRouter
|
|
2388
|
+
|
|
2389
|
+
from ..routing.websockets import create_websocket_endpoint
|
|
2390
|
+
|
|
2391
|
+
for endpoint_name, endpoint_config in websockets_config.items():
|
|
2392
|
+
ws_path = endpoint_config.get("path", f"/{endpoint_name}")
|
|
2393
|
+
# Combine mount prefix with WebSocket path
|
|
2394
|
+
full_ws_path = f"{path_prefix.rstrip('/')}{ws_path}"
|
|
2395
|
+
|
|
2396
|
+
# Handle auth configuration
|
|
2397
|
+
auth_config = endpoint_config.get("auth", {})
|
|
2398
|
+
if isinstance(auth_config, dict) and "required" in auth_config:
|
|
2399
|
+
require_auth = auth_config.get("required", True)
|
|
2400
|
+
elif "require_auth" in endpoint_config:
|
|
2401
|
+
require_auth = endpoint_config.get("require_auth", True)
|
|
2402
|
+
else:
|
|
2403
|
+
# Use app's auth_policy if available
|
|
2404
|
+
if "auth_policy" in app_manifest_data:
|
|
2405
|
+
require_auth = app_manifest_data["auth_policy"].get(
|
|
2406
|
+
"required", True
|
|
2407
|
+
)
|
|
2408
|
+
else:
|
|
2409
|
+
require_auth = True
|
|
2410
|
+
|
|
2411
|
+
ping_interval = endpoint_config.get("ping_interval", 30)
|
|
2412
|
+
|
|
2413
|
+
# Create WebSocket handler
|
|
2414
|
+
# Use original path for handler (mount handled internally)
|
|
2415
|
+
handler = create_websocket_endpoint(
|
|
2416
|
+
app_slug=slug,
|
|
2417
|
+
path=ws_path,
|
|
2418
|
+
endpoint_name=endpoint_name,
|
|
2419
|
+
handler=None,
|
|
2420
|
+
require_auth=require_auth,
|
|
2421
|
+
ping_interval=ping_interval,
|
|
2422
|
+
)
|
|
2423
|
+
|
|
2424
|
+
# Register on parent app with full path
|
|
2425
|
+
ws_router = APIRouter()
|
|
2426
|
+
ws_router.websocket(full_ws_path)(handler)
|
|
2427
|
+
app.include_router(ws_router)
|
|
2428
|
+
|
|
2429
|
+
logger.info(
|
|
2430
|
+
f"✅ Registered WebSocket route '{full_ws_path}' "
|
|
2431
|
+
f"for mounted app '{slug}' (mounted at '{path_prefix}')"
|
|
2432
|
+
)
|
|
2433
|
+
except ImportError:
|
|
2434
|
+
logger.warning(
|
|
2435
|
+
f"WebSocket support not available - skipping WebSocket routes "
|
|
2436
|
+
f"for mounted app '{slug}'"
|
|
2437
|
+
)
|
|
2438
|
+
except (ValueError, TypeError, AttributeError, RuntimeError) as e:
|
|
2439
|
+
logger.error(
|
|
2440
|
+
f"Failed to register WebSocket routes for mounted app "
|
|
2441
|
+
f"'{slug}': {e}",
|
|
2442
|
+
exc_info=True,
|
|
2443
|
+
)
|
|
2444
|
+
|
|
2378
2445
|
# Update existing entry instead of appending
|
|
2379
2446
|
entry = _find_mounted_app_entry(slug)
|
|
2380
2447
|
if entry:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mdb_engine/README.md,sha256=T3EFGcPopY9LslYW3lxgG3hohWkAOmBNbYG0FDMUJiY,3502
|
|
2
|
-
mdb_engine/__init__.py,sha256=
|
|
2
|
+
mdb_engine/__init__.py,sha256=7zLdK6_1wi46ioNWkSwTdp8-hjVyu1KFVK5xqN1iqOE,3247
|
|
3
3
|
mdb_engine/config.py,sha256=DTAyxfKB8ogyI0v5QR9Y-SJOgXQr_eDBCKxNBSqEyLc,7269
|
|
4
4
|
mdb_engine/constants.py,sha256=eaotvW57TVOg7rRbLziGrVNoP7adgw_G9iVByHezc_A,7837
|
|
5
5
|
mdb_engine/dependencies.py,sha256=MJuYQhZ9ZGzXlip1ha5zba9Rvn04HDPWahJFJH81Q2s,14107
|
|
@@ -14,7 +14,7 @@ mdb_engine/auth/casbin_models.py,sha256=7XtFmRBhhjw1nKprnluvjyJoTj5fzdPeQwVvo6fI
|
|
|
14
14
|
mdb_engine/auth/config_defaults.py,sha256=1YI_hIHuTiEXpkEYMcufNHdLr1oxPiJylg3CKrJCSGY,2012
|
|
15
15
|
mdb_engine/auth/config_helpers.py,sha256=Qharb2YagLOKDGtE7XhYRDbBoQ_KGykrcIKrsOwWIJ4,6303
|
|
16
16
|
mdb_engine/auth/cookie_utils.py,sha256=j04qXq5GiJrnnJUAP5Z_N1CAFbx9CZiyF5u9xIiQ3vo,4876
|
|
17
|
-
mdb_engine/auth/csrf.py,sha256=
|
|
17
|
+
mdb_engine/auth/csrf.py,sha256=Xt_u4V8QXxsCS6KPh5Q54pN5BTbvWMBdJW-CFmBQtEY,14916
|
|
18
18
|
mdb_engine/auth/decorators.py,sha256=LkVVEuRrT0Iz8EwctN14BEi3fSV-xtN6DaGXgtbiYYo,12287
|
|
19
19
|
mdb_engine/auth/dependencies.py,sha256=JB1iYvZJgTR6gcaiGe_GJFCS6NdUKMxWBZRv6vVxnzw,27112
|
|
20
20
|
mdb_engine/auth/helpers.py,sha256=BCrid985cYh-3h5ZMUV9TES0q40uJXio4oYKQZta7KA,1970
|
|
@@ -46,7 +46,7 @@ mdb_engine/core/app_registration.py,sha256=7szt2a7aBkpSppjmhdkkPPYMKGKo0MkLKZeEe
|
|
|
46
46
|
mdb_engine/core/app_secrets.py,sha256=bo-syg9UUATibNyXEZs-0TTYWG-JaY-2S0yNSGA12n0,10524
|
|
47
47
|
mdb_engine/core/connection.py,sha256=XnwuPG34pJ7kJGJ84T0mhj1UZ6_CLz_9qZf6NRYGIS8,8346
|
|
48
48
|
mdb_engine/core/encryption.py,sha256=RZ5LPF5g28E3ZBn6v1IMw_oas7u9YGFtBcEj8lTi9LM,7515
|
|
49
|
-
mdb_engine/core/engine.py,sha256=
|
|
49
|
+
mdb_engine/core/engine.py,sha256=vSHQT-ZWTD4tpcodvnCUn5CDCElpL4__i6-l7_4HwTg,137691
|
|
50
50
|
mdb_engine/core/index_management.py,sha256=9-r7MIy3JnjQ35sGqsbj8K_I07vAUWtAVgSWC99lJcE,5555
|
|
51
51
|
mdb_engine/core/manifest.py,sha256=jguhjVPAHMZGxOJcdSGouv9_XiKmxUjDmyjn2yXHCj4,139205
|
|
52
52
|
mdb_engine/core/ray_integration.py,sha256=vexYOzztscvRYje1xTNmXJbi99oJxCaVJAwKfTNTF_E,13610
|
|
@@ -89,9 +89,9 @@ mdb_engine/routing/__init__.py,sha256=reupjHi_RTc2ZBA4AH5XzobAmqy4EQIsfSUcTkFknU
|
|
|
89
89
|
mdb_engine/routing/websockets.py,sha256=3X4OjQv_Nln4UmeifJky0gFhMG8A6alR77I8g1iIOLY,29311
|
|
90
90
|
mdb_engine/utils/__init__.py,sha256=lDxQSGqkV4fVw5TWIk6FA6_eey_ZnEtMY0fir3cpAe8,236
|
|
91
91
|
mdb_engine/utils/mongo.py,sha256=Oqtv4tQdpiiZzrilGLEYQPo8Vmh8WsTQypxQs8Of53s,3369
|
|
92
|
-
mdb_engine-0.4.
|
|
93
|
-
mdb_engine-0.4.
|
|
94
|
-
mdb_engine-0.4.
|
|
95
|
-
mdb_engine-0.4.
|
|
96
|
-
mdb_engine-0.4.
|
|
97
|
-
mdb_engine-0.4.
|
|
92
|
+
mdb_engine-0.4.8.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
93
|
+
mdb_engine-0.4.8.dist-info/METADATA,sha256=lQYaHnEn84QZ_RvPLl5AfcXrKw2nEH2le519x7SnNB8,15810
|
|
94
|
+
mdb_engine-0.4.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
95
|
+
mdb_engine-0.4.8.dist-info/entry_points.txt,sha256=INCbYdFbBzJalwPwxliEzLmPfR57IvQ7RAXG_pn8cL8,48
|
|
96
|
+
mdb_engine-0.4.8.dist-info/top_level.txt,sha256=PH0UEBwTtgkm2vWvC9He_EOMn7hVn_Wg_Jyc0SmeO8k,11
|
|
97
|
+
mdb_engine-0.4.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|