mdb-engine 0.2.0__py3-none-any.whl → 0.2.3__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 +1 -1
- 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 +16 -15
- 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 +59 -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 +31 -43
- mdb_engine/exceptions.py +20 -20
- mdb_engine/indexes/helpers.py +11 -11
- mdb_engine/indexes/manager.py +9 -9
- mdb_engine/memory/service.py +30 -30
- 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-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/METADATA +8 -8
- mdb_engine-0.2.3.dist-info/RECORD +96 -0
- mdb_engine-0.2.0.dist-info/RECORD +0 -96
- {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/WHEEL +0 -0
- {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.2.0.dist-info → mdb_engine-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -32,7 +32,8 @@ Manual usage:
|
|
|
32
32
|
import fnmatch
|
|
33
33
|
import hashlib
|
|
34
34
|
import logging
|
|
35
|
-
from
|
|
35
|
+
from collections.abc import Callable
|
|
36
|
+
from typing import Any
|
|
36
37
|
|
|
37
38
|
import jwt
|
|
38
39
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
@@ -49,7 +50,7 @@ AUTH_HEADER_NAME = "Authorization"
|
|
|
49
50
|
AUTH_HEADER_PREFIX = "Bearer "
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def _get_client_ip(request: Request) ->
|
|
53
|
+
def _get_client_ip(request: Request) -> str | None:
|
|
53
54
|
"""Extract client IP address from request, handling proxies."""
|
|
54
55
|
# Check X-Forwarded-For header (behind load balancer/proxy)
|
|
55
56
|
forwarded_for = request.headers.get("x-forwarded-for")
|
|
@@ -99,12 +100,12 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
99
100
|
def __init__(
|
|
100
101
|
self,
|
|
101
102
|
app: Callable,
|
|
102
|
-
user_pool:
|
|
103
|
+
user_pool: SharedUserPool | None,
|
|
103
104
|
app_slug: str,
|
|
104
|
-
require_role:
|
|
105
|
-
public_routes:
|
|
106
|
-
role_hierarchy:
|
|
107
|
-
session_binding:
|
|
105
|
+
require_role: str | None = None,
|
|
106
|
+
public_routes: list[str] | None = None,
|
|
107
|
+
role_hierarchy: dict[str, list[str]] | None = None,
|
|
108
|
+
session_binding: dict[str, Any] | None = None,
|
|
108
109
|
cookie_name: str = AUTH_COOKIE_NAME,
|
|
109
110
|
header_name: str = AUTH_HEADER_NAME,
|
|
110
111
|
header_prefix: str = AUTH_HEADER_PREFIX,
|
|
@@ -145,7 +146,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
145
146
|
f"session_binding={bool(self._session_binding)})"
|
|
146
147
|
)
|
|
147
148
|
|
|
148
|
-
def get_user_pool(self, request: Request) ->
|
|
149
|
+
def get_user_pool(self, request: Request) -> SharedUserPool | None:
|
|
149
150
|
"""Get the user pool instance. Override in subclasses for lazy loading."""
|
|
150
151
|
return self._user_pool
|
|
151
152
|
|
|
@@ -219,7 +220,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
219
220
|
self,
|
|
220
221
|
request: Request,
|
|
221
222
|
token: str,
|
|
222
|
-
) ->
|
|
223
|
+
) -> str | None:
|
|
223
224
|
"""
|
|
224
225
|
Validate session binding claims in token.
|
|
225
226
|
|
|
@@ -260,7 +261,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
260
261
|
logger.warning(f"Error validating session binding: {e}")
|
|
261
262
|
return None # Don't reject for binding check errors
|
|
262
263
|
|
|
263
|
-
def _extract_token(self, request: Request) ->
|
|
264
|
+
def _extract_token(self, request: Request) -> str | None:
|
|
264
265
|
"""Extract JWT token from cookie or header."""
|
|
265
266
|
# Try cookie first
|
|
266
267
|
token = request.cookies.get(self._cookie_name)
|
|
@@ -317,7 +318,7 @@ class SharedAuthMiddleware(BaseHTTPMiddleware):
|
|
|
317
318
|
def create_shared_auth_middleware(
|
|
318
319
|
user_pool: SharedUserPool,
|
|
319
320
|
app_slug: str,
|
|
320
|
-
manifest_auth:
|
|
321
|
+
manifest_auth: dict[str, Any],
|
|
321
322
|
) -> type:
|
|
322
323
|
"""
|
|
323
324
|
Factory function to create SharedAuthMiddleware configured from manifest.
|
|
@@ -365,7 +366,7 @@ def create_shared_auth_middleware(
|
|
|
365
366
|
|
|
366
367
|
def create_shared_auth_middleware_lazy(
|
|
367
368
|
app_slug: str,
|
|
368
|
-
manifest_auth:
|
|
369
|
+
manifest_auth: dict[str, Any],
|
|
369
370
|
) -> type:
|
|
370
371
|
"""
|
|
371
372
|
Factory function to create a lazy SharedAuthMiddleware that reads user_pool from app.state.
|
|
@@ -441,7 +442,7 @@ def create_shared_auth_middleware_lazy(
|
|
|
441
442
|
request.state.user_roles = []
|
|
442
443
|
|
|
443
444
|
# Get user_pool from app.state (set during lifespan)
|
|
444
|
-
user_pool:
|
|
445
|
+
user_pool: SharedUserPool | None = getattr(request.app.state, "user_pool", None)
|
|
445
446
|
|
|
446
447
|
if user_pool is None:
|
|
447
448
|
# User pool not initialized yet, skip auth
|
|
@@ -498,7 +499,7 @@ def create_shared_auth_middleware_lazy(
|
|
|
498
499
|
|
|
499
500
|
return await call_next(request)
|
|
500
501
|
|
|
501
|
-
def _extract_token(self, request: Request) ->
|
|
502
|
+
def _extract_token(self, request: Request) -> str | None:
|
|
502
503
|
"""Extract JWT token from cookie or header."""
|
|
503
504
|
# Try cookie first
|
|
504
505
|
token = request.cookies.get(self._cookie_name)
|
|
@@ -555,7 +556,7 @@ def create_shared_auth_middleware_lazy(
|
|
|
555
556
|
self,
|
|
556
557
|
request: Request,
|
|
557
558
|
token: str,
|
|
558
|
-
) ->
|
|
559
|
+
) -> str | None:
|
|
559
560
|
"""
|
|
560
561
|
Validate session binding claims in token.
|
|
561
562
|
|
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
|
|