mdb-engine 0.1.6__py3-none-any.whl → 0.1.7__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/__init__.py +38 -6
  2. mdb_engine/auth/README.md +534 -11
  3. mdb_engine/auth/__init__.py +129 -28
  4. mdb_engine/auth/audit.py +592 -0
  5. mdb_engine/auth/casbin_factory.py +10 -14
  6. mdb_engine/auth/config_helpers.py +7 -6
  7. mdb_engine/auth/cookie_utils.py +3 -7
  8. mdb_engine/auth/csrf.py +373 -0
  9. mdb_engine/auth/decorators.py +3 -10
  10. mdb_engine/auth/dependencies.py +37 -45
  11. mdb_engine/auth/helpers.py +3 -3
  12. mdb_engine/auth/integration.py +30 -73
  13. mdb_engine/auth/jwt.py +2 -6
  14. mdb_engine/auth/middleware.py +77 -34
  15. mdb_engine/auth/oso_factory.py +16 -36
  16. mdb_engine/auth/provider.py +17 -38
  17. mdb_engine/auth/rate_limiter.py +504 -0
  18. mdb_engine/auth/restrictions.py +8 -24
  19. mdb_engine/auth/session_manager.py +14 -29
  20. mdb_engine/auth/shared_middleware.py +600 -0
  21. mdb_engine/auth/shared_users.py +759 -0
  22. mdb_engine/auth/token_store.py +14 -28
  23. mdb_engine/auth/users.py +54 -113
  24. mdb_engine/auth/utils.py +213 -15
  25. mdb_engine/cli/commands/generate.py +545 -9
  26. mdb_engine/cli/commands/validate.py +3 -7
  27. mdb_engine/cli/utils.py +3 -3
  28. mdb_engine/config.py +7 -21
  29. mdb_engine/constants.py +65 -0
  30. mdb_engine/core/README.md +117 -6
  31. mdb_engine/core/__init__.py +39 -7
  32. mdb_engine/core/app_registration.py +22 -41
  33. mdb_engine/core/app_secrets.py +290 -0
  34. mdb_engine/core/connection.py +18 -9
  35. mdb_engine/core/encryption.py +223 -0
  36. mdb_engine/core/engine.py +758 -95
  37. mdb_engine/core/index_management.py +12 -16
  38. mdb_engine/core/manifest.py +424 -135
  39. mdb_engine/core/ray_integration.py +435 -0
  40. mdb_engine/core/seeding.py +10 -18
  41. mdb_engine/core/service_initialization.py +12 -23
  42. mdb_engine/core/types.py +2 -5
  43. mdb_engine/database/README.md +112 -16
  44. mdb_engine/database/__init__.py +17 -6
  45. mdb_engine/database/abstraction.py +25 -37
  46. mdb_engine/database/connection.py +11 -18
  47. mdb_engine/database/query_validator.py +367 -0
  48. mdb_engine/database/resource_limiter.py +204 -0
  49. mdb_engine/database/scoped_wrapper.py +713 -196
  50. mdb_engine/embeddings/__init__.py +17 -9
  51. mdb_engine/embeddings/dependencies.py +1 -3
  52. mdb_engine/embeddings/service.py +11 -25
  53. mdb_engine/exceptions.py +92 -0
  54. mdb_engine/indexes/README.md +30 -13
  55. mdb_engine/indexes/__init__.py +1 -0
  56. mdb_engine/indexes/helpers.py +1 -1
  57. mdb_engine/indexes/manager.py +50 -114
  58. mdb_engine/memory/README.md +2 -2
  59. mdb_engine/memory/__init__.py +1 -2
  60. mdb_engine/memory/service.py +30 -87
  61. mdb_engine/observability/README.md +4 -2
  62. mdb_engine/observability/__init__.py +26 -9
  63. mdb_engine/observability/health.py +8 -9
  64. mdb_engine/observability/metrics.py +32 -12
  65. mdb_engine/routing/README.md +1 -1
  66. mdb_engine/routing/__init__.py +1 -3
  67. mdb_engine/routing/websockets.py +25 -60
  68. mdb_engine-0.1.7.dist-info/METADATA +285 -0
  69. mdb_engine-0.1.7.dist-info/RECORD +85 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +0 -213
  71. mdb_engine-0.1.6.dist-info/RECORD +0 -75
  72. {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/WHEEL +0 -0
  73. {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/entry_points.txt +0 -0
  74. {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/licenses/LICENSE +0 -0
  75. {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,592 @@
1
+ """
2
+ Authentication Audit Logging
3
+
4
+ Structured audit logging for authentication events. Provides forensics,
5
+ compliance support, and security monitoring capabilities.
6
+
7
+ This module is part of MDB_ENGINE - MongoDB Engine.
8
+
9
+ Features:
10
+ - Structured event logging to MongoDB
11
+ - TTL-based automatic cleanup
12
+ - Query helpers for security analysis
13
+ - Integration with shared auth middleware
14
+
15
+ Usage:
16
+ # Initialize audit logger
17
+ audit = AuthAuditLog(mongo_db)
18
+
19
+ # Log authentication events
20
+ await audit.log_event(
21
+ action=AuthAction.LOGIN_SUCCESS,
22
+ user_email="user@example.com",
23
+ success=True,
24
+ ip_address="192.168.1.1",
25
+ user_agent="Mozilla/5.0...",
26
+ )
27
+
28
+ # Query for security analysis
29
+ failed_logins = await audit.get_failed_logins(
30
+ email="user@example.com",
31
+ hours=24,
32
+ )
33
+ """
34
+
35
+ import logging
36
+ from datetime import datetime, timedelta
37
+ from enum import Enum
38
+ from typing import Any, Dict, List, Optional
39
+
40
+ from motor.motor_asyncio import AsyncIOMotorDatabase
41
+ from pymongo.errors import OperationFailure
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ # Collection name for audit logs
46
+ AUDIT_COLLECTION = "_mdb_engine_auth_audit"
47
+
48
+ # Default retention period (90 days)
49
+ DEFAULT_RETENTION_DAYS = 90
50
+
51
+
52
+ class AuthAction(str, Enum):
53
+ """Authentication action types for audit logging."""
54
+
55
+ # Authentication events
56
+ LOGIN_SUCCESS = "login_success"
57
+ LOGIN_FAILED = "login_failed"
58
+ LOGOUT = "logout"
59
+ REGISTER = "register"
60
+
61
+ # Token events
62
+ TOKEN_REFRESHED = "token_refreshed"
63
+ TOKEN_REVOKED = "token_revoked"
64
+ TOKEN_EXPIRED = "token_expired"
65
+
66
+ # Account events
67
+ PASSWORD_CHANGED = "password_changed"
68
+ PASSWORD_RESET_REQUESTED = "password_reset_requested"
69
+ PASSWORD_RESET_COMPLETED = "password_reset_completed"
70
+
71
+ # Role/permission events
72
+ ROLE_GRANTED = "role_granted"
73
+ ROLE_REVOKED = "role_revoked"
74
+
75
+ # Security events
76
+ ACCOUNT_LOCKED = "account_locked"
77
+ ACCOUNT_UNLOCKED = "account_unlocked"
78
+ RATE_LIMIT_EXCEEDED = "rate_limit_exceeded"
79
+ SUSPICIOUS_ACTIVITY = "suspicious_activity"
80
+
81
+
82
+ class AuthAuditLog:
83
+ """
84
+ Manages structured audit logging for authentication events.
85
+
86
+ Logs are stored in MongoDB with TTL index for automatic cleanup.
87
+ Provides query helpers for security analysis and compliance.
88
+
89
+ Schema for audit documents:
90
+ {
91
+ "_id": ObjectId,
92
+ "timestamp": datetime,
93
+ "action": str (AuthAction value),
94
+ "success": bool,
95
+ "user_email": str | None,
96
+ "user_id": str | None,
97
+ "app_slug": str | None,
98
+ "ip_address": str | None,
99
+ "user_agent": str | None,
100
+ "details": dict | None,
101
+ "expires_at": datetime (for TTL)
102
+ }
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ mongo_db: AsyncIOMotorDatabase,
108
+ retention_days: int = DEFAULT_RETENTION_DAYS,
109
+ ):
110
+ """
111
+ Initialize the audit logger.
112
+
113
+ Args:
114
+ mongo_db: MongoDB database instance
115
+ retention_days: How long to keep audit logs (default: 90 days)
116
+ """
117
+ self._db = mongo_db
118
+ self._collection = mongo_db[AUDIT_COLLECTION]
119
+ self._retention_days = retention_days
120
+ self._indexes_created = False
121
+
122
+ logger.info(f"AuthAuditLog initialized (retention: {retention_days} days)")
123
+
124
+ async def ensure_indexes(self) -> None:
125
+ """Create necessary indexes for the audit collection."""
126
+ if self._indexes_created:
127
+ return
128
+
129
+ try:
130
+ # Index for querying by user
131
+ await self._collection.create_index(
132
+ [("user_email", 1), ("timestamp", -1)], name="user_email_timestamp_idx"
133
+ )
134
+
135
+ # Index for querying by action type
136
+ await self._collection.create_index(
137
+ [("action", 1), ("timestamp", -1)], name="action_timestamp_idx"
138
+ )
139
+
140
+ # Index for querying by IP address
141
+ await self._collection.create_index(
142
+ [("ip_address", 1), ("timestamp", -1)], name="ip_timestamp_idx"
143
+ )
144
+
145
+ # Index for querying by app
146
+ await self._collection.create_index(
147
+ [("app_slug", 1), ("timestamp", -1)], name="app_timestamp_idx"
148
+ )
149
+
150
+ # TTL index for automatic cleanup
151
+ await self._collection.create_index(
152
+ "expires_at", expireAfterSeconds=0, name="expires_at_ttl_idx"
153
+ )
154
+
155
+ self._indexes_created = True
156
+ logger.info("AuthAuditLog indexes ensured")
157
+ except OperationFailure as e:
158
+ logger.warning(f"Failed to create audit indexes: {e}")
159
+
160
+ async def log_event(
161
+ self,
162
+ action: AuthAction,
163
+ success: bool,
164
+ user_email: Optional[str] = None,
165
+ user_id: Optional[str] = None,
166
+ app_slug: Optional[str] = None,
167
+ ip_address: Optional[str] = None,
168
+ user_agent: Optional[str] = None,
169
+ details: Optional[Dict[str, Any]] = None,
170
+ ) -> str:
171
+ """
172
+ Log an authentication event.
173
+
174
+ Args:
175
+ action: Type of authentication action
176
+ success: Whether the action succeeded
177
+ user_email: User's email address
178
+ user_id: User's ID
179
+ app_slug: App where the action occurred
180
+ ip_address: Client IP address
181
+ user_agent: Client user agent string
182
+ details: Additional details (e.g., failure reason)
183
+
184
+ Returns:
185
+ Inserted document ID as string
186
+ """
187
+ await self.ensure_indexes()
188
+
189
+ now = datetime.utcnow()
190
+ expires_at = now + timedelta(days=self._retention_days)
191
+
192
+ doc = {
193
+ "timestamp": now,
194
+ "action": action.value if isinstance(action, AuthAction) else action,
195
+ "success": success,
196
+ "user_email": user_email,
197
+ "user_id": user_id,
198
+ "app_slug": app_slug,
199
+ "ip_address": ip_address,
200
+ "user_agent": user_agent,
201
+ "details": details,
202
+ "expires_at": expires_at,
203
+ }
204
+
205
+ result = await self._collection.insert_one(doc)
206
+
207
+ # Log to application logger as well
208
+ log_level = logging.INFO if success else logging.WARNING
209
+ logger.log(
210
+ log_level,
211
+ f"AUTH_AUDIT: {action.value if isinstance(action, AuthAction) else action} "
212
+ f"success={success} email={user_email} ip={ip_address} app={app_slug}",
213
+ )
214
+
215
+ return str(result.inserted_id)
216
+
217
+ async def log_login_success(
218
+ self,
219
+ email: str,
220
+ ip_address: Optional[str] = None,
221
+ user_agent: Optional[str] = None,
222
+ app_slug: Optional[str] = None,
223
+ ) -> str:
224
+ """Convenience method to log successful login."""
225
+ return await self.log_event(
226
+ action=AuthAction.LOGIN_SUCCESS,
227
+ success=True,
228
+ user_email=email,
229
+ ip_address=ip_address,
230
+ user_agent=user_agent,
231
+ app_slug=app_slug,
232
+ )
233
+
234
+ async def log_login_failed(
235
+ self,
236
+ email: str,
237
+ reason: str = "invalid_credentials",
238
+ ip_address: Optional[str] = None,
239
+ user_agent: Optional[str] = None,
240
+ app_slug: Optional[str] = None,
241
+ ) -> str:
242
+ """Convenience method to log failed login."""
243
+ return await self.log_event(
244
+ action=AuthAction.LOGIN_FAILED,
245
+ success=False,
246
+ user_email=email,
247
+ ip_address=ip_address,
248
+ user_agent=user_agent,
249
+ app_slug=app_slug,
250
+ details={"reason": reason},
251
+ )
252
+
253
+ async def log_logout(
254
+ self,
255
+ email: str,
256
+ ip_address: Optional[str] = None,
257
+ app_slug: Optional[str] = None,
258
+ ) -> str:
259
+ """Convenience method to log logout."""
260
+ return await self.log_event(
261
+ action=AuthAction.LOGOUT,
262
+ success=True,
263
+ user_email=email,
264
+ ip_address=ip_address,
265
+ app_slug=app_slug,
266
+ )
267
+
268
+ async def log_register(
269
+ self,
270
+ email: str,
271
+ ip_address: Optional[str] = None,
272
+ user_agent: Optional[str] = None,
273
+ app_slug: Optional[str] = None,
274
+ ) -> str:
275
+ """Convenience method to log new user registration."""
276
+ return await self.log_event(
277
+ action=AuthAction.REGISTER,
278
+ success=True,
279
+ user_email=email,
280
+ ip_address=ip_address,
281
+ user_agent=user_agent,
282
+ app_slug=app_slug,
283
+ )
284
+
285
+ async def log_role_change(
286
+ self,
287
+ email: str,
288
+ app_slug: str,
289
+ old_roles: List[str],
290
+ new_roles: List[str],
291
+ changed_by: Optional[str] = None,
292
+ ip_address: Optional[str] = None,
293
+ ) -> str:
294
+ """Log a role change event."""
295
+ action = (
296
+ AuthAction.ROLE_GRANTED if len(new_roles) > len(old_roles) else AuthAction.ROLE_REVOKED
297
+ )
298
+ return await self.log_event(
299
+ action=action,
300
+ success=True,
301
+ user_email=email,
302
+ app_slug=app_slug,
303
+ ip_address=ip_address,
304
+ details={
305
+ "old_roles": old_roles,
306
+ "new_roles": new_roles,
307
+ "changed_by": changed_by,
308
+ },
309
+ )
310
+
311
+ async def log_token_revoked(
312
+ self,
313
+ email: str,
314
+ reason: str = "logout",
315
+ ip_address: Optional[str] = None,
316
+ app_slug: Optional[str] = None,
317
+ ) -> str:
318
+ """Log token revocation."""
319
+ return await self.log_event(
320
+ action=AuthAction.TOKEN_REVOKED,
321
+ success=True,
322
+ user_email=email,
323
+ ip_address=ip_address,
324
+ app_slug=app_slug,
325
+ details={"reason": reason},
326
+ )
327
+
328
+ async def log_rate_limit_exceeded(
329
+ self,
330
+ ip_address: str,
331
+ endpoint: str,
332
+ email: Optional[str] = None,
333
+ app_slug: Optional[str] = None,
334
+ ) -> str:
335
+ """Log rate limit exceeded event."""
336
+ return await self.log_event(
337
+ action=AuthAction.RATE_LIMIT_EXCEEDED,
338
+ success=False,
339
+ user_email=email,
340
+ ip_address=ip_address,
341
+ app_slug=app_slug,
342
+ details={"endpoint": endpoint},
343
+ )
344
+
345
+ # ==========================================================================
346
+ # Query Methods for Security Analysis
347
+ # ==========================================================================
348
+
349
+ async def get_recent_events(
350
+ self,
351
+ hours: int = 24,
352
+ action: Optional[AuthAction] = None,
353
+ success: Optional[bool] = None,
354
+ limit: int = 100,
355
+ ) -> List[Dict[str, Any]]:
356
+ """
357
+ Get recent audit events.
358
+
359
+ Args:
360
+ hours: How many hours back to search
361
+ action: Filter by action type
362
+ success: Filter by success status
363
+ limit: Maximum results to return
364
+
365
+ Returns:
366
+ List of audit event documents
367
+ """
368
+ since = datetime.utcnow() - timedelta(hours=hours)
369
+
370
+ query: Dict[str, Any] = {"timestamp": {"$gte": since}}
371
+ if action:
372
+ query["action"] = action.value if isinstance(action, AuthAction) else action
373
+ if success is not None:
374
+ query["success"] = success
375
+
376
+ cursor = self._collection.find(query).sort("timestamp", -1).limit(limit)
377
+ events = await cursor.to_list(length=limit)
378
+
379
+ # Convert ObjectIds to strings
380
+ for event in events:
381
+ event["_id"] = str(event["_id"])
382
+
383
+ return events
384
+
385
+ async def get_failed_logins(
386
+ self,
387
+ email: Optional[str] = None,
388
+ ip_address: Optional[str] = None,
389
+ hours: int = 24,
390
+ limit: int = 100,
391
+ ) -> List[Dict[str, Any]]:
392
+ """
393
+ Get failed login attempts.
394
+
395
+ Args:
396
+ email: Filter by email
397
+ ip_address: Filter by IP address
398
+ hours: How many hours back to search
399
+ limit: Maximum results
400
+
401
+ Returns:
402
+ List of failed login events
403
+ """
404
+ since = datetime.utcnow() - timedelta(hours=hours)
405
+
406
+ query: Dict[str, Any] = {
407
+ "action": AuthAction.LOGIN_FAILED.value,
408
+ "timestamp": {"$gte": since},
409
+ }
410
+ if email:
411
+ query["user_email"] = email
412
+ if ip_address:
413
+ query["ip_address"] = ip_address
414
+
415
+ cursor = self._collection.find(query).sort("timestamp", -1).limit(limit)
416
+ events = await cursor.to_list(length=limit)
417
+
418
+ for event in events:
419
+ event["_id"] = str(event["_id"])
420
+
421
+ return events
422
+
423
+ async def get_user_activity(
424
+ self,
425
+ email: str,
426
+ hours: int = 168, # 7 days
427
+ limit: int = 100,
428
+ ) -> List[Dict[str, Any]]:
429
+ """
430
+ Get all activity for a specific user.
431
+
432
+ Args:
433
+ email: User email
434
+ hours: How many hours back to search
435
+ limit: Maximum results
436
+
437
+ Returns:
438
+ List of user's audit events
439
+ """
440
+ since = datetime.utcnow() - timedelta(hours=hours)
441
+
442
+ cursor = (
443
+ self._collection.find(
444
+ {
445
+ "user_email": email,
446
+ "timestamp": {"$gte": since},
447
+ }
448
+ )
449
+ .sort("timestamp", -1)
450
+ .limit(limit)
451
+ )
452
+
453
+ events = await cursor.to_list(length=limit)
454
+
455
+ for event in events:
456
+ event["_id"] = str(event["_id"])
457
+
458
+ return events
459
+
460
+ async def get_ip_activity(
461
+ self,
462
+ ip_address: str,
463
+ hours: int = 24,
464
+ limit: int = 100,
465
+ ) -> List[Dict[str, Any]]:
466
+ """
467
+ Get all activity from a specific IP address.
468
+
469
+ Useful for investigating suspicious activity.
470
+
471
+ Args:
472
+ ip_address: IP address to search
473
+ hours: How many hours back
474
+ limit: Maximum results
475
+
476
+ Returns:
477
+ List of audit events from that IP
478
+ """
479
+ since = datetime.utcnow() - timedelta(hours=hours)
480
+
481
+ cursor = (
482
+ self._collection.find(
483
+ {
484
+ "ip_address": ip_address,
485
+ "timestamp": {"$gte": since},
486
+ }
487
+ )
488
+ .sort("timestamp", -1)
489
+ .limit(limit)
490
+ )
491
+
492
+ events = await cursor.to_list(length=limit)
493
+
494
+ for event in events:
495
+ event["_id"] = str(event["_id"])
496
+
497
+ return events
498
+
499
+ async def count_failed_logins(
500
+ self,
501
+ email: Optional[str] = None,
502
+ ip_address: Optional[str] = None,
503
+ hours: int = 1,
504
+ ) -> int:
505
+ """
506
+ Count failed login attempts in a time window.
507
+
508
+ Useful for detecting brute-force attacks.
509
+
510
+ Args:
511
+ email: Filter by email
512
+ ip_address: Filter by IP
513
+ hours: Time window
514
+
515
+ Returns:
516
+ Number of failed login attempts
517
+ """
518
+ since = datetime.utcnow() - timedelta(hours=hours)
519
+
520
+ query: Dict[str, Any] = {
521
+ "action": AuthAction.LOGIN_FAILED.value,
522
+ "timestamp": {"$gte": since},
523
+ }
524
+ if email:
525
+ query["user_email"] = email
526
+ if ip_address:
527
+ query["ip_address"] = ip_address
528
+
529
+ return await self._collection.count_documents(query)
530
+
531
+ async def get_security_summary(
532
+ self,
533
+ hours: int = 24,
534
+ ) -> Dict[str, Any]:
535
+ """
536
+ Get security summary statistics.
537
+
538
+ Args:
539
+ hours: Time window for statistics
540
+
541
+ Returns:
542
+ Dict with security metrics
543
+ """
544
+ since = datetime.utcnow() - timedelta(hours=hours)
545
+
546
+ # Use aggregation for efficient counting
547
+ pipeline = [
548
+ {"$match": {"timestamp": {"$gte": since}}},
549
+ {
550
+ "$group": {
551
+ "_id": {"action": "$action", "success": "$success"},
552
+ "count": {"$sum": 1},
553
+ }
554
+ },
555
+ ]
556
+
557
+ cursor = self._collection.aggregate(pipeline)
558
+ results = await cursor.to_list(length=100)
559
+
560
+ # Build summary
561
+ summary = {
562
+ "period_hours": hours,
563
+ "total_events": 0,
564
+ "login_success": 0,
565
+ "login_failed": 0,
566
+ "registrations": 0,
567
+ "logouts": 0,
568
+ "tokens_revoked": 0,
569
+ "rate_limits_exceeded": 0,
570
+ }
571
+
572
+ for result in results:
573
+ action = result["_id"]["action"]
574
+ _success = result["_id"]["success"] # noqa: F841 - extracted for potential future use
575
+ count = result["count"]
576
+
577
+ summary["total_events"] += count
578
+
579
+ if action == AuthAction.LOGIN_SUCCESS.value:
580
+ summary["login_success"] = count
581
+ elif action == AuthAction.LOGIN_FAILED.value:
582
+ summary["login_failed"] = count
583
+ elif action == AuthAction.REGISTER.value:
584
+ summary["registrations"] = count
585
+ elif action == AuthAction.LOGOUT.value:
586
+ summary["logouts"] = count
587
+ elif action == AuthAction.TOKEN_REVOKED.value:
588
+ summary["tokens_revoked"] = count
589
+ elif action == AuthAction.RATE_LIMIT_EXCEEDED.value:
590
+ summary["rate_limits_exceeded"] = count
591
+
592
+ return summary
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import logging
13
13
  from pathlib import Path
14
- from typing import TYPE_CHECKING, Any, Dict, Optional
14
+ from typing import TYPE_CHECKING, Any, Optional
15
15
 
16
16
  from .casbin_models import DEFAULT_RBAC_MODEL, SIMPLE_ACL_MODEL
17
17
 
@@ -43,9 +43,7 @@ def get_casbin_model(model_type: str = "rbac") -> str:
43
43
  if model_path.exists():
44
44
  return model_path.read_text()
45
45
  else:
46
- logger.warning(
47
- f"Casbin model file not found: {model_type}, using default RBAC model"
48
- )
46
+ logger.warning(f"Casbin model file not found: {model_type}, using default RBAC model")
49
47
  return DEFAULT_RBAC_MODEL
50
48
 
51
49
 
@@ -59,9 +57,9 @@ async def create_casbin_enforcer(
59
57
  Create a Casbin AsyncEnforcer with MongoDB adapter.
60
58
 
61
59
  Args:
62
- db: MongoDB database instance (Motor AsyncIOMotorDatabase)
60
+ db: Scoped MongoDB database instance (ScopedMongoWrapper)
63
61
  model: Casbin model type ("rbac", "acl") or path to model file
64
- policies_collection: MongoDB collection name for policies
62
+ policies_collection: MongoDB collection name for policies (will be app-scoped)
65
63
  default_roles: List of default roles to create (optional)
66
64
 
67
65
  Returns:
@@ -104,7 +102,7 @@ async def create_casbin_enforcer(
104
102
  return enforcer
105
103
 
106
104
 
107
- async def _create_default_roles(enforcer: "casbin.AsyncEnforcer", roles: list) -> None:
105
+ async def _create_default_roles(enforcer: casbin.AsyncEnforcer, roles: list) -> None:
108
106
  """
109
107
  Create default roles in Casbin (as grouping rules).
110
108
 
@@ -129,8 +127,8 @@ async def _create_default_roles(enforcer: "casbin.AsyncEnforcer", roles: list) -
129
127
 
130
128
 
131
129
  async def initialize_casbin_from_manifest(
132
- engine, app_slug: str, auth_config: Dict[str, Any]
133
- ) -> Optional["CasbinAdapter"]:
130
+ engine, app_slug: str, auth_config: dict[str, Any]
131
+ ) -> Optional[CasbinAdapter]:
134
132
  """
135
133
  Initialize Casbin provider from manifest configuration.
136
134
 
@@ -155,13 +153,11 @@ async def initialize_casbin_from_manifest(
155
153
  # Get authorization config
156
154
  authorization = auth_policy.get("authorization", {})
157
155
  model = authorization.get("model", "rbac")
158
- policies_collection = authorization.get(
159
- "policies_collection", "casbin_policies"
160
- )
156
+ policies_collection = authorization.get("policies_collection", "casbin_policies")
161
157
  default_roles = authorization.get("default_roles", [])
162
158
 
163
- # Get database from engine
164
- db = engine.get_database()
159
+ # Get scoped database from engine
160
+ db = engine.get_scoped_db(app_slug)
165
161
 
166
162
  # Create enforcer
167
163
  enforcer = await create_casbin_enforcer(
@@ -12,9 +12,12 @@ from typing import Any, Dict
12
12
 
13
13
  from fastapi import Request
14
14
 
15
- from .config_defaults import (CORS_DEFAULTS, OBSERVABILITY_DEFAULTS,
16
- SECURITY_CONFIG_DEFAULTS,
17
- TOKEN_MANAGEMENT_DEFAULTS)
15
+ from .config_defaults import (
16
+ CORS_DEFAULTS,
17
+ OBSERVABILITY_DEFAULTS,
18
+ SECURITY_CONFIG_DEFAULTS,
19
+ TOKEN_MANAGEMENT_DEFAULTS,
20
+ )
18
21
 
19
22
  logger = logging.getLogger(__name__)
20
23
 
@@ -132,9 +135,7 @@ def get_ip_validation_config(request: Request) -> Dict[str, Any]:
132
135
  IP validation configuration dictionary
133
136
  """
134
137
  security_config = get_security_config(request)
135
- return security_config.get(
136
- "ip_validation", SECURITY_CONFIG_DEFAULTS["ip_validation"].copy()
137
- )
138
+ return security_config.get("ip_validation", SECURITY_CONFIG_DEFAULTS["ip_validation"].copy())
138
139
 
139
140
 
140
141
  def get_token_fingerprinting_config(request: Request) -> Dict[str, Any]: