mdb-engine 0.1.6__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.
Files changed (75) hide show
  1. mdb_engine/README.md +144 -0
  2. mdb_engine/__init__.py +37 -0
  3. mdb_engine/auth/README.md +631 -0
  4. mdb_engine/auth/__init__.py +128 -0
  5. mdb_engine/auth/casbin_factory.py +199 -0
  6. mdb_engine/auth/casbin_models.py +46 -0
  7. mdb_engine/auth/config_defaults.py +71 -0
  8. mdb_engine/auth/config_helpers.py +213 -0
  9. mdb_engine/auth/cookie_utils.py +158 -0
  10. mdb_engine/auth/decorators.py +350 -0
  11. mdb_engine/auth/dependencies.py +747 -0
  12. mdb_engine/auth/helpers.py +64 -0
  13. mdb_engine/auth/integration.py +578 -0
  14. mdb_engine/auth/jwt.py +225 -0
  15. mdb_engine/auth/middleware.py +241 -0
  16. mdb_engine/auth/oso_factory.py +323 -0
  17. mdb_engine/auth/provider.py +570 -0
  18. mdb_engine/auth/restrictions.py +271 -0
  19. mdb_engine/auth/session_manager.py +477 -0
  20. mdb_engine/auth/token_lifecycle.py +213 -0
  21. mdb_engine/auth/token_store.py +289 -0
  22. mdb_engine/auth/users.py +1516 -0
  23. mdb_engine/auth/utils.py +614 -0
  24. mdb_engine/cli/__init__.py +13 -0
  25. mdb_engine/cli/commands/__init__.py +7 -0
  26. mdb_engine/cli/commands/generate.py +105 -0
  27. mdb_engine/cli/commands/migrate.py +83 -0
  28. mdb_engine/cli/commands/show.py +70 -0
  29. mdb_engine/cli/commands/validate.py +63 -0
  30. mdb_engine/cli/main.py +41 -0
  31. mdb_engine/cli/utils.py +92 -0
  32. mdb_engine/config.py +217 -0
  33. mdb_engine/constants.py +160 -0
  34. mdb_engine/core/README.md +542 -0
  35. mdb_engine/core/__init__.py +42 -0
  36. mdb_engine/core/app_registration.py +392 -0
  37. mdb_engine/core/connection.py +243 -0
  38. mdb_engine/core/engine.py +749 -0
  39. mdb_engine/core/index_management.py +162 -0
  40. mdb_engine/core/manifest.py +2793 -0
  41. mdb_engine/core/seeding.py +179 -0
  42. mdb_engine/core/service_initialization.py +355 -0
  43. mdb_engine/core/types.py +413 -0
  44. mdb_engine/database/README.md +522 -0
  45. mdb_engine/database/__init__.py +31 -0
  46. mdb_engine/database/abstraction.py +635 -0
  47. mdb_engine/database/connection.py +387 -0
  48. mdb_engine/database/scoped_wrapper.py +1721 -0
  49. mdb_engine/embeddings/README.md +184 -0
  50. mdb_engine/embeddings/__init__.py +62 -0
  51. mdb_engine/embeddings/dependencies.py +193 -0
  52. mdb_engine/embeddings/service.py +759 -0
  53. mdb_engine/exceptions.py +167 -0
  54. mdb_engine/indexes/README.md +651 -0
  55. mdb_engine/indexes/__init__.py +21 -0
  56. mdb_engine/indexes/helpers.py +145 -0
  57. mdb_engine/indexes/manager.py +895 -0
  58. mdb_engine/memory/README.md +451 -0
  59. mdb_engine/memory/__init__.py +30 -0
  60. mdb_engine/memory/service.py +1285 -0
  61. mdb_engine/observability/README.md +515 -0
  62. mdb_engine/observability/__init__.py +42 -0
  63. mdb_engine/observability/health.py +296 -0
  64. mdb_engine/observability/logging.py +161 -0
  65. mdb_engine/observability/metrics.py +297 -0
  66. mdb_engine/routing/README.md +462 -0
  67. mdb_engine/routing/__init__.py +73 -0
  68. mdb_engine/routing/websockets.py +813 -0
  69. mdb_engine/utils/__init__.py +7 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +213 -0
  71. mdb_engine-0.1.6.dist-info/RECORD +75 -0
  72. mdb_engine-0.1.6.dist-info/WHEEL +5 -0
  73. mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
  74. mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
  75. mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,64 @@
