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.
- mdb_engine/README.md +144 -0
- mdb_engine/__init__.py +37 -0
- mdb_engine/auth/README.md +631 -0
- mdb_engine/auth/__init__.py +128 -0
- mdb_engine/auth/casbin_factory.py +199 -0
- mdb_engine/auth/casbin_models.py +46 -0
- mdb_engine/auth/config_defaults.py +71 -0
- mdb_engine/auth/config_helpers.py +213 -0
- mdb_engine/auth/cookie_utils.py +158 -0
- mdb_engine/auth/decorators.py +350 -0
- mdb_engine/auth/dependencies.py +747 -0
- mdb_engine/auth/helpers.py +64 -0
- mdb_engine/auth/integration.py +578 -0
- mdb_engine/auth/jwt.py +225 -0
- mdb_engine/auth/middleware.py +241 -0
- mdb_engine/auth/oso_factory.py +323 -0
- mdb_engine/auth/provider.py +570 -0
- mdb_engine/auth/restrictions.py +271 -0
- mdb_engine/auth/session_manager.py +477 -0
- mdb_engine/auth/token_lifecycle.py +213 -0
- mdb_engine/auth/token_store.py +289 -0
- mdb_engine/auth/users.py +1516 -0
- mdb_engine/auth/utils.py +614 -0
- mdb_engine/cli/__init__.py +13 -0
- mdb_engine/cli/commands/__init__.py +7 -0
- mdb_engine/cli/commands/generate.py +105 -0
- mdb_engine/cli/commands/migrate.py +83 -0
- mdb_engine/cli/commands/show.py +70 -0
- mdb_engine/cli/commands/validate.py +63 -0
- mdb_engine/cli/main.py +41 -0
- mdb_engine/cli/utils.py +92 -0
- mdb_engine/config.py +217 -0
- mdb_engine/constants.py +160 -0
- mdb_engine/core/README.md +542 -0
- mdb_engine/core/__init__.py +42 -0
- mdb_engine/core/app_registration.py +392 -0
- mdb_engine/core/connection.py +243 -0
- mdb_engine/core/engine.py +749 -0
- mdb_engine/core/index_management.py +162 -0
- mdb_engine/core/manifest.py +2793 -0
- mdb_engine/core/seeding.py +179 -0
- mdb_engine/core/service_initialization.py +355 -0
- mdb_engine/core/types.py +413 -0
- mdb_engine/database/README.md +522 -0
- mdb_engine/database/__init__.py +31 -0
- mdb_engine/database/abstraction.py +635 -0
- mdb_engine/database/connection.py +387 -0
- mdb_engine/database/scoped_wrapper.py +1721 -0
- mdb_engine/embeddings/README.md +184 -0
- mdb_engine/embeddings/__init__.py +62 -0
- mdb_engine/embeddings/dependencies.py +193 -0
- mdb_engine/embeddings/service.py +759 -0
- mdb_engine/exceptions.py +167 -0
- mdb_engine/indexes/README.md +651 -0
- mdb_engine/indexes/__init__.py +21 -0
- mdb_engine/indexes/helpers.py +145 -0
- mdb_engine/indexes/manager.py +895 -0
- mdb_engine/memory/README.md +451 -0
- mdb_engine/memory/__init__.py +30 -0
- mdb_engine/memory/service.py +1285 -0
- mdb_engine/observability/README.md +515 -0
- mdb_engine/observability/__init__.py +42 -0
- mdb_engine/observability/health.py +296 -0
- mdb_engine/observability/logging.py +161 -0
- mdb_engine/observability/metrics.py +297 -0
- mdb_engine/routing/README.md +462 -0
- mdb_engine/routing/__init__.py +73 -0
- mdb_engine/routing/websockets.py +813 -0
- mdb_engine/utils/__init__.py +7 -0
- mdb_engine-0.1.6.dist-info/METADATA +213 -0
- mdb_engine-0.1.6.dist-info/RECORD +75 -0
- mdb_engine-0.1.6.dist-info/WHEEL +5 -0
- mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
- mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
- mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
App Authentication Restrictions
|
|
3
|
+
|
|
4
|
+
This module provides reusable decorators and dependencies for restricting
|
|
5
|
+
demo users from certain endpoints across apps.
|
|
6
|
+
|
|
7
|
+
Pattern:
|
|
8
|
+
- Demo users are "trapped" in their demo role for security
|
|
9
|
+
- They cannot access authentication routes (login/register/logout)
|
|
10
|
+
- They cannot create new content (projects, etc.)
|
|
11
|
+
- They can only view and interact with pre-seeded demo content
|
|
12
|
+
|
|
13
|
+
This ensures demo users remain in a safe, read-only demo environment.
|
|
14
|
+
|
|
15
|
+
This module is part of MDB_ENGINE - MongoDB Engine.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
20
|
+
|
|
21
|
+
from fastapi import HTTPException, Request, status
|
|
22
|
+
|
|
23
|
+
from ..config import DEMO_EMAIL_DEFAULT
|
|
24
|
+
from .dependencies import get_current_user_from_request
|
|
25
|
+
from .users import get_app_user
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_demo_user(
|
|
31
|
+
user: Optional[Dict[str, Any]] = None, email: Optional[str] = None
|
|
32
|
+
) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Check if a user is a demo user.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
user: User dict (from authentication)
|
|
38
|
+
email: Email address (optional, for fallback check)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
bool: True if user is a demo user, False otherwise
|
|
42
|
+
"""
|
|
43
|
+
if user:
|
|
44
|
+
# Check user flags
|
|
45
|
+
if user.get("is_demo") or user.get("demo_mode"):
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# Check email
|
|
49
|
+
user_email = user.get("email", "")
|
|
50
|
+
if user_email == DEMO_EMAIL_DEFAULT or user_email.startswith("demo@"):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
if email:
|
|
54
|
+
if email == DEMO_EMAIL_DEFAULT or email.startswith("demo@"):
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def _get_platform_user(request: Request) -> Optional[Dict[str, Any]]:
|
|
61
|
+
"""Try to get user from platform authentication."""
|
|
62
|
+
try:
|
|
63
|
+
platform_user = await get_current_user_from_request(request)
|
|
64
|
+
return platform_user if platform_user else None
|
|
65
|
+
except HTTPException:
|
|
66
|
+
return None # Not authenticated via platform
|
|
67
|
+
except (ValueError, TypeError, AttributeError, KeyError, RuntimeError) as e:
|
|
68
|
+
logger.debug(f"Error checking platform auth: {e}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _get_sub_auth_user(
|
|
73
|
+
request: Request,
|
|
74
|
+
slug_id: str,
|
|
75
|
+
get_app_config_func: Callable[[Request, str, Dict], Awaitable[Dict]],
|
|
76
|
+
get_app_db_func: Callable[[Request], Awaitable[Any]],
|
|
77
|
+
) -> Optional[Dict[str, Any]]:
|
|
78
|
+
"""Try to get user from sub-authentication."""
|
|
79
|
+
try:
|
|
80
|
+
db = await get_app_db_func(request)
|
|
81
|
+
config = await get_app_config_func(request, slug_id, {"auth": 1})
|
|
82
|
+
|
|
83
|
+
auth = config.get("auth", {}) if config else {}
|
|
84
|
+
users_config = auth.get("users", {})
|
|
85
|
+
if not (config and users_config.get("enabled", False)):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
app_user = await get_app_user(
|
|
89
|
+
request, slug_id, db, config, allow_demo_fallback=False
|
|
90
|
+
)
|
|
91
|
+
if not app_user:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"user_id": str(app_user.get("_id")),
|
|
96
|
+
"email": app_user.get("email"),
|
|
97
|
+
"app_user_id": str(app_user.get("_id")),
|
|
98
|
+
"is_demo": app_user.get("is_demo", False),
|
|
99
|
+
"demo_mode": app_user.get("demo_mode", False),
|
|
100
|
+
}
|
|
101
|
+
except HTTPException:
|
|
102
|
+
return None # Not authenticated
|
|
103
|
+
except (ValueError, TypeError, AttributeError, KeyError, RuntimeError) as e:
|
|
104
|
+
logger.debug(f"Error checking sub-auth: {e}")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def _get_authenticated_user(
|
|
109
|
+
request: Request,
|
|
110
|
+
slug_id: str,
|
|
111
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
|
|
112
|
+
get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]],
|
|
113
|
+
) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""Get authenticated user from platform or sub-auth."""
|
|
115
|
+
# Try platform auth first
|
|
116
|
+
user = await _get_platform_user(request)
|
|
117
|
+
if user:
|
|
118
|
+
return user
|
|
119
|
+
|
|
120
|
+
# Try sub-auth if platform auth didn't work
|
|
121
|
+
if get_app_db_func and get_app_config_func:
|
|
122
|
+
return await _get_sub_auth_user(
|
|
123
|
+
request, slug_id, get_app_config_func, get_app_db_func
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _validate_slug_id(request: Request) -> str:
|
|
130
|
+
"""Validate and return slug_id from request state."""
|
|
131
|
+
slug_id = getattr(request.state, "slug_id", None)
|
|
132
|
+
if not slug_id:
|
|
133
|
+
raise HTTPException(
|
|
134
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
135
|
+
detail="App slug_id not found in request state",
|
|
136
|
+
)
|
|
137
|
+
return slug_id
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _validate_dependencies(
|
|
141
|
+
get_app_config_func: Optional[Callable[[Request, str, Dict], Awaitable[Dict]]],
|
|
142
|
+
get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]],
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Validate that required dependencies are provided."""
|
|
145
|
+
if not get_app_db_func:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
"get_app_db_func must be provided. "
|
|
148
|
+
"Provide a callable that takes a Request and returns "
|
|
149
|
+
"AppDB or ScopedMongoWrapper."
|
|
150
|
+
)
|
|
151
|
+
if not get_app_config_func:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"get_app_config_func must be provided. "
|
|
154
|
+
"Provide a callable that takes (Request, slug_id, options) "
|
|
155
|
+
"and returns config dict."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def require_non_demo_user(
|
|
160
|
+
request: Request,
|
|
161
|
+
get_app_config_func: Optional[
|
|
162
|
+
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
163
|
+
] = None,
|
|
164
|
+
get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]] = None,
|
|
165
|
+
) -> Dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
FastAPI dependency that blocks demo users from accessing an endpoint.
|
|
168
|
+
|
|
169
|
+
This is a reusable dependency that apps can use to restrict
|
|
170
|
+
certain endpoints from demo users (e.g., login, register, logout, create).
|
|
171
|
+
|
|
172
|
+
Usage:
|
|
173
|
+
@bp.post("/api/projects")
|
|
174
|
+
async def create_project(
|
|
175
|
+
request: Request,
|
|
176
|
+
user: Dict[str, Any] = Depends(require_non_demo_user)
|
|
177
|
+
):
|
|
178
|
+
# user is guaranteed to NOT be a demo user
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
HTTPException: 403 Forbidden if user is a demo user
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Dict[str, Any]: User dict (guaranteed to be non-demo)
|
|
186
|
+
"""
|
|
187
|
+
slug_id = _validate_slug_id(request)
|
|
188
|
+
|
|
189
|
+
if get_app_db_func and get_app_config_func:
|
|
190
|
+
_validate_dependencies(get_app_config_func, get_app_db_func)
|
|
191
|
+
|
|
192
|
+
user = await _get_authenticated_user(
|
|
193
|
+
request, slug_id, get_app_config_func, get_app_db_func
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Check if user is demo
|
|
197
|
+
if user and is_demo_user(user):
|
|
198
|
+
logger.info(
|
|
199
|
+
f"Demo user '{user.get('email')}' blocked from accessing "
|
|
200
|
+
f"restricted endpoint: {request.url.path}"
|
|
201
|
+
)
|
|
202
|
+
raise HTTPException(
|
|
203
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
204
|
+
detail="Demo users cannot access this endpoint. Demo mode is read-only.",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# If no user found, raise unauthorized
|
|
208
|
+
if not user:
|
|
209
|
+
raise HTTPException(
|
|
210
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return user
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def block_demo_users(
|
|
217
|
+
request: Request,
|
|
218
|
+
get_app_config_func: Optional[
|
|
219
|
+
Callable[[Request, str, Dict], Awaitable[Dict]]
|
|
220
|
+
] = None,
|
|
221
|
+
get_app_db_func: Optional[Callable[[Request], Awaitable[Any]]] = None,
|
|
222
|
+
):
|
|
223
|
+
"""
|
|
224
|
+
FastAPI dependency that blocks demo users and returns an error response.
|
|
225
|
+
|
|
226
|
+
This dependency can be used in routes that should reject demo users
|
|
227
|
+
with a clear error message. If no user is authenticated, it allows access
|
|
228
|
+
(useful for login/register pages where unauthenticated users should be allowed).
|
|
229
|
+
|
|
230
|
+
Usage:
|
|
231
|
+
@bp.post("/logout")
|
|
232
|
+
async def logout(
|
|
233
|
+
request: Request,
|
|
234
|
+
_ = Depends(block_demo_users)
|
|
235
|
+
):
|
|
236
|
+
# This route is blocked for demo users, but allows unauthenticated users
|
|
237
|
+
...
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
HTTPException: 403 Forbidden if user is a demo user
|
|
241
|
+
|
|
242
|
+
Note: This dependency doesn't return a user object, it just blocks demo users.
|
|
243
|
+
For routes that need the user object, use `require_non_demo_user` instead.
|
|
244
|
+
|
|
245
|
+
Important: This allows unauthenticated users to pass through (useful for login/register pages).
|
|
246
|
+
Only authenticated demo users are blocked.
|
|
247
|
+
"""
|
|
248
|
+
slug_id = getattr(request.state, "slug_id", None)
|
|
249
|
+
|
|
250
|
+
# Try to get user to check if they're demo
|
|
251
|
+
# If no user is found, allow access (for login/register pages)
|
|
252
|
+
user = await _get_platform_user(request)
|
|
253
|
+
|
|
254
|
+
# Check sub-auth if platform auth didn't work
|
|
255
|
+
if not user and get_app_db_func and get_app_config_func and slug_id:
|
|
256
|
+
user = await _get_sub_auth_user(
|
|
257
|
+
request, slug_id, get_app_config_func, get_app_db_func
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Block if demo user (only if user exists)
|
|
261
|
+
if user and is_demo_user(user):
|
|
262
|
+
logger.info(
|
|
263
|
+
f"Demo user '{user.get('email')}' blocked from accessing: {request.url.path}"
|
|
264
|
+
)
|
|
265
|
+
raise HTTPException(
|
|
266
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
267
|
+
detail="Demo users cannot access this endpoint. Demo mode is read-only.",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Allow access (either no user or non-demo user)
|
|
271
|
+
return None
|