mdb-engine 0.2.1__py3-none-any.whl → 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mdb_engine/__init__.py +7 -1
- mdb_engine/auth/README.md +6 -0
- mdb_engine/auth/audit.py +40 -40
- mdb_engine/auth/base.py +3 -3
- mdb_engine/auth/casbin_factory.py +6 -6
- mdb_engine/auth/config_defaults.py +5 -5
- mdb_engine/auth/config_helpers.py +12 -12
- mdb_engine/auth/cookie_utils.py +9 -9
- mdb_engine/auth/csrf.py +9 -8
- mdb_engine/auth/decorators.py +7 -6
- mdb_engine/auth/dependencies.py +22 -21
- mdb_engine/auth/integration.py +9 -9
- mdb_engine/auth/jwt.py +9 -9
- mdb_engine/auth/middleware.py +4 -3
- mdb_engine/auth/oso_factory.py +6 -6
- mdb_engine/auth/provider.py +4 -4
- mdb_engine/auth/rate_limiter.py +12 -11
- mdb_engine/auth/restrictions.py +16 -15
- mdb_engine/auth/session_manager.py +11 -13
- mdb_engine/auth/shared_middleware.py +344 -132
- mdb_engine/auth/shared_users.py +20 -20
- mdb_engine/auth/token_lifecycle.py +10 -12
- mdb_engine/auth/token_store.py +4 -5
- mdb_engine/auth/users.py +51 -52
- mdb_engine/auth/utils.py +29 -33
- mdb_engine/cli/commands/generate.py +6 -6
- mdb_engine/cli/utils.py +4 -4
- mdb_engine/config.py +6 -7
- mdb_engine/core/app_registration.py +12 -12
- mdb_engine/core/app_secrets.py +1 -2
- mdb_engine/core/connection.py +3 -4
- mdb_engine/core/encryption.py +1 -2
- mdb_engine/core/engine.py +43 -44
- mdb_engine/core/manifest.py +80 -58
- mdb_engine/core/ray_integration.py +10 -9
- mdb_engine/core/seeding.py +3 -3
- mdb_engine/core/service_initialization.py +10 -9
- mdb_engine/core/types.py +40 -40
- mdb_engine/database/abstraction.py +15 -16
- mdb_engine/database/connection.py +40 -12
- mdb_engine/database/query_validator.py +8 -8
- mdb_engine/database/resource_limiter.py +7 -7
- mdb_engine/database/scoped_wrapper.py +51 -58
- mdb_engine/dependencies.py +14 -13
- mdb_engine/di/container.py +12 -13
- mdb_engine/di/providers.py +14 -13
- mdb_engine/di/scopes.py +5 -5
- mdb_engine/embeddings/dependencies.py +2 -2
- mdb_engine/embeddings/service.py +67 -50
- mdb_engine/exceptions.py +20 -20
- mdb_engine/indexes/helpers.py +11 -11
- mdb_engine/indexes/manager.py +9 -9
- mdb_engine/memory/README.md +93 -2
- mdb_engine/memory/service.py +361 -1109
- mdb_engine/observability/health.py +10 -9
- mdb_engine/observability/logging.py +10 -10
- mdb_engine/observability/metrics.py +8 -7
- mdb_engine/repositories/base.py +25 -25
- mdb_engine/repositories/mongo.py +17 -17
- mdb_engine/repositories/unit_of_work.py +6 -6
- mdb_engine/routing/websockets.py +19 -18
- mdb_engine/utils/__init__.py +3 -1
- mdb_engine/utils/mongo.py +117 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/METADATA +88 -13
- mdb_engine-0.2.4.dist-info/RECORD +97 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/WHEEL +1 -1
- mdb_engine-0.2.1.dist-info/RECORD +0 -96
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.2.1.dist-info → mdb_engine-0.2.4.dist-info}/top_level.txt +0 -0
mdb_engine/auth/shared_users.py
CHANGED
|
@@ -45,7 +45,7 @@ import logging
|
|
|
45
45
|
import os
|
|
46
46
|
import secrets
|
|
47
47
|
from datetime import datetime, timedelta
|
|
48
|
-
from typing import TYPE_CHECKING, Any
|
|
48
|
+
from typing import TYPE_CHECKING, Any
|
|
49
49
|
|
|
50
50
|
import bcrypt
|
|
51
51
|
import jwt
|
|
@@ -114,8 +114,8 @@ class SharedUserPool:
|
|
|
114
114
|
def __init__(
|
|
115
115
|
self,
|
|
116
116
|
mongo_db: AsyncIOMotorDatabase,
|
|
117
|
-
jwt_secret:
|
|
118
|
-
jwt_public_key:
|
|
117
|
+
jwt_secret: str | None = None,
|
|
118
|
+
jwt_public_key: str | None = None,
|
|
119
119
|
jwt_algorithm: str = DEFAULT_JWT_ALGORITHM,
|
|
120
120
|
token_expiry_hours: int = DEFAULT_TOKEN_EXPIRY_HOURS,
|
|
121
121
|
allow_insecure_dev: bool = False,
|
|
@@ -285,9 +285,9 @@ class SharedUserPool:
|
|
|
285
285
|
self,
|
|
286
286
|
email: str,
|
|
287
287
|
password: str,
|
|
288
|
-
app_roles:
|
|
288
|
+
app_roles: dict[str, list[str]] | None = None,
|
|
289
289
|
is_active: bool = True,
|
|
290
|
-
) ->
|
|
290
|
+
) -> dict[str, Any]:
|
|
291
291
|
"""
|
|
292
292
|
Create a new shared user.
|
|
293
293
|
|
|
@@ -332,10 +332,10 @@ class SharedUserPool:
|
|
|
332
332
|
self,
|
|
333
333
|
email: str,
|
|
334
334
|
password: str,
|
|
335
|
-
ip_address:
|
|
336
|
-
fingerprint:
|
|
337
|
-
session_binding:
|
|
338
|
-
) ->
|
|
335
|
+
ip_address: str | None = None,
|
|
336
|
+
fingerprint: str | None = None,
|
|
337
|
+
session_binding: dict[str, Any] | None = None,
|
|
338
|
+
) -> str | None:
|
|
339
339
|
"""
|
|
340
340
|
Authenticate user and return JWT token.
|
|
341
341
|
|
|
@@ -390,7 +390,7 @@ class SharedUserPool:
|
|
|
390
390
|
logger.info(f"User '{email}' authenticated successfully")
|
|
391
391
|
return token
|
|
392
392
|
|
|
393
|
-
async def validate_token(self, token: str) ->
|
|
393
|
+
async def validate_token(self, token: str) -> dict[str, Any] | None:
|
|
394
394
|
"""
|
|
395
395
|
Validate JWT token and return user data.
|
|
396
396
|
|
|
@@ -547,7 +547,7 @@ class SharedUserPool:
|
|
|
547
547
|
)
|
|
548
548
|
logger.info(f"All tokens revoked for user {user_id}: {reason}")
|
|
549
549
|
|
|
550
|
-
async def get_user_by_email(self, email: str) ->
|
|
550
|
+
async def get_user_by_email(self, email: str) -> dict[str, Any] | None:
|
|
551
551
|
"""Get user by email."""
|
|
552
552
|
user = await self._collection.find_one({"email": email})
|
|
553
553
|
if user:
|
|
@@ -558,7 +558,7 @@ class SharedUserPool:
|
|
|
558
558
|
self,
|
|
559
559
|
email: str,
|
|
560
560
|
app_slug: str,
|
|
561
|
-
roles:
|
|
561
|
+
roles: list[str],
|
|
562
562
|
) -> bool:
|
|
563
563
|
"""
|
|
564
564
|
Update a user's roles for a specific app.
|
|
@@ -638,10 +638,10 @@ class SharedUserPool:
|
|
|
638
638
|
|
|
639
639
|
@staticmethod
|
|
640
640
|
def user_has_role(
|
|
641
|
-
user:
|
|
641
|
+
user: dict[str, Any],
|
|
642
642
|
app_slug: str,
|
|
643
643
|
required_role: str,
|
|
644
|
-
role_hierarchy:
|
|
644
|
+
role_hierarchy: dict[str, list[str]] | None = None,
|
|
645
645
|
) -> bool:
|
|
646
646
|
"""
|
|
647
647
|
Check if user has a required role for an app.
|
|
@@ -673,16 +673,16 @@ class SharedUserPool:
|
|
|
673
673
|
|
|
674
674
|
@staticmethod
|
|
675
675
|
def get_user_roles_for_app(
|
|
676
|
-
user:
|
|
676
|
+
user: dict[str, Any],
|
|
677
677
|
app_slug: str,
|
|
678
|
-
) ->
|
|
678
|
+
) -> list[str]:
|
|
679
679
|
"""Get user's roles for a specific app."""
|
|
680
680
|
return user.get("app_roles", {}).get(app_slug, [])
|
|
681
681
|
|
|
682
682
|
def _generate_token(
|
|
683
683
|
self,
|
|
684
|
-
user:
|
|
685
|
-
extra_claims:
|
|
684
|
+
user: dict[str, Any],
|
|
685
|
+
extra_claims: dict[str, Any] | None = None,
|
|
686
686
|
) -> str:
|
|
687
687
|
"""
|
|
688
688
|
Generate JWT token for user with unique JTI for revocation support.
|
|
@@ -718,7 +718,7 @@ class SharedUserPool:
|
|
|
718
718
|
return jwt.encode(payload, self._signing_key, algorithm=self._jwt_algorithm)
|
|
719
719
|
|
|
720
720
|
@staticmethod
|
|
721
|
-
def _sanitize_user(user:
|
|
721
|
+
def _sanitize_user(user: dict[str, Any]) -> dict[str, Any]:
|
|
722
722
|
"""Remove sensitive fields from user document."""
|
|
723
723
|
sanitized = dict(user)
|
|
724
724
|
sanitized.pop("password_hash", None)
|
|
@@ -727,7 +727,7 @@ class SharedUserPool:
|
|
|
727
727
|
sanitized["_id"] = str(sanitized["_id"])
|
|
728
728
|
return sanitized
|
|
729
729
|
|
|
730
|
-
def get_secure_cookie_config(self, request: "Request") ->
|
|
730
|
+
def get_secure_cookie_config(self, request: "Request") -> dict[str, Any]:
|
|
731
731
|
"""
|
|
732
732
|
Get secure cookie settings for auth tokens.
|
|
733
733
|
|
|
@@ -8,7 +8,7 @@ This module is part of MDB_ENGINE - MongoDB Engine.
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
from datetime import datetime
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
12
12
|
|
|
13
13
|
from ..config import ACCESS_TOKEN_TTL as CONFIG_ACCESS_TTL
|
|
14
14
|
from .jwt import extract_token_metadata
|
|
@@ -16,7 +16,7 @@ from .jwt import extract_token_metadata
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def get_token_expiry_time(token: str, secret_key: str) ->
|
|
19
|
+
def get_token_expiry_time(token: str, secret_key: str) -> datetime | None:
|
|
20
20
|
"""
|
|
21
21
|
Get the expiration time of a token.
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ def get_token_expiry_time(token: str, secret_key: str) -> Optional[datetime]:
|
|
|
31
31
|
metadata = extract_token_metadata(token, secret_key)
|
|
32
32
|
if metadata and metadata.get("exp"):
|
|
33
33
|
exp_timestamp = metadata["exp"]
|
|
34
|
-
if isinstance(exp_timestamp,
|
|
34
|
+
if isinstance(exp_timestamp, int | float):
|
|
35
35
|
return datetime.utcfromtimestamp(exp_timestamp)
|
|
36
36
|
return None
|
|
37
37
|
except (ValueError, TypeError, AttributeError, KeyError) as e:
|
|
@@ -40,7 +40,7 @@ def get_token_expiry_time(token: str, secret_key: str) -> Optional[datetime]:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def is_token_expiring_soon(
|
|
43
|
-
token: str, secret_key: str, threshold_seconds:
|
|
43
|
+
token: str, secret_key: str, threshold_seconds: int | None = None
|
|
44
44
|
) -> bool:
|
|
45
45
|
"""
|
|
46
46
|
Check if a token is expiring soon.
|
|
@@ -68,9 +68,7 @@ def is_token_expiring_soon(
|
|
|
68
68
|
return False
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def should_refresh_token(
|
|
72
|
-
token: str, secret_key: str, refresh_threshold: Optional[int] = None
|
|
73
|
-
) -> bool:
|
|
71
|
+
def should_refresh_token(token: str, secret_key: str, refresh_threshold: int | None = None) -> bool:
|
|
74
72
|
"""
|
|
75
73
|
Determine if a token should be refreshed.
|
|
76
74
|
|
|
@@ -97,7 +95,7 @@ def should_refresh_token(
|
|
|
97
95
|
return False
|
|
98
96
|
|
|
99
97
|
|
|
100
|
-
def get_token_age(token: str, secret_key: str) ->
|
|
98
|
+
def get_token_age(token: str, secret_key: str) -> float | None:
|
|
101
99
|
"""
|
|
102
100
|
Get the age of a token in seconds.
|
|
103
101
|
|
|
@@ -112,7 +110,7 @@ def get_token_age(token: str, secret_key: str) -> Optional[float]:
|
|
|
112
110
|
metadata = extract_token_metadata(token, secret_key)
|
|
113
111
|
if metadata and metadata.get("iat"):
|
|
114
112
|
iat_timestamp = metadata["iat"]
|
|
115
|
-
if isinstance(iat_timestamp,
|
|
113
|
+
if isinstance(iat_timestamp, int | float):
|
|
116
114
|
issued_at = datetime.utcfromtimestamp(iat_timestamp)
|
|
117
115
|
age = (datetime.utcnow() - issued_at).total_seconds()
|
|
118
116
|
return age
|
|
@@ -122,7 +120,7 @@ def get_token_age(token: str, secret_key: str) -> Optional[float]:
|
|
|
122
120
|
return None
|
|
123
121
|
|
|
124
122
|
|
|
125
|
-
def get_time_until_expiry(token: str, secret_key: str) ->
|
|
123
|
+
def get_time_until_expiry(token: str, secret_key: str) -> float | None:
|
|
126
124
|
"""
|
|
127
125
|
Get time until token expiry in seconds.
|
|
128
126
|
|
|
@@ -146,7 +144,7 @@ def get_time_until_expiry(token: str, secret_key: str) -> Optional[float]:
|
|
|
146
144
|
|
|
147
145
|
|
|
148
146
|
def validate_token_version(
|
|
149
|
-
token: str, secret_key: str, required_version:
|
|
147
|
+
token: str, secret_key: str, required_version: str | None = None
|
|
150
148
|
) -> bool:
|
|
151
149
|
"""
|
|
152
150
|
Validate token version compatibility.
|
|
@@ -177,7 +175,7 @@ def validate_token_version(
|
|
|
177
175
|
return False
|
|
178
176
|
|
|
179
177
|
|
|
180
|
-
def get_token_info(token: str, secret_key: str) ->
|
|
178
|
+
def get_token_info(token: str, secret_key: str) -> dict[str, Any] | None:
|
|
181
179
|
"""
|
|
182
180
|
Get comprehensive token information.
|
|
183
181
|
|
mdb_engine/auth/token_store.py
CHANGED
|
@@ -8,7 +8,6 @@ This module is part of MDB_ENGINE - MongoDB Engine.
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
from datetime import datetime, timedelta
|
|
11
|
-
from typing import Optional
|
|
12
11
|
|
|
13
12
|
try:
|
|
14
13
|
from pymongo.errors import (
|
|
@@ -79,9 +78,9 @@ class TokenBlacklist:
|
|
|
79
78
|
async def revoke_token(
|
|
80
79
|
self,
|
|
81
80
|
jti: str,
|
|
82
|
-
user_id:
|
|
83
|
-
expires_at:
|
|
84
|
-
reason:
|
|
81
|
+
user_id: str | None = None,
|
|
82
|
+
expires_at: datetime | None = None,
|
|
83
|
+
reason: str | None = None,
|
|
85
84
|
) -> bool:
|
|
86
85
|
"""
|
|
87
86
|
Revoke a token by adding it to the blacklist.
|
|
@@ -161,7 +160,7 @@ class TokenBlacklist:
|
|
|
161
160
|
# On error, assume not revoked (fail open for availability)
|
|
162
161
|
return False
|
|
163
162
|
|
|
164
|
-
async def revoke_all_user_tokens(self, user_id: str, reason:
|
|
163
|
+
async def revoke_all_user_tokens(self, user_id: str, reason: str | None = None) -> int:
|
|
165
164
|
"""
|
|
166
165
|
Revoke all tokens for a specific user.
|
|
167
166
|
|
mdb_engine/auth/users.py
CHANGED
|
@@ -13,8 +13,9 @@ This module is part of MDB_ENGINE - MongoDB Engine.
|
|
|
13
13
|
import logging
|
|
14
14
|
import os
|
|
15
15
|
import uuid
|
|
16
|
+
from collections.abc import Awaitable, Callable
|
|
16
17
|
from datetime import datetime, timedelta
|
|
17
|
-
from typing import Any
|
|
18
|
+
from typing import Any
|
|
18
19
|
|
|
19
20
|
import bcrypt
|
|
20
21
|
import jwt
|
|
@@ -49,9 +50,9 @@ def _is_auth_route(request_path: str) -> bool:
|
|
|
49
50
|
async def _get_app_user_config(
|
|
50
51
|
request: Request,
|
|
51
52
|
slug_id: str,
|
|
52
|
-
config:
|
|
53
|
-
get_app_config_func:
|
|
54
|
-
) ->
|
|
53
|
+
config: dict[str, Any] | None,
|
|
54
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None,
|
|
55
|
+
) -> dict[str, Any] | None:
|
|
55
56
|
"""Fetch and validate app user config."""
|
|
56
57
|
if config is None:
|
|
57
58
|
if not get_app_config_func:
|
|
@@ -72,7 +73,7 @@ async def _get_app_user_config(
|
|
|
72
73
|
return config
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
def _convert_user_id_to_objectid(user_id: Any) ->
|
|
76
|
+
def _convert_user_id_to_objectid(user_id: Any) -> tuple[Any, str | None]:
|
|
76
77
|
"""
|
|
77
78
|
Convert user_id to ObjectId if valid, otherwise keep as string.
|
|
78
79
|
|
|
@@ -104,7 +105,7 @@ def _convert_user_id_to_objectid(user_id: Any) -> Tuple[Any, Optional[str]]:
|
|
|
104
105
|
|
|
105
106
|
async def _validate_and_decode_session_token(
|
|
106
107
|
session_token: str, slug_id: str
|
|
107
|
-
) ->
|
|
108
|
+
) -> tuple[dict[str, Any] | None, Exception | None]:
|
|
108
109
|
"""Validate and decode session token."""
|
|
109
110
|
try:
|
|
110
111
|
from .jwt import decode_jwt_token
|
|
@@ -135,9 +136,7 @@ async def _validate_and_decode_session_token(
|
|
|
135
136
|
return None, e
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
async def _fetch_app_user_from_db(
|
|
139
|
-
db, collection_name: str, user_id: Any
|
|
140
|
-
) -> Optional[Dict[str, Any]]:
|
|
139
|
+
async def _fetch_app_user_from_db(db, collection_name: str, user_id: Any) -> dict[str, Any] | None:
|
|
141
140
|
"""Fetch user from database."""
|
|
142
141
|
# Use getattr for attribute access (works with both AppDB and ScopedMongoWrapper)
|
|
143
142
|
collection = getattr(db, collection_name)
|
|
@@ -155,10 +154,10 @@ async def get_app_user(
|
|
|
155
154
|
request: Request,
|
|
156
155
|
slug_id: str,
|
|
157
156
|
db,
|
|
158
|
-
config:
|
|
157
|
+
config: dict[str, Any] | None = None,
|
|
159
158
|
allow_demo_fallback: bool = False,
|
|
160
|
-
get_app_config_func:
|
|
161
|
-
) ->
|
|
159
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
160
|
+
) -> dict[str, Any] | None:
|
|
162
161
|
"""
|
|
163
162
|
Get app-level user from session cookie.
|
|
164
163
|
|
|
@@ -234,8 +233,8 @@ async def get_app_user(
|
|
|
234
233
|
|
|
235
234
|
|
|
236
235
|
async def _try_demo_mode(
|
|
237
|
-
request: Request, slug_id: str, db, config:
|
|
238
|
-
) ->
|
|
236
|
+
request: Request, slug_id: str, db, config: dict[str, Any]
|
|
237
|
+
) -> dict[str, Any] | None:
|
|
239
238
|
"""
|
|
240
239
|
Internal helper: Try to authenticate as demo user if demo mode is enabled.
|
|
241
240
|
|
|
@@ -318,9 +317,9 @@ async def create_app_session(
|
|
|
318
317
|
request: Request,
|
|
319
318
|
slug_id: str,
|
|
320
319
|
user_id: str,
|
|
321
|
-
config:
|
|
322
|
-
response:
|
|
323
|
-
get_app_config_func:
|
|
320
|
+
config: dict[str, Any] | None = None,
|
|
321
|
+
response: Response | None = None,
|
|
322
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
324
323
|
) -> str:
|
|
325
324
|
"""
|
|
326
325
|
Create a app-specific session token and set cookie.
|
|
@@ -402,9 +401,9 @@ async def authenticate_app_user(
|
|
|
402
401
|
db,
|
|
403
402
|
email: str,
|
|
404
403
|
password: str,
|
|
405
|
-
store_id:
|
|
404
|
+
store_id: str | None = None,
|
|
406
405
|
collection_name: str = "users",
|
|
407
|
-
) ->
|
|
406
|
+
) -> dict[str, Any] | None:
|
|
408
407
|
"""
|
|
409
408
|
Authenticate a user against app-specific users collection.
|
|
410
409
|
|
|
@@ -484,9 +483,9 @@ async def create_app_user(
|
|
|
484
483
|
email: str,
|
|
485
484
|
password: str,
|
|
486
485
|
role: str = "user",
|
|
487
|
-
store_id:
|
|
486
|
+
store_id: str | None = None,
|
|
488
487
|
collection_name: str = "users",
|
|
489
|
-
) ->
|
|
488
|
+
) -> dict[str, Any] | None:
|
|
490
489
|
"""
|
|
491
490
|
Create a new user in app-specific users collection.
|
|
492
491
|
|
|
@@ -573,9 +572,9 @@ async def get_or_create_anonymous_user(
|
|
|
573
572
|
request: Request,
|
|
574
573
|
slug_id: str,
|
|
575
574
|
db,
|
|
576
|
-
config:
|
|
577
|
-
get_app_config_func:
|
|
578
|
-
) ->
|
|
575
|
+
config: dict[str, Any] | None = None,
|
|
576
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
577
|
+
) -> dict[str, Any] | None:
|
|
579
578
|
"""
|
|
580
579
|
Get or create an anonymous user for anonymous_session strategy.
|
|
581
580
|
|
|
@@ -640,7 +639,7 @@ async def get_or_create_anonymous_user(
|
|
|
640
639
|
return user
|
|
641
640
|
|
|
642
641
|
|
|
643
|
-
async def get_platform_demo_user(mongo_uri: str, db_name: str) ->
|
|
642
|
+
async def get_platform_demo_user(mongo_uri: str, db_name: str) -> dict[str, Any] | None:
|
|
644
643
|
"""
|
|
645
644
|
Get platform demo user information from top-level database.
|
|
646
645
|
|
|
@@ -693,7 +692,7 @@ async def get_platform_demo_user(mongo_uri: str, db_name: str) -> Optional[Dict[
|
|
|
693
692
|
|
|
694
693
|
async def _link_platform_demo_user(
|
|
695
694
|
db, slug_id: str, collection_name: str, mongo_uri: str, db_name: str
|
|
696
|
-
) ->
|
|
695
|
+
) -> dict[str, Any] | None:
|
|
697
696
|
"""Link platform demo user to app demo user."""
|
|
698
697
|
import datetime
|
|
699
698
|
|
|
@@ -761,7 +760,7 @@ async def _link_platform_demo_user(
|
|
|
761
760
|
|
|
762
761
|
def _validate_demo_user_config(
|
|
763
762
|
demo_user_config: Any, slug_id: str
|
|
764
|
-
) ->
|
|
763
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
765
764
|
"""Validate demo user configuration."""
|
|
766
765
|
if not isinstance(demo_user_config, dict):
|
|
767
766
|
return None, f"Invalid demo_user_config entry (not a dict): {demo_user_config}"
|
|
@@ -782,12 +781,12 @@ def _validate_demo_user_config(
|
|
|
782
781
|
|
|
783
782
|
|
|
784
783
|
async def _resolve_demo_user_email_password(
|
|
785
|
-
email:
|
|
786
|
-
password:
|
|
787
|
-
mongo_uri:
|
|
788
|
-
db_name:
|
|
784
|
+
email: str | None,
|
|
785
|
+
password: str | None,
|
|
786
|
+
mongo_uri: str | None,
|
|
787
|
+
db_name: str | None,
|
|
789
788
|
slug_id: str,
|
|
790
|
-
) ->
|
|
789
|
+
) -> tuple[str | None, str | None]:
|
|
791
790
|
"""Resolve email and password from config or platform demo."""
|
|
792
791
|
# If email not specified, try platform demo
|
|
793
792
|
if not email:
|
|
@@ -834,11 +833,11 @@ async def _create_demo_user_from_config(
|
|
|
834
833
|
email: str,
|
|
835
834
|
password: str,
|
|
836
835
|
role: str,
|
|
837
|
-
extra_data:
|
|
836
|
+
extra_data: dict[str, Any],
|
|
838
837
|
link_to_platform: bool,
|
|
839
|
-
mongo_uri:
|
|
840
|
-
db_name:
|
|
841
|
-
) ->
|
|
838
|
+
mongo_uri: str | None,
|
|
839
|
+
db_name: str | None,
|
|
840
|
+
) -> dict[str, Any] | None:
|
|
842
841
|
"""Create a demo user from configuration."""
|
|
843
842
|
import datetime
|
|
844
843
|
|
|
@@ -933,10 +932,10 @@ async def _create_demo_user_from_config(
|
|
|
933
932
|
async def ensure_demo_users_exist(
|
|
934
933
|
db,
|
|
935
934
|
slug_id: str,
|
|
936
|
-
config:
|
|
937
|
-
mongo_uri:
|
|
938
|
-
db_name:
|
|
939
|
-
) ->
|
|
935
|
+
config: dict[str, Any] | None = None,
|
|
936
|
+
mongo_uri: str | None = None,
|
|
937
|
+
db_name: str | None = None,
|
|
938
|
+
) -> list[dict[str, Any]]:
|
|
940
939
|
"""
|
|
941
940
|
Intelligently ensure demo users exist for a app based on manifest configuration.
|
|
942
941
|
|
|
@@ -1058,9 +1057,9 @@ async def get_or_create_demo_user_for_request(
|
|
|
1058
1057
|
request: Request,
|
|
1059
1058
|
slug_id: str,
|
|
1060
1059
|
db,
|
|
1061
|
-
config:
|
|
1062
|
-
get_app_config_func:
|
|
1063
|
-
) ->
|
|
1060
|
+
config: dict[str, Any] | None = None,
|
|
1061
|
+
get_app_config_func: Callable[[Request, str, dict], Awaitable[dict]] | None = None,
|
|
1062
|
+
) -> dict[str, Any] | None:
|
|
1064
1063
|
"""
|
|
1065
1064
|
Get or create a demo user for the current request context.
|
|
1066
1065
|
|
|
@@ -1150,10 +1149,10 @@ async def get_or_create_demo_user_for_request(
|
|
|
1150
1149
|
async def get_or_create_demo_user(
|
|
1151
1150
|
db,
|
|
1152
1151
|
slug_id: str,
|
|
1153
|
-
config:
|
|
1154
|
-
mongo_uri:
|
|
1155
|
-
db_name:
|
|
1156
|
-
) ->
|
|
1152
|
+
config: dict[str, Any],
|
|
1153
|
+
mongo_uri: str | None = None,
|
|
1154
|
+
db_name: str | None = None,
|
|
1155
|
+
) -> dict[str, Any] | None:
|
|
1157
1156
|
"""
|
|
1158
1157
|
Get or create a demo user for an app.
|
|
1159
1158
|
|
|
@@ -1252,7 +1251,7 @@ async def get_or_create_demo_user(
|
|
|
1252
1251
|
|
|
1253
1252
|
async def ensure_demo_users_for_actor(
|
|
1254
1253
|
db, slug_id: str, mongo_uri: str, db_name: str
|
|
1255
|
-
) ->
|
|
1254
|
+
) -> list[dict[str, Any]]:
|
|
1256
1255
|
"""
|
|
1257
1256
|
Convenience function for actors to ensure demo users exist.
|
|
1258
1257
|
|
|
@@ -1351,10 +1350,10 @@ async def ensure_demo_users_for_actor(
|
|
|
1351
1350
|
|
|
1352
1351
|
|
|
1353
1352
|
async def sync_app_user_to_casbin(
|
|
1354
|
-
user:
|
|
1353
|
+
user: dict[str, Any],
|
|
1355
1354
|
authz_provider,
|
|
1356
|
-
role:
|
|
1357
|
-
app_slug:
|
|
1355
|
+
role: str | None = None,
|
|
1356
|
+
app_slug: str | None = None,
|
|
1358
1357
|
) -> bool:
|
|
1359
1358
|
"""
|
|
1360
1359
|
Sync app-level user to Casbin by assigning a role.
|
|
@@ -1428,7 +1427,7 @@ async def sync_app_user_to_casbin(
|
|
|
1428
1427
|
return False
|
|
1429
1428
|
|
|
1430
1429
|
|
|
1431
|
-
def get_app_user_role(user:
|
|
1430
|
+
def get_app_user_role(user: dict[str, Any], config: dict[str, Any] | None = None) -> str:
|
|
1432
1431
|
"""
|
|
1433
1432
|
Determine Casbin role for app-level user.
|
|
1434
1433
|
|
mdb_engine/auth/utils.py
CHANGED
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
import re
|
|
12
12
|
import uuid
|
|
13
13
|
from datetime import datetime
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
16
|
import bcrypt
|
|
17
17
|
from fastapi import Request, Response
|
|
@@ -43,7 +43,7 @@ def _detect_browser(user_agent: str) -> str:
|
|
|
43
43
|
return "unknown"
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def _detect_os_and_device_type(user_agent: str) ->
|
|
46
|
+
def _detect_os_and_device_type(user_agent: str) -> tuple[str, str]:
|
|
47
47
|
"""Detect OS and device type from user agent string."""
|
|
48
48
|
if not user_agent:
|
|
49
49
|
return "unknown", "desktop"
|
|
@@ -64,7 +64,7 @@ def _detect_os_and_device_type(user_agent: str) -> Tuple[str, str]:
|
|
|
64
64
|
return "unknown", "desktop"
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def get_device_info(request: Request) ->
|
|
67
|
+
def get_device_info(request: Request) -> dict[str, Any]:
|
|
68
68
|
"""
|
|
69
69
|
Extract device information from request.
|
|
70
70
|
|
|
@@ -236,15 +236,15 @@ async def check_password_breach(password: str) -> bool:
|
|
|
236
236
|
|
|
237
237
|
def validate_password_strength(
|
|
238
238
|
password: str,
|
|
239
|
-
min_length:
|
|
240
|
-
require_uppercase:
|
|
241
|
-
require_lowercase:
|
|
242
|
-
require_numbers:
|
|
243
|
-
require_special:
|
|
244
|
-
min_entropy_bits:
|
|
245
|
-
check_common_passwords:
|
|
246
|
-
config:
|
|
247
|
-
) ->
|
|
239
|
+
min_length: int | None = None,
|
|
240
|
+
require_uppercase: bool | None = None,
|
|
241
|
+
require_lowercase: bool | None = None,
|
|
242
|
+
require_numbers: bool | None = None,
|
|
243
|
+
require_special: bool | None = None,
|
|
244
|
+
min_entropy_bits: int | None = None,
|
|
245
|
+
check_common_passwords: bool | None = None,
|
|
246
|
+
config: dict[str, Any] | None = None,
|
|
247
|
+
) -> tuple[bool, list[str]]:
|
|
248
248
|
"""
|
|
249
249
|
Validate password strength with configurable rules.
|
|
250
250
|
|
|
@@ -338,9 +338,9 @@ def validate_password_strength(
|
|
|
338
338
|
|
|
339
339
|
async def validate_password_strength_async(
|
|
340
340
|
password: str,
|
|
341
|
-
config:
|
|
342
|
-
check_breaches:
|
|
343
|
-
) ->
|
|
341
|
+
config: dict[str, Any] | None = None,
|
|
342
|
+
check_breaches: bool | None = None,
|
|
343
|
+
) -> tuple[bool, list[str]]:
|
|
344
344
|
"""
|
|
345
345
|
Async version of validate_password_strength with breach checking.
|
|
346
346
|
|
|
@@ -404,10 +404,10 @@ async def login_user(
|
|
|
404
404
|
email: str,
|
|
405
405
|
password: str,
|
|
406
406
|
db,
|
|
407
|
-
config:
|
|
407
|
+
config: dict[str, Any] | None = None,
|
|
408
408
|
remember_me: bool = False,
|
|
409
|
-
redirect_url:
|
|
410
|
-
) ->
|
|
409
|
+
redirect_url: str | None = None,
|
|
410
|
+
) -> dict[str, Any]:
|
|
411
411
|
"""
|
|
412
412
|
Handle user login with automatic token generation and cookie setting.
|
|
413
413
|
|
|
@@ -571,8 +571,8 @@ def _validate_email_format(email: str) -> bool:
|
|
|
571
571
|
|
|
572
572
|
|
|
573
573
|
def _get_password_policy_from_config(
|
|
574
|
-
request: Request, config:
|
|
575
|
-
) ->
|
|
574
|
+
request: Request, config: dict[str, Any] | None
|
|
575
|
+
) -> dict[str, Any] | None:
|
|
576
576
|
"""Get password policy from config or request."""
|
|
577
577
|
if config:
|
|
578
578
|
security = config.get("security", {})
|
|
@@ -585,8 +585,8 @@ def _get_password_policy_from_config(
|
|
|
585
585
|
|
|
586
586
|
|
|
587
587
|
async def _create_user_document(
|
|
588
|
-
email: str, password: str, extra_data:
|
|
589
|
-
) ->
|
|
588
|
+
email: str, password: str, extra_data: dict[str, Any] | None
|
|
589
|
+
) -> dict[str, Any]:
|
|
590
590
|
"""Create user document with hashed password."""
|
|
591
591
|
password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
|
592
592
|
user_doc = {
|
|
@@ -600,9 +600,7 @@ async def _create_user_document(
|
|
|
600
600
|
return user_doc
|
|
601
601
|
|
|
602
602
|
|
|
603
|
-
def _create_registration_response(
|
|
604
|
-
user_doc: Dict[str, Any], redirect_url: Optional[str]
|
|
605
|
-
) -> Response:
|
|
603
|
+
def _create_registration_response(user_doc: dict[str, Any], redirect_url: str | None) -> Response:
|
|
606
604
|
"""Create response for registration."""
|
|
607
605
|
if redirect_url:
|
|
608
606
|
return RedirectResponse(url=redirect_url, status_code=302)
|
|
@@ -622,10 +620,10 @@ async def register_user(
|
|
|
622
620
|
email: str,
|
|
623
621
|
password: str,
|
|
624
622
|
db,
|
|
625
|
-
config:
|
|
626
|
-
extra_data:
|
|
627
|
-
redirect_url:
|
|
628
|
-
) ->
|
|
623
|
+
config: dict[str, Any] | None = None,
|
|
624
|
+
extra_data: dict[str, Any] | None = None,
|
|
625
|
+
redirect_url: str | None = None,
|
|
626
|
+
) -> dict[str, Any]:
|
|
629
627
|
"""
|
|
630
628
|
Handle user registration with automatic token generation.
|
|
631
629
|
|
|
@@ -710,7 +708,7 @@ async def register_user(
|
|
|
710
708
|
return {"success": False, "error": "Registration failed. Please try again."}
|
|
711
709
|
|
|
712
710
|
|
|
713
|
-
async def _get_user_id_from_request(request: Request, user_id:
|
|
711
|
+
async def _get_user_id_from_request(request: Request, user_id: str | None) -> str | None:
|
|
714
712
|
"""Extract user_id from request if not provided."""
|
|
715
713
|
if user_id:
|
|
716
714
|
return user_id
|
|
@@ -773,9 +771,7 @@ async def _revoke_session(request: Request) -> None:
|
|
|
773
771
|
await session_mgr.revoke_session_by_refresh_token(refresh_jti)
|
|
774
772
|
|
|
775
773
|
|
|
776
|
-
async def logout_user(
|
|
777
|
-
request: Request, response: Response, user_id: Optional[str] = None
|
|
778
|
-
) -> Response:
|
|
774
|
+
async def logout_user(request: Request, response: Response, user_id: str | None = None) -> Response:
|
|
779
775
|
"""
|
|
780
776
|
Handle user logout with token revocation and cookie clearing.
|
|
781
777
|
|