1
+ """
2
+ Authentication Helper Functions
3
+
4
+ Provides helper functions for initializing token management components.
5
+
6
+ This module is part of MDB_ENGINE - MongoDB Engine.
7
+ """
8
+
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ async def initialize_token_management(app, db):
15
+ """
16
+ Initialize token management components (blacklist and session manager) on app startup.
17
+
18
+ This function should be called in the app's lifespan startup event.
19
+
20
+ Args:
21
+ app: FastAPI application instance
22
+ db: MongoDB database instance (Motor AsyncIOMotorDatabase)
23
+
24
+ Example:
25
+ from mdb_engine.auth.helpers import initialize_token_management
26
+ from mdb_engine.auth import TokenBlacklist, SessionManager
27
+
28
+ @app.on_event("startup")
29
+ async def startup():
30
+ # Get database from engine
31
+ db = engine.get_database()
32
+
33
+ # Initialize token management
34
+ await initialize_token_management(app, db)
35
+ """
36
+ try:
37
+ from .session_manager import SessionManager
38
+ from .token_store import TokenBlacklist
39
+
40
+ # Initialize token blacklist
41
+ blacklist = TokenBlacklist(db)
42
+ await blacklist.ensure_indexes()
43
+ app.state.token_blacklist = blacklist
44
+ logger.info("Token blacklist initialized")
45
+
46
+ # Initialize session manager
47
+ session_mgr = SessionManager(db)
48
+ await session_mgr.ensure_indexes()
49
+ app.state.session_manager = session_mgr
50
+ logger.info("Session manager initialized")
51
+
52
+ except (
53
+ ImportError,
54
+ AttributeError,
55
+ TypeError,
56
+ ValueError,
57
+ RuntimeError,
58
+ KeyError,
59
+ ) as e:
60
+ logger.error(f"Error initializing token management: {e}", exc_info=True)
61
+ # Don't raise - allow app to start without token management (backward compatibility)
62
+ logger.warning(
63
+ "Token management not available - continuing without enhanced token features"
64
+ )
@@ -0,0 +1,578 @@
1
+ """
2
+ Authentication Integration Helpers
3
+
4
+ Helpers for integrating authentication features from manifest configuration.
5
+
6
+ This module is part of MDB_ENGINE - MongoDB Engine.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from typing import Any, Dict, Optional
12
+
13
+ from fastapi import FastAPI
14
+
15
+ from .config_defaults import (CORS_DEFAULTS, OBSERVABILITY_DEFAULTS,
16
+ SECURITY_CONFIG_DEFAULTS,
17
+ TOKEN_MANAGEMENT_DEFAULTS)
18
+ from .config_helpers import merge_config_with_defaults
19
+ from .helpers import initialize_token_management
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Cache for auth configs
24
+ _auth_config_cache: Dict[str, Dict[str, Any]] = {}
25
+
26
+
27
+ def _has_cors_middleware(app: FastAPI) -> bool:
28
+ """
29
+ Check if CORS middleware is already added to the FastAPI app.
30
+
31
+ Args:
32
+ app: FastAPI application instance
33
+
34
+ Returns:
35
+ True if CORS middleware exists, False otherwise
36
+ """
37
+ try:
38
+ from fastapi.middleware.cors import CORSMiddleware
39
+
40
+ # Check if CORS middleware is in the middleware stack
41
+ # FastAPI stores middleware in app.user_middleware list
42
+ for middleware in app.user_middleware:
43
+ # Middleware is stored as (middleware_class, options) tuple
44
+ if len(middleware) >= 1:
45
+ middleware_cls = middleware[0]
46
+ # Check if it's CORSMiddleware or a subclass
47
+ if middleware_cls == CORSMiddleware or (
48
+ hasattr(middleware_cls, "__name__")
49
+ and "CORS" in middleware_cls.__name__
50
+ ):
51
+ return True
52
+ return False
53
+ except (AttributeError, TypeError, ValueError) as e:
54
+ logger.debug(f"Error checking for CORS middleware: {e}")
55
+ return False
56
+
57
+
58
+ def invalidate_auth_config_cache(slug_id: Optional[str] = None) -> None:
59
+ """
60
+ Invalidate auth config cache for a specific app or all apps.
61
+
62
+ Args:
63
+ slug_id: App slug identifier. If None, invalidates entire cache.
64
+ """
65
+ if slug_id:
66
+ _auth_config_cache.pop(slug_id, None)
67
+ logger.debug(f"Invalidated auth config cache for {slug_id}")
68
+ else:
69
+ _auth_config_cache.clear()
70
+ logger.debug("Invalidated entire auth config cache")
71
+
72
+
73
+ async def get_auth_config(slug_id: str, engine) -> Dict[str, Any]:
74
+ """
75
+ Retrieve authentication configuration from manifest.
76
+
77
+ Caches results for performance.
78
+
79
+ Args:
80
+ slug_id: App slug identifier
81
+ engine: MongoDBEngine instance
82
+
83
+ Returns:
84
+ Dictionary with token_management and auth (containing policy and users) configs
85
+ """
86
+ # Check cache first
87
+ if slug_id in _auth_config_cache:
88
+ return _auth_config_cache[slug_id]
89
+
90
+ try:
91
+ # Get manifest
92
+ manifest = await engine.get_manifest(slug_id)
93
+ if not manifest:
94
+ logger.warning(f"Manifest not found for {slug_id}")
95
+ return {}
96
+
97
+ # Extract auth configs - support both old and new format for backward compatibility
98
+ auth_config = manifest.get("auth", {})
99
+
100
+ # Migrate old format if present
101
+ if "auth_policy" in manifest or "sub_auth" in manifest:
102
+ if "policy" not in auth_config and "auth_policy" in manifest:
103
+ auth_config["policy"] = manifest.get("auth_policy", {})
104
+ if "users" not in auth_config and "sub_auth" in manifest:
105
+ auth_config["users"] = manifest.get("sub_auth", {})
106
+
107
+ config = {
108
+ "token_management": manifest.get("token_management", {}),
109
+ "auth": auth_config,
110
+ }
111
+
112
+ # Cache it
113
+ _auth_config_cache[slug_id] = config
114
+
115
+ return config
116
+ except (AttributeError, TypeError, ValueError, KeyError, RuntimeError) as e:
117
+ logger.error(f"Error getting auth config for {slug_id}: {e}", exc_info=True)
118
+ return {}
119
+
120
+
121
+ async def _setup_authorization_provider(
122
+ app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
123
+ ) -> None:
124
+ """Set up authorization provider (Casbin/OSO/custom) from manifest."""
125
+ auth = config.get("auth", {})
126
+ auth_policy = auth.get("policy", {})
127
+ provider = auth_policy.get("provider", "casbin" if auth_policy else None)
128
+
129
+ if provider == "casbin" or (provider is None and auth_policy):
130
+ # Auto-create Casbin provider
131
+ try:
132
+ from .casbin_factory import initialize_casbin_from_manifest
133
+
134
+ authz_provider = await initialize_casbin_from_manifest(
135
+ engine, slug_id, config
136
+ )
137
+ if authz_provider:
138
+ app.state.authz_provider = authz_provider
139
+ logger.info(
140
+ f"Authorization provider (Casbin) auto-created for {slug_id}"
141
+ )
142
+ else:
143
+ logger.debug(
144
+ f"Casbin provider not created for {slug_id} (may not be installed)"
145
+ )
146
+ except (
147
+ ImportError,
148
+ AttributeError,
149
+ TypeError,
150
+ ValueError,
151
+ RuntimeError,
152
+ KeyError,
153
+ ) as e:
154
+ logger.warning(f"Could not auto-create Casbin provider for {slug_id}: {e}")
155
+ elif provider == "oso":
156
+ # Auto-create OSO Cloud provider
157
+ try:
158
+ from .oso_factory import initialize_oso_from_manifest
159
+
160
+ authz_provider = await initialize_oso_from_manifest(engine, slug_id, config)
161
+ if authz_provider:
162
+ app.state.authz_provider = authz_provider
163
+ logger.info(
164
+ f"✅ Authorization provider (OSO Cloud) auto-created for {slug_id}"
165
+ )
166
+ else:
167
+ logger.error(
168
+ f"❌ OSO Cloud provider not created for {slug_id}. "
169
+ f"Check logs above for details. "
170
+ f"OSO_AUTH={'SET' if os.getenv('OSO_AUTH') else 'NOT SET'}, "
171
+ f"OSO_URL={os.getenv('OSO_URL', 'NOT SET')}"
172
+ )
173
+ except (
174
+ ImportError,
175
+ AttributeError,
176
+ TypeError,
177
+ ValueError,
178
+ RuntimeError,
179
+ ConnectionError,
180
+ KeyError,
181
+ ) as e:
182
+ logger.error(
183
+ f"❌ Could not auto-create OSO Cloud provider for {slug_id}: {e}",
184
+ exc_info=True,
185
+ )
186
+ elif provider == "custom":
187
+ logger.info(f"Custom provider specified for {slug_id} - manual setup required")
188
+
189
+
190
+ async def _setup_demo_users(
191
+ app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
192
+ ) -> list:
193
+ """Set up demo users and link with OSO roles if applicable."""
194
+ auth = config.get("auth", {})
195
+ users_config = auth.get("users", {})
196
+ demo_users = []
197
+
198
+ if not users_config.get("enabled", False):
199
+ return demo_users
200
+
201
+ seed_strategy = users_config.get("demo_user_seed_strategy", "auto")
202
+ demo_users_config = users_config.get("demo_users", [])
203
+
204
+ # Only auto-create if strategy is explicitly "auto" or default (when not specified)
205
+ if seed_strategy == "auto":
206
+ # Check if demo_users is explicitly configured or using defaults
207
+ has_explicit_demo_users = len(demo_users_config) > 0
208
+ auto_link_platform = users_config.get("auto_link_platform_demo", True)
209
+
210
+ if has_explicit_demo_users or auto_link_platform:
211
+ try:
212
+ from .users import ensure_demo_users_exist
213
+
214
+ db = engine.get_scoped_db(slug_id)
215
+
216
+ logger.info(
217
+ f"Auto-creating demo users for {slug_id} "
218
+ f"(strategy: {seed_strategy}, "
219
+ f"explicit_users: {has_explicit_demo_users}, "
220
+ f"auto_link_platform: {auto_link_platform})"
221
+ )
222
+
223
+ demo_users = await ensure_demo_users_exist(
224
+ db=db,
225
+ slug_id=slug_id,
226
+ config=config,
227
+ mongo_uri=engine.mongo_uri,
228
+ db_name=engine.db_name,
229
+ )
230
+ if demo_users:
231
+ logger.info(
232
+ f"✅ Created/verified {len(demo_users)} demo user(s) for {slug_id}: "
233
+ f"{', '.join([u.get('email', 'unknown') for u in demo_users])}"
234
+ )
235
+ else:
236
+ logger.debug(
237
+ f"No demo users created for {slug_id} "
238
+ f"(may already exist or config disabled)"
239
+ )
240
+ except (
241
+ ValueError,
242
+ TypeError,
243
+ AttributeError,
244
+ RuntimeError,
245
+ ConnectionError,
246
+ KeyError,
247
+ ) as e:
248
+ logger.warning(
249
+ f"Could not create demo users for {slug_id}: {e}",
250
+ exc_info=True,
251
+ )
252
+ else:
253
+ logger.debug(
254
+ f"Skipping demo user creation for {slug_id}: "
255
+ f"demo_user_seed_strategy is 'auto' but no demo_users configured "
256
+ f"and auto_link_platform_demo is disabled"
257
+ )
258
+ elif seed_strategy == "disabled":
259
+ logger.debug(
260
+ f"Demo user creation disabled for {slug_id} (demo_user_seed_strategy: disabled)"
261
+ )
262
+ elif seed_strategy == "manual":
263
+ logger.debug(
264
+ f"Demo user creation set to manual for {slug_id} - skipping auto-creation"
265
+ )
266
+
267
+ # Link demo users with OSO initial_roles if auth provider is OSO
268
+ if hasattr(app.state, "authz_provider") and demo_users:
269
+ try:
270
+ auth = config.get("auth", {})
271
+ auth_policy = auth.get("policy", {})
272
+ authorization = auth_policy.get("authorization", {})
273
+ initial_roles = authorization.get("initial_roles", [])
274
+
275
+ if initial_roles:
276
+ # Match demo users by email to initial_roles entries
277
+ for demo_user in demo_users:
278
+ user_email = demo_user.get("email")
279
+ if not user_email:
280
+ continue
281
+
282
+ for role_assignment in initial_roles:
283
+ if role_assignment.get("user") == user_email:
284
+ role = role_assignment.get("role")
285
+ resource = role_assignment.get("resource", "documents")
286
+ try:
287
+ await app.state.authz_provider.add_role_for_user(
288
+ user_email, role, resource
289
+ )
290
+ logger.info(
291
+ f"✅ Assigned role '{role}' on resource '{resource}' "
292
+ f"to demo user '{user_email}' for {slug_id}"
293
+ )
294
+ except (
295
+ ValueError,
296
+ TypeError,
297
+ AttributeError,
298
+ RuntimeError,
299
+ ConnectionError,
300
+ ) as e:
301
+ logger.warning(
302
+ f"Failed to assign role '{role}' to user '{user_email}' "
303
+ f"for {slug_id}: {e}"
304
+ )
305
+ except (
306
+ ValueError,
307
+ TypeError,
308
+ AttributeError,
309
+ RuntimeError,
310
+ ConnectionError,
311
+ KeyError,
312
+ ) as e:
313
+ logger.warning(
314
+ f"Could not link demo users with OSO roles for {slug_id}: {e}",
315
+ exc_info=True,
316
+ )
317
+
318
+ return demo_users
319
+
320
+
321
+ async def _setup_token_management(
322
+ app: FastAPI, engine, slug_id: str, token_management: Dict[str, Any]
323
+ ) -> None:
324
+ """Initialize token management (blacklist and session manager)."""
325
+ if token_management.get("auto_setup", True):
326
+ try:
327
+ db = engine.get_database()
328
+ await initialize_token_management(app, db)
329
+
330
+ # Configure session fingerprinting if session manager exists
331
+ session_mgr = getattr(app.state, "session_manager", None)
332
+ if session_mgr:
333
+ fingerprinting_config = app.state.security_config.get(
334
+ "session_fingerprinting", {}
335
+ )
336
+ session_mgr.configure_fingerprinting(
337
+ enabled=fingerprinting_config.get("enabled", True),
338
+ strict=fingerprinting_config.get("strict_mode", False),
339
+ )
340
+
341
+ logger.info(f"Token management initialized for {slug_id}")
342
+ except (
343
+ ImportError,
344
+ AttributeError,
345
+ TypeError,
346
+ ValueError,
347
+ RuntimeError,
348
+ KeyError,
349
+ ) as e:
350
+ logger.warning(f"Could not initialize token management for {slug_id}: {e}")
351
+ # Continue without token management (backward compatibility)
352
+
353
+
354
+ async def _setup_security_middleware(
355
+ app: FastAPI, slug_id: str, security_config: Dict[str, Any]
356
+ ) -> None:
357
+ """Set up security middleware (if not already added)."""
358
+ if security_config.get("csrf_protection", True) or security_config.get(
359
+ "require_https", False
360
+ ):
361
+ try:
362
+ from .middleware import SecurityMiddleware
363
+
364
+ # Try to add middleware - FastAPI will raise RuntimeError if app has started
365
+ # FastAPI with lifespan might have initialized middleware stack already
366
+ # Try to add middleware and catch the error if it fails
367
+ try:
368
+ app.add_middleware(
369
+ SecurityMiddleware,
370
+ require_https=security_config.get("require_https", False),
371
+ csrf_protection=security_config.get("csrf_protection", True),
372
+ security_headers=True,
373
+ )
374
+ logger.info(f"Security middleware added for {slug_id}")
375
+ except (RuntimeError, ValueError) as e:
376
+ error_msg = str(e).lower()
377
+ if "cannot add middleware" in error_msg or "middleware" in error_msg:
378
+ # App has already started - this is expected with lifespan context managers
379
+ # The middleware is optional for security, so we just log
380
+ # a debug message
381
+ logger.debug(
382
+ f"Security middleware not added for {slug_id} - "
383
+ f"app middleware stack already initialized. "
384
+ f"This is normal when using lifespan context managers."
385
+ )
386
+ else:
387
+ logger.warning(
388
+ f"Could not set up security middleware for {slug_id}: {e}"
389
+ )
390
+ except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
391
+ logger.warning(f"Could not set up security middleware for {slug_id}: {e}")
392
+
393
+
394
+ async def _setup_cors_and_observability(
395
+ app: FastAPI, engine, slug_id: str, config: Dict[str, Any]
396
+ ) -> None:
397
+ """Set up CORS and observability configs and middleware."""
398
+ # Get manifest data first if available
399
+ manifest_data = None
400
+ if hasattr(engine, "get_manifest"):
401
+ try:
402
+ manifest_data = await engine.get_manifest(slug_id)
403
+ except (AttributeError, TypeError, ValueError, RuntimeError, KeyError) as e:
404
+ logger.warning(f"Could not retrieve manifest for {slug_id}: {e}")
405
+ manifest_data = None
406
+
407
+ # Extract and store CORS config
408
+ cors_config = manifest_data.get("cors", {}) if manifest_data else {}
409
+ app.state.cors_config = merge_config_with_defaults(cors_config, CORS_DEFAULTS)
410
+
411
+ # Extract and store observability config
412
+ observability_config = (
413
+ manifest_data.get("observability", {}) if manifest_data else {}
414
+ )
415
+ app.state.observability_config = merge_config_with_defaults(
416
+ observability_config, OBSERVABILITY_DEFAULTS
417
+ )
418
+
419
+ # Set up CORS middleware if enabled
420
+ if app.state.cors_config.get("enabled", False):
421
+ try:
422
+ # Check if CORS middleware already exists to avoid duplication
423
+ if _has_cors_middleware(app):
424
+ logger.debug(
425
+ f"CORS middleware already exists for {slug_id}, skipping addition"
426
+ )
427
+ else:
428
+ from fastapi.middleware.cors import CORSMiddleware
429
+
430
+ if not hasattr(app.state, "_started"):
431
+ try:
432
+ app.add_middleware(
433
+ CORSMiddleware,
434
+ allow_origins=app.state.cors_config.get(
435
+ "allow_origins", ["*"]
436
+ ),
437
+ allow_credentials=app.state.cors_config.get(
438
+ "allow_credentials", False
439
+ ),
440
+ allow_methods=app.state.cors_config.get(
441
+ "allow_methods",
442
+ ["GET", "POST", "PUT", "DELETE", "PATCH"],
443
+ ),
444
+ allow_headers=app.state.cors_config.get(
445
+ "allow_headers", ["*"]
446
+ ),
447
+ expose_headers=app.state.cors_config.get(
448
+ "expose_headers", []
449
+ ),
450
+ max_age=app.state.cors_config.get("max_age", 3600),
451
+ )
452
+ logger.info(f"CORS middleware added for {slug_id}")
453
+ except (RuntimeError, ValueError) as e:
454
+ error_msg = str(e).lower()
455
+ if (
456
+ "cannot add middleware" in error_msg
457
+ or "middleware" in error_msg
458
+ ):
459
+ logger.debug(
460
+ f"CORS middleware not added for {slug_id} - "
461
+ f"app middleware stack already initialized. "
462
+ f"This is normal when using lifespan context managers."
463
+ )
464
+ else:
465
+ logger.warning(
466
+ f"Could not set up CORS middleware for {slug_id}: {e}"
467
+ )
468
+ else:
469
+ logger.warning(
470
+ f"CORS middleware not added for {slug_id} - app already started"
471
+ )
472
+ except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
473
+ logger.warning(f"Could not set up CORS middleware for {slug_id}: {e}")
474
+
475
+ # Add stale session cleanup middleware if auth.users is enabled
476
+ auth = config.get("auth", {})
477
+ users_config = auth.get("users", {})
478
+ if users_config.get("enabled", False):
479
+ try:
480
+ from .middleware import StaleSessionMiddleware
481
+
482
+ try:
483
+ app.add_middleware(
484
+ StaleSessionMiddleware, slug_id=slug_id, engine=engine
485
+ )
486
+ logger.info(f"Stale session cleanup middleware added for {slug_id}")
487
+ except (RuntimeError, ValueError) as e:
488
+ error_msg = str(e).lower()
489
+ if "cannot add middleware" in error_msg or "middleware" in error_msg:
490
+ logger.debug(
491
+ f"Stale session middleware not added for {slug_id} - "
492
+ f"app middleware stack already initialized. "
493
+ f"This is normal when using lifespan context managers."
494
+ )
495
+ else:
496
+ logger.warning(
497
+ f"Could not set up stale session middleware for {slug_id}: {e}"
498
+ )
499
+ except (AttributeError, TypeError, ValueError, RuntimeError, ImportError) as e:
500
+ logger.warning(
501
+ f"Could not set up stale session middleware for {slug_id}: {e}"
502
+ )
503
+
504
+
505
+ async def setup_auth_from_manifest(app: FastAPI, engine, slug_id: str) -> bool:
506
+ """
507
+ Set up authentication features from manifest configuration.
508
+
509
+ This function:
510
+ 1. Reads auth.policy and token_management config from manifest
511
+ 2. Auto-creates authorization provider (default: Casbin) if configured
512
+ 3. Initializes TokenBlacklist and SessionManager if enabled
513
+ 4. Sets up security middleware if configured
514
+ 5. Links auth.users to authorization if enabled
515
+ 6. Stores config in app.state for easy access
516
+
517
+ Args:
518
+ app: FastAPI application instance
519
+ engine: MongoDBEngine instance
520
+ slug_id: App slug identifier
521
+
522
+ Returns:
523
+ True if setup was successful, False otherwise
524
+ """
525
+ try:
526
+ # Get auth config
527
+ config = await get_auth_config(slug_id, engine)
528
+ token_management = config.get("token_management", {})
529
+
530
+ # Set up authorization provider
531
+ await _setup_authorization_provider(app, engine, slug_id, config)
532
+
533
+ # Set up demo users
534
+ await _setup_demo_users(app, engine, slug_id, config)
535
+
536
+ # Check if token management is enabled
537
+ if not token_management.get("enabled", True):
538
+ logger.info(f"Token management disabled for {slug_id}")
539
+ return False
540
+
541
+ # Store config in app state for easy access
542
+ merged_token_config = merge_config_with_defaults(
543
+ token_management, TOKEN_MANAGEMENT_DEFAULTS
544
+ )
545
+ app.state.token_management_config = merged_token_config
546
+ app.state.auth_config = config
547
+
548
+ # Extract and store security policy config with defaults merged
549
+ security_config = token_management.get("security", {})
550
+ app.state.security_config = merge_config_with_defaults(
551
+ security_config, SECURITY_CONFIG_DEFAULTS
552
+ )
553
+
554
+ # Initialize token management
555
+ await _setup_token_management(app, engine, slug_id, token_management)
556
+
557
+ # Set up security middleware
558
+ await _setup_security_middleware(app, slug_id, security_config)
559
+
560
+ # Set up CORS and observability
561
+ await _setup_cors_and_observability(app, engine, slug_id, config)
562
+
563
+ logger.info(f"Auth setup completed for {slug_id}")
564
+ return True
565
+
566
+ except (
567
+ ValueError,
568
+ TypeError,
569
+ AttributeError,
570
+ RuntimeError,
571
+ ImportError,
572
+ KeyError,
573
+ ConnectionError,
574
+ ) as e:
575
+ logger.error(
576
+ f"Error setting up auth from manifest for {slug_id}: {e}", exc_info=True
577
+ )
578
+ return False