mdb-engine 0.1.7__tar.gz → 0.2.1__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 (106) hide show
  1. {mdb_engine-0.1.7/mdb_engine.egg-info → mdb_engine-0.2.1}/PKG-INFO +42 -14
  2. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/README.md +41 -13
  3. mdb_engine-0.2.1/mdb_engine/__init__.py +130 -0
  4. mdb_engine-0.2.1/mdb_engine/auth/ARCHITECTURE.md +112 -0
  5. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/README.md +125 -11
  6. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/__init__.py +7 -1
  7. mdb_engine-0.2.1/mdb_engine/auth/base.py +252 -0
  8. mdb_engine-0.2.1/mdb_engine/auth/casbin_factory.py +394 -0
  9. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/dependencies.py +10 -5
  10. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/integration.py +23 -7
  11. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/oso_factory.py +2 -2
  12. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/provider.py +263 -143
  13. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/engine.py +307 -6
  14. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/manifest.py +35 -15
  15. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/README.md +28 -1
  16. mdb_engine-0.2.1/mdb_engine/dependencies.py +426 -0
  17. mdb_engine-0.2.1/mdb_engine/di/__init__.py +34 -0
  18. mdb_engine-0.2.1/mdb_engine/di/container.py +248 -0
  19. mdb_engine-0.2.1/mdb_engine/di/providers.py +205 -0
  20. mdb_engine-0.2.1/mdb_engine/di/scopes.py +139 -0
  21. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/embeddings/README.md +54 -24
  22. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/embeddings/__init__.py +22 -23
  23. mdb_engine-0.2.1/mdb_engine/embeddings/dependencies.py +76 -0
  24. mdb_engine-0.2.1/mdb_engine/repositories/__init__.py +34 -0
  25. mdb_engine-0.2.1/mdb_engine/repositories/base.py +325 -0
  26. mdb_engine-0.2.1/mdb_engine/repositories/mongo.py +233 -0
  27. mdb_engine-0.2.1/mdb_engine/repositories/unit_of_work.py +166 -0
  28. {mdb_engine-0.1.7 → mdb_engine-0.2.1/mdb_engine.egg-info}/PKG-INFO +42 -14
  29. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine.egg-info/SOURCES.txt +11 -0
  30. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/pyproject.toml +1 -1
  31. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/setup.py +1 -1
  32. mdb_engine-0.1.7/mdb_engine/__init__.py +0 -69
  33. mdb_engine-0.1.7/mdb_engine/auth/casbin_factory.py +0 -195
  34. mdb_engine-0.1.7/mdb_engine/embeddings/dependencies.py +0 -191
  35. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/LICENSE +0 -0
  36. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/MANIFEST.in +0 -0
  37. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/README.md +0 -0
  38. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/audit.py +0 -0
  39. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/casbin_models.py +0 -0
  40. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/config_defaults.py +0 -0
  41. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/config_helpers.py +0 -0
  42. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/cookie_utils.py +0 -0
  43. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/csrf.py +0 -0
  44. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/decorators.py +0 -0
  45. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/helpers.py +0 -0
  46. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/jwt.py +0 -0
  47. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/middleware.py +0 -0
  48. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/rate_limiter.py +0 -0
  49. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/restrictions.py +0 -0
  50. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/session_manager.py +0 -0
  51. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/shared_middleware.py +0 -0
  52. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/shared_users.py +0 -0
  53. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/token_lifecycle.py +0 -0
  54. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/token_store.py +0 -0
  55. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/users.py +0 -0
  56. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/auth/utils.py +0 -0
  57. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/__init__.py +0 -0
  58. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/commands/__init__.py +0 -0
  59. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/commands/generate.py +0 -0
  60. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/commands/migrate.py +0 -0
  61. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/commands/show.py +0 -0
  62. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/commands/validate.py +0 -0
  63. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/main.py +0 -0
  64. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/cli/utils.py +0 -0
  65. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/config.py +0 -0
  66. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/constants.py +0 -0
  67. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/README.md +0 -0
  68. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/__init__.py +0 -0
  69. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/app_registration.py +0 -0
  70. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/app_secrets.py +0 -0
  71. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/connection.py +0 -0
  72. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/encryption.py +0 -0
  73. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/index_management.py +0 -0
  74. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/ray_integration.py +0 -0
  75. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/seeding.py +0 -0
  76. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/service_initialization.py +0 -0
  77. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/core/types.py +0 -0
  78. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/__init__.py +0 -0
  79. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/abstraction.py +0 -0
  80. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/connection.py +0 -0
  81. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/query_validator.py +0 -0
  82. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/resource_limiter.py +0 -0
  83. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/database/scoped_wrapper.py +0 -0
  84. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/embeddings/service.py +0 -0
  85. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/exceptions.py +0 -0
  86. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/indexes/README.md +0 -0
  87. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/indexes/__init__.py +0 -0
  88. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/indexes/helpers.py +0 -0
  89. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/indexes/manager.py +0 -0
  90. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/memory/README.md +0 -0
  91. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/memory/__init__.py +0 -0
  92. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/memory/service.py +0 -0
  93. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/observability/README.md +0 -0
  94. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/observability/__init__.py +0 -0
  95. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/observability/health.py +0 -0
  96. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/observability/logging.py +0 -0
  97. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/observability/metrics.py +0 -0
  98. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/routing/README.md +0 -0
  99. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/routing/__init__.py +0 -0
  100. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/routing/websockets.py +0 -0
  101. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine/utils/__init__.py +0 -0
  102. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine.egg-info/dependency_links.txt +0 -0
  103. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine.egg-info/entry_points.txt +0 -0
  104. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine.egg-info/requires.txt +0 -0
  105. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/mdb_engine.egg-info/top_level.txt +0 -0
  106. {mdb_engine-0.1.7 → mdb_engine-0.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdb-engine
3
- Version: 0.1.7
3
+ Version: 0.2.1
4
4
  Summary: MongoDB Engine
5
5
  Home-page: https://github.com/ranfysvalle02/mdb-engine
6
6
  Author: Your Name
@@ -85,7 +85,9 @@ pip install mdb-engine
85
85
 
86
86
  ```python
87
87
  from pathlib import Path
88
+ from fastapi import Depends
88
89
  from mdb_engine import MongoDBEngine
90
+ from mdb_engine.dependencies import get_scoped_db
89
91
 
90
92
  # 1. Initialize the engine
91
93
  engine = MongoDBEngine(
@@ -96,10 +98,9 @@ engine = MongoDBEngine(
96
98
  # 2. Create a FastAPI app with automatic lifecycle management
97
99
  app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
98
100
 
99
- # 3. Use the scoped database - all queries automatically isolated
101
+ # 3. Use request-scoped dependencies - all queries automatically isolated
100
102
  @app.post("/tasks")
101
- async def create_task(task: dict):
102
- db = engine.get_scoped_db("my_app")
103
+ async def create_task(task: dict, db=Depends(get_scoped_db)):
103
104
  result = await db.tasks.insert_one(task)
104
105
  return {"id": str(result.inserted_id)}
105
106
  ```
@@ -149,22 +150,29 @@ Supported index types: `regular`, `text`, `vector`, `ttl`, `compound`.
149
150
 
150
151
  ### 2. CRUD Operations (Auto-Scoped)
151
152
 
152
- All database operations are automatically scoped to your app:
153
+ All database operations are automatically scoped to your app. Use `Depends(get_scoped_db)` in route handlers:
153
154
 
154
155
  ```python
155
- db = engine.get_scoped_db("my_app")
156
+ from mdb_engine.dependencies import get_scoped_db
156
157
 
157
- # Create
158
- await db.tasks.insert_one({"title": "Build feature", "status": "pending"})
158
+ @app.post("/tasks")
159
+ async def create_task(task: dict, db=Depends(get_scoped_db)):
160
+ result = await db.tasks.insert_one(task)
161
+ return {"id": str(result.inserted_id)}
159
162
 
160
- # Read
161
- tasks = await db.tasks.find({"status": "pending"}).to_list(length=10)
163
+ @app.get("/tasks")
164
+ async def list_tasks(db=Depends(get_scoped_db)):
165
+ return await db.tasks.find({"status": "pending"}).to_list(length=10)
162
166
 
163
- # Update
164
- await db.tasks.update_one({"_id": task_id}, {"$set": {"status": "done"}})
167
+ @app.put("/tasks/{task_id}")
168
+ async def update_task(task_id: str, db=Depends(get_scoped_db)):
169
+ await db.tasks.update_one({"_id": task_id}, {"$set": {"status": "done"}})
170
+ return {"updated": True}
165
171
 
166
- # Delete
167
- await db.tasks.delete_one({"_id": task_id})
172
+ @app.delete("/tasks/{task_id}")
173
+ async def delete_task(task_id: str, db=Depends(get_scoped_db)):
174
+ await db.tasks.delete_one({"_id": task_id})
175
+ return {"deleted": True}
168
176
  ```
169
177
 
170
178
  **What happens under the hood:**
@@ -210,6 +218,26 @@ async def health():
210
218
  | **Multi-App** | Secure cross-app data access | [Multi-App Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app) |
211
219
  | **SSO** | Shared auth across apps | [Shared Auth Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app_shared) |
212
220
 
221
+ ### AppContext — All Services in One Place ✨
222
+
223
+ ```python
224
+ from fastapi import Depends
225
+ from mdb_engine.dependencies import AppContext
226
+
227
+ @app.post("/ai-chat")
228
+ async def chat(query: str, ctx: AppContext = Depends()):
229
+ user = ctx.require_user() # 401 if not logged in
230
+ ctx.require_role("user") # 403 if missing role
231
+
232
+ # Everything available: ctx.db, ctx.embedding_service, ctx.memory, ctx.llm
233
+ if ctx.llm:
234
+ response = ctx.llm.chat.completions.create(
235
+ model=ctx.llm_model,
236
+ messages=[{"role": "user", "content": query}]
237
+ )
238
+ return {"response": response.choices[0].message.content}
239
+ ```
240
+
213
241
  ---
214
242
 
215
243
  ## Full Examples
@@ -20,7 +20,9 @@ pip install mdb-engine
20
20
 
21
21
  ```python
22
22
  from pathlib import Path
23
+ from fastapi import Depends
23
24
  from mdb_engine import MongoDBEngine
25
+ from mdb_engine.dependencies import get_scoped_db
24
26
 
25
27
  # 1. Initialize the engine
26
28
  engine = MongoDBEngine(
@@ -31,10 +33,9 @@ engine = MongoDBEngine(
31
33
  # 2. Create a FastAPI app with automatic lifecycle management
32
34
  app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
33
35
 
34
- # 3. Use the scoped database - all queries automatically isolated
36
+ # 3. Use request-scoped dependencies - all queries automatically isolated
35
37
  @app.post("/tasks")
36
- async def create_task(task: dict):
37
- db = engine.get_scoped_db("my_app")
38
+ async def create_task(task: dict, db=Depends(get_scoped_db)):
38
39
  result = await db.tasks.insert_one(task)
39
40
  return {"id": str(result.inserted_id)}
40
41
  ```
@@ -84,22 +85,29 @@ Supported index types: `regular`, `text`, `vector`, `ttl`, `compound`.
84
85
 
85
86
  ### 2. CRUD Operations (Auto-Scoped)
86
87
 
87
- All database operations are automatically scoped to your app:
88
+ All database operations are automatically scoped to your app. Use `Depends(get_scoped_db)` in route handlers:
88
89
 
89
90
  ```python
90
- db = engine.get_scoped_db("my_app")
91
+ from mdb_engine.dependencies import get_scoped_db
91
92
 
92
- # Create
93
- await db.tasks.insert_one({"title": "Build feature", "status": "pending"})
93
+ @app.post("/tasks")
94
+ async def create_task(task: dict, db=Depends(get_scoped_db)):
95
+ result = await db.tasks.insert_one(task)
96
+ return {"id": str(result.inserted_id)}
94
97
 
95
- # Read
96
- tasks = await db.tasks.find({"status": "pending"}).to_list(length=10)
98
+ @app.get("/tasks")
99
+ async def list_tasks(db=Depends(get_scoped_db)):
100
+ return await db.tasks.find({"status": "pending"}).to_list(length=10)
97
101
 
98
- # Update
99
- await db.tasks.update_one({"_id": task_id}, {"$set": {"status": "done"}})
102
+ @app.put("/tasks/{task_id}")
103
+ async def update_task(task_id: str, db=Depends(get_scoped_db)):
104
+ await db.tasks.update_one({"_id": task_id}, {"$set": {"status": "done"}})
105
+ return {"updated": True}
100
106
 
101
- # Delete
102
- await db.tasks.delete_one({"_id": task_id})
107
+ @app.delete("/tasks/{task_id}")
108
+ async def delete_task(task_id: str, db=Depends(get_scoped_db)):
109
+ await db.tasks.delete_one({"_id": task_id})
110
+ return {"deleted": True}
103
111
  ```
104
112
 
105
113
  **What happens under the hood:**
@@ -145,6 +153,26 @@ async def health():
145
153
  | **Multi-App** | Secure cross-app data access | [Multi-App Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app) |
146
154
  | **SSO** | Shared auth across apps | [Shared Auth Example](https://github.com/ranfysvalle02/mdb-engine/tree/main/examples/advanced/multi_app_shared) |
147
155
 
156
+ ### AppContext — All Services in One Place ✨
157
+
158
+ ```python
159
+ from fastapi import Depends
160
+ from mdb_engine.dependencies import AppContext
161
+
162
+ @app.post("/ai-chat")
163
+ async def chat(query: str, ctx: AppContext = Depends()):
164
+ user = ctx.require_user() # 401 if not logged in
165
+ ctx.require_role("user") # 403 if missing role
166
+
167
+ # Everything available: ctx.db, ctx.embedding_service, ctx.memory, ctx.llm
168
+ if ctx.llm:
169
+ response = ctx.llm.chat.completions.create(
170
+ model=ctx.llm_model,
171
+ messages=[{"role": "user", "content": query}]
172
+ )
173
+ return {"response": response.choices[0].message.content}
174
+ ```
175
+
148
176
  ---
149
177
 
150
178
  ## Full Examples
@@ -0,0 +1,130 @@
1
+ """
2
+ MDB_ENGINE - MongoDB Engine
3
+
4
+ Enterprise-grade engine for building applications with:
5
+ - Automatic database scoping and data isolation
6
+ - Proper dependency injection with service lifetimes
7
+ - Repository pattern for clean data access
8
+ - Authentication and authorization
9
+
10
+ Usage:
11
+ # Simple usage
12
+ from mdb_engine import MongoDBEngine
13
+ engine = MongoDBEngine(mongo_uri=..., db_name=...)
14
+ await engine.initialize()
15
+ db = engine.get_scoped_db("my_app")
16
+
17
+ # With FastAPI integration
18
+ app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
19
+
20
+ # In routes - use RequestContext for clean DI
21
+ from mdb_engine import RequestContext
22
+
23
+ @app.get("/users/{user_id}")
24
+ async def get_user(user_id: str, ctx: RequestContext = Depends()):
25
+ user = await ctx.uow.users.get(user_id)
26
+ return user
27
+ """
28
+
29
+ # Authentication
30
+ from .auth import AuthorizationProvider, require_admin
31
+ from .auth import get_current_user as auth_get_current_user # noqa: F401
32
+
33
+ # Core MongoDB Engine
34
+ from .core import (
35
+ RAY_AVAILABLE,
36
+ AppRayActor,
37
+ ManifestParser,
38
+ ManifestValidator,
39
+ MongoDBEngine,
40
+ get_ray_actor_handle,
41
+ ray_actor_decorator,
42
+ )
43
+
44
+ # Database layer
45
+ from .database import AppDB, ScopedMongoWrapper
46
+
47
+ # FastAPI dependencies
48
+ from .dependencies import (
49
+ Inject,
50
+ RequestContext,
51
+ get_app_config,
52
+ get_app_slug,
53
+ get_authz_provider,
54
+ get_current_user,
55
+ get_embedding_service,
56
+ get_engine,
57
+ get_llm_client,
58
+ get_llm_model_name,
59
+ get_memory_service,
60
+ get_scoped_db,
61
+ get_unit_of_work,
62
+ get_user_roles,
63
+ inject,
64
+ require_role,
65
+ require_user,
66
+ )
67
+
68
+ # DI Container
69
+ from .di import Container, Scope, ScopeManager
70
+
71
+ # Index management
72
+ from .indexes import (
73
+ AsyncAtlasIndexManager,
74
+ AutoIndexManager,
75
+ run_index_creation_for_collection,
76
+ )
77
+
78
+ # Repository pattern
79
+ from .repositories import Entity, MongoRepository, Repository, UnitOfWork
80
+
81
+ __version__ = "0.2.1" # Major version bump for new DI system
82
+
83
+ __all__ = [
84
+ # Core Engine
85
+ "MongoDBEngine",
86
+ "ManifestValidator",
87
+ "ManifestParser",
88
+ # Ray Integration (optional)
89
+ "RAY_AVAILABLE",
90
+ "AppRayActor",
91
+ "get_ray_actor_handle",
92
+ "ray_actor_decorator",
93
+ # Database
94
+ "ScopedMongoWrapper",
95
+ "AppDB",
96
+ # DI Container
97
+ "Container",
98
+ "Scope",
99
+ "ScopeManager",
100
+ # Repository Pattern
101
+ "Repository",
102
+ "MongoRepository",
103
+ "Entity",
104
+ "UnitOfWork",
105
+ # Auth
106
+ "AuthorizationProvider",
107
+ "require_admin",
108
+ # FastAPI Dependencies
109
+ "RequestContext",
110
+ "get_engine",
111
+ "get_app_slug",
112
+ "get_app_config",
113
+ "get_scoped_db",
114
+ "get_unit_of_work",
115
+ "get_embedding_service",
116
+ "get_memory_service",
117
+ "get_llm_client",
118
+ "get_llm_model_name",
119
+ "get_authz_provider",
120
+ "get_current_user",
121
+ "get_user_roles",
122
+ "require_user",
123
+ "require_role",
124
+ "inject",
125
+ "Inject",
126
+ # Indexes
127
+ "AsyncAtlasIndexManager",
128
+ "AutoIndexManager",
129
+ "run_index_creation_for_collection",
130
+ ]
@@ -0,0 +1,112 @@
1
+ # Authorization Provider Architecture
2
+
3
+ ## Overview
4
+
5
+ The authorization system uses the **Adapter Pattern** with a strict abstract base class to ensure type safety, fail-closed security, and proper abstraction from third-party libraries.
6
+
7
+ ## Design Principles
8
+
9
+ 1. **Adapter Pattern**: We wrap third-party libraries (Casbin, OSO) without modifying their source code
10
+ 2. **Fail-Closed Security**: If authorization evaluation fails, access is denied (never granted)
11
+ 3. **Type Safety**: Clear contracts with proper type hints and abstract methods
12
+ 4. **Interface Segregation**: Application code only needs `check()`, not engine internals
13
+ 5. **Observability**: Structured logging for all authorization decisions and errors
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ BaseAuthorizationProvider (ABC)
19
+ ├── CasbinAdapter
20
+ │ └── Wraps casbin.AsyncEnforcer
21
+ └── OsoAdapter
22
+ └── Wraps oso_cloud.Client or oso.Oso
23
+ ```
24
+
25
+ ## Base Class: `BaseAuthorizationProvider`
26
+
27
+ Defines the contract that all authorization providers must implement:
28
+
29
+ - `check(subject, resource, action)` - Primary authorization decision method
30
+ - `add_policy(*params)` - Add policy rules
31
+ - `add_role_for_user(*params)` - Assign roles to users
32
+ - `save_policy()` - Persist policies to storage
33
+ - `has_policy(*params)` - Check if policy exists
34
+ - `has_role_for_user(*params)` - Check if user has role
35
+ - `clear_cache()` - Clear authorization cache
36
+
37
+ ### Fail-Closed Security
38
+
39
+ All `check()` implementations must:
40
+ 1. Catch all exceptions during evaluation
41
+ 2. Log errors with full context
42
+ 3. Return `False` (deny access) on any error
43
+ 4. Never raise exceptions from evaluation failures
44
+
45
+ ### Error Handling
46
+
47
+ - **Evaluation Errors**: Handled by `_handle_evaluation_error()` - denies access, logs critically
48
+ - **Operation Errors**: Handled by `_handle_operation_error()` - returns False, logs warning
49
+
50
+ ## CasbinAdapter
51
+
52
+ Wraps `casbin.AsyncEnforcer` and handles:
53
+ - Casbin's `(subject, object, action)` format
54
+ - Thread pool execution to prevent blocking
55
+ - Caching for performance
56
+ - MongoDB persistence via MotorAdapter
57
+
58
+ ### Format Mapping
59
+
60
+ - `check(subject, resource, action)` → `enforcer.enforce(subject, resource, action)`
61
+ - `add_policy(role, resource, action)` → `enforcer.add_policy(role, resource, action)`
62
+ - `add_role_for_user(user, role)` → `enforcer.add_role_for_user(user, role)`
63
+
64
+ ## OsoAdapter
65
+
66
+ Wraps OSO Cloud client and handles:
67
+ - OSO's `authorize(actor, action, resource)` format
68
+ - Type marshalling (strings → TypedObject)
69
+ - Thread pool execution
70
+ - Caching for performance
71
+
72
+ ### Format Mapping
73
+
74
+ - `check(subject, resource, action)` → `client.authorize(TypedObject("User", subject), action, TypedObject("Document", resource))`
75
+ - `add_policy(role, resource, action)` → `client.insert(["grants_permission", role, action, resource])`
76
+ - `add_role_for_user(user, role, [resource])` → `client.insert(["has_role", Value("User", user), role, Value("Document", resource)])`
77
+
78
+ ## Backward Compatibility
79
+
80
+ The `AuthorizationProvider` Protocol is maintained for backward compatibility. All adapters implement both:
81
+ - The Protocol (for structural typing)
82
+ - The BaseAuthorizationProvider ABC (for inheritance)
83
+
84
+ ## Usage
85
+
86
+ ```python
87
+ from mdb_engine.auth import BaseAuthorizationProvider, CasbinAdapter, OsoAdapter
88
+
89
+ # Type checking works with both Protocol and ABC
90
+ async def check_permission(
91
+ provider: BaseAuthorizationProvider, # or AuthorizationProvider
92
+ user: str,
93
+ resource: str,
94
+ action: str,
95
+ ) -> bool:
96
+ return await provider.check(user, resource, action)
97
+
98
+ # Runtime type checking
99
+ if provider.is_casbin():
100
+ # Casbin-specific operations
101
+ enforcer = provider._enforcer # Still accessible
102
+ elif provider.is_oso():
103
+ # OSO-specific operations
104
+ client = provider._oso # Still accessible
105
+ ```
106
+
107
+ ## Migration Notes
108
+
109
+ - Existing code using `AuthorizationProvider` Protocol continues to work
110
+ - Code checking `hasattr(provider, "_enforcer")` continues to work
111
+ - New code should use `BaseAuthorizationProvider` for better type safety
112
+ - Helper methods `is_casbin()` and `is_oso()` available for type checking
@@ -926,18 +926,132 @@ CSRF protection is **auto-enabled for shared auth mode**. The middleware uses th
926
926
  4. SameSite=Lax cookies provide additional protection
927
927
 
928
928
  **Frontend Integration:**
929
+
930
+ Helper function for reading cookies:
931
+
932
+ ```javascript
933
+ // Reusable cookie helper
934
+ function getCookie(name) {
935
+ const value = `; ${document.cookie}`;
936
+ const parts = value.split(`; ${name}=`);
937
+ if (parts.length === 2) return parts.pop().split(';').shift();
938
+ return null;
939
+ }
940
+ ```
941
+
942
+ Include in all state-changing requests:
943
+
944
+ ```javascript
945
+ // POST request with CSRF token
946
+ async function createItem(data) {
947
+ const response = await fetch('/api/items', {
948
+ method: 'POST',
949
+ headers: {
950
+ 'Content-Type': 'application/json',
951
+ 'X-CSRF-Token': getCookie('csrf_token')
952
+ },
953
+ credentials: 'same-origin',
954
+ body: JSON.stringify(data)
955
+ });
956
+ return response.json();
957
+ }
958
+
959
+ // DELETE request with CSRF token
960
+ async function deleteItem(id) {
961
+ const response = await fetch(`/api/items/${id}`, {
962
+ method: 'DELETE',
963
+ headers: {
964
+ 'X-CSRF-Token': getCookie('csrf_token')
965
+ },
966
+ credentials: 'same-origin'
967
+ });
968
+ return response.json();
969
+ }
970
+ ```
971
+
972
+ **Logout Must Be POST:**
973
+
974
+ For security, logout endpoints should use POST method, not GET:
975
+
976
+ ```javascript
977
+ // Correct: POST with CSRF token
978
+ async function logout() {
979
+ const response = await fetch('/logout', {
980
+ method: 'POST',
981
+ headers: {
982
+ 'X-CSRF-Token': getCookie('csrf_token')
983
+ },
984
+ credentials: 'same-origin'
985
+ });
986
+
987
+ const result = await response.json();
988
+ if (result.success) {
989
+ window.location.href = result.redirect || '/login';
990
+ }
991
+ }
992
+ ```
993
+
994
+ Backend endpoint:
995
+
996
+ ```python
997
+ @app.post("/logout")
998
+ async def logout(request: Request):
999
+ """Logout must be POST with CSRF token."""
1000
+ response = JSONResponse({"success": True, "redirect": "/login"})
1001
+ response = await logout_user(request, response)
1002
+ return response
1003
+ ```
1004
+
1005
+ **Login/Register JSON Pattern:**
1006
+
1007
+ Return JSON responses for AJAX forms:
1008
+
1009
+ ```python
1010
+ @app.post("/login")
1011
+ async def login(request: Request, email: str = Form(...), password: str = Form(...)):
1012
+ """Login returning JSON for JavaScript frontend."""
1013
+ result = await authenticate_user(email, password)
1014
+
1015
+ if result["success"]:
1016
+ json_response = JSONResponse({"success": True, "redirect": "/dashboard"})
1017
+ # Copy auth cookies from result
1018
+ for key, value in result["response"].headers.items():
1019
+ if key.lower() == "set-cookie":
1020
+ json_response.headers.append(key, value)
1021
+ return json_response
1022
+
1023
+ return JSONResponse(
1024
+ {"success": False, "detail": result.get("error", "Login failed")},
1025
+ status_code=401
1026
+ )
1027
+ ```
1028
+
1029
+ **Error Handling:**
1030
+
1031
+ Handle CSRF validation failures (403 status):
1032
+
929
1033
  ```javascript
930
- // Read token from cookie
931
- const csrfToken = document.cookie
932
- .split('; ')
933
- .find(row => row.startsWith('csrf_token='))
934
- ?.split('=')[1];
935
-
936
- // Include in requests
937
- fetch('/api/submit', {
938
- method: 'POST',
939
- headers: {'X-CSRF-Token': csrfToken}
940
- });
1034
+ async function secureRequest(url, options = {}) {
1035
+ const response = await fetch(url, {
1036
+ ...options,
1037
+ headers: {
1038
+ ...options.headers,
1039
+ 'X-CSRF-Token': getCookie('csrf_token')
1040
+ },
1041
+ credentials: 'same-origin'
1042
+ });
1043
+
1044
+ if (response.status === 403) {
1045
+ const data = await response.json();
1046
+ if (data.detail?.includes('CSRF')) {
1047
+ // Token expired - refresh the page
1048
+ window.location.reload();
1049
+ return null;
1050
+ }
1051
+ }
1052
+
1053
+ return response;
1054
+ }
941
1055
  ```
942
1056
 
943
1057
  ### HSTS (HTTP Strict Transport Security)
@@ -9,6 +9,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
9
9
  # Audit logging
10
10
  from .audit import AuthAction, AuthAuditLog
11
11
 
12
+ # Base classes
13
+ from .base import AuthorizationError, BaseAuthorizationProvider
14
+
12
15
  # Casbin Factory
13
16
  from .casbin_factory import (
14
17
  create_casbin_enforcer,
@@ -123,7 +126,10 @@ from .utils import (
123
126
  )
124
127
 
125
128
  __all__ = [
126
- # Provider
129
+ # Base classes
130
+ "BaseAuthorizationProvider",
131
+ "AuthorizationError",
132
+ # Provider (Protocol for backward compatibility)
127
133
  "AuthorizationProvider",
128
134
  "CasbinAdapter",
129
135
  "OsoAdapter